@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 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
+2
-2
| { | ||
| "name": "@shadowob/cli", | ||
| "version": "0.4.0", | ||
| "version": "0.4.1", | ||
| "description": "Shadow CLI — command-line interface for Shadow servers", | ||
@@ -15,3 +15,3 @@ "type": "module", | ||
| "chalk": "^5.4.1", | ||
| "@shadowob/sdk": "0.4.0" | ||
| "@shadowob/sdk": "0.4.1" | ||
| }, | ||
@@ -18,0 +18,0 @@ "devDependencies": { |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances
Empty package
Supply chain riskPackage does not contain any code. It may be removed, is name squatting, or the result of a faulty package publish.
136845
290.49%5
150%2363
Infinity%5
400%+ Added
+ Added
- Removed
- Removed
Updated