@soat/cli 0.5.3 → 0.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/soat CHANGED
@@ -1,2 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import('../dist/esm/index.js');
2
+ import('../dist/esm/index.js').then(({ runCli }) => {
3
+ return runCli(process.argv);
4
+ });
package/dist/esm/index.js CHANGED
@@ -8,20 +8,21 @@ var __name = (target, value) => __defProp(target, "name", {
8
8
  // src/index.ts
9
9
  import { createHmac, timingSafeEqual } from "crypto";
10
10
  import { createServer } from "http";
11
- import input from "@inquirer/input";
12
- import password from "@inquirer/password";
11
+ import * as nodePath from "path";
12
+ import { fileURLToPath } from "url";
13
13
  import * as sdk from "@soat/sdk";
14
14
  import { program } from "commander";
15
15
 
16
16
  // package.json
17
17
  var package_default = {
18
18
  name: "@soat/cli",
19
- version: "0.5.3",
19
+ version: "0.5.5",
20
20
  type: "module",
21
21
  scripts: {
22
22
  generate: "tsx scripts/generate.ts",
23
- lint: "eslint src",
24
- typecheck: "tsc --noEmit",
23
+ lint: "eslint src tests",
24
+ test: "pnpm --filter @soat/sdk build && jest --config tests/unit/jest.config.ts --coverage=false",
25
+ typecheck: "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
25
26
  build: "pnpm generate && tsup"
26
27
  },
27
28
  dependencies: {
@@ -29,15 +30,16 @@ var package_default = {
29
30
  "@inquirer/password": "^5.0.12",
30
31
  "@soat/sdk": "workspace:*",
31
32
  "@ttoss/logger": "^0.8.10",
32
- commander: "^14.0.3"
33
+ commander: "^14.0.3",
34
+ "js-yaml": "^4.1.1"
33
35
  },
34
36
  devDependencies: {
35
37
  "@ttoss/config": "^1.37.10",
38
+ "@ttoss/test-utils": "^4.2.10",
36
39
  "@types/jest": "^30.0.0",
37
40
  "@types/js-yaml": "^4.0.9",
38
41
  "@types/node": "^24",
39
42
  jest: "^30.3.0",
40
- "js-yaml": "^4.1.1",
41
43
  tsup: "^8.5.1",
42
44
  tsx: "^4.21.0"
43
45
  },
@@ -55,15 +57,246 @@ var package_default = {
55
57
  }
56
58
  };
57
59
 
58
- // src/config.ts
60
+ // src/cli-wrappers/wrappers/agentFormations.ts
59
61
  import * as fs from "fs";
62
+ import yaml from "js-yaml";
63
+ var FORMATION_COMMANDS = ["validate-agent-formation", "plan-agent-formation", "create-agent-formation", "update-agent-formation"];
64
+ var TEMPLATE_PATH_FLAG = "template-path";
65
+ var TEMPLATE_FILE_FLAG = "template-file";
66
+ var ENV_FILE_FLAG = "env-file";
67
+ var PARAMETER_FLAG = "parameter";
68
+ var TEMPLATE_FIELD = "template";
69
+ var PARAMETERS_FIELD = "parameters";
70
+ var parseEnvFile = /* @__PURE__ */__name(args => {
71
+ const {
72
+ envPath
73
+ } = args;
74
+ let content;
75
+ try {
76
+ content = fs.readFileSync(envPath, "utf8");
77
+ } catch {
78
+ throw new Error(`Unable to read env file: ${envPath}`);
79
+ }
80
+ const vars = {};
81
+ for (const rawLine of content.split(/\r?\n/)) {
82
+ const line = rawLine.trim();
83
+ if (!line || line.startsWith("#")) continue;
84
+ const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
85
+ const eqIdx = withoutExport.indexOf("=");
86
+ if (eqIdx <= 0) continue;
87
+ const key = withoutExport.slice(0, eqIdx).trim();
88
+ let value = withoutExport.slice(eqIdx + 1).trim();
89
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
90
+ value = value.slice(1, -1);
91
+ }
92
+ vars[key] = value;
93
+ }
94
+ return vars;
95
+ }, "parseEnvFile");
96
+ var readTemplateFromPath = /* @__PURE__ */__name(args => {
97
+ const {
98
+ templatePath
99
+ } = args;
100
+ let content;
101
+ try {
102
+ content = fs.readFileSync(templatePath, "utf8");
103
+ } catch {
104
+ throw new Error(`Unable to read template file: ${templatePath}`);
105
+ }
106
+ const trimmed = content.trim();
107
+ if (!trimmed) {
108
+ throw new Error(`Template file is empty: ${templatePath}`);
109
+ }
110
+ try {
111
+ return JSON.parse(trimmed);
112
+ } catch {}
113
+ try {
114
+ return yaml.load(trimmed);
115
+ } catch {
116
+ throw new Error(`Template file must contain valid JSON or YAML: ${templatePath}`);
117
+ }
118
+ }, "readTemplateFromPath");
119
+ var resolveEnvRef = /* @__PURE__ */__name(args => {
120
+ const {
121
+ value,
122
+ env
123
+ } = args;
124
+ const simple = /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);
125
+ if (simple) {
126
+ const resolved = env[simple[1]];
127
+ if (resolved === void 0) {
128
+ throw new Error(`Missing environment variable: ${simple[1]}`);
129
+ }
130
+ return resolved;
131
+ }
132
+ const bracketed = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(value);
133
+ if (bracketed) {
134
+ const resolved = env[bracketed[1]];
135
+ if (resolved === void 0) {
136
+ throw new Error(`Missing environment variable: ${bracketed[1]}`);
137
+ }
138
+ return resolved;
139
+ }
140
+ return value;
141
+ }, "resolveEnvRef");
142
+ var agentFormationsWrapper = {
143
+ id: "agent-formations-wrapper",
144
+ commands: FORMATION_COMMANDS,
145
+ // eslint-disable-next-line complexity
146
+ apply: /* @__PURE__ */__name(({
147
+ context
148
+ }) => {
149
+ const forcedBody = {};
150
+ const flags = {
151
+ single: {
152
+ ...context.parsedFlags.single
153
+ },
154
+ repeated: {
155
+ ...context.parsedFlags.repeated
156
+ }
157
+ };
158
+ const templatePath = flags.single[TEMPLATE_PATH_FLAG];
159
+ const templateFile = flags.single[TEMPLATE_FILE_FLAG];
160
+ const templateInline = flags.single[TEMPLATE_FIELD];
161
+ const parametersInline = flags.single[PARAMETERS_FIELD];
162
+ const parameterValues = flags.repeated[PARAMETER_FLAG] ?? [];
163
+ const envFile = flags.single[ENV_FILE_FLAG];
164
+ if (templatePath && templateFile) {
165
+ throw new Error(`Use either --${TEMPLATE_PATH_FLAG} or --${TEMPLATE_FILE_FLAG}, not both.`);
166
+ }
167
+ const effectiveTemplatePath = templatePath ?? templateFile;
168
+ if (templateInline && effectiveTemplatePath) {
169
+ throw new Error(`Use either --${TEMPLATE_FIELD} or --${TEMPLATE_PATH_FLAG}, not both.`);
170
+ }
171
+ if (parametersInline && parameterValues.length > 0) {
172
+ throw new Error(`Use either --${PARAMETERS_FIELD} or repeatable --${PARAMETER_FLAG}, not both.`);
173
+ }
174
+ let envFileVars = {};
175
+ if (envFile) {
176
+ envFileVars = parseEnvFile({
177
+ envPath: envFile
178
+ });
179
+ }
180
+ const mergedEnv = {
181
+ ...envFileVars,
182
+ ...process.env
183
+ };
184
+ if (effectiveTemplatePath) {
185
+ forcedBody[TEMPLATE_FIELD] = readTemplateFromPath({
186
+ templatePath: effectiveTemplatePath
187
+ });
188
+ }
189
+ if (parameterValues.length > 0) {
190
+ const resolvedParameters = {};
191
+ for (const pair of parameterValues) {
192
+ const eqIdx = pair.indexOf("=");
193
+ if (eqIdx <= 0) {
194
+ throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Expected key=value.`);
195
+ }
196
+ const key = pair.slice(0, eqIdx).trim();
197
+ const rawValue = pair.slice(eqIdx + 1);
198
+ if (!key) {
199
+ throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Parameter key cannot be empty.`);
200
+ }
201
+ resolvedParameters[key] = resolveEnvRef({
202
+ value: rawValue,
203
+ env: mergedEnv
204
+ });
205
+ }
206
+ forcedBody[PARAMETERS_FIELD] = resolvedParameters;
207
+ }
208
+ delete flags.single[TEMPLATE_PATH_FLAG];
209
+ delete flags.single[TEMPLATE_FILE_FLAG];
210
+ delete flags.single[ENV_FILE_FLAG];
211
+ delete flags.single[PARAMETER_FLAG];
212
+ delete flags.repeated[PARAMETER_FLAG];
213
+ return {
214
+ flags,
215
+ forcedBody
216
+ };
217
+ }, "apply")
218
+ };
219
+
220
+ // src/cli-wrappers/flagParser.ts
221
+ var parseUnknownWithRepeats = /* @__PURE__ */__name(args => {
222
+ const {
223
+ cliArgs
224
+ } = args;
225
+ const single = {};
226
+ const repeated = {};
227
+ for (let i = 0; i < cliArgs.length; i++) {
228
+ const arg = cliArgs[i];
229
+ if (!arg?.startsWith("--")) continue;
230
+ const inlineSplitIdx = arg.indexOf("=");
231
+ const hasInlineValue = inlineSplitIdx > 2;
232
+ const key = hasInlineValue ? arg.slice(2, inlineSplitIdx) : arg.slice(2);
233
+ let value;
234
+ if (hasInlineValue) {
235
+ value = arg.slice(inlineSplitIdx + 1);
236
+ } else {
237
+ const next = cliArgs[i + 1];
238
+ if (next !== void 0 && !next.startsWith("--")) {
239
+ value = next;
240
+ i++;
241
+ } else {
242
+ value = "true";
243
+ }
244
+ }
245
+ single[key] = value;
246
+ if (!repeated[key]) {
247
+ repeated[key] = [];
248
+ }
249
+ repeated[key].push(value);
250
+ }
251
+ return {
252
+ single,
253
+ repeated
254
+ };
255
+ }, "parseUnknownWithRepeats");
256
+
257
+ // src/cli-wrappers/index.ts
258
+ var WRAPPERS = [agentFormationsWrapper];
259
+ var resolveWrapperForCommand = /* @__PURE__ */__name(args => {
260
+ const {
261
+ commandName
262
+ } = args;
263
+ return WRAPPERS.find(wrapper => {
264
+ return wrapper.commands.includes(commandName);
265
+ });
266
+ }, "resolveWrapperForCommand");
267
+ var applyWrapperForCommand = /* @__PURE__ */__name(args => {
268
+ const {
269
+ commandName,
270
+ route,
271
+ parsedFlags
272
+ } = args;
273
+ const wrapper = resolveWrapperForCommand({
274
+ commandName
275
+ });
276
+ if (!wrapper) {
277
+ return {
278
+ flags: parsedFlags,
279
+ forcedBody: {}
280
+ };
281
+ }
282
+ return wrapper.apply({
283
+ context: {
284
+ commandName,
285
+ route,
286
+ parsedFlags
287
+ }
288
+ });
289
+ }, "applyWrapperForCommand");
290
+
291
+ // src/config.ts
292
+ import * as fs2 from "fs";
60
293
  import * as os from "os";
