+278
-30
@@ -11,4 +11,4 @@ /** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */ | ||
| import { createServer } from "http"; | ||
| import input from "@inquirer/input"; | ||
| import password from "@inquirer/password"; | ||
| import * as nodePath from "path"; | ||
| import { fileURLToPath } from "url"; | ||
| import * as sdk from "@soat/sdk"; | ||
@@ -20,8 +20,9 @@ import { program } from "commander"; | ||
| name: "@soat/cli", | ||
| version: "0.5.4", | ||
| version: "0.5.5", | ||
| type: "module", | ||
| scripts: { | ||
| generate: "tsx scripts/generate.ts", | ||
| lint: "eslint src", | ||
| typecheck: "tsc --noEmit", | ||
| lint: "eslint src tests", | ||
| test: "pnpm --filter @soat/sdk build && jest --config tests/unit/jest.config.ts --coverage=false", | ||
| typecheck: "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json", | ||
| build: "pnpm generate && tsup" | ||
@@ -34,6 +35,8 @@ }, | ||
| "@ttoss/logger": "^0.8.10", | ||
| commander: "^14.0.3" | ||
| commander: "^14.0.3", | ||
| "js-yaml": "^4.1.1" | ||
| }, | ||
| devDependencies: { | ||
| "@ttoss/config": "^1.37.10", | ||
| "@ttoss/test-utils": "^4.2.10", | ||
| "@types/jest": "^30.0.0", | ||
@@ -43,3 +46,2 @@ "@types/js-yaml": "^4.0.9", | ||
| jest: "^30.3.0", | ||
| "js-yaml": "^4.1.1", | ||
| tsup: "^8.5.1", | ||
@@ -62,4 +64,235 @@ tsx: "^4.21.0" | ||
| // src/cli-wrappers/wrappers/agentFormations.ts | ||
| import * as fs from "fs"; | ||
| import yaml from "js-yaml"; | ||
| var FORMATION_COMMANDS = ["validate-agent-formation", "plan-agent-formation", "create-agent-formation", "update-agent-formation"]; | ||
| var TEMPLATE_PATH_FLAG = "template-path"; | ||
| var TEMPLATE_FILE_FLAG = "template-file"; | ||
| var ENV_FILE_FLAG = "env-file"; | ||
| var PARAMETER_FLAG = "parameter"; | ||
| var TEMPLATE_FIELD = "template"; | ||
| var PARAMETERS_FIELD = "parameters"; | ||
| var parseEnvFile = /* @__PURE__ */__name(args => { | ||
| const { | ||
| envPath | ||
| } = args; | ||
| let content; | ||
| try { | ||
| content = fs.readFileSync(envPath, "utf8"); | ||
| } catch { | ||
| throw new Error(`Unable to read env file: ${envPath}`); | ||
| } | ||
| const vars = {}; | ||
| for (const rawLine of content.split(/\r?\n/)) { | ||
| const line = rawLine.trim(); | ||
| if (!line || line.startsWith("#")) continue; | ||
| const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line; | ||
| const eqIdx = withoutExport.indexOf("="); | ||
| if (eqIdx <= 0) continue; | ||
| const key = withoutExport.slice(0, eqIdx).trim(); | ||
| let value = withoutExport.slice(eqIdx + 1).trim(); | ||
| if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) { | ||
| value = value.slice(1, -1); | ||
| } | ||
| vars[key] = value; | ||
| } | ||
| return vars; | ||
| }, "parseEnvFile"); | ||
| var readTemplateFromPath = /* @__PURE__ */__name(args => { | ||
| const { | ||
| templatePath | ||
| } = args; | ||
| let content; | ||
| try { | ||
| content = fs.readFileSync(templatePath, "utf8"); | ||
| } catch { | ||
| throw new Error(`Unable to read template file: ${templatePath}`); | ||
| } | ||
| const trimmed = content.trim(); | ||
| if (!trimmed) { | ||
| throw new Error(`Template file is empty: ${templatePath}`); | ||
| } | ||
| try { | ||
| return JSON.parse(trimmed); | ||
| } catch {} | ||
| try { | ||
| return yaml.load(trimmed); | ||
| } catch { | ||
| throw new Error(`Template file must contain valid JSON or YAML: ${templatePath}`); | ||
| } | ||
| }, "readTemplateFromPath"); | ||
| var resolveEnvRef = /* @__PURE__ */__name(args => { | ||
| const { | ||
| value, | ||
| env | ||
| } = args; | ||
| const simple = /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value); | ||
| if (simple) { | ||
| const resolved = env[simple[1]]; | ||
| if (resolved === void 0) { | ||
| throw new Error(`Missing environment variable: ${simple[1]}`); | ||
| } | ||
| return resolved; | ||
| } | ||
| const bracketed = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(value); | ||
| if (bracketed) { | ||
| const resolved = env[bracketed[1]]; | ||
| if (resolved === void 0) { | ||
| throw new Error(`Missing environment variable: ${bracketed[1]}`); | ||
| } | ||
| return resolved; | ||
| } | ||
| return value; | ||
| }, "resolveEnvRef"); | ||
| var agentFormationsWrapper = { | ||
| id: "agent-formations-wrapper", | ||
| commands: FORMATION_COMMANDS, | ||
| // eslint-disable-next-line complexity | ||
| apply: /* @__PURE__ */__name(({ | ||
| context | ||
| }) => { | ||
| const forcedBody = {}; | ||
| const flags = { | ||
| single: { | ||
| ...context.parsedFlags.single | ||
| }, | ||
| repeated: { | ||
| ...context.parsedFlags.repeated | ||
| } | ||
| }; | ||
| const templatePath = flags.single[TEMPLATE_PATH_FLAG]; | ||
| const templateFile = flags.single[TEMPLATE_FILE_FLAG]; | ||
| const templateInline = flags.single[TEMPLATE_FIELD]; | ||
| const parametersInline = flags.single[PARAMETERS_FIELD]; | ||
| const parameterValues = flags.repeated[PARAMETER_FLAG] ?? []; | ||
| const envFile = flags.single[ENV_FILE_FLAG]; | ||
| if (templatePath && templateFile) { | ||
| throw new Error(`Use either --${TEMPLATE_PATH_FLAG} or --${TEMPLATE_FILE_FLAG}, not both.`); | ||
| } | ||
| const effectiveTemplatePath = templatePath ?? templateFile; | ||
| if (templateInline && effectiveTemplatePath) { | ||
| throw new Error(`Use either --${TEMPLATE_FIELD} or --${TEMPLATE_PATH_FLAG}, not both.`); | ||
| } | ||
| if (parametersInline && parameterValues.length > 0) { | ||
| throw new Error(`Use either --${PARAMETERS_FIELD} or repeatable --${PARAMETER_FLAG}, not both.`); | ||
| } | ||
| let envFileVars = {}; | ||
| if (envFile) { | ||
| envFileVars = parseEnvFile({ | ||
| envPath: envFile | ||
| }); | ||
| } | ||
| const mergedEnv = { | ||
| ...envFileVars, | ||
| ...process.env | ||
| }; | ||
| if (effectiveTemplatePath) { | ||
| forcedBody[TEMPLATE_FIELD] = readTemplateFromPath({ | ||
| templatePath: effectiveTemplatePath | ||
| }); | ||
| } | ||
| if (parameterValues.length > 0) { | ||
| const resolvedParameters = {}; | ||
| for (const pair of parameterValues) { | ||
| const eqIdx = pair.indexOf("="); | ||
| if (eqIdx <= 0) { | ||
| throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Expected key=value.`); | ||
| } | ||
| const key = pair.slice(0, eqIdx).trim(); | ||
| const rawValue = pair.slice(eqIdx + 1); | ||
| if (!key) { | ||
| throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Parameter key cannot be empty.`); | ||
| } | ||
| resolvedParameters[key] = resolveEnvRef({ | ||
| value: rawValue, | ||
| env: mergedEnv | ||
| }); | ||
| } | ||
| forcedBody[PARAMETERS_FIELD] = resolvedParameters; | ||
| } | ||
| delete flags.single[TEMPLATE_PATH_FLAG]; | ||
| delete flags.single[TEMPLATE_FILE_FLAG]; | ||
| delete flags.single[ENV_FILE_FLAG]; | ||
| delete flags.single[PARAMETER_FLAG]; | ||
| delete flags.repeated[PARAMETER_FLAG]; | ||
| return { | ||
| flags, | ||
| forcedBody | ||
| }; | ||
| }, "apply") | ||
| }; | ||
| // src/cli-wrappers/flagParser.ts | ||
| var parseUnknownWithRepeats = /* @__PURE__ */__name(args => { | ||
| const { | ||
| cliArgs | ||
| } = args; | ||
| const single = {}; | ||
| const repeated = {}; | ||
| for (let i = 0; i < cliArgs.length; i++) { | ||
| const arg = cliArgs[i]; | ||
| if (!arg?.startsWith("--")) continue; | ||
| const inlineSplitIdx = arg.indexOf("="); | ||
| const hasInlineValue = inlineSplitIdx > 2; | ||
| const key = hasInlineValue ? arg.slice(2, inlineSplitIdx) : arg.slice(2); | ||
| let value; | ||
| if (hasInlineValue) { | ||
| value = arg.slice(inlineSplitIdx + 1); | ||
| } else { | ||
| const next = cliArgs[i + 1]; | ||
| if (next !== void 0 && !next.startsWith("--")) { | ||
| value = next; | ||
| i++; | ||
| } else { | ||
| value = "true"; | ||
| } | ||
| } | ||
| single[key] = value; | ||
| if (!repeated[key]) { | ||
| repeated[key] = []; | ||
| } | ||
| repeated[key].push(value); | ||
| } | ||
| return { | ||
| single, | ||
| repeated | ||
| }; | ||
| }, "parseUnknownWithRepeats"); | ||
| // src/cli-wrappers/index.ts | ||
| var WRAPPERS = [agentFormationsWrapper]; | ||
| var resolveWrapperForCommand = /* @__PURE__ */__name(args => { | ||
| const { | ||
| commandName | ||
| } = args; | ||
| return WRAPPERS.find(wrapper => { | ||
| return wrapper.commands.includes(commandName); | ||
| }); | ||
| }, "resolveWrapperForCommand"); | ||
| var applyWrapperForCommand = /* @__PURE__ */__name(args => { | ||
| const { | ||
| commandName, | ||
| route, | ||
| parsedFlags | ||
| } = args; | ||
| const wrapper = resolveWrapperForCommand({ | ||
| commandName | ||
| }); | ||
| if (!wrapper) { | ||
| return { | ||
| flags: parsedFlags, | ||
| forcedBody: {} | ||
| }; | ||
| } | ||
| return wrapper.apply({ | ||
| context: { | ||
| commandName, | ||
| route, | ||
| parsedFlags | ||
| } | ||
| }); | ||
| }, "applyWrapperForCommand"); | ||
| // src/config.ts | ||
| import * as fs from "fs"; | ||
| import * as fs2 from "fs"; | ||
| import * as os from "os"; | ||
@@ -71,3 +304,3 @@ import * as path from "path"; | ||
| try { | ||
| return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8")); | ||
| return JSON.parse(fs2.readFileSync(CONFIG_FILE, "utf8")); | ||
| } catch { | ||
@@ -80,6 +313,6 @@ return {}; | ||
| config[name] = profile; | ||
| fs.mkdirSync(path.dirname(CONFIG_FILE), { | ||
| fs2.mkdirSync(path.dirname(CONFIG_FILE), { | ||
| recursive: true | ||
| }); | ||
| fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); | ||
| fs2.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2)); | ||
| }, "writeProfile"); | ||
@@ -1068,19 +1301,2 @@ var resolveClient = /* @__PURE__ */__name(profileName => { | ||
| }, "kebabToSnake"); | ||
| var parseUnknown = /* @__PURE__ */__name(args => { | ||
| const result = {}; | ||
| for (let i = 0; i < args.length; i++) { | ||
| const arg = args[i]; | ||
| if (arg?.startsWith("--")) { | ||
| const key = arg.slice(2); | ||
| const val = args[i + 1]; | ||
| if (val !== void 0 && !val.startsWith("--")) { | ||
| result[key] = val; | ||
| i++; | ||
| } else { | ||
| result[key] = "true"; | ||
| } | ||
| } | ||
| } | ||
| return result; | ||
| }, "parseUnknown"); | ||
| var parseFlagValue = /* @__PURE__ */__name(value => { | ||
@@ -1115,2 +1331,7 @@ const trimmed = value.trim(); | ||
| program.command("configure").description("Save credentials to a named profile (~/.soat/config.json)").option("-p, --profile <name>", "profile name", "default").action(async opts => { | ||
| const [{ | ||
| default: input | ||
| }, { | ||
| default: password | ||
| }] = await Promise.all([import("@inquirer/input"), import("@inquirer/password")]); | ||
| const baseUrl = await input({ | ||
@@ -1262,3 +1483,11 @@ message: "Base URL:" | ||
| const rawArgs = rawIdx >= 0 ? process.argv.slice(rawIdx + 1) : []; | ||
| const flags = parseUnknown(rawArgs); | ||
| const parsedFlags = parseUnknownWithRepeats({ | ||
| cliArgs: rawArgs | ||
| }); | ||
| const wrapped = applyWrapperForCommand({ | ||
| commandName, | ||
| route, | ||
| parsedFlags | ||
| }); | ||
| const flags = wrapped.flags.single; | ||
| const pathArgs = {}; | ||
@@ -1300,2 +1529,5 @@ const queryArgs = {}; | ||
| }; | ||
| if (Object.keys(wrapped.forcedBody).length) { | ||
| Object.assign(bodyArgs, wrapped.forcedBody); | ||
| } | ||
| if (Object.keys(pathArgs).length) callOpts["path"] = pathArgs; | ||
@@ -1319,2 +1551,18 @@ if (Object.keys(queryArgs).length) callOpts["query"] = queryArgs; | ||
| }); | ||
| program.parse(); | ||
| var runCli = /* @__PURE__ */__name(async args => { | ||
| const previousArgv = process.argv; | ||
| process.argv = args; | ||
| try { | ||
| await program.parseAsync(args); | ||
| } finally { | ||
| process.argv = previousArgv; | ||
| } | ||
| }, "runCli"); | ||
| var isMainModule = (() => { | ||
| if (!process.argv[1]) return false; | ||
| return nodePath.resolve(process.argv[1]) === fileURLToPath(import.meta.url); | ||
| })(); | ||
| if (isMainModule) { | ||
| void runCli(process.argv); | ||
| } | ||
| export { runCli }; |
+7
-5
| { | ||
| "name": "@soat/cli", | ||
| "version": "0.5.4", | ||
| "version": "0.5.5", | ||
| "type": "module", | ||
@@ -10,6 +10,8 @@ "dependencies": { | ||
| "commander": "^14.0.3", | ||
| "@soat/sdk": "0.5.4" | ||
| "js-yaml": "^4.1.1", | ||
| "@soat/sdk": "0.5.5" | ||
| }, | ||
| "devDependencies": { | ||
| "@ttoss/config": "^1.37.10", | ||
| "@ttoss/test-utils": "^4.2.10", | ||
| "@types/jest": "^30.0.0", | ||
@@ -19,3 +21,2 @@ "@types/js-yaml": "^4.0.9", | ||
| "jest": "^30.3.0", | ||
| "js-yaml": "^4.1.1", | ||
| "tsup": "^8.5.1", | ||
@@ -41,6 +42,7 @@ "tsx": "^4.21.0" | ||
| "generate": "tsx scripts/generate.ts", | ||
| "lint": "eslint src", | ||
| "typecheck": "tsc --noEmit", | ||
| "lint": "eslint src tests", | ||
| "test": "pnpm --filter @soat/sdk build && jest --config tests/unit/jest.config.ts --coverage=false", | ||
| "typecheck": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json", | ||
| "build": "pnpm generate && tsup" | ||
| } | ||
| } |
Sorry, the diff of this file is not supported yet
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 4 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
47172
19.29%1548
18.8%6
20%7
40%+ Added
+ Added
+ Added
+ Added
- Removed
Updated