@soat/cli 0.6.5 → 0.6.10
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 +1 -1
- package/dist/{esm/index.js → index.mjs} +170 -274
- package/package.json +13 -13
package/bin/soat
CHANGED
|
@@ -1,65 +1,20 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
});
|
|
7
|
-
|
|
8
|
-
// src/index.ts
|
|
9
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
10
|
-
import { createServer } from "http";
|
|
11
|
-
import * as nodePath from "path";
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
2
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
3
|
+
import { createServer } from "node:http";
|
|
4
|
+
import * as path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
13
6
|
import * as sdk from "@soat/sdk";
|
|
7
|
+
import { createClient, createConfig } from "@soat/sdk";
|
|
14
8
|
import { program } from "commander";
|
|
9
|
+
import * as fs from "node:fs";
|
|
10
|
+
import yaml from "js-yaml";
|
|
11
|
+
import * as os from "node:os";
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
var
|
|
18
|
-
name: "@soat/cli",
|
|
19
|
-
version: "0.6.5",
|
|
20
|
-
type: "module",
|
|
21
|
-
scripts: {
|
|
22
|
-
generate: "tsx scripts/generate.ts",
|
|
23
|
-
lint: "eslint src tests",
|
|
24
|
-
test: "jest --config tests/unit/jest.config.ts --coverage=false",
|
|
25
|
-
typecheck: "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
|
|
26
|
-
build: "pnpm generate && tsup"
|
|
27
|
-
},
|
|
28
|
-
dependencies: {
|
|
29
|
-
"@inquirer/input": "^5.0.12",
|
|
30
|
-
"@inquirer/password": "^5.0.12",
|
|
31
|
-
"@soat/sdk": "workspace:*",
|
|
32
|
-
"@ttoss/logger": "^0.8.10",
|
|
33
|
-
commander: "^14.0.3",
|
|
34
|
-
"js-yaml": "^4.1.1"
|
|
35
|
-
},
|
|
36
|
-
devDependencies: {
|
|
37
|
-
"@ttoss/config": "^1.37.10",
|
|
38
|
-
"@ttoss/test-utils": "^4.2.10",
|
|
39
|
-
"@types/jest": "^30.0.0",
|
|
40
|
-
"@types/js-yaml": "^4.0.9",
|
|
41
|
-
"@types/node": "^24",
|
|
42
|
-
jest: "^30.3.0",
|
|
43
|
-
tsup: "^8.5.1",
|
|
44
|
-
tsx: "^4.21.0"
|
|
45
|
-
},
|
|
46
|
-
files: ["dist", "bin"],
|
|
47
|
-
repository: {
|
|
48
|
-
type: "git",
|
|
49
|
-
url: "https://github.com/ttoss/soat"
|
|
50
|
-
},
|
|
51
|
-
bin: {
|
|
52
|
-
soat: "./bin/soat"
|
|
53
|
-
},
|
|
54
|
-
publishConfig: {
|
|
55
|
-
access: "public",
|
|
56
|
-
provenance: true
|
|
57
|
-
}
|
|
58
|
-
};
|
|
13
|
+
//#region package.json
|
|
14
|
+
var version = "0.6.10";
|
|
59
15
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
import yaml from "js-yaml";
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/cli-wrappers/wrappers/formations.ts
|
|
63
18
|
var FORMATION_COMMANDS = ["validate-formation", "plan-formation", "create-formation", "update-formation"];
|
|
64
19
|
var TEMPLATE_PATH_FLAG = "template-path";
|
|
65
20
|
var TEMPLATE_FILE_FLAG = "template-file";
|
|
@@ -67,7 +22,7 @@ var ENV_FILE_FLAG = "env-file";
|
|
|
67
22
|
var PARAMETER_FLAG = "parameter";
|
|
68
23
|
var TEMPLATE_FIELD = "template";
|
|
69
24
|
var PARAMETERS_FIELD = "parameters";
|
|
70
|
-
var parseEnvFile =
|
|
25
|
+
var parseEnvFile = args => {
|
|
71
26
|
const {
|
|
72
27
|
envPath
|
|
73
28
|
} = args;
|
|
@@ -81,19 +36,17 @@ var parseEnvFile = /* @__PURE__ */__name(args => {
|
|
|
81
36
|
for (const rawLine of content.split(/\r?\n/)) {
|
|
82
37
|
const line = rawLine.trim();
|
|
83
38
|
if (!line || line.startsWith("#")) continue;
|
|
84
|
-
const withoutExport = line.startsWith("export ") ? line.slice(
|
|
39
|
+
const withoutExport = line.startsWith("export ") ? line.slice(7).trim() : line;
|
|
85
40
|
const eqIdx = withoutExport.indexOf("=");
|
|
86
41
|
if (eqIdx <= 0) continue;
|
|
87
42
|
const key = withoutExport.slice(0, eqIdx).trim();
|
|
88
43
|
let value = withoutExport.slice(eqIdx + 1).trim();
|
|
89
|
-
if (value.startsWith(
|
|
90
|
-
value = value.slice(1, -1);
|
|
91
|
-
}
|
|
44
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
92
45
|
vars[key] = value;
|
|
93
46
|
}
|
|
94
47
|
return vars;
|
|
95
|
-
}
|
|
96
|
-
var readTemplateFromPath =
|
|
48
|
+
};
|
|
49
|
+
var readTemplateFromPath = args => {
|
|
97
50
|
const {
|
|
98
51
|
templatePath
|
|
99
52
|
} = args;
|
|
@@ -104,9 +57,7 @@ var readTemplateFromPath = /* @__PURE__ */__name(args => {
|
|
|
104
57
|
throw new Error(`Unable to read template file: ${templatePath}`);
|
|
105
58
|
}
|
|
106
59
|
const trimmed = content.trim();
|
|
107
|
-
if (!trimmed) {
|
|
108
|
-
throw new Error(`Template file is empty: ${templatePath}`);
|
|
109
|
-
}
|
|
60
|
+
if (!trimmed) throw new Error(`Template file is empty: ${templatePath}`);
|
|
110
61
|
try {
|
|
111
62
|
return JSON.parse(trimmed);
|
|
112
63
|
} catch {}
|
|
@@ -115,8 +66,8 @@ var readTemplateFromPath = /* @__PURE__ */__name(args => {
|
|
|
115
66
|
} catch {
|
|
116
67
|
throw new Error(`Template file must contain valid JSON or YAML: ${templatePath}`);
|
|
117
68
|
}
|
|
118
|
-
}
|
|
119
|
-
var resolveEnvRef =
|
|
69
|
+
};
|
|
70
|
+
var resolveEnvRef = args => {
|
|
120
71
|
const {
|
|
121
72
|
value,
|
|
122
73
|
env
|
|
@@ -124,57 +75,43 @@ var resolveEnvRef = /* @__PURE__ */__name(args => {
|
|
|
124
75
|
const atRef = /^@([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);
|
|
125
76
|
if (atRef) {
|
|
126
77
|
const resolved = env[atRef[1]];
|
|
127
|
-
if (resolved === void 0) {
|
|
128
|
-
throw new Error(`Missing environment variable: ${atRef[1]}`);
|
|
129
|
-
}
|
|
78
|
+
if (resolved === void 0) throw new Error(`Missing environment variable: ${atRef[1]}`);
|
|
130
79
|
return resolved;
|
|
131
80
|
}
|
|
132
81
|
const simple = /^\$([A-Za-z_][A-Za-z0-9_]*)$/.exec(value);
|
|
133
82
|
if (simple) {
|
|
134
83
|
const resolved = env[simple[1]];
|
|
135
|
-
if (resolved === void 0) {
|
|
136
|
-
throw new Error(`Missing environment variable: ${simple[1]}`);
|
|
137
|
-
}
|
|
84
|
+
if (resolved === void 0) throw new Error(`Missing environment variable: ${simple[1]}`);
|
|
138
85
|
return resolved;
|
|
139
86
|
}
|
|
140
87
|
const bracketed = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/.exec(value);
|
|
141
88
|
if (bracketed) {
|
|
142
89
|
const resolved = env[bracketed[1]];
|
|
143
|
-
if (resolved === void 0) {
|
|
144
|
-
throw new Error(`Missing environment variable: ${bracketed[1]}`);
|
|
145
|
-
}
|
|
90
|
+
if (resolved === void 0) throw new Error(`Missing environment variable: ${bracketed[1]}`);
|
|
146
91
|
return resolved;
|
|
147
92
|
}
|
|
148
93
|
return value;
|
|
149
|
-
}
|
|
150
|
-
var resolveParameterPair =
|
|
94
|
+
};
|
|
95
|
+
var resolveParameterPair = args => {
|
|
151
96
|
const {
|
|
152
97
|
pair,
|
|
153
98
|
env
|
|
154
99
|
} = args;
|
|
155
100
|
const eqIdx = pair.indexOf("=");
|
|
156
|
-
if (eqIdx === 0) {
|
|
157
|
-
throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Parameter key cannot be empty.`);
|
|
158
|
-
}
|
|
101
|
+
if (eqIdx === 0) throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Parameter key cannot be empty.`);
|
|
159
102
|
if (eqIdx === -1) {
|
|
160
|
-
const
|
|
161
|
-
if (!
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
const resolved = env[key2];
|
|
165
|
-
if (resolved === void 0) {
|
|
166
|
-
throw new Error(`Missing environment variable: ${key2}`);
|
|
167
|
-
}
|
|
103
|
+
const key = pair.trim();
|
|
104
|
+
if (!key) throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Parameter key cannot be empty.`);
|
|
105
|
+
const resolved = env[key];
|
|
106
|
+
if (resolved === void 0) throw new Error(`Missing environment variable: ${key}`);
|
|
168
107
|
return {
|
|
169
|
-
key
|
|
108
|
+
key,
|
|
170
109
|
value: resolved
|
|
171
110
|
};
|
|
172
111
|
}
|
|
173
112
|
const key = pair.slice(0, eqIdx).trim();
|
|
174
113
|
const rawValue = pair.slice(eqIdx + 1);
|
|
175
|
-
if (!key) {
|
|
176
|
-
throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Parameter key cannot be empty.`);
|
|
177
|
-
}
|
|
114
|
+
if (!key) throw new Error(`Invalid --${PARAMETER_FLAG} value "${pair}". Parameter key cannot be empty.`);
|
|
178
115
|
return {
|
|
179
116
|
key,
|
|
180
117
|
value: resolveEnvRef({
|
|
@@ -182,7 +119,7 @@ var resolveParameterPair = /* @__PURE__ */__name(args => {
|
|
|
182
119
|
env
|
|
183
120
|
})
|
|
184
121
|
};
|
|
185
|
-
}
|
|
122
|
+
};
|
|
186
123
|
var formationsWrapper = {
|
|
187
124
|
id: "formations-wrapper",
|
|
188
125
|
commands: FORMATION_COMMANDS,
|
|
@@ -202,8 +139,7 @@ var formationsWrapper = {
|
|
|
202
139
|
required: false,
|
|
203
140
|
type: "string"
|
|
204
141
|
}],
|
|
205
|
-
|
|
206
|
-
apply: /* @__PURE__ */__name(({
|
|
142
|
+
apply: ({
|
|
207
143
|
context
|
|
208
144
|
}) => {
|
|
209
145
|
const forcedBody = {};
|
|
@@ -221,31 +157,21 @@ var formationsWrapper = {
|
|
|
221
157
|
const parametersInline = flags.single[PARAMETERS_FIELD];
|
|
222
158
|
const parameterValues = flags.repeated[PARAMETER_FLAG] ?? [];
|
|
223
159
|
const envFile = flags.single[ENV_FILE_FLAG];
|
|
224
|
-
if (templatePath && templateFile) {
|
|
225
|
-
throw new Error(`Use either --${TEMPLATE_PATH_FLAG} or --${TEMPLATE_FILE_FLAG}, not both.`);
|
|
226
|
-
}
|
|
160
|
+
if (templatePath && templateFile) throw new Error(`Use either --${TEMPLATE_PATH_FLAG} or --${TEMPLATE_FILE_FLAG}, not both.`);
|
|
227
161
|
const effectiveTemplatePath = templatePath ?? templateFile;
|
|
228
|
-
if (templateInline && effectiveTemplatePath) {
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
if (parametersInline && parameterValues.length > 0) {
|
|
232
|
-
throw new Error(`Use either --${PARAMETERS_FIELD} or repeatable --${PARAMETER_FLAG}, not both.`);
|
|
233
|
-
}
|
|
162
|
+
if (templateInline && effectiveTemplatePath) throw new Error(`Use either --${TEMPLATE_FIELD} or --${TEMPLATE_PATH_FLAG}, not both.`);
|
|
163
|
+
if (parametersInline && parameterValues.length > 0) throw new Error(`Use either --${PARAMETERS_FIELD} or repeatable --${PARAMETER_FLAG}, not both.`);
|
|
234
164
|
let envFileVars = {};
|
|
235
|
-
if (envFile) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
});
|
|
239
|
-
}
|
|
165
|
+
if (envFile) envFileVars = parseEnvFile({
|
|
166
|
+
envPath: envFile
|
|
167
|
+
});
|
|
240
168
|
const mergedEnv = {
|
|
241
169
|
...envFileVars,
|
|
242
170
|
...process.env
|
|
243
171
|
};
|
|
244
|
-
if (effectiveTemplatePath) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
});
|
|
248
|
-
}
|
|
172
|
+
if (effectiveTemplatePath) forcedBody[TEMPLATE_FIELD] = readTemplateFromPath({
|
|
173
|
+
templatePath: effectiveTemplatePath
|
|
174
|
+
});
|
|
249
175
|
if (parameterValues.length > 0) {
|
|
250
176
|
const resolvedParameters = {};
|
|
251
177
|
for (const pair of parameterValues) {
|
|
@@ -269,11 +195,12 @@ var formationsWrapper = {
|
|
|
269
195
|
flags,
|
|
270
196
|
forcedBody
|
|
271
197
|
};
|
|
272
|
-
}
|
|
198
|
+
}
|
|
273
199
|
};
|
|
274
200
|
|
|
275
|
-
|
|
276
|
-
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region src/cli-wrappers/flagParser.ts
|
|
203
|
+
var parseUnknownWithRepeats = args => {
|
|
277
204
|
const {
|
|
278
205
|
cliArgs
|
|
279
206
|
} = args;
|
|
@@ -286,40 +213,35 @@ var parseUnknownWithRepeats = /* @__PURE__ */__name(args => {
|
|
|
286
213
|
const hasInlineValue = inlineSplitIdx > 2;
|
|
287
214
|
const key = hasInlineValue ? arg.slice(2, inlineSplitIdx) : arg.slice(2);
|
|
288
215
|
let value;
|
|
289
|
-
if (hasInlineValue) {
|
|
290
|
-
value = arg.slice(inlineSplitIdx + 1);
|
|
291
|
-
} else {
|
|
216
|
+
if (hasInlineValue) value = arg.slice(inlineSplitIdx + 1);else {
|
|
292
217
|
const next = cliArgs[i + 1];
|
|
293
218
|
if (next !== void 0 && !next.startsWith("--")) {
|
|
294
219
|
value = next;
|
|
295
220
|
i++;
|
|
296
|
-
} else
|
|
297
|
-
value = "true";
|
|
298
|
-
}
|
|
221
|
+
} else value = "true";
|
|
299
222
|
}
|
|
300
223
|
single[key] = value;
|
|
301
|
-
if (!repeated[key])
|
|
302
|
-
repeated[key] = [];
|
|
303
|
-
}
|
|
224
|
+
if (!repeated[key]) repeated[key] = [];
|
|
304
225
|
repeated[key].push(value);
|
|
305
226
|
}
|
|
306
227
|
return {
|
|
307
228
|
single,
|
|
308
229
|
repeated
|
|
309
230
|
};
|
|
310
|
-
}
|
|
231
|
+
};
|
|
311
232
|
|
|
312
|
-
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/cli-wrappers/index.ts
|
|
313
235
|
var WRAPPERS = [formationsWrapper];
|
|
314
|
-
var resolveWrapperForCommand =
|
|
236
|
+
var resolveWrapperForCommand = args => {
|
|
315
237
|
const {
|
|
316
238
|
commandName
|
|
317
239
|
} = args;
|
|
318
240
|
return WRAPPERS.find(wrapper => {
|
|
319
241
|
return wrapper.commands.includes(commandName);
|
|
320
242
|
});
|
|
321
|
-
}
|
|
322
|
-
var applyWrapperForCommand =
|
|
243
|
+
};
|
|
244
|
+
var applyWrapperForCommand = args => {
|
|
323
245
|
const {
|
|
324
246
|
commandName,
|
|
325
247
|
route,
|
|
@@ -328,12 +250,10 @@ var applyWrapperForCommand = /* @__PURE__ */__name(args => {
|
|
|
328
250
|
const wrapper = resolveWrapperForCommand({
|
|
329
251
|
commandName
|
|
330
252
|
});
|
|
331
|
-
if (!wrapper) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
};
|
|
336
|
-
}
|
|
253
|
+
if (!wrapper) return {
|
|
254
|
+
flags: parsedFlags,
|
|
255
|
+
forcedBody: {}
|
|
256
|
+
};
|
|
337
257
|
return wrapper.apply({
|
|
338
258
|
context: {
|
|
339
259
|
commandName,
|
|
@@ -341,67 +261,57 @@ var applyWrapperForCommand = /* @__PURE__ */__name(args => {
|
|
|
341
261
|
parsedFlags
|
|
342
262
|
}
|
|
343
263
|
});
|
|
344
|
-
}
|
|
345
|
-
var getWrapperHelpFlags =
|
|
346
|
-
|
|
264
|
+
};
|
|
265
|
+
var getWrapperHelpFlags = commandName => {
|
|
266
|
+
return WRAPPERS.find(w => {
|
|
347
267
|
return w.commands.includes(commandName);
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
}, "getWrapperHelpFlags");
|
|
268
|
+
})?.helpFlags ?? [];
|
|
269
|
+
};
|
|
351
270
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
import * as os from "os";
|
|
355
|
-
import * as path from "path";
|
|
356
|
-
import { createClient, createConfig } from "@soat/sdk";
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/config.ts
|
|
357
273
|
var CONFIG_FILE = path.join(os.homedir(), ".soat", "config.json");
|
|
358
|
-
var readConfig =
|
|
274
|
+
var readConfig = () => {
|
|
359
275
|
try {
|
|
360
|
-
return JSON.parse(
|
|
276
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
|
|
361
277
|
} catch {
|
|
362
278
|
return {};
|
|
363
279
|
}
|
|
364
|
-
}
|
|
365
|
-
var writeProfile =
|
|
280
|
+
};
|
|
281
|
+
var writeProfile = (name, profile) => {
|
|
366
282
|
const config = readConfig();
|
|
367
283
|
config[name] = profile;
|
|
368
|
-
|
|
284
|
+
fs.mkdirSync(path.dirname(CONFIG_FILE), {
|
|
369
285
|
recursive: true
|
|
370
286
|
});
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
var resolveClient =
|
|
287
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
288
|
+
};
|
|
289
|
+
var resolveClient = profileName => {
|
|
374
290
|
const envBaseUrl = process.env["SOAT_BASE_URL"];
|
|
375
291
|
const envToken = process.env["SOAT_TOKEN"];
|
|
376
|
-
if (envBaseUrl && envToken) {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}));
|
|
383
|
-
}
|
|
292
|
+
if (envBaseUrl && envToken) return createClient(createConfig({
|
|
293
|
+
baseUrl: envBaseUrl,
|
|
294
|
+
headers: {
|
|
295
|
+
Authorization: `Bearer ${envToken}`
|
|
296
|
+
}
|
|
297
|
+
}));
|
|
384
298
|
const name = profileName ?? process.env["SOAT_PROFILE"] ?? "default";
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
if (envBaseUrl) {
|
|
396
|
-
return createClient(createConfig({
|
|
397
|
-
baseUrl: envBaseUrl
|
|
398
|
-
}));
|
|
399
|
-
}
|
|
299
|
+
const profile = readConfig()[name];
|
|
300
|
+
if (profile) return createClient(createConfig({
|
|
301
|
+
baseUrl: envBaseUrl ?? profile.baseUrl,
|
|
302
|
+
headers: {
|
|
303
|
+
Authorization: `Bearer ${profile.token}`
|
|
304
|
+
}
|
|
305
|
+
}));
|
|
306
|
+
if (envBaseUrl) return createClient(createConfig({
|
|
307
|
+
baseUrl: envBaseUrl
|
|
308
|
+
}));
|
|
400
309
|
console.error(`Profile "${name}" not found. Run: soat configure${name !== "default" ? ` --profile ${name}` : ""}`);
|
|
401
310
|
process.exit(1);
|
|
402
|
-
}
|
|
311
|
+
};
|
|
403
312
|
|
|
404
|
-
|
|
313
|
+
//#endregion
|
|
314
|
+
//#region src/generated/routes.ts
|
|
405
315
|
var routes = {
|
|
406
316
|
"list-actors": {
|
|
407
317
|
serviceClass: "Actors",
|
|
@@ -457,7 +367,7 @@ var routes = {
|
|
|
457
367
|
"in": "body"
|
|
458
368
|
}, {
|
|
459
369
|
"name": "external_id",
|
|
460
|
-
"description": "Optional external identifier (e.g. WhatsApp phone number). If provided and an actor with this externalId already exists in the project, the existing actor is returned (idempotent
|
|
370
|
+
"description": "Optional external identifier (e.g. WhatsApp phone number). If provided and an actor with this externalId already exists in the project, the existing actor is returned (idempotent — 200 OK).",
|
|
461
371
|
"required": false,
|
|
462
372
|
"type": "string",
|
|
463
373
|
"in": "body"
|
|
@@ -655,9 +565,9 @@ var routes = {
|
|
|
655
565
|
"in": "body"
|
|
656
566
|
}, {
|
|
657
567
|
"name": "tool_choice",
|
|
658
|
-
"description": "",
|
|
568
|
+
"description": "Tool choice strategy. Accepts a string (`\"auto\"`, `\"required\"`) or an object (`{ \"type\": \"tool\", \"name\": \"my_tool\" }`).",
|
|
659
569
|
"required": false,
|
|
660
|
-
"type": "
|
|
570
|
+
"type": "string",
|
|
661
571
|
"in": "body"
|
|
662
572
|
}, {
|
|
663
573
|
"name": "stop_conditions",
|
|
@@ -775,9 +685,9 @@ var routes = {
|
|
|
775
685
|
"in": "body"
|
|
776
686
|
}, {
|
|
777
687
|
"name": "tool_choice",
|
|
778
|
-
"description": "",
|
|
688
|
+
"description": "Tool choice strategy. Accepts a string (`\"auto\"`, `\"required\"`) or an object (`{ \"type\": \"tool\", \"name\": \"my_tool\" }`).",
|
|
779
689
|
"required": false,
|
|
780
|
-
"type": "
|
|
690
|
+
"type": "string",
|
|
781
691
|
"in": "body"
|
|
782
692
|
}, {
|
|
783
693
|
"name": "stop_conditions",
|
|
@@ -1339,7 +1249,7 @@ var routes = {
|
|
|
1339
1249
|
"create-chat-completion": {
|
|
1340
1250
|
serviceClass: "Chats",
|
|
1341
1251
|
operationId: "createChatCompletion",
|
|
1342
|
-
description: "OpenAI Chat Completions-compatible endpoint. Resolves the AI provider from `ai_provider_id`, decrypts its secret, and calls the appropriate Vercel AI SDK provider. `ai_provider_id` is required
|
|
1252
|
+
description: "OpenAI Chat Completions-compatible endpoint. Resolves the AI provider from `ai_provider_id`, decrypts its secret, and calls the appropriate Vercel AI SDK provider. `ai_provider_id` is required — there is no server-side model fallback.",
|
|
1343
1253
|
moduleDocsUrl: "https://soat.ttoss.dev/docs/modules/chats",
|
|
1344
1254
|
pathParams: [],
|
|
1345
1255
|
queryParams: [],
|
|
@@ -1624,7 +1534,7 @@ var routes = {
|
|
|
1624
1534
|
"in": "body"
|
|
1625
1535
|
}, {
|
|
1626
1536
|
"name": "stream",
|
|
1627
|
-
"description": "If true, stream tokens via SSE. NOT IMPLEMENTED in v1
|
|
1537
|
+
"description": "If true, stream tokens via SSE. NOT IMPLEMENTED in v1 — returns 501.",
|
|
1628
1538
|
"required": false,
|
|
1629
1539
|
"type": "boolean",
|
|
1630
1540
|
"in": "body"
|
|
@@ -2356,7 +2266,7 @@ var routes = {
|
|
|
2356
2266
|
"in": "body"
|
|
2357
2267
|
}, {
|
|
2358
2268
|
"name": "min_score",
|
|
2359
|
-
"description": "Minimum similarity score (0
|
|
2269
|
+
"description": "Minimum similarity score (0–1). Results with lower scores are excluded. Only applies when `query` is provided.",
|
|
2360
2270
|
"required": false,
|
|
2361
2271
|
"type": "number",
|
|
2362
2272
|
"in": "body"
|
|
@@ -2407,7 +2317,7 @@ var routes = {
|
|
|
2407
2317
|
"in": "query"
|
|
2408
2318
|
}, {
|
|
2409
2319
|
"name": "tags",
|
|
2410
|
-
"description": "Filter memories by tag patterns. Supports glob syntax (`*` matches any substring, `?` matches any single character). Multiple values are ORed
|
|
2320
|
+
"description": "Filter memories by tag patterns. Supports glob syntax (`*` matches any substring, `?` matches any single character). Multiple values are ORed — a memory is returned if any of its tags match any of the provided patterns. Omit to return all memories.\n",
|
|
2411
2321
|
"required": false,
|
|
2412
2322
|
"type": "array",
|
|
2413
2323
|
"in": "query"
|
|
@@ -3248,6 +3158,12 @@ var routes = {
|
|
|
3248
3158
|
"required": false,
|
|
3249
3159
|
"type": "integer",
|
|
3250
3160
|
"in": "body"
|
|
3161
|
+
}, {
|
|
3162
|
+
"name": "message_delay_seconds",
|
|
3163
|
+
"description": "Number of seconds to wait after the last user message before sending to the LLM. Acts as a debounce: each new message resets the timer. null or absent means no delay (immediate processing).\n",
|
|
3164
|
+
"required": false,
|
|
3165
|
+
"type": "integer",
|
|
3166
|
+
"in": "body"
|
|
3251
3167
|
}]
|
|
3252
3168
|
},
|
|
3253
3169
|
"get-agent-session": {
|
|
@@ -3314,6 +3230,12 @@ var routes = {
|
|
|
3314
3230
|
"required": false,
|
|
3315
3231
|
"type": "object",
|
|
3316
3232
|
"in": "body"
|
|
3233
|
+
}, {
|
|
3234
|
+
"name": "message_delay_seconds",
|
|
3235
|
+
"description": "Number of seconds to wait after the last user message before sending to the LLM. Acts as a debounce: each new message resets the timer. Set to null to disable the delay.\n",
|
|
3236
|
+
"required": false,
|
|
3237
|
+
"type": "integer",
|
|
3238
|
+
"in": "body"
|
|
3317
3239
|
}]
|
|
3318
3240
|
},
|
|
3319
3241
|
"delete-agent-session": {
|
|
@@ -4227,30 +4149,36 @@ var routes = {
|
|
|
4227
4149
|
}
|
|
4228
4150
|
};
|
|
4229
4151
|
|
|
4230
|
-
|
|
4231
|
-
|
|
4152
|
+
//#endregion
|
|
4153
|
+
//#region src/index.ts
|
|
4154
|
+
/**
|
|
4155
|
+
* Normalize kebab-case, snake_case, or camelCase to camelCase for param matching.
|
|
4156
|
+
* e.g. agent-id → agentId, actor_id → actorId, agentId → agentId
|
|
4157
|
+
*/
|
|
4158
|
+
var toCanonical = s => {
|
|
4232
4159
|
return s.replace(/[-_]([a-z0-9])/g, (_, c) => {
|
|
4233
4160
|
return c.toUpperCase();
|
|
4234
4161
|
});
|
|
4235
|
-
}
|
|
4236
|
-
|
|
4162
|
+
};
|
|
4163
|
+
/** Convert kebab-case to snake_case for body/query keys (e.g. project-id → project_id). */
|
|
4164
|
+
var kebabToSnake = s => {
|
|
4237
4165
|
return s.replace(/-/g, "_");
|
|
4238
|
-
}
|
|
4239
|
-
var parseFlagValue =
|
|
4166
|
+
};
|
|
4167
|
+
var parseFlagValue = value => {
|
|
4240
4168
|
const trimmed = value.trim();
|
|
4241
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed === "true" || trimmed === "false" || trimmed === "null" || /^-?\d+(\.\d+)?$/.test(trimmed)) {
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
return value;
|
|
4246
|
-
}
|
|
4169
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed === "true" || trimmed === "false" || trimmed === "null" || /^-?\d+(\.\d+)?$/.test(trimmed)) try {
|
|
4170
|
+
return JSON.parse(trimmed);
|
|
4171
|
+
} catch {
|
|
4172
|
+
return value;
|
|
4247
4173
|
}
|
|
4248
4174
|
return value;
|
|
4249
|
-
}
|
|
4250
|
-
|
|
4175
|
+
};
|
|
4176
|
+
/** Normalize symbol names to compare exports across acronym casing differences. */
|
|
4177
|
+
var normalizeSymbol = name => {
|
|
4251
4178
|
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
4252
|
-
}
|
|
4253
|
-
|
|
4179
|
+
};
|
|
4180
|
+
/** Resolve SDK service exports even when generated names differ by acronym casing. */
|
|
4181
|
+
var resolveServiceClass = serviceClassName => {
|
|
4254
4182
|
const sdkExports = sdk;
|
|
4255
4183
|
const exactMatch = sdkExports[serviceClassName];
|
|
4256
4184
|
if (exactMatch) return exactMatch;
|
|
@@ -4258,12 +4186,9 @@ var resolveServiceClass = /* @__PURE__ */__name(serviceClassName => {
|
|
|
4258
4186
|
const fuzzyMatches = Object.entries(sdkExports).filter(([exportName, value]) => {
|
|
4259
4187
|
return Boolean(value) && normalizeSymbol(exportName) === normalizedTarget;
|
|
4260
4188
|
});
|
|
4261
|
-
if (fuzzyMatches.length === 1)
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
return void 0;
|
|
4265
|
-
}, "resolveServiceClass");
|
|
4266
|
-
program.name("soat").description("SOAT CLI").version(package_default.version).option("-p, --profile <name>", "config profile to use");
|
|
4189
|
+
if (fuzzyMatches.length === 1) return fuzzyMatches[0][1];
|
|
4190
|
+
};
|
|
4191
|
+
program.name("soat").description("SOAT CLI").version(version).option("-p, --profile <name>", "config profile to use");
|
|
4267
4192
|
program.addHelpText("after", "\nTip: Run `soat <command> --help` to see command-specific flags and docs.");
|
|
4268
4193
|
program.command("configure").description("Save credentials to a named profile (~/.soat/config.json)").option("-p, --profile <name>", "profile name", "default").action(async opts => {
|
|
4269
4194
|
const [{
|
|
@@ -4287,33 +4212,29 @@ program.command("list-commands").description("List all available API commands").
|
|
|
4287
4212
|
const pad = Math.max(...Object.keys(routes).map(k => {
|
|
4288
4213
|
return k.length;
|
|
4289
4214
|
}));
|
|
4290
|
-
for (const [cmd, r] of Object.entries(routes).sort()) {
|
|
4291
|
-
console.log(` ${cmd.padEnd(pad)} ${r.description}`);
|
|
4292
|
-
}
|
|
4215
|
+
for (const [cmd, r] of Object.entries(routes).sort()) console.log(` ${cmd.padEnd(pad)} ${r.description}`);
|
|
4293
4216
|
});
|
|
4294
|
-
var matchesFilter =
|
|
4217
|
+
var matchesFilter = (eventType, filter) => {
|
|
4295
4218
|
const patterns = filter.split(",").map(part => {
|
|
4296
4219
|
return part.trim();
|
|
4297
4220
|
}).filter(Boolean);
|
|
4298
4221
|
if (patterns.length === 0) return true;
|
|
4299
4222
|
return patterns.some(pattern => {
|
|
4300
4223
|
if (pattern === "*") return true;
|
|
4301
|
-
if (pattern.endsWith("*"))
|
|
4302
|
-
return eventType.startsWith(pattern.slice(0, -1));
|
|
4303
|
-
}
|
|
4224
|
+
if (pattern.endsWith("*")) return eventType.startsWith(pattern.slice(0, -1));
|
|
4304
4225
|
return eventType === pattern;
|
|
4305
4226
|
});
|
|
4306
|
-
}
|
|
4307
|
-
var verifySignature =
|
|
4227
|
+
};
|
|
4228
|
+
var verifySignature = (secret, payload, signatureHeader) => {
|
|
4308
4229
|
const expected = createHmac("sha256", secret).update(payload).digest("hex");
|
|
4309
4230
|
const expectedBuffer = Buffer.from(expected, "utf8");
|
|
4310
4231
|
const actualBuffer = Buffer.from(signatureHeader, "utf8");
|
|
4311
4232
|
if (expectedBuffer.length !== actualBuffer.length) return false;
|
|
4312
4233
|
return timingSafeEqual(expectedBuffer, actualBuffer);
|
|
4313
|
-
}
|
|
4234
|
+
};
|
|
4314
4235
|
program.command("listen").description("Start a local webhook listener for testing deliveries").option("--port <number>", "port to listen on", "8787").option("--path <path>", "request path to accept", "/webhook").option("--secret <secret>", "verify X-Soat-Signature with this webhook secret").option("--filter <pattern>", "filter event type(s), supports prefix wildcard and comma separation (e.g. sessions.generation.*,files.*)").option("--json", "print one JSON object per line").action(opts => {
|
|
4315
4236
|
const port = Number(opts.port);
|
|
4316
|
-
const
|
|
4237
|
+
const path = opts.path;
|
|
4317
4238
|
const secret = opts.secret;
|
|
4318
4239
|
const filter = opts.filter;
|
|
4319
4240
|
const asJson = Boolean(opts.json);
|
|
@@ -4322,7 +4243,7 @@ program.command("listen").description("Start a local webhook listener for testin
|
|
|
4322
4243
|
process.exit(1);
|
|
4323
4244
|
}
|
|
4324
4245
|
const server = createServer((req, res) => {
|
|
4325
|
-
if (req.method !== "POST" || req.url !==
|
|
4246
|
+
if (req.method !== "POST" || req.url !== path) {
|
|
4326
4247
|
res.writeHead(404, {
|
|
4327
4248
|
"Content-Type": "application/json"
|
|
4328
4249
|
});
|
|
@@ -4355,9 +4276,7 @@ program.command("listen").description("Start a local webhook listener for testin
|
|
|
4355
4276
|
parsedPayload = JSON.parse(rawBody);
|
|
4356
4277
|
} catch {}
|
|
4357
4278
|
let isSignatureValid = null;
|
|
4358
|
-
if (secret)
|
|
4359
|
-
isSignatureValid = verifySignature(secret, rawBody, signature);
|
|
4360
|
-
}
|
|
4279
|
+
if (secret) isSignatureValid = verifySignature(secret, rawBody, signature);
|
|
4361
4280
|
const record = {
|
|
4362
4281
|
timestamp: (/* @__PURE__ */new Date()).toISOString(),
|
|
4363
4282
|
event_type: eventType,
|
|
@@ -4366,15 +4285,11 @@ program.command("listen").description("Start a local webhook listener for testin
|
|
|
4366
4285
|
signature_valid: isSignatureValid,
|
|
4367
4286
|
payload: parsedPayload
|
|
4368
4287
|
};
|
|
4369
|
-
if (asJson) {
|
|
4370
|
-
console.log(JSON.stringify(record));
|
|
4371
|
-
} else {
|
|
4288
|
+
if (asJson) console.log(JSON.stringify(record));else {
|
|
4372
4289
|
console.log("--- webhook received ---");
|
|
4373
4290
|
console.log("event_type:", eventType);
|
|
4374
4291
|
console.log("delivery_id:", deliveryId);
|
|
4375
|
-
if (secret)
|
|
4376
|
-
console.log("signature_valid:", isSignatureValid);
|
|
4377
|
-
}
|
|
4292
|
+
if (secret) console.log("signature_valid:", isSignatureValid);
|
|
4378
4293
|
console.log("payload:", JSON.stringify(parsedPayload, null, 2));
|
|
4379
4294
|
}
|
|
4380
4295
|
const responseStatus = secret && isSignatureValid === false ? 401 : 200;
|
|
@@ -4390,13 +4305,9 @@ program.command("listen").description("Start a local webhook listener for testin
|
|
|
4390
4305
|
});
|
|
4391
4306
|
});
|
|
4392
4307
|
server.listen(port, () => {
|
|
4393
|
-
console.log(`Listening for SOAT webhooks on http://localhost:${port}${
|
|
4394
|
-
if (filter) {
|
|
4395
|
-
|
|
4396
|
-
}
|
|
4397
|
-
if (secret) {
|
|
4398
|
-
console.log("Signature verification: enabled");
|
|
4399
|
-
}
|
|
4308
|
+
console.log(`Listening for SOAT webhooks on http://localhost:${port}${path}`);
|
|
4309
|
+
if (filter) console.log(`Filter: ${filter}`);
|
|
4310
|
+
if (secret) console.log("Signature verification: enabled");
|
|
4400
4311
|
});
|
|
4401
4312
|
process.on("SIGINT", () => {
|
|
4402
4313
|
server.close(() => {
|
|
@@ -4425,13 +4336,9 @@ program.argument("[command]", "API command in kebab-case (e.g. list-actors)").ar
|
|
|
4425
4336
|
in: "wrapper"
|
|
4426
4337
|
};
|
|
4427
4338
|
})];
|
|
4428
|
-
console.log(`
|
|
4429
|
-
|
|
4430
|
-
`);
|
|
4431
|
-
console.log(` ${route.description}
|
|
4432
|
-
`);
|
|
4433
|
-
console.log(` Module docs: ${route.moduleDocsUrl}
|
|
4434
|
-
`);
|
|
4339
|
+
console.log(`\nUsage: soat ${commandName} [flags]\n`);
|
|
4340
|
+
console.log(` ${route.description}\n`);
|
|
4341
|
+
console.log(` Module docs: ${route.moduleDocsUrl}\n`);
|
|
4435
4342
|
if (allFlags.length > 0) {
|
|
4436
4343
|
console.log("Flags:");
|
|
4437
4344
|
for (const f of allFlags) {
|
|
@@ -4442,13 +4349,12 @@ Usage: soat ${commandName} [flags]
|
|
|
4442
4349
|
}
|
|
4443
4350
|
process.exit(0);
|
|
4444
4351
|
}
|
|
4445
|
-
const parsedFlags = parseUnknownWithRepeats({
|
|
4446
|
-
cliArgs: rawArgs
|
|
4447
|
-
});
|
|
4448
4352
|
const wrapped = applyWrapperForCommand({
|
|
4449
4353
|
commandName,
|
|
4450
4354
|
route,
|
|
4451
|
-
parsedFlags
|
|
4355
|
+
parsedFlags: parseUnknownWithRepeats({
|
|
4356
|
+
cliArgs: rawArgs
|
|
4357
|
+
})
|
|
4452
4358
|
});
|
|
4453
4359
|
const flags = wrapped.flags.single;
|
|
4454
4360
|
const pathArgs = {};
|
|
@@ -4464,16 +4370,9 @@ Usage: soat ${commandName} [flags]
|
|
|
4464
4370
|
const queryParam = route.queryParams.find(p => {
|
|
4465
4371
|
return toCanonical(p) === canonical;
|
|
4466
4372
|
});
|
|
4467
|
-
if (pathParam)
|
|
4468
|
-
pathArgs[pathParam] = parsedValue;
|
|
4469
|
-
} else if (queryParam) {
|
|
4470
|
-
queryArgs[queryParam] = parsedValue;
|
|
4471
|
-
} else {
|
|
4472
|
-
bodyArgs[kebabToSnake(flagKey)] = parsedValue;
|
|
4473
|
-
}
|
|
4373
|
+
if (pathParam) pathArgs[pathParam] = parsedValue;else if (queryParam) queryArgs[queryParam] = parsedValue;else bodyArgs[kebabToSnake(flagKey)] = parsedValue;
|
|
4474
4374
|
}
|
|
4475
|
-
const
|
|
4476
|
-
const client = resolveClient(profileOpt);
|
|
4375
|
+
const client = resolveClient(flags["profile"] ?? program.opts().profile);
|
|
4477
4376
|
const serviceClass = resolveServiceClass(route.serviceClass);
|
|
4478
4377
|
if (!serviceClass) {
|
|
4479
4378
|
console.error(`SDK class "${route.serviceClass}" not found.`);
|
|
@@ -4487,9 +4386,7 @@ Usage: soat ${commandName} [flags]
|
|
|
4487
4386
|
const callOpts = {
|
|
4488
4387
|
client
|
|
4489
4388
|
};
|
|
4490
|
-
if (Object.keys(wrapped.forcedBody).length)
|
|
4491
|
-
Object.assign(bodyArgs, wrapped.forcedBody);
|
|
4492
|
-
}
|
|
4389
|
+
if (Object.keys(wrapped.forcedBody).length) Object.assign(bodyArgs, wrapped.forcedBody);
|
|
4493
4390
|
if (Object.keys(pathArgs).length) callOpts["path"] = pathArgs;
|
|
4494
4391
|
if (Object.keys(queryArgs).length) callOpts["query"] = queryArgs;
|
|
4495
4392
|
if (Object.keys(bodyArgs).length) callOpts["body"] = bodyArgs;
|
|
@@ -4515,7 +4412,7 @@ Usage: soat ${commandName} [flags]
|
|
|
4515
4412
|
}
|
|
4516
4413
|
console.log(JSON.stringify(result.data, null, 2));
|
|
4517
4414
|
});
|
|
4518
|
-
var runCli =
|
|
4415
|
+
var runCli = async args => {
|
|
4519
4416
|
const previousArgv = process.argv;
|
|
4520
4417
|
process.argv = args;
|
|
4521
4418
|
try {
|
|
@@ -4523,12 +4420,11 @@ var runCli = /* @__PURE__ */__name(async args => {
|
|
|
4523
4420
|
} finally {
|
|
4524
4421
|
process.argv = previousArgv;
|
|
4525
4422
|
}
|
|
4526
|
-
}
|
|
4527
|
-
|
|
4423
|
+
};
|
|
4424
|
+
if ((() => {
|
|
4528
4425
|
if (!process.argv[1]) return false;
|
|
4529
|
-
return
|
|
4530
|
-
})();
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
}
|
|
4426
|
+
return path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
4427
|
+
})()) runCli(process.argv);
|
|
4428
|
+
|
|
4429
|
+
//#endregion
|
|
4534
4430
|
export { runCli };
|
package/package.json
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soat/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@inquirer/input": "^5.
|
|
7
|
-
"@inquirer/password": "^5.
|
|
8
|
-
"@ttoss/logger": "^0.8.
|
|
9
|
-
"commander": "^
|
|
10
|
-
"js-yaml": "^4.
|
|
11
|
-
"@soat/sdk": "0.6.
|
|
6
|
+
"@inquirer/input": "^5.1.2",
|
|
7
|
+
"@inquirer/password": "^5.1.1",
|
|
8
|
+
"@ttoss/logger": "^0.8.16",
|
|
9
|
+
"commander": "^15.0.0",
|
|
10
|
+
"js-yaml": "^4.2.0",
|
|
11
|
+
"@soat/sdk": "0.6.10"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@ttoss/config": "^1.37.
|
|
15
|
-
"@ttoss/test-utils": "^4.2.
|
|
14
|
+
"@ttoss/config": "^1.37.15",
|
|
15
|
+
"@ttoss/test-utils": "^4.2.15",
|
|
16
16
|
"@types/jest": "^30.0.0",
|
|
17
17
|
"@types/js-yaml": "^4.0.9",
|
|
18
18
|
"@types/node": "^24",
|
|
19
|
-
"jest": "^30.
|
|
20
|
-
"
|
|
21
|
-
"tsx": "^4.
|
|
19
|
+
"jest": "^30.4.2",
|
|
20
|
+
"tsdown": "^0.22.1",
|
|
21
|
+
"tsx": "^4.22.4"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"dist",
|
|
@@ -40,6 +40,6 @@
|
|
|
40
40
|
"lint": "eslint src tests",
|
|
41
41
|
"test": "jest --config tests/unit/jest.config.ts --coverage=false",
|
|
42
42
|
"typecheck": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json",
|
|
43
|
-
"build": "pnpm generate &&
|
|
43
|
+
"build": "pnpm generate && tsdown"
|
|
44
44
|
}
|
|
45
45
|
}
|