61
294
  import * as path from "path";
62
295
  import { createClient, createConfig } from "@soat/sdk";
63
296
  var CONFIG_FILE = path.join(os.homedir(), ".soat", "config.json");
64
297
  var readConfig = /* @__PURE__ */__name(() => {
65
298
  try {
66
- return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
299
+ return JSON.parse(fs2.readFileSync(CONFIG_FILE, "utf8"));
67
300
  } catch {
68
301
  return {};
69
302
  }
@@ -71,10 +304,10 @@ var readConfig = /* @__PURE__ */__name(() => {
71
304
  var writeProfile = /* @__PURE__ */__name((name, profile) => {
72
305
  const config = readConfig();
73
306
  config[name] = profile;
74
- fs.mkdirSync(path.dirname(CONFIG_FILE), {
307
+ fs2.mkdirSync(path.dirname(CONFIG_FILE), {
75
308
  recursive: true
76
309
  });
77
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
310
+ fs2.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
78
311
  }, "writeProfile");
79
312
  var resolveClient = /* @__PURE__ */__name(profileName => {
80
313
  const envBaseUrl = process.env["SOAT_BASE_URL"];
@@ -1058,23 +1291,6 @@ var toCanonical = /* @__PURE__ */__name(s => {
1058
1291
  var kebabToSnake = /* @__PURE__ */__name(s => {
1059
1292
  return s.replace(/-/g, "_");
1060
1293
  }, "kebabToSnake");
1061
- var parseUnknown = /* @__PURE__ */__name(args => {
1062
- const result = {};
1063
- for (let i = 0; i < args.length; i++) {
1064
- const arg = args[i];
1065
- if (arg?.startsWith("--")) {
1066
- const key = arg.slice(2);
1067
- const val = args[i + 1];
1068
- if (val !== void 0 && !val.startsWith("--")) {
1069
- result[key] = val;
1070
- i++;
1071
- } else {
1072
- result[key] = "true";
1073
- }
1074
- }
1075
- }
1076
- return result;
1077
- }, "parseUnknown");
1078
1294
  var parseFlagValue = /* @__PURE__ */__name(value => {
1079
1295
  const trimmed = value.trim();
1080
1296
  if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed === "true" || trimmed === "false" || trimmed === "null" || /^-?\d+(\.\d+)?$/.test(trimmed)) {
@@ -1104,6 +1320,11 @@ var resolveServiceClass = /* @__PURE__ */__name(serviceClassName => {
1104
1320
  }, "resolveServiceClass");
1105
1321
  program.name("soat").description("SOAT CLI").version(package_default.version).option("-p, --profile <name>", "config profile to use");
1106
1322
  program.command("configure").description("Save credentials to a named profile (~/.soat/config.json)").option("-p, --profile <name>", "profile name", "default").action(async opts => {
1323
+ const [{
1324
+ default: input
1325
+ }, {
1326
+ default: password
1327
+ }] = await Promise.all([import("@inquirer/input"), import("@inquirer/password")]);
1107
1328
  const baseUrl = await input({
1108
1329
  message: "Base URL:"
1109
1330
  });
@@ -1250,7 +1471,15 @@ program.argument("[command]", "API command in kebab-case (e.g. list-actors)").ar
1250
1471
  }
1251
1472
  const rawIdx = process.argv.indexOf(commandName);
1252
1473
  const rawArgs = rawIdx >= 0 ? process.argv.slice(rawIdx + 1) : [];
1253
- const flags = parseUnknown(rawArgs);
1474
+ const parsedFlags = parseUnknownWithRepeats({
1475
+ cliArgs: rawArgs
1476
+ });
1477
+ const wrapped = applyWrapperForCommand({
1478
+ commandName,
1479
+ route,
1480
+ parsedFlags
1481
+ });
1482
+ const flags = wrapped.flags.single;
1254
1483
  const pathArgs = {};
1255
1484
  const queryArgs = {};
1256
1485
  const bodyArgs = {};
@@ -1287,6 +1516,9 @@ program.argument("[command]", "API command in kebab-case (e.g. list-actors)").ar
1287
1516
  const callOpts = {
1288
1517
  client
1289
1518
  };
1519
+ if (Object.keys(wrapped.forcedBody).length) {
1520
+ Object.assign(bodyArgs, wrapped.forcedBody);
1521
+ }
1290
1522
  if (Object.keys(pathArgs).length) callOpts["path"] = pathArgs;
1291
1523
  if (Object.keys(queryArgs).length) callOpts["query"] = queryArgs;
1292
1524
  if (Object.keys(bodyArgs).length) callOpts["body"] = bodyArgs;
@@ -1305,4 +1537,20 @@ program.argument("[command]", "API command in kebab-case (e.g. list-actors)").ar
1305
1537
  }
1306
1538
  console.log(JSON.stringify(result.data, null, 2));
1307
1539
  });
1308
- program.parse();
1540
+ var runCli = /* @__PURE__ */__name(async args => {
1541
+ const previousArgv = process.argv;
1542
+ process.argv = args;
1543
+ try {
1544
+ await program.parseAsync(args);
1545
+ } finally {
1546
+ process.argv = previousArgv;
1547
+ }
1548
+ }, "runCli");
1549
+ var isMainModule = (() => {
1550
+ if (!process.argv[1]) return false;
1551
+ return nodePath.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
1552
+ })();
1553
+ if (isMainModule) {
1554
+ void runCli(process.argv);
1555
+ }
1556
+ export { runCli };
package/package.json CHANGED
@@ -1,21 +1,22 @@
1
1
  {
2
2
  "name": "@soat/cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.5",
4
4
  "type": "module",
5
5
  "dependencies": {
6
6
  "@inquirer/input": "^5.0.12",
7
7
  "@inquirer/password": "^5.0.12",
8
8
  "@ttoss/logger": "^0.8.10",
9
9
  "commander": "^14.0.3",
10
- "@soat/sdk": "0.5.3"
10
+ "js-yaml": "^4.1.1",
11
+ "@soat/sdk": "0.5.5"
11
12
  },
12
13
  "devDependencies": {
13
14
  "@ttoss/config": "^1.37.10",
15
+ "@ttoss/test-utils": "^4.2.10",
14
16
  "@types/jest": "^30.0.0",
15
17
  "@types/js-yaml": "^4.0.9",
16
18
  "@types/node": "^24",
17
19
  "jest": "^30.3.0",
18
- "js-yaml": "^4.1.1",
19
20
  "tsup": "^8.5.1",
20
21
  "tsx": "^4.21.0"
21
22
  },
@@ -36,8 +37,9 @@
36
37
  },
37
38
  "scripts": {
38
39
  "generate": "tsx scripts/generate.ts",
39
- "lint": "eslint src",
40
- "typecheck": "tsc --noEmit",
40
+ "lint": "eslint src tests",
41
+ "test": "pnpm --filter @soat/sdk build && jest --config tests/unit/jest.config.ts --coverage=false",
42
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
41
43
  "build": "pnpm generate && tsup"
42
44
  }
43
45
  }