@shadowob/cli
Advanced tools
| // src/utils/client.ts | ||
| import { ShadowClient, ShadowSocket } from "@shadowob/sdk"; | ||
| // src/config/manager.ts | ||
| import { existsSync } from "fs"; | ||
| import { mkdir, readFile, writeFile } from "fs/promises"; | ||
| import { homedir } from "os"; | ||
| import { dirname, join } from "path"; | ||
| var DEFAULT_CONFIG_DIR = join(homedir(), ".shadowob"); | ||
| var _DEFAULT_CONFIG_FILE = join(DEFAULT_CONFIG_DIR, "shadowob.config.json"); | ||
| var ConfigManager = class { | ||
| config = null; | ||
| configFile; | ||
| constructor(configDir) { | ||
| const dir = configDir ?? DEFAULT_CONFIG_DIR; | ||
| this.configFile = join(dir, "shadowob.config.json"); | ||
| } | ||
| async load() { | ||
| if (this.config) return this.config; | ||
| if (!existsSync(this.configFile)) { | ||
| this.config = { profiles: {} }; | ||
| return this.config; | ||
| } | ||
| try { | ||
| const content = await readFile(this.configFile, "utf-8"); | ||
| this.config = JSON.parse(content); | ||
| return this.config; | ||
| } catch { | ||
| this.config = { profiles: {} }; | ||
| return this.config; | ||
| } | ||
| } | ||
| async save() { | ||
| if (!this.config) return; | ||
| await mkdir(dirname(this.configFile), { recursive: true }); | ||
| await writeFile(this.configFile, JSON.stringify(this.config, null, 2)); | ||
| } | ||
| async getProfile(name) { | ||
| const config = await this.load(); | ||
| const profileName = name ?? config.currentProfile; | ||
| if (!profileName) return null; | ||
| return config.profiles[profileName] ?? null; | ||
| } | ||
| async getCurrentProfileName() { | ||
| const config = await this.load(); | ||
| return config.currentProfile ?? null; | ||
| } | ||
| async setProfile(name, profile) { | ||
| const config = await this.load(); | ||
| config.profiles[name] = profile; | ||
| await this.save(); | ||
| } | ||
| async deleteProfile(name) { | ||
| const config = await this.load(); | ||
| if (!config.profiles[name]) return false; | ||
| delete config.profiles[name]; | ||
| if (config.currentProfile === name) { | ||
| delete config.currentProfile; | ||
| } | ||
| await this.save(); | ||
| return true; | ||
| } | ||
| async switchProfile(name) { | ||
| const config = await this.load(); | ||
| if (!config.profiles[name]) return false; | ||
| config.currentProfile = name; | ||
| await this.save(); | ||
| return true; | ||
| } | ||
| async listProfiles() { | ||
| const config = await this.load(); | ||
| return Object.keys(config.profiles); | ||
| } | ||
| getConfigPath() { | ||
| return this.configFile; | ||
| } | ||
| async validate() { | ||
| const result = { | ||
| valid: true, | ||
| errors: [], | ||
| warnings: [], | ||
| profileResults: {} | ||
| }; | ||
| if (!existsSync(this.configFile)) { | ||
| result.valid = false; | ||
| result.errors.push("Config file does not exist"); | ||
| return result; | ||
| } | ||
| let config; | ||
| try { | ||
| const content = await readFile(this.configFile, "utf-8"); | ||
| config = JSON.parse(content); | ||
| } catch (error) { | ||
| result.valid = false; | ||
| result.errors.push(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`); | ||
| return result; | ||
| } | ||
| if (!config.profiles || typeof config.profiles !== "object") { | ||
| result.valid = false; | ||
| result.errors.push('Missing or invalid "profiles" field'); | ||
| return result; | ||
| } | ||
| if (config.currentProfile) { | ||
| if (!config.profiles[config.currentProfile]) { | ||
| result.valid = false; | ||
| result.errors.push(`Current profile "${config.currentProfile}" does not exist`); | ||
| } | ||
| } else { | ||
| result.warnings.push("No current profile set"); | ||
| } | ||
| for (const [name, profile] of Object.entries(config.profiles)) { | ||
| const profileResult = { valid: true }; | ||
| if (!profile.serverUrl) { | ||
| profileResult.valid = false; | ||
| result.errors.push(`Profile "${name}" missing serverUrl`); | ||
| } else { | ||
| try { | ||
| new URL(profile.serverUrl); | ||
| } catch { | ||
| profileResult.valid = false; | ||
| result.errors.push(`Profile "${name}" has invalid serverUrl: ${profile.serverUrl}`); | ||
| } | ||
| } | ||
| if (!profile.token) { | ||
| profileResult.valid = false; | ||
| result.errors.push(`Profile "${name}" missing token`); | ||
| } else if (!profile.token.includes(".")) { | ||
| result.warnings.push(`Profile "${name}" token does not look like a JWT`); | ||
| } | ||
| result.profileResults[name] = profileResult; | ||
| if (!profileResult.valid) { | ||
| result.valid = false; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| async fix() { | ||
| const changes = []; | ||
| const config = await this.load(); | ||
| for (const [name, profile] of Object.entries(config.profiles)) { | ||
| if (!profile.serverUrl || !profile.token) { | ||
| delete config.profiles[name]; | ||
| changes.push(`Removed invalid profile "${name}"`); | ||
| } | ||
| } | ||
| if (config.currentProfile && !config.profiles[config.currentProfile]) { | ||
| const remainingProfiles = Object.keys(config.profiles); | ||
| if (remainingProfiles.length > 0) { | ||
| config.currentProfile = remainingProfiles[0]; | ||
| changes.push(`Reset current profile to "${config.currentProfile}"`); | ||
| } else { | ||
| delete config.currentProfile; | ||
| changes.push("Removed invalid current profile reference"); | ||
| } | ||
| } | ||
| if (!config.profiles) { | ||
| config.profiles = {}; | ||
| changes.push("Created empty profiles object"); | ||
| } | ||
| await this.save(); | ||
| return { fixed: changes.length > 0, changes }; | ||
| } | ||
| }; | ||
| var configManager = new ConfigManager(); | ||
| // src/utils/client.ts | ||
| async function getConfig(profile) { | ||
| const config = await configManager.getProfile(profile); | ||
| if (!config) { | ||
| throw new Error( | ||
| profile ? `Profile "${profile}" not found. Run: shadowob auth login --profile ${profile}` : "Not authenticated. Run: shadowob auth login" | ||
| ); | ||
| } | ||
| return config; | ||
| } | ||
| async function getClient(profile) { | ||
| const config = await getConfig(profile); | ||
| return new ShadowClient(config.serverUrl, config.token); | ||
| } | ||
| async function getClientWithToken(token, profile) { | ||
| const config = await getConfig(profile); | ||
| return new ShadowClient(config.serverUrl, token); | ||
| } | ||
| async function getSocket(profile) { | ||
| const config = await getConfig(profile); | ||
| return new ShadowSocket({ serverUrl: config.serverUrl, token: config.token }); | ||
| } | ||
| function formatError(error) { | ||
| if (error instanceof Error) { | ||
| return error.message; | ||
| } | ||
| return String(error); | ||
| } | ||
| function parseLimit(value, defaultValue = 50, maxValue = 100) { | ||
| if (!value) return defaultValue; | ||
| const parsed = parseInt(value, 10); | ||
| if (Number.isNaN(parsed) || parsed < 1) return defaultValue; | ||
| return Math.min(parsed, maxValue); | ||
| } | ||
| function parsePrice(value) { | ||
| const parsed = parseFloat(value); | ||
| if (Number.isNaN(parsed) || parsed < 0) { | ||
| throw new Error("Price must be a non-negative number"); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parseIntOrThrow(value, fieldName) { | ||
| const parsed = parseInt(value, 10); | ||
| if (Number.isNaN(parsed)) { | ||
| throw new Error(`${fieldName} must be a valid integer`); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parsePositiveInt(value, fieldName) { | ||
| const parsed = parseIntOrThrow(value, fieldName); | ||
| if (parsed < 1) { | ||
| throw new Error(`${fieldName} must be a positive integer`); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parseNonNegativeInt(value, fieldName) { | ||
| const parsed = parseIntOrThrow(value, fieldName); | ||
| if (parsed < 0) { | ||
| throw new Error(`${fieldName} must be a non-negative integer`); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parseBoolean(value) { | ||
| if (value === void 0) return void 0; | ||
| if (value === "true" || value === "1") return true; | ||
| if (value === "false" || value === "0") return false; | ||
| return void 0; | ||
| } | ||
| function requireOption(value, name) { | ||
| if (value === void 0 || value === null || value === "") { | ||
| throw new Error(`Missing required option: --${name}`); | ||
| } | ||
| return value; | ||
| } | ||
| async function handleCommand(fn, options, outputFn, errorFn) { | ||
| try { | ||
| const result = await fn(); | ||
| outputFn(result, options.json); | ||
| process.exit(0); | ||
| } catch (error) { | ||
| const message = formatError(error); | ||
| errorFn(message, options.json); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| export { | ||
| configManager, | ||
| getClient, | ||
| getClientWithToken, | ||
| getSocket, | ||
| formatError, | ||
| parseLimit, | ||
| parsePrice, | ||
| parseIntOrThrow, | ||
| parsePositiveInt, | ||
| parseNonNegativeInt, | ||
| parseBoolean, | ||
| requireOption, | ||
| handleCommand | ||
| }; |
| import { | ||
| formatError, | ||
| getClient, | ||
| getClientWithToken, | ||
| getSocket, | ||
| handleCommand, | ||
| parseBoolean, | ||
| parseIntOrThrow, | ||
| parseLimit, | ||
| parseNonNegativeInt, | ||
| parsePositiveInt, | ||
| parsePrice, | ||
| requireOption | ||
| } from "./chunk-E364BDQO.js"; | ||
| export { | ||
| formatError, | ||
| getClient, | ||
| getClientWithToken, | ||
| getSocket, | ||
| handleCommand, | ||
| parseBoolean, | ||
| parseIntOrThrow, | ||
| parseLimit, | ||
| parseNonNegativeInt, | ||
| parsePositiveInt, | ||
| parsePrice, | ||
| requireOption | ||
| }; |
+2
-2
| { | ||
| "name": "@shadowob/cli", | ||
| "version": "1.1.4", | ||
| "version": "1.1.5", | ||
| "description": "Shadow CLI — command-line interface for Shadow servers", | ||
@@ -16,3 +16,3 @@ "license": "MIT", | ||
| "chalk": "^5.4.1", | ||
| "@shadowob/sdk": "1.1.4" | ||
| "@shadowob/sdk": "1.1.5" | ||
| }, | ||
@@ -19,0 +19,0 @@ "devDependencies": { |
| // src/utils/client.ts | ||
| import { ShadowClient, ShadowSocket } from "@shadowob/sdk"; | ||
| // src/config/manager.ts | ||
| import { existsSync } from "fs"; | ||
| import { mkdir, readFile, writeFile } from "fs/promises"; | ||
| import { homedir } from "os"; | ||
| import { dirname, join } from "path"; | ||
| var DEFAULT_CONFIG_DIR = join(homedir(), ".shadowob"); | ||
| var _DEFAULT_CONFIG_FILE = join(DEFAULT_CONFIG_DIR, "shadowob.config.json"); | ||
| var ConfigManager = class { | ||
| config = null; | ||
| configFile; | ||
| constructor(configDir) { | ||
| const dir = configDir ?? DEFAULT_CONFIG_DIR; | ||
| this.configFile = join(dir, "shadowob.config.json"); | ||
| } | ||
| async load() { | ||
| if (this.config) return this.config; | ||
| if (!existsSync(this.configFile)) { | ||
| this.config = { profiles: {} }; | ||
| return this.config; | ||
| } | ||
| try { | ||
| const content = await readFile(this.configFile, "utf-8"); | ||
| this.config = JSON.parse(content); | ||
| return this.config; | ||
| } catch { | ||
| this.config = { profiles: {} }; | ||
| return this.config; | ||
| } | ||
| } | ||
| async save() { | ||
| if (!this.config) return; | ||
| await mkdir(dirname(this.configFile), { recursive: true }); | ||
| await writeFile(this.configFile, JSON.stringify(this.config, null, 2)); | ||
| } | ||
| async getProfile(name) { | ||
| const config = await this.load(); | ||
| const profileName = name ?? config.currentProfile; | ||
| if (!profileName) return null; | ||
| return config.profiles[profileName] ?? null; | ||
| } | ||
| async getCurrentProfileName() { | ||
| const config = await this.load(); | ||
| return config.currentProfile ?? null; | ||
| } | ||
| async setProfile(name, profile) { | ||
| const config = await this.load(); | ||
| config.profiles[name] = profile; | ||
| await this.save(); | ||
| } | ||
| async deleteProfile(name) { | ||
| const config = await this.load(); | ||
| if (!config.profiles[name]) return false; | ||
| delete config.profiles[name]; | ||
| if (config.currentProfile === name) { | ||
| delete config.currentProfile; | ||
| } | ||
| await this.save(); | ||
| return true; | ||
| } | ||
| async switchProfile(name) { | ||
| const config = await this.load(); | ||
| if (!config.profiles[name]) return false; | ||
| config.currentProfile = name; | ||
| await this.save(); | ||
| return true; | ||
| } | ||
| async listProfiles() { | ||
| const config = await this.load(); | ||
| return Object.keys(config.profiles); | ||
| } | ||
| getConfigPath() { | ||
| return this.configFile; | ||
| } | ||
| async validate() { | ||
| const result = { | ||
| valid: true, | ||
| errors: [], | ||
| warnings: [], | ||
| profileResults: {} | ||
| }; | ||
| if (!existsSync(this.configFile)) { | ||
| result.valid = false; | ||
| result.errors.push("Config file does not exist"); | ||
| return result; | ||
| } | ||
| let config; | ||
| try { | ||
| const content = await readFile(this.configFile, "utf-8"); | ||
| config = JSON.parse(content); | ||
| } catch (error) { | ||
| result.valid = false; | ||
| result.errors.push(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`); | ||
| return result; | ||
| } | ||
| if (!config.profiles || typeof config.profiles !== "object") { | ||
| result.valid = false; | ||
| result.errors.push('Missing or invalid "profiles" field'); | ||
| return result; | ||
| } | ||
| if (config.currentProfile) { | ||
| if (!config.profiles[config.currentProfile]) { | ||
| result.valid = false; | ||
| result.errors.push(`Current profile "${config.currentProfile}" does not exist`); | ||
| } | ||
| } else { | ||
| result.warnings.push("No current profile set"); | ||
| } | ||
| for (const [name, profile] of Object.entries(config.profiles)) { | ||
| const profileResult = { valid: true }; | ||
| if (!profile.serverUrl) { | ||
| profileResult.valid = false; | ||
| result.errors.push(`Profile "${name}" missing serverUrl`); | ||
| } else { | ||
| try { | ||
| new URL(profile.serverUrl); | ||
| } catch { | ||
| profileResult.valid = false; | ||
| result.errors.push(`Profile "${name}" has invalid serverUrl: ${profile.serverUrl}`); | ||
| } | ||
| } | ||
| if (!profile.token) { | ||
| profileResult.valid = false; | ||
| result.errors.push(`Profile "${name}" missing token`); | ||
| } else if (!profile.token.includes(".")) { | ||
| result.warnings.push(`Profile "${name}" token does not look like a JWT`); | ||
| } | ||
| result.profileResults[name] = profileResult; | ||
| if (!profileResult.valid) { | ||
| result.valid = false; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| async fix() { | ||
| const changes = []; | ||
| const config = await this.load(); | ||
| for (const [name, profile] of Object.entries(config.profiles)) { | ||
| if (!profile.serverUrl || !profile.token) { | ||
| delete config.profiles[name]; | ||
| changes.push(`Removed invalid profile "${name}"`); | ||
| } | ||
| } | ||
| if (config.currentProfile && !config.profiles[config.currentProfile]) { | ||
| const remainingProfiles = Object.keys(config.profiles); | ||
| if (remainingProfiles.length > 0) { | ||
| config.currentProfile = remainingProfiles[0]; | ||
| changes.push(`Reset current profile to "${config.currentProfile}"`); | ||
| } else { | ||
| delete config.currentProfile; | ||
| changes.push("Removed invalid current profile reference"); | ||
| } | ||
| } | ||
| if (!config.profiles) { | ||
| config.profiles = {}; | ||
| changes.push("Created empty profiles object"); | ||
| } | ||
| await this.save(); | ||
| return { fixed: changes.length > 0, changes }; | ||
| } | ||
| }; | ||
| var configManager = new ConfigManager(); | ||
| // src/utils/client.ts | ||
| async function getConfig(profile) { | ||
| const config = await configManager.getProfile(profile); | ||
| if (!config) { | ||
| throw new Error( | ||
| profile ? `Profile "${profile}" not found. Run: shadowob auth login --profile ${profile}` : "Not authenticated. Run: shadowob auth login" | ||
| ); | ||
| } | ||
| return config; | ||
| } | ||
| async function getClient(profile) { | ||
| const config = await getConfig(profile); | ||
| return new ShadowClient(config.serverUrl, config.token); | ||
| } | ||
| async function getSocket(profile) { | ||
| const config = await getConfig(profile); | ||
| return new ShadowSocket({ serverUrl: config.serverUrl, token: config.token }); | ||
| } | ||
| function formatError(error) { | ||
| if (error instanceof Error) { | ||
| return error.message; | ||
| } | ||
| return String(error); | ||
| } | ||
| function parseLimit(value, defaultValue = 50, maxValue = 100) { | ||
| if (!value) return defaultValue; | ||
| const parsed = parseInt(value, 10); | ||
| if (Number.isNaN(parsed) || parsed < 1) return defaultValue; | ||
| return Math.min(parsed, maxValue); | ||
| } | ||
| function parsePrice(value) { | ||
| const parsed = parseFloat(value); | ||
| if (Number.isNaN(parsed) || parsed < 0) { | ||
| throw new Error("Price must be a non-negative number"); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parseIntOrThrow(value, fieldName) { | ||
| const parsed = parseInt(value, 10); | ||
| if (Number.isNaN(parsed)) { | ||
| throw new Error(`${fieldName} must be a valid integer`); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parsePositiveInt(value, fieldName) { | ||
| const parsed = parseIntOrThrow(value, fieldName); | ||
| if (parsed < 1) { | ||
| throw new Error(`${fieldName} must be a positive integer`); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parseNonNegativeInt(value, fieldName) { | ||
| const parsed = parseIntOrThrow(value, fieldName); | ||
| if (parsed < 0) { | ||
| throw new Error(`${fieldName} must be a non-negative integer`); | ||
| } | ||
| return parsed; | ||
| } | ||
| function parseBoolean(value) { | ||
| if (value === void 0) return void 0; | ||
| if (value === "true" || value === "1") return true; | ||
| if (value === "false" || value === "0") return false; | ||
| return void 0; | ||
| } | ||
| function requireOption(value, name) { | ||
| if (value === void 0 || value === null || value === "") { | ||
| throw new Error(`Missing required option: --${name}`); | ||
| } | ||
| return value; | ||
| } | ||
| async function handleCommand(fn, options, outputFn, errorFn) { | ||
| try { | ||
| const result = await fn(); | ||
| outputFn(result, options.json); | ||
| process.exit(0); | ||
| } catch (error) { | ||
| const message = formatError(error); | ||
| errorFn(message, options.json); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| export { | ||
| configManager, | ||
| getClient, | ||
| getSocket, | ||
| formatError, | ||
| parseLimit, | ||
| parsePrice, | ||
| parseIntOrThrow, | ||
| parsePositiveInt, | ||
| parseNonNegativeInt, | ||
| parseBoolean, | ||
| requireOption, | ||
| handleCommand | ||
| }; |
| import { | ||
| formatError, | ||
| getClient, | ||
| getSocket, | ||
| handleCommand, | ||
| parseBoolean, | ||
| parseIntOrThrow, | ||
| parseLimit, | ||
| parseNonNegativeInt, | ||
| parsePositiveInt, | ||
| parsePrice, | ||
| requireOption | ||
| } from "./chunk-T3BKMB7N.js"; | ||
| export { | ||
| formatError, | ||
| getClient, | ||
| getSocket, | ||
| handleCommand, | ||
| parseBoolean, | ||
| parseIntOrThrow, | ||
| parseLimit, | ||
| parseNonNegativeInt, | ||
| parsePositiveInt, | ||
| parsePrice, | ||
| requireOption | ||
| }; |
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 2 instances
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 6 instances
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
230780
53.19%4495
70.65%25
257.14%4
300%+ Added
+ Added
- Removed
- Removed
Updated