apcore-cli 0.6.0 → 0.7.0
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/CHANGELOG.md +64 -0
- package/LICENSE +13 -17
- package/README.md +134 -22
- package/dist/bin/apcore-cli.js +3360 -516
- package/dist/bin/apcore-cli.js.map +1 -1
- package/dist/index.d.ts +390 -134
- package/dist/index.js +1893 -1027
- package/dist/index.js.map +1 -1
- package/package.json +16 -10
package/dist/bin/apcore-cli.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
2
3
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
4
|
var __esm = (fn, res) => function __init() {
|
|
10
5
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
6
|
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
12
11
|
|
|
13
12
|
// node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
|
|
14
13
|
import path from "path";
|
|
@@ -48,6 +47,8 @@ function exitCodeForError(error) {
|
|
|
48
47
|
MODULE_NOT_FOUND: EXIT_CODES.MODULE_NOT_FOUND,
|
|
49
48
|
MODULE_LOAD_ERROR: EXIT_CODES.MODULE_LOAD_ERROR,
|
|
50
49
|
MODULE_DISABLED: EXIT_CODES.MODULE_DISABLED,
|
|
50
|
+
DEPENDENCY_NOT_FOUND: EXIT_CODES.DEPENDENCY_NOT_FOUND,
|
|
51
|
+
DEPENDENCY_VERSION_MISMATCH: EXIT_CODES.DEPENDENCY_VERSION_MISMATCH,
|
|
51
52
|
SCHEMA_VALIDATION_ERROR: EXIT_CODES.SCHEMA_VALIDATION_ERROR,
|
|
52
53
|
SCHEMA_CIRCULAR_REF: EXIT_CODES.SCHEMA_CIRCULAR_REF,
|
|
53
54
|
APPROVAL_DENIED: EXIT_CODES.APPROVAL_DENIED,
|
|
@@ -62,6 +63,7 @@ function exitCodeForError(error) {
|
|
|
62
63
|
CONFIG_NAMESPACE_RESERVED: EXIT_CODES.CONFIG_NAMESPACE_RESERVED,
|
|
63
64
|
CONFIG_NAMESPACE_DUPLICATE: EXIT_CODES.CONFIG_NAMESPACE_DUPLICATE,
|
|
64
65
|
CONFIG_ENV_PREFIX_CONFLICT: EXIT_CODES.CONFIG_ENV_PREFIX_CONFLICT,
|
|
66
|
+
CONFIG_ENV_MAP_CONFLICT: EXIT_CODES.CONFIG_ENV_MAP_CONFLICT,
|
|
65
67
|
CONFIG_MOUNT_ERROR: EXIT_CODES.CONFIG_MOUNT_ERROR,
|
|
66
68
|
CONFIG_BIND_ERROR: EXIT_CODES.CONFIG_BIND_ERROR,
|
|
67
69
|
ERROR_FORMATTER_DUPLICATE: EXIT_CODES.ERROR_FORMATTER_DUPLICATE
|
|
@@ -127,6 +129,8 @@ var init_errors = __esm({
|
|
|
127
129
|
MODULE_NOT_FOUND: 44,
|
|
128
130
|
MODULE_LOAD_ERROR: 44,
|
|
129
131
|
MODULE_DISABLED: 44,
|
|
132
|
+
DEPENDENCY_NOT_FOUND: 44,
|
|
133
|
+
DEPENDENCY_VERSION_MISMATCH: 44,
|
|
130
134
|
SCHEMA_VALIDATION_ERROR: 45,
|
|
131
135
|
APPROVAL_DENIED: 46,
|
|
132
136
|
APPROVAL_TIMEOUT: 46,
|
|
@@ -147,38 +151,635 @@ var init_errors = __esm({
|
|
|
147
151
|
}
|
|
148
152
|
});
|
|
149
153
|
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
154
|
+
// src/security/sandbox.ts
|
|
155
|
+
var sandbox_exports = {};
|
|
156
|
+
__export(sandbox_exports, {
|
|
157
|
+
Sandbox: () => Sandbox,
|
|
158
|
+
runSandboxRunner: () => runSandboxRunner
|
|
159
|
+
});
|
|
160
|
+
async function runSandboxRunner(moduleId) {
|
|
161
|
+
const extensionsRoot = process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
|
|
162
|
+
const apcore = await import("apcore-js").catch(() => {
|
|
163
|
+
process.stderr.write(
|
|
164
|
+
"sandbox runner: apcore-js is not available in the sandboxed environment.\n"
|
|
165
|
+
);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
});
|
|
168
|
+
if (!apcore) return;
|
|
169
|
+
let inputJson = "";
|
|
170
|
+
for await (const chunk of process.stdin) {
|
|
171
|
+
inputJson += chunk.toString();
|
|
172
|
+
}
|
|
173
|
+
let inputData;
|
|
174
|
+
try {
|
|
175
|
+
inputData = JSON.parse(inputJson);
|
|
176
|
+
} catch {
|
|
177
|
+
process.stderr.write("sandbox runner: failed to parse stdin as JSON.\n");
|
|
178
|
+
process.exit(1);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const registry = new apcore.Registry({ extensionsDir: extensionsRoot });
|
|
182
|
+
await registry.discover();
|
|
183
|
+
const executor = new apcore.Executor({ registry });
|
|
184
|
+
try {
|
|
185
|
+
const result = await executor.call(moduleId, inputData);
|
|
186
|
+
process.stdout.write(JSON.stringify(result));
|
|
187
|
+
process.exit(0);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
process.stderr.write(`sandbox runner error: ${err}
|
|
190
|
+
`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
function buildSandboxEnv(tmpDir) {
|
|
195
|
+
const env = {};
|
|
196
|
+
for (const key of SANDBOX_ALLOW_KEYS) {
|
|
197
|
+
if (process.env[key]) env[key] = process.env[key];
|
|
198
|
+
}
|
|
199
|
+
for (const [key, val] of Object.entries(process.env)) {
|
|
200
|
+
if (key.startsWith(SANDBOX_ALLOW_PREFIX) && !key.startsWith(SANDBOX_DENY_PREFIX) && !SANDBOX_DENY_KEYS.includes(key)) {
|
|
201
|
+
env[key] = val;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
env.HOME = tmpDir;
|
|
205
|
+
env.TMPDIR = tmpDir;
|
|
206
|
+
return env;
|
|
207
|
+
}
|
|
208
|
+
var SANDBOX_ALLOW_KEYS, SANDBOX_ALLOW_PREFIX, SANDBOX_DENY_PREFIX, SANDBOX_DENY_KEYS, SANDBOX_OUTPUT_SIZE_LIMIT, Sandbox;
|
|
209
|
+
var init_sandbox = __esm({
|
|
210
|
+
"src/security/sandbox.ts"() {
|
|
211
|
+
"use strict";
|
|
212
|
+
init_esm_shims();
|
|
213
|
+
init_errors();
|
|
214
|
+
SANDBOX_ALLOW_KEYS = ["PATH", "LANG", "LC_ALL"];
|
|
215
|
+
SANDBOX_ALLOW_PREFIX = "APCORE_";
|
|
216
|
+
SANDBOX_DENY_PREFIX = "APCORE_AUTH_";
|
|
217
|
+
SANDBOX_DENY_KEYS = ["APCORE_AUTH_API_KEY"];
|
|
218
|
+
SANDBOX_OUTPUT_SIZE_LIMIT = 64 * 1024 * 1024;
|
|
219
|
+
Sandbox = class {
|
|
220
|
+
enabled;
|
|
221
|
+
timeoutSeconds;
|
|
222
|
+
constructor(enabled = false, timeoutSeconds = 300) {
|
|
223
|
+
this.enabled = enabled;
|
|
224
|
+
this.timeoutSeconds = timeoutSeconds;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Execute a module, optionally inside a sandboxed subprocess.
|
|
228
|
+
*/
|
|
229
|
+
async execute(moduleId, inputData, executor) {
|
|
230
|
+
if (!this.enabled) {
|
|
231
|
+
return executor.execute(moduleId, inputData);
|
|
232
|
+
}
|
|
233
|
+
return this._sandboxedExecute(moduleId, inputData);
|
|
234
|
+
}
|
|
235
|
+
async _sandboxedExecute(moduleId, inputData) {
|
|
236
|
+
const { spawn } = await import("child_process");
|
|
237
|
+
const { tmpdir } = await import("os");
|
|
238
|
+
const { join: join3 } = await import("path");
|
|
239
|
+
const { mkdtempSync, rmSync } = await import("fs");
|
|
240
|
+
const tmpDir = mkdtempSync(join3(tmpdir(), "apcore_sandbox_"));
|
|
241
|
+
const env = buildSandboxEnv(tmpDir);
|
|
242
|
+
const binaryPath = process.argv[1];
|
|
243
|
+
const child = spawn(process.execPath, [binaryPath, "--internal-sandbox-runner", moduleId], {
|
|
244
|
+
env,
|
|
245
|
+
cwd: tmpDir,
|
|
246
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
247
|
+
});
|
|
248
|
+
let stdout = "";
|
|
249
|
+
let stderr = "";
|
|
250
|
+
let totalBytes = 0;
|
|
251
|
+
let sizeExceeded = false;
|
|
252
|
+
child.stdout.on("data", (chunk) => {
|
|
253
|
+
totalBytes += chunk.length;
|
|
254
|
+
if (totalBytes > SANDBOX_OUTPUT_SIZE_LIMIT) {
|
|
255
|
+
sizeExceeded = true;
|
|
256
|
+
child.kill("SIGKILL");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
stdout += chunk.toString();
|
|
260
|
+
});
|
|
261
|
+
child.stderr.on("data", (chunk) => {
|
|
262
|
+
stderr += chunk.toString();
|
|
263
|
+
});
|
|
264
|
+
child.stdin.write(JSON.stringify(inputData));
|
|
265
|
+
child.stdin.end();
|
|
266
|
+
return new Promise((resolve2, reject) => {
|
|
267
|
+
const timer = setTimeout(() => {
|
|
268
|
+
child.kill("SIGKILL");
|
|
269
|
+
reject(
|
|
270
|
+
new ModuleExecutionError(
|
|
271
|
+
`Sandbox module '${moduleId}' timed out after ${this.timeoutSeconds}s.`
|
|
272
|
+
)
|
|
273
|
+
);
|
|
274
|
+
}, this.timeoutSeconds * 1e3);
|
|
275
|
+
child.on("close", (code) => {
|
|
276
|
+
clearTimeout(timer);
|
|
277
|
+
try {
|
|
278
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
279
|
+
} catch {
|
|
280
|
+
}
|
|
281
|
+
if (sizeExceeded) {
|
|
282
|
+
reject(new ModuleExecutionError(`Sandbox module '${moduleId}' output exceeded 64MiB limit.`));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (code !== 0) {
|
|
286
|
+
reject(new ModuleExecutionError(
|
|
287
|
+
`Sandbox module '${moduleId}' exited with code ${code}.${stderr ? ` stderr: ${stderr}` : ""}`
|
|
288
|
+
));
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
resolve2(JSON.parse(stdout));
|
|
293
|
+
} catch {
|
|
294
|
+
reject(new ModuleExecutionError(
|
|
295
|
+
`Sandbox module '${moduleId}' returned non-JSON output: ${stdout.slice(0, 200)}`
|
|
296
|
+
));
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
child.on("error", (err) => {
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
reject(new ModuleExecutionError(`Failed to spawn sandbox process: ${err.message}`));
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
});
|
|
160
308
|
|
|
161
309
|
// src/ref-resolver.ts
|
|
162
|
-
|
|
163
|
-
|
|
310
|
+
function resolveRefs(schema, maxDepth = 32, moduleId = "") {
|
|
311
|
+
const cloned = structuredClone(schema);
|
|
312
|
+
const defs = cloned.$defs ?? cloned.definitions ?? {};
|
|
313
|
+
const result = resolveNode(
|
|
314
|
+
cloned,
|
|
315
|
+
defs,
|
|
316
|
+
/* @__PURE__ */ new Set(),
|
|
317
|
+
0,
|
|
318
|
+
maxDepth,
|
|
319
|
+
moduleId
|
|
320
|
+
);
|
|
321
|
+
delete result.$defs;
|
|
322
|
+
delete result.definitions;
|
|
323
|
+
return result;
|
|
324
|
+
}
|
|
325
|
+
function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
|
|
326
|
+
if (typeof node !== "object" || node === null || Array.isArray(node)) {
|
|
327
|
+
return node;
|
|
328
|
+
}
|
|
329
|
+
const obj = node;
|
|
330
|
+
if ("$ref" in obj) {
|
|
331
|
+
const refPath = obj.$ref;
|
|
332
|
+
if (depth >= maxDepth) {
|
|
333
|
+
process.stderr.write(
|
|
334
|
+
`Error: $ref resolution depth exceeded maximum of ${maxDepth} for module '${moduleId}'.
|
|
335
|
+
`
|
|
336
|
+
);
|
|
337
|
+
process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
|
|
338
|
+
}
|
|
339
|
+
if (visited.has(refPath)) {
|
|
340
|
+
process.stderr.write(
|
|
341
|
+
`Error: Circular $ref detected in schema for module '${moduleId}' at path '${refPath}'.
|
|
342
|
+
`
|
|
343
|
+
);
|
|
344
|
+
process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
|
|
345
|
+
}
|
|
346
|
+
const parts = refPath.split("/");
|
|
347
|
+
const key = parts[parts.length - 1];
|
|
348
|
+
if (!(key in defs)) {
|
|
349
|
+
process.stderr.write(
|
|
350
|
+
`Error: Unresolvable $ref '${refPath}' in schema for module '${moduleId}'.
|
|
351
|
+
`
|
|
352
|
+
);
|
|
353
|
+
process.exit(EXIT_CODES.SCHEMA_VALIDATION_ERROR);
|
|
354
|
+
}
|
|
355
|
+
const newVisited = new Set(visited);
|
|
356
|
+
newVisited.add(refPath);
|
|
357
|
+
return resolveNode(defs[key], defs, newVisited, depth + 1, maxDepth, moduleId);
|
|
358
|
+
}
|
|
359
|
+
if ("allOf" in obj && Array.isArray(obj.allOf)) {
|
|
360
|
+
const merged = {
|
|
361
|
+
properties: {},
|
|
362
|
+
required: []
|
|
363
|
+
};
|
|
364
|
+
if (typeof obj.properties === "object" && obj.properties !== null) {
|
|
365
|
+
Object.assign(merged.properties, obj.properties);
|
|
366
|
+
}
|
|
367
|
+
if (Array.isArray(obj.required)) {
|
|
368
|
+
merged.required.push(...obj.required);
|
|
369
|
+
}
|
|
370
|
+
for (const subSchema of obj.allOf) {
|
|
371
|
+
const resolved = resolveNode(
|
|
372
|
+
subSchema,
|
|
373
|
+
defs,
|
|
374
|
+
visited,
|
|
375
|
+
depth + 1,
|
|
376
|
+
maxDepth,
|
|
377
|
+
moduleId
|
|
378
|
+
);
|
|
379
|
+
if (resolved.properties) {
|
|
380
|
+
Object.assign(
|
|
381
|
+
merged.properties,
|
|
382
|
+
resolved.properties
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
if (Array.isArray(resolved.required)) {
|
|
386
|
+
merged.required.push(...resolved.required);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
merged.required = [...new Set(merged.required)];
|
|
390
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
391
|
+
if (k !== "allOf" && !(k in merged)) {
|
|
392
|
+
merged[k] = v;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return merged;
|
|
396
|
+
}
|
|
397
|
+
for (const keyword of ["anyOf", "oneOf"]) {
|
|
398
|
+
if (keyword in obj && Array.isArray(obj[keyword])) {
|
|
399
|
+
const merged = {
|
|
400
|
+
properties: {},
|
|
401
|
+
required: []
|
|
402
|
+
};
|
|
403
|
+
const allRequiredSets = [];
|
|
404
|
+
for (const subSchema of obj[keyword]) {
|
|
405
|
+
const resolved = resolveNode(
|
|
406
|
+
subSchema,
|
|
407
|
+
defs,
|
|
408
|
+
visited,
|
|
409
|
+
depth + 1,
|
|
410
|
+
maxDepth,
|
|
411
|
+
moduleId
|
|
412
|
+
);
|
|
413
|
+
if (resolved.properties) {
|
|
414
|
+
Object.assign(
|
|
415
|
+
merged.properties,
|
|
416
|
+
resolved.properties
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
if (Array.isArray(resolved.required)) {
|
|
420
|
+
allRequiredSets.push(new Set(resolved.required));
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
if (allRequiredSets.length > 0) {
|
|
424
|
+
let intersection = allRequiredSets[0];
|
|
425
|
+
for (let i = 1; i < allRequiredSets.length; i++) {
|
|
426
|
+
intersection = new Set(
|
|
427
|
+
[...intersection].filter((x) => allRequiredSets[i].has(x))
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
merged.required = [...intersection];
|
|
431
|
+
} else {
|
|
432
|
+
merged.required = [];
|
|
433
|
+
}
|
|
434
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
435
|
+
if (k !== keyword && !(k in merged)) {
|
|
436
|
+
merged[k] = v;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return merged;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if ("properties" in obj && typeof obj.properties === "object" && obj.properties !== null) {
|
|
443
|
+
const props = obj.properties;
|
|
444
|
+
for (const [propName, propSchema] of Object.entries(props)) {
|
|
445
|
+
props[propName] = resolveNode(
|
|
446
|
+
propSchema,
|
|
447
|
+
defs,
|
|
448
|
+
visited,
|
|
449
|
+
depth + 1,
|
|
450
|
+
maxDepth,
|
|
451
|
+
moduleId
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return obj;
|
|
456
|
+
}
|
|
457
|
+
var init_ref_resolver = __esm({
|
|
458
|
+
"src/ref-resolver.ts"() {
|
|
459
|
+
"use strict";
|
|
460
|
+
init_esm_shims();
|
|
461
|
+
init_errors();
|
|
462
|
+
}
|
|
463
|
+
});
|
|
164
464
|
|
|
165
465
|
// src/schema-parser.ts
|
|
166
|
-
|
|
167
|
-
|
|
466
|
+
function mapType(propName, propSchema) {
|
|
467
|
+
const schemaType = propSchema.type;
|
|
468
|
+
if (schemaType === "string" && (propName.endsWith("_file") || propSchema["x-cli-file"] === true)) {
|
|
469
|
+
return "file";
|
|
470
|
+
}
|
|
471
|
+
const typeMap = {
|
|
472
|
+
string: "string",
|
|
473
|
+
integer: "int",
|
|
474
|
+
number: "float",
|
|
475
|
+
boolean: BOOLEAN_FLAG,
|
|
476
|
+
object: "string",
|
|
477
|
+
array: "string"
|
|
478
|
+
};
|
|
479
|
+
if (!schemaType) {
|
|
480
|
+
return "string";
|
|
481
|
+
}
|
|
482
|
+
return typeMap[schemaType] ?? "string";
|
|
483
|
+
}
|
|
484
|
+
function extractHelp(propSchema, maxLength = 1e3) {
|
|
485
|
+
let text = propSchema["x-llm-description"];
|
|
486
|
+
if (!text) {
|
|
487
|
+
text = propSchema.description;
|
|
488
|
+
}
|
|
489
|
+
if (!text) {
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
if (maxLength > 0 && text.length > maxLength) {
|
|
493
|
+
return text.slice(0, maxLength - 3) + "...";
|
|
494
|
+
}
|
|
495
|
+
return text;
|
|
496
|
+
}
|
|
497
|
+
function schemaToCliOptions(schema, maxHelpLength = 1e3) {
|
|
498
|
+
const properties = schema.properties ?? {};
|
|
499
|
+
const requiredList = schema.required ?? [];
|
|
500
|
+
const options = [];
|
|
501
|
+
const flagNames = {};
|
|
502
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
503
|
+
const flagName = "--" + propName.replace(/_/g, "-");
|
|
504
|
+
if (RESERVED_NAMES.has(propName)) {
|
|
505
|
+
process.stderr.write(
|
|
506
|
+
`Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.
|
|
507
|
+
`
|
|
508
|
+
);
|
|
509
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
510
|
+
}
|
|
511
|
+
if (flagName in flagNames) {
|
|
512
|
+
process.stderr.write(
|
|
513
|
+
`Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.
|
|
514
|
+
`
|
|
515
|
+
);
|
|
516
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
517
|
+
}
|
|
518
|
+
flagNames[flagName] = propName;
|
|
519
|
+
const typeResult = mapType(propName, propSchema);
|
|
520
|
+
const isRequired = requiredList.includes(propName);
|
|
521
|
+
const helpBase = extractHelp(propSchema, maxHelpLength);
|
|
522
|
+
const helpText = isRequired ? (helpBase ? helpBase + " " : "") + "[required]" : helpBase ?? "";
|
|
523
|
+
const defaultValue = propSchema.default;
|
|
524
|
+
if (typeResult === BOOLEAN_FLAG) {
|
|
525
|
+
const flagBase = propName.replace(/_/g, "-");
|
|
526
|
+
const noFlag = `--no-${flagBase}`;
|
|
527
|
+
if (noFlag in flagNames) {
|
|
528
|
+
process.stderr.write(
|
|
529
|
+
`Error: Flag name collision: boolean property '${propName}' auto-generates '${noFlag}' which is already used by property '${flagNames[noFlag]}'.
|
|
530
|
+
`
|
|
531
|
+
);
|
|
532
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
533
|
+
}
|
|
534
|
+
flagNames[noFlag] = propName;
|
|
535
|
+
const defaultVal = propSchema.default ?? false;
|
|
536
|
+
options.push({
|
|
537
|
+
name: propName,
|
|
538
|
+
flags: `--${flagBase}, --no-${flagBase}`,
|
|
539
|
+
description: helpText,
|
|
540
|
+
defaultValue: defaultVal,
|
|
541
|
+
required: isRequired,
|
|
542
|
+
isBooleanFlag: true
|
|
543
|
+
});
|
|
544
|
+
} else if ("enum" in propSchema && Array.isArray(propSchema.enum)) {
|
|
545
|
+
const enumValues = propSchema.enum;
|
|
546
|
+
if (enumValues.length === 0) {
|
|
547
|
+
options.push({
|
|
548
|
+
name: propName,
|
|
549
|
+
flags: `${flagName} <value>`,
|
|
550
|
+
description: helpText,
|
|
551
|
+
defaultValue,
|
|
552
|
+
required: isRequired
|
|
553
|
+
});
|
|
554
|
+
} else {
|
|
555
|
+
const stringValues = enumValues.map(String);
|
|
556
|
+
const enumOriginalTypes = {};
|
|
557
|
+
for (const v of enumValues) {
|
|
558
|
+
if (typeof v === "number" && Number.isInteger(v)) {
|
|
559
|
+
enumOriginalTypes[String(v)] = "int";
|
|
560
|
+
} else if (typeof v === "number") {
|
|
561
|
+
enumOriginalTypes[String(v)] = "float";
|
|
562
|
+
} else if (typeof v === "boolean") {
|
|
563
|
+
enumOriginalTypes[String(v)] = "bool";
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
options.push({
|
|
567
|
+
name: propName,
|
|
568
|
+
flags: `${flagName} <value>`,
|
|
569
|
+
description: helpText,
|
|
570
|
+
defaultValue: defaultValue !== void 0 ? String(defaultValue) : void 0,
|
|
571
|
+
required: isRequired,
|
|
572
|
+
choices: stringValues,
|
|
573
|
+
enumOriginalTypes: Object.keys(enumOriginalTypes).length > 0 ? enumOriginalTypes : void 0
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
} else {
|
|
577
|
+
let parseArg;
|
|
578
|
+
if (typeResult === "int") {
|
|
579
|
+
parseArg = (v) => {
|
|
580
|
+
const n = parseInt(v, 10);
|
|
581
|
+
if (isNaN(n)) throw new Error(`Invalid integer: ${v}`);
|
|
582
|
+
return n;
|
|
583
|
+
};
|
|
584
|
+
} else if (typeResult === "float") {
|
|
585
|
+
parseArg = (v) => {
|
|
586
|
+
const n = parseFloat(v);
|
|
587
|
+
if (isNaN(n)) throw new Error(`Invalid number: ${v}`);
|
|
588
|
+
return n;
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
options.push({
|
|
592
|
+
name: propName,
|
|
593
|
+
flags: `${flagName} <value>`,
|
|
594
|
+
description: helpText,
|
|
595
|
+
defaultValue,
|
|
596
|
+
required: isRequired,
|
|
597
|
+
parseArg
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
return options;
|
|
602
|
+
}
|
|
603
|
+
var BOOLEAN_FLAG, RESERVED_NAMES;
|
|
604
|
+
var init_schema_parser = __esm({
|
|
605
|
+
"src/schema-parser.ts"() {
|
|
606
|
+
"use strict";
|
|
607
|
+
init_esm_shims();
|
|
608
|
+
init_errors();
|
|
609
|
+
BOOLEAN_FLAG = /* @__PURE__ */ Symbol("BOOLEAN_FLAG");
|
|
610
|
+
RESERVED_NAMES = /* @__PURE__ */ new Set([
|
|
611
|
+
"input",
|
|
612
|
+
"yes",
|
|
613
|
+
"large_input",
|
|
614
|
+
"format",
|
|
615
|
+
"fields",
|
|
616
|
+
"sandbox",
|
|
617
|
+
"verbose",
|
|
618
|
+
"dry_run",
|
|
619
|
+
"trace",
|
|
620
|
+
"stream",
|
|
621
|
+
"strategy",
|
|
622
|
+
"approval_timeout",
|
|
623
|
+
"approval_token"
|
|
624
|
+
]);
|
|
625
|
+
}
|
|
626
|
+
});
|
|
168
627
|
|
|
169
628
|
// src/approval.ts
|
|
170
|
-
init_esm_shims();
|
|
171
|
-
init_errors();
|
|
172
629
|
import * as readline from "readline";
|
|
630
|
+
function getAnnotation(annotations, key, defaultValue = void 0) {
|
|
631
|
+
if (!annotations || typeof annotations !== "object") return defaultValue;
|
|
632
|
+
const ann = annotations;
|
|
633
|
+
return key in ann ? ann[key] : defaultValue;
|
|
634
|
+
}
|
|
635
|
+
function readTimeoutFromEnv() {
|
|
636
|
+
const raw = process.env.APCORE_CLI_APPROVAL_TIMEOUT;
|
|
637
|
+
if (raw === void 0 || raw === "") return void 0;
|
|
638
|
+
const parsed = parseInt(raw, 10);
|
|
639
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
|
|
640
|
+
return parsed;
|
|
641
|
+
}
|
|
642
|
+
async function checkApproval(moduleDef, autoApprove, timeout) {
|
|
643
|
+
const annotations = moduleDef.annotations;
|
|
644
|
+
let requiresApproval;
|
|
645
|
+
if (moduleDef.requiresApproval !== void 0) {
|
|
646
|
+
requiresApproval = moduleDef.requiresApproval;
|
|
647
|
+
} else if (annotations) {
|
|
648
|
+
requiresApproval = getAnnotation(annotations, "requires_approval", false) === true;
|
|
649
|
+
} else {
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (!requiresApproval) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const moduleId = moduleDef.id;
|
|
656
|
+
if (autoApprove) {
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const envVal = process.env.APCORE_CLI_AUTO_APPROVE ?? "";
|
|
660
|
+
if (envVal === "1") {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (envVal !== "" && envVal !== "1") {
|
|
664
|
+
process.stderr.write(
|
|
665
|
+
`Warning: APCORE_CLI_AUTO_APPROVE is set to '${envVal}', expected '1'. Ignoring.
|
|
666
|
+
`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
if (!process.stdin.isTTY) {
|
|
670
|
+
throw new ApprovalDeniedError(
|
|
671
|
+
`Module '${moduleId}' requires approval but no interactive terminal is available. Use --yes or set APCORE_CLI_AUTO_APPROVE=1 to bypass.`
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
const effectiveTimeout = timeout ?? readTimeoutFromEnv() ?? 60;
|
|
675
|
+
await promptWithTimeout(moduleDef, effectiveTimeout);
|
|
676
|
+
}
|
|
677
|
+
async function promptWithTimeout(moduleDef, timeout) {
|
|
678
|
+
timeout = Math.max(1, Math.min(timeout, 3600));
|
|
679
|
+
const moduleId = moduleDef.id;
|
|
680
|
+
const annotations = moduleDef.annotations;
|
|
681
|
+
const message = (annotations ? getAnnotation(annotations, "approval_message") : void 0) ?? `Module '${moduleId}' requires approval to execute.`;
|
|
682
|
+
process.stderr.write(message + "\n");
|
|
683
|
+
const rl = readline.createInterface({
|
|
684
|
+
input: process.stdin,
|
|
685
|
+
output: process.stderr
|
|
686
|
+
});
|
|
687
|
+
let timer;
|
|
688
|
+
try {
|
|
689
|
+
const answer = await Promise.race([
|
|
690
|
+
new Promise((resolve2) => {
|
|
691
|
+
rl.question("Proceed? [y/N] ", (ans) => resolve2(ans));
|
|
692
|
+
}),
|
|
693
|
+
new Promise((_, reject) => {
|
|
694
|
+
timer = setTimeout(() => {
|
|
695
|
+
reject(new ApprovalTimeoutError(
|
|
696
|
+
`Approval prompt timed out after ${timeout} seconds.`
|
|
697
|
+
));
|
|
698
|
+
}, timeout * 1e3);
|
|
699
|
+
})
|
|
700
|
+
]);
|
|
701
|
+
if (timer) clearTimeout(timer);
|
|
702
|
+
const normalized = answer.trim().toLowerCase();
|
|
703
|
+
if (normalized === "y" || normalized === "yes") {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
throw new ApprovalDeniedError("Approval denied");
|
|
707
|
+
} catch (err) {
|
|
708
|
+
if (timer) clearTimeout(timer);
|
|
709
|
+
throw err;
|
|
710
|
+
} finally {
|
|
711
|
+
rl.close();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
var CliApprovalHandler;
|
|
715
|
+
var init_approval = __esm({
|
|
716
|
+
"src/approval.ts"() {
|
|
717
|
+
"use strict";
|
|
718
|
+
init_esm_shims();
|
|
719
|
+
init_errors();
|
|
720
|
+
CliApprovalHandler = class {
|
|
721
|
+
autoApprove;
|
|
722
|
+
timeout;
|
|
723
|
+
constructor(autoApprove = false, timeout) {
|
|
724
|
+
this.autoApprove = autoApprove;
|
|
725
|
+
const resolved = timeout ?? readTimeoutFromEnv() ?? 60;
|
|
726
|
+
this.timeout = Math.max(1, Math.min(resolved, 3600));
|
|
727
|
+
}
|
|
728
|
+
async requestApproval(request) {
|
|
729
|
+
const moduleId = request.module_id ?? "unknown";
|
|
730
|
+
if (this.autoApprove) {
|
|
731
|
+
return { status: "approved", approved_by: "auto_approve" };
|
|
732
|
+
}
|
|
733
|
+
const envVal = process.env.APCORE_CLI_AUTO_APPROVE ?? "";
|
|
734
|
+
if (envVal === "1") {
|
|
735
|
+
return { status: "approved", approved_by: "env_auto_approve" };
|
|
736
|
+
}
|
|
737
|
+
if (envVal !== "" && envVal !== "1") {
|
|
738
|
+
process.stderr.write(
|
|
739
|
+
`Warning: APCORE_CLI_AUTO_APPROVE is set to '${envVal}', expected '1'. Ignoring.
|
|
740
|
+
`
|
|
741
|
+
);
|
|
742
|
+
}
|
|
743
|
+
if (!process.stdin.isTTY) {
|
|
744
|
+
return { status: "rejected", reason: "Non-interactive session without --yes" };
|
|
745
|
+
}
|
|
746
|
+
const annotations = request.annotations;
|
|
747
|
+
const extra = annotations?.extra ?? {};
|
|
748
|
+
const message = extra.approval_message ?? `Module '${moduleId}' requires approval to execute.`;
|
|
749
|
+
process.stderr.write(message + "\n");
|
|
750
|
+
try {
|
|
751
|
+
await promptWithTimeout({ id: moduleId }, this.timeout);
|
|
752
|
+
return { status: "approved", approved_by: "tty_user" };
|
|
753
|
+
} catch {
|
|
754
|
+
return { status: "rejected", reason: "User rejected or timed out" };
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
async checkApproval(_approvalId) {
|
|
758
|
+
return { status: "rejected", reason: "CLI does not support async approval polling" };
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
});
|
|
173
763
|
|
|
174
764
|
// src/output.ts
|
|
175
|
-
|
|
765
|
+
import yaml from "js-yaml";
|
|
766
|
+
function csvCellString(value) {
|
|
767
|
+
if (value === null || value === void 0) return "";
|
|
768
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
769
|
+
return String(value);
|
|
770
|
+
}
|
|
176
771
|
function resolveFormat(explicitFormat) {
|
|
177
772
|
if (explicitFormat !== void 0) {
|
|
178
773
|
return explicitFormat;
|
|
179
774
|
}
|
|
180
775
|
return process.stdout.isTTY ? "table" : "json";
|
|
181
776
|
}
|
|
777
|
+
function truncate(text, maxLength = 80) {
|
|
778
|
+
if (text.length <= maxLength) {
|
|
779
|
+
return text;
|
|
780
|
+
}
|
|
781
|
+
return text.slice(0, maxLength - 3) + "...";
|
|
782
|
+
}
|
|
182
783
|
function formatTable(headers, rows) {
|
|
183
784
|
const colWidths = headers.map(
|
|
184
785
|
(h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
|
|
@@ -190,6 +791,137 @@ function formatTable(headers, rows) {
|
|
|
190
791
|
);
|
|
191
792
|
return [headerLine, sep2, ...dataLines].join("\n") + "\n";
|
|
192
793
|
}
|
|
794
|
+
function formatModuleList(modules, format, filterTags, showDeps = false, exposureFilter) {
|
|
795
|
+
if (format === "table") {
|
|
796
|
+
if (modules.length === 0 && filterTags && filterTags.length > 0) {
|
|
797
|
+
process.stdout.write(
|
|
798
|
+
`No modules found matching tags: ${filterTags.join(", ")}.
|
|
799
|
+
`
|
|
800
|
+
);
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (modules.length === 0) {
|
|
804
|
+
process.stdout.write("No modules found.\n");
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const headers = ["ID", "Description", "Tags"];
|
|
808
|
+
if (showDeps) headers.push("Deps");
|
|
809
|
+
if (exposureFilter) headers.push("Exposure");
|
|
810
|
+
const rows = modules.map((m) => {
|
|
811
|
+
const base = [m.id, truncate(m.description, 80), (m.tags ?? []).join(", ")];
|
|
812
|
+
if (showDeps) {
|
|
813
|
+
const deps = m.dependencies;
|
|
814
|
+
base.push(String(Array.isArray(deps) ? deps.length : 0));
|
|
815
|
+
}
|
|
816
|
+
if (exposureFilter) {
|
|
817
|
+
base.push(exposureFilter.isExposed(m.id ?? "") ? "\u2713" : "\u2014");
|
|
818
|
+
}
|
|
819
|
+
return base;
|
|
820
|
+
});
|
|
821
|
+
process.stdout.write(formatTable(headers, rows));
|
|
822
|
+
} else if (format === "json") {
|
|
823
|
+
const result = modules.map((m) => {
|
|
824
|
+
const entry = {
|
|
825
|
+
id: m.id,
|
|
826
|
+
description: m.description,
|
|
827
|
+
tags: m.tags ?? []
|
|
828
|
+
};
|
|
829
|
+
if (showDeps) {
|
|
830
|
+
const deps = m.dependencies;
|
|
831
|
+
entry.dependency_count = Array.isArray(deps) ? deps.length : 0;
|
|
832
|
+
}
|
|
833
|
+
if (exposureFilter) {
|
|
834
|
+
entry.exposed = exposureFilter.isExposed(m.id ?? "");
|
|
835
|
+
}
|
|
836
|
+
return entry;
|
|
837
|
+
});
|
|
838
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function annotationsToDict(annotations) {
|
|
842
|
+
if (!annotations) return null;
|
|
843
|
+
if (typeof annotations !== "object" || Array.isArray(annotations)) return null;
|
|
844
|
+
const result = {};
|
|
845
|
+
for (const [k, v] of Object.entries(annotations)) {
|
|
846
|
+
if (v !== null && v !== void 0 && v !== false && v !== 0 && !(Array.isArray(v) && v.length === 0)) {
|
|
847
|
+
result[k] = v;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
851
|
+
}
|
|
852
|
+
function formatModuleDetail(moduleDef, format) {
|
|
853
|
+
if (format === "table") {
|
|
854
|
+
process.stdout.write(`
|
|
855
|
+
Module: ${moduleDef.id}
|
|
856
|
+
`);
|
|
857
|
+
process.stdout.write(`
|
|
858
|
+
Description:
|
|
859
|
+
${moduleDef.description}
|
|
860
|
+
`);
|
|
861
|
+
if (moduleDef.inputSchema && Object.keys(moduleDef.inputSchema).length > 0) {
|
|
862
|
+
process.stdout.write("\nInput Schema:\n");
|
|
863
|
+
process.stdout.write(JSON.stringify(moduleDef.inputSchema, null, 2) + "\n");
|
|
864
|
+
}
|
|
865
|
+
if (moduleDef.outputSchema && Object.keys(moduleDef.outputSchema).length > 0) {
|
|
866
|
+
process.stdout.write("\nOutput Schema:\n");
|
|
867
|
+
process.stdout.write(JSON.stringify(moduleDef.outputSchema, null, 2) + "\n");
|
|
868
|
+
}
|
|
869
|
+
const annDict = annotationsToDict(
|
|
870
|
+
moduleDef.annotations
|
|
871
|
+
);
|
|
872
|
+
if (annDict) {
|
|
873
|
+
process.stdout.write("\nAnnotations:\n");
|
|
874
|
+
for (const [k, v] of Object.entries(annDict)) {
|
|
875
|
+
process.stdout.write(` ${k}: ${v}
|
|
876
|
+
`);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
const metadata = moduleDef.metadata;
|
|
880
|
+
if (metadata) {
|
|
881
|
+
const xFields = {};
|
|
882
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
883
|
+
if (k.startsWith("x-") || k.startsWith("x_")) {
|
|
884
|
+
xFields[k] = v;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
if (Object.keys(xFields).length > 0) {
|
|
888
|
+
process.stdout.write("\nExtension Metadata:\n");
|
|
889
|
+
for (const [k, v] of Object.entries(xFields)) {
|
|
890
|
+
process.stdout.write(` ${k}: ${v}
|
|
891
|
+
`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
const tags = moduleDef.tags ?? [];
|
|
896
|
+
if (tags.length > 0) {
|
|
897
|
+
process.stdout.write(`
|
|
898
|
+
Tags: ${tags.join(", ")}
|
|
899
|
+
`);
|
|
900
|
+
}
|
|
901
|
+
} else if (format === "json") {
|
|
902
|
+
const result = {
|
|
903
|
+
id: moduleDef.id,
|
|
904
|
+
description: moduleDef.description
|
|
905
|
+
};
|
|
906
|
+
if (moduleDef.inputSchema) result.input_schema = moduleDef.inputSchema;
|
|
907
|
+
if (moduleDef.outputSchema) result.output_schema = moduleDef.outputSchema;
|
|
908
|
+
const annDict = annotationsToDict(
|
|
909
|
+
moduleDef.annotations
|
|
910
|
+
);
|
|
911
|
+
if (annDict) result.annotations = annDict;
|
|
912
|
+
const tags = moduleDef.tags ?? [];
|
|
913
|
+
if (tags.length > 0) result.tags = tags;
|
|
914
|
+
const metadata = moduleDef.metadata;
|
|
915
|
+
if (metadata) {
|
|
916
|
+
for (const [k, v] of Object.entries(metadata)) {
|
|
917
|
+
if (k.startsWith("x-") || k.startsWith("x_")) {
|
|
918
|
+
result[k] = v;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
923
|
+
}
|
|
924
|
+
}
|
|
193
925
|
function selectFields(result, fields) {
|
|
194
926
|
const selected = {};
|
|
195
927
|
for (const f of fields.split(",")) {
|
|
@@ -221,42 +953,21 @@ function formatExecResult(result, format, fields) {
|
|
|
221
953
|
const obj = effective_result;
|
|
222
954
|
const keys = Object.keys(obj);
|
|
223
955
|
const header = keys.map(escapeCsvField).join(",");
|
|
224
|
-
const row = keys.map((k) => escapeCsvField(
|
|
956
|
+
const row = keys.map((k) => escapeCsvField(csvCellString(obj[k]))).join(",");
|
|
225
957
|
process.stdout.write(header + "\n" + row + "\n");
|
|
226
958
|
} else if (Array.isArray(effective_result) && effective_result.length > 0 && typeof effective_result[0] === "object") {
|
|
227
959
|
const keys = Object.keys(effective_result[0]);
|
|
228
960
|
const header = keys.map(escapeCsvField).join(",");
|
|
229
961
|
const rows = effective_result.map((item) => {
|
|
230
962
|
const obj = item;
|
|
231
|
-
return keys.map((k) => escapeCsvField(
|
|
963
|
+
return keys.map((k) => escapeCsvField(csvCellString(obj[k]))).join(",");
|
|
232
964
|
});
|
|
233
965
|
process.stdout.write(header + "\n" + rows.join("\n") + "\n");
|
|
234
966
|
} else {
|
|
235
967
|
process.stdout.write(JSON.stringify(effective_result) + "\n");
|
|
236
968
|
}
|
|
237
969
|
} else if (effective === "yaml") {
|
|
238
|
-
|
|
239
|
-
const obj = effective_result;
|
|
240
|
-
const lines = Object.entries(obj).map(([k, v]) => {
|
|
241
|
-
if (v === null || v === void 0) return `${k}: null`;
|
|
242
|
-
if (typeof v === "object") return `${k}: ${JSON.stringify(v)}`;
|
|
243
|
-
return `${k}: ${v}`;
|
|
244
|
-
});
|
|
245
|
-
process.stdout.write(lines.join("\n") + "\n");
|
|
246
|
-
} else if (Array.isArray(effective_result)) {
|
|
247
|
-
for (const item of effective_result) {
|
|
248
|
-
if (typeof item === "object" && item !== null) {
|
|
249
|
-
const obj = item;
|
|
250
|
-
const lines = Object.entries(obj).map(([k, v]) => ` ${k}: ${v}`);
|
|
251
|
-
process.stdout.write("- " + lines.join("\n ") + "\n");
|
|
252
|
-
} else {
|
|
253
|
-
process.stdout.write(`- ${item}
|
|
254
|
-
`);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
} else {
|
|
258
|
-
process.stdout.write(String(effective_result) + "\n");
|
|
259
|
-
}
|
|
970
|
+
process.stdout.write(yaml.dump(effective_result, { lineWidth: -1 }));
|
|
260
971
|
} else if (effective === "jsonl") {
|
|
261
972
|
if (Array.isArray(effective_result)) {
|
|
262
973
|
for (const item of effective_result) {
|
|
@@ -279,7 +990,7 @@ function formatExecResult(result, format, fields) {
|
|
|
279
990
|
}
|
|
280
991
|
}
|
|
281
992
|
function escapeCsvField(value) {
|
|
282
|
-
if (value.includes(",") || value.includes('"') || value.includes("\n")) {
|
|
993
|
+
if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
|
|
283
994
|
return '"' + value.replace(/"/g, '""') + '"';
|
|
284
995
|
}
|
|
285
996
|
return value;
|
|
@@ -289,7 +1000,9 @@ function formatPreflightResult(result, format) {
|
|
|
289
1000
|
if (resolved === "json" || !process.stdout.isTTY) {
|
|
290
1001
|
const payload = {
|
|
291
1002
|
valid: result.valid,
|
|
292
|
-
|
|
1003
|
+
// JSON output key stays snake_case for cross-language CLI contract;
|
|
1004
|
+
// runtime read uses camelCase to match apcore-js PreflightResult.
|
|
1005
|
+
requires_approval: result.requiresApproval,
|
|
293
1006
|
checks: result.checks.map((c) => {
|
|
294
1007
|
const entry = { check: c.check, passed: c.passed };
|
|
295
1008
|
if (c.error !== void 0 && c.error !== null) {
|
|
@@ -340,26 +1053,30 @@ Result: ${tag} (${errors} error(s), ${warnings} warning(s))
|
|
|
340
1053
|
}
|
|
341
1054
|
function firstFailedExitCode(result) {
|
|
342
1055
|
const checkToExit = {
|
|
343
|
-
module_id:
|
|
344
|
-
module_lookup:
|
|
345
|
-
call_chain:
|
|
346
|
-
acl:
|
|
347
|
-
schema:
|
|
348
|
-
approval:
|
|
349
|
-
module_preflight:
|
|
1056
|
+
module_id: EXIT_CODES.INVALID_CLI_INPUT,
|
|
1057
|
+
module_lookup: EXIT_CODES.MODULE_NOT_FOUND,
|
|
1058
|
+
call_chain: EXIT_CODES.MODULE_EXECUTE_ERROR,
|
|
1059
|
+
acl: EXIT_CODES.ACL_DENIED,
|
|
1060
|
+
schema: EXIT_CODES.SCHEMA_VALIDATION_ERROR,
|
|
1061
|
+
approval: EXIT_CODES.APPROVAL_DENIED,
|
|
1062
|
+
module_preflight: EXIT_CODES.MODULE_EXECUTE_ERROR
|
|
350
1063
|
};
|
|
351
1064
|
for (const check of result.checks) {
|
|
352
1065
|
if (!check.passed) {
|
|
353
|
-
return checkToExit[check.check] ??
|
|
1066
|
+
return checkToExit[check.check] ?? EXIT_CODES.MODULE_EXECUTE_ERROR;
|
|
354
1067
|
}
|
|
355
1068
|
}
|
|
356
|
-
return
|
|
1069
|
+
return EXIT_CODES.MODULE_EXECUTE_ERROR;
|
|
357
1070
|
}
|
|
1071
|
+
var init_output = __esm({
|
|
1072
|
+
"src/output.ts"() {
|
|
1073
|
+
"use strict";
|
|
1074
|
+
init_esm_shims();
|
|
1075
|
+
init_errors();
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
358
1078
|
|
|
359
1079
|
// src/logger.ts
|
|
360
|
-
init_esm_shims();
|
|
361
|
-
var LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 };
|
|
362
|
-
var currentLevel = "WARNING";
|
|
363
1080
|
function setLogLevel(level) {
|
|
364
1081
|
const upper = level.toUpperCase();
|
|
365
1082
|
if (upper in LEVELS) {
|
|
@@ -369,44 +1086,39 @@ function setLogLevel(level) {
|
|
|
369
1086
|
function shouldLog(level) {
|
|
370
1087
|
return LEVELS[level] >= LEVELS[currentLevel];
|
|
371
1088
|
}
|
|
372
|
-
function
|
|
373
|
-
if (shouldLog("
|
|
1089
|
+
function warn(message) {
|
|
1090
|
+
if (shouldLog("WARNING")) process.stderr.write(`WARNING: ${message}
|
|
374
1091
|
`);
|
|
375
1092
|
}
|
|
1093
|
+
var LEVELS, currentLevel;
|
|
1094
|
+
var init_logger = __esm({
|
|
1095
|
+
"src/logger.ts"() {
|
|
1096
|
+
"use strict";
|
|
1097
|
+
init_esm_shims();
|
|
1098
|
+
LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 };
|
|
1099
|
+
currentLevel = "WARNING";
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
376
1102
|
|
|
377
1103
|
// src/init-cmd.ts
|
|
378
|
-
init_esm_shims();
|
|
379
1104
|
import * as fs from "fs";
|
|
380
1105
|
import * as path2 from "path";
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
* {description}
|
|
397
|
-
*/
|
|
398
|
-
{cliGroupLine}
|
|
399
|
-
export function {funcName}(): Record<string, unknown> {
|
|
400
|
-
// TODO: implement
|
|
401
|
-
return { status: "ok" };
|
|
1106
|
+
function runFsOp(op, targetPath, fn, partial) {
|
|
1107
|
+
try {
|
|
1108
|
+
return fn();
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1111
|
+
process.stderr.write(`Error: failed to ${op} ${targetPath}: ${msg}
|
|
1112
|
+
`);
|
|
1113
|
+
if (partial && partial.length > 0) {
|
|
1114
|
+
process.stderr.write(
|
|
1115
|
+
` Partial scaffold left on disk \u2014 you may want to remove: ${partial.join(", ")}
|
|
1116
|
+
`
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
|
|
1120
|
+
}
|
|
402
1121
|
}
|
|
403
|
-
`;
|
|
404
|
-
var BINDING_TEMPLATE = `bindings:
|
|
405
|
-
- module_id: "{moduleId}"
|
|
406
|
-
target: "{target}"
|
|
407
|
-
description: "{description}"
|
|
408
|
-
auto_schema: true
|
|
409
|
-
`;
|
|
410
1122
|
function renderTemplate(template, context) {
|
|
411
1123
|
let result = template;
|
|
412
1124
|
for (const [key, value] of Object.entries(context)) {
|
|
@@ -450,7 +1162,7 @@ function registerInitCommand(cli) {
|
|
|
450
1162
|
});
|
|
451
1163
|
}
|
|
452
1164
|
function createDecoratorModule(moduleId, _prefix, funcName, description, outputDir) {
|
|
453
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
1165
|
+
runFsOp("create directory", outputDir, () => fs.mkdirSync(outputDir, { recursive: true }));
|
|
454
1166
|
const filename = moduleId.replace(/\./g, "_") + ".ts";
|
|
455
1167
|
const filepath = path2.join(outputDir, filename);
|
|
456
1168
|
const varName = funcName + "Module";
|
|
@@ -460,14 +1172,14 @@ function createDecoratorModule(moduleId, _prefix, funcName, description, outputD
|
|
|
460
1172
|
funcName,
|
|
461
1173
|
description
|
|
462
1174
|
});
|
|
463
|
-
fs.writeFileSync(filepath, content);
|
|
1175
|
+
runFsOp("write file", filepath, () => fs.writeFileSync(filepath, content));
|
|
464
1176
|
process.stdout.write(`Created ${filepath}
|
|
465
1177
|
`);
|
|
466
1178
|
}
|
|
467
1179
|
function createConventionModule(moduleId, prefix, funcName, description, outputDir) {
|
|
468
1180
|
const prefixParts = prefix.split(".");
|
|
469
1181
|
const dirPath = prefixParts.length > 1 ? path2.join(outputDir, ...prefixParts.slice(0, -1)) : outputDir;
|
|
470
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
1182
|
+
runFsOp("create directory", dirPath, () => fs.mkdirSync(dirPath, { recursive: true }));
|
|
471
1183
|
let filename;
|
|
472
1184
|
if (prefixParts.length > 1) {
|
|
473
1185
|
filename = prefixParts[prefixParts.length - 1] + ".ts";
|
|
@@ -485,12 +1197,13 @@ function createConventionModule(moduleId, prefix, funcName, description, outputD
|
|
|
485
1197
|
description,
|
|
486
1198
|
cliGroupLine
|
|
487
1199
|
});
|
|
488
|
-
fs.writeFileSync(filepath, content);
|
|
1200
|
+
runFsOp("write file", filepath, () => fs.writeFileSync(filepath, content));
|
|
489
1201
|
process.stdout.write(`Created ${filepath}
|
|
490
1202
|
`);
|
|
491
1203
|
}
|
|
492
1204
|
function createBindingModule(moduleId, prefix, funcName, description, outputDir) {
|
|
493
|
-
|
|
1205
|
+
const partial = [];
|
|
1206
|
+
runFsOp("create directory", outputDir, () => fs.mkdirSync(outputDir, { recursive: true }));
|
|
494
1207
|
const yamlFile = path2.join(outputDir, moduleId.replace(/\./g, "_") + ".binding.yaml");
|
|
495
1208
|
const target = `commands.${prefix}:${funcName}`;
|
|
496
1209
|
const yamlContent = renderTemplate(BINDING_TEMPLATE, {
|
|
@@ -498,11 +1211,12 @@ function createBindingModule(moduleId, prefix, funcName, description, outputDir)
|
|
|
498
1211
|
target,
|
|
499
1212
|
description
|
|
500
1213
|
});
|
|
501
|
-
fs.writeFileSync(yamlFile, yamlContent);
|
|
1214
|
+
runFsOp("write file", yamlFile, () => fs.writeFileSync(yamlFile, yamlContent));
|
|
1215
|
+
partial.push(yamlFile);
|
|
502
1216
|
process.stdout.write(`Created ${yamlFile}
|
|
503
1217
|
`);
|
|
504
1218
|
const baseSrc = "commands";
|
|
505
|
-
fs.mkdirSync(baseSrc, { recursive: true });
|
|
1219
|
+
runFsOp("create directory", baseSrc, () => fs.mkdirSync(baseSrc, { recursive: true }), partial);
|
|
506
1220
|
const srcFile = path2.join(baseSrc, prefix.replace(/\./g, "_") + ".ts");
|
|
507
1221
|
if (!fs.existsSync(srcFile)) {
|
|
508
1222
|
const srcContent = `export function ${funcName}(): Record<string, unknown> {
|
|
@@ -511,69 +1225,421 @@ function createBindingModule(moduleId, prefix, funcName, description, outputDir)
|
|
|
511
1225
|
return { status: "ok" };
|
|
512
1226
|
}
|
|
513
1227
|
`;
|
|
514
|
-
fs.writeFileSync(srcFile, srcContent);
|
|
1228
|
+
runFsOp("write file", srcFile, () => fs.writeFileSync(srcFile, srcContent), partial);
|
|
515
1229
|
process.stdout.write(`Created ${srcFile}
|
|
516
1230
|
`);
|
|
517
1231
|
}
|
|
518
1232
|
}
|
|
1233
|
+
var DECORATOR_TEMPLATE, CONVENTION_TEMPLATE, BINDING_TEMPLATE;
|
|
1234
|
+
var init_init_cmd = __esm({
|
|
1235
|
+
"src/init-cmd.ts"() {
|
|
1236
|
+
"use strict";
|
|
1237
|
+
init_esm_shims();
|
|
1238
|
+
init_errors();
|
|
1239
|
+
DECORATOR_TEMPLATE = `import { module } from "apcore-js";
|
|
1240
|
+
import { Type } from "@sinclair/typebox";
|
|
1241
|
+
|
|
1242
|
+
export const {varName} = module({
|
|
1243
|
+
id: "{moduleId}",
|
|
1244
|
+
description: "{description}",
|
|
1245
|
+
inputSchema: Type.Object({}),
|
|
1246
|
+
outputSchema: Type.Object({ status: Type.String() }),
|
|
1247
|
+
execute: (_inputs) => {
|
|
1248
|
+
// TODO: implement
|
|
1249
|
+
return { status: "ok" };
|
|
1250
|
+
},
|
|
1251
|
+
});
|
|
1252
|
+
`;
|
|
1253
|
+
CONVENTION_TEMPLATE = `/**
|
|
1254
|
+
* {description}
|
|
1255
|
+
*/
|
|
1256
|
+
{cliGroupLine}
|
|
1257
|
+
export function {funcName}(): Record<string, unknown> {
|
|
1258
|
+
// TODO: implement
|
|
1259
|
+
return { status: "ok" };
|
|
1260
|
+
}
|
|
1261
|
+
`;
|
|
1262
|
+
BINDING_TEMPLATE = `spec_version: "1.0"
|
|
1263
|
+
bindings:
|
|
1264
|
+
- module_id: "{moduleId}"
|
|
1265
|
+
target: "{target}"
|
|
1266
|
+
description: "{description}"
|
|
1267
|
+
auto_schema: true
|
|
1268
|
+
`;
|
|
1269
|
+
}
|
|
1270
|
+
});
|
|
519
1271
|
|
|
520
1272
|
// src/display-helpers.ts
|
|
521
|
-
|
|
1273
|
+
function getDisplay(descriptor) {
|
|
1274
|
+
const metadata = descriptor.metadata ?? {};
|
|
1275
|
+
const display = metadata.display;
|
|
1276
|
+
if (display && typeof display === "object" && !Array.isArray(display)) {
|
|
1277
|
+
return display;
|
|
1278
|
+
}
|
|
1279
|
+
const overlay = lookupBindingDisplay(descriptor.id);
|
|
1280
|
+
return overlay ?? {};
|
|
1281
|
+
}
|
|
1282
|
+
var init_display_helpers = __esm({
|
|
1283
|
+
"src/display-helpers.ts"() {
|
|
1284
|
+
"use strict";
|
|
1285
|
+
init_esm_shims();
|
|
1286
|
+
init_main();
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
522
1289
|
|
|
523
1290
|
// src/config.ts
|
|
524
|
-
init_esm_shims();
|
|
525
1291
|
import * as fs2 from "fs";
|
|
526
|
-
import
|
|
527
|
-
|
|
528
|
-
"apcore-cli.stdin_buffer_limit": "cli.stdin_buffer_limit",
|
|
529
|
-
"apcore-cli.auto_approve": "cli.auto_approve",
|
|
530
|
-
"apcore-cli.help_text_max_length": "cli.help_text_max_length",
|
|
531
|
-
"apcore-cli.logging_level": "logging.level"
|
|
532
|
-
};
|
|
533
|
-
var LEGACY_TO_NAMESPACE = Object.fromEntries(
|
|
534
|
-
Object.entries(NAMESPACE_TO_LEGACY).map(([k, v]) => [v, k])
|
|
535
|
-
);
|
|
1292
|
+
import { createRequire } from "module";
|
|
1293
|
+
import yaml2 from "js-yaml";
|
|
536
1294
|
function registerConfigNamespace() {
|
|
537
1295
|
try {
|
|
538
|
-
const
|
|
1296
|
+
const nodeRequire = createRequire(import.meta.url);
|
|
1297
|
+
const { Config } = nodeRequire("apcore-js");
|
|
539
1298
|
if (typeof Config?.registerNamespace === "function") {
|
|
540
1299
|
Config.registerNamespace({
|
|
541
1300
|
name: "apcore-cli",
|
|
542
1301
|
envPrefix: "APCORE_CLI",
|
|
543
|
-
defaults:
|
|
544
|
-
stdin_buffer_limit: 10485760,
|
|
545
|
-
auto_approve: false,
|
|
546
|
-
help_text_max_length: 1e3,
|
|
547
|
-
logging_level: "WARNING",
|
|
548
|
-
approval_timeout: 60,
|
|
549
|
-
strategy: "standard",
|
|
550
|
-
group_depth: 1
|
|
551
|
-
}
|
|
1302
|
+
defaults: NAMESPACE_DEFAULTS
|
|
552
1303
|
});
|
|
553
1304
|
}
|
|
554
1305
|
} catch {
|
|
555
1306
|
}
|
|
556
1307
|
}
|
|
1308
|
+
var DEFAULTS, NAMESPACE_DEFAULTS, NAMESPACE_TO_LEGACY, LEGACY_TO_NAMESPACE, ConfigResolver;
|
|
1309
|
+
var init_config = __esm({
|
|
1310
|
+
"src/config.ts"() {
|
|
1311
|
+
"use strict";
|
|
1312
|
+
init_esm_shims();
|
|
1313
|
+
init_logger();
|
|
1314
|
+
DEFAULTS = {
|
|
1315
|
+
"extensions.root": "./extensions",
|
|
1316
|
+
"logging.level": "WARNING",
|
|
1317
|
+
"cli.help_text_max_length": 1e3,
|
|
1318
|
+
// FE-11 config keys
|
|
1319
|
+
"cli.approval_timeout": 60,
|
|
1320
|
+
"cli.strategy": "standard",
|
|
1321
|
+
"cli.group_depth": 1,
|
|
1322
|
+
// Exposure filtering (FE-12)
|
|
1323
|
+
"expose.mode": "all",
|
|
1324
|
+
"expose.include": [],
|
|
1325
|
+
"expose.exclude": []
|
|
1326
|
+
// Builtin group visibility (FE-13) — apcli.* keys are NOT in DEFAULTS.
|
|
1327
|
+
// The runtime reads them via resolveObject('apcli') (raw yaml walk) and
|
|
1328
|
+
// does not use the flat-key resolve() path. Python and Rust have no such
|
|
1329
|
+
// entries either. (D11-008 cleanup)
|
|
1330
|
+
};
|
|
1331
|
+
NAMESPACE_DEFAULTS = {
|
|
1332
|
+
stdin_buffer_limit: 10485760,
|
|
1333
|
+
auto_approve: false,
|
|
1334
|
+
help_text_max_length: 1e3,
|
|
1335
|
+
logging_level: "WARNING",
|
|
1336
|
+
approval_timeout: 60,
|
|
1337
|
+
strategy: "standard",
|
|
1338
|
+
group_depth: 1,
|
|
1339
|
+
// FE-13 — builtin group visibility configuration
|
|
1340
|
+
apcli: {
|
|
1341
|
+
mode: null,
|
|
1342
|
+
include: [],
|
|
1343
|
+
exclude: [],
|
|
1344
|
+
disable_env: false
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
NAMESPACE_TO_LEGACY = {
|
|
1348
|
+
"apcore-cli.stdin_buffer_limit": "cli.stdin_buffer_limit",
|
|
1349
|
+
"apcore-cli.auto_approve": "cli.auto_approve",
|
|
1350
|
+
"apcore-cli.help_text_max_length": "cli.help_text_max_length",
|
|
1351
|
+
"apcore-cli.logging_level": "logging.level"
|
|
1352
|
+
};
|
|
1353
|
+
LEGACY_TO_NAMESPACE = Object.fromEntries(
|
|
1354
|
+
Object.entries(NAMESPACE_TO_LEGACY).map(([k, v]) => [v, k])
|
|
1355
|
+
);
|
|
1356
|
+
ConfigResolver = class {
|
|
1357
|
+
cliFlags;
|
|
1358
|
+
configPath;
|
|
1359
|
+
fileCache = null;
|
|
1360
|
+
fileCacheLoaded = false;
|
|
1361
|
+
/**
|
|
1362
|
+
* Raw parsed yaml root (pre-flatten). Populated alongside `fileCache`
|
|
1363
|
+
* on load. Used by `resolveObject()` to walk nested paths without
|
|
1364
|
+
* invoking `flattenDict` — see FE-13 spec §4.8 M1 note.
|
|
1365
|
+
* `null` when no config file is present or parsing fails.
|
|
1366
|
+
*/
|
|
1367
|
+
_rawConfig = null;
|
|
1368
|
+
constructor(cliFlags, configPath) {
|
|
1369
|
+
this.cliFlags = cliFlags ?? {};
|
|
1370
|
+
this.configPath = configPath ?? "apcore.yaml";
|
|
1371
|
+
}
|
|
1372
|
+
/**
|
|
1373
|
+
* Resolve a single configuration key across all four tiers.
|
|
1374
|
+
*/
|
|
1375
|
+
resolve(key, cliFlag, envVar) {
|
|
1376
|
+
if (cliFlag !== void 0 && cliFlag in this.cliFlags) {
|
|
1377
|
+
const value = this.cliFlags[cliFlag];
|
|
1378
|
+
if (value !== null && value !== void 0) {
|
|
1379
|
+
return value;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
if (envVar) {
|
|
1383
|
+
const envValue = process.env[envVar];
|
|
1384
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
1385
|
+
return envValue;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
const fileValue = this.resolveFromFile(key);
|
|
1389
|
+
if (fileValue !== void 0) {
|
|
1390
|
+
return fileValue;
|
|
1391
|
+
}
|
|
1392
|
+
const altKey = NAMESPACE_TO_LEGACY[key] ?? LEGACY_TO_NAMESPACE[key];
|
|
1393
|
+
if (altKey) {
|
|
1394
|
+
const altFileValue = this.resolveFromFile(altKey);
|
|
1395
|
+
if (altFileValue !== void 0) {
|
|
1396
|
+
return altFileValue;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return DEFAULTS[key];
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Load a value from the config file using a dot-separated key path.
|
|
1403
|
+
*/
|
|
1404
|
+
resolveFromFile(key) {
|
|
1405
|
+
if (!this.fileCacheLoaded) {
|
|
1406
|
+
this.fileCache = this.loadConfigFile();
|
|
1407
|
+
this.fileCacheLoaded = true;
|
|
1408
|
+
}
|
|
1409
|
+
if (this.fileCache === null) {
|
|
1410
|
+
return void 0;
|
|
1411
|
+
}
|
|
1412
|
+
return this.fileCache[key];
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Resolve a configuration key to its raw nested value (FE-13).
|
|
1416
|
+
*
|
|
1417
|
+
* Unlike `resolve()`, this method does NOT flatten the yaml tree — it
|
|
1418
|
+
* walks the dot-separated path directly against the parsed yaml root.
|
|
1419
|
+
* This lets callers retrieve non-leaf structures (booleans, arrays,
|
|
1420
|
+
* objects) such as the `apcli` visibility config, which is naturally
|
|
1421
|
+
* shaped as a nested object in apcore.yaml.
|
|
1422
|
+
*
|
|
1423
|
+
* Semantics:
|
|
1424
|
+
* - Returns `null` when no config file is loaded or when the path is
|
|
1425
|
+
* not present / descends into a non-object node (including arrays).
|
|
1426
|
+
* - Returns the raw value (boolean / array / object / scalar) when the
|
|
1427
|
+
* full path resolves to a leaf or intermediate node.
|
|
1428
|
+
*
|
|
1429
|
+
* Intentionally DOES NOT consult DEFAULTS, env vars, or CLI flags — it is
|
|
1430
|
+
* strictly a yaml-tree accessor. Scalar `resolve()` semantics are
|
|
1431
|
+
* unaffected.
|
|
1432
|
+
*/
|
|
1433
|
+
resolveObject(key) {
|
|
1434
|
+
if (!this.fileCacheLoaded) {
|
|
1435
|
+
this.fileCache = this.loadConfigFile();
|
|
1436
|
+
this.fileCacheLoaded = true;
|
|
1437
|
+
}
|
|
1438
|
+
if (this._rawConfig == null) {
|
|
1439
|
+
return null;
|
|
1440
|
+
}
|
|
1441
|
+
const parts = key.split(".");
|
|
1442
|
+
let cur = this._rawConfig;
|
|
1443
|
+
for (const p of parts) {
|
|
1444
|
+
if (cur != null && typeof cur === "object" && !Array.isArray(cur) && p in cur) {
|
|
1445
|
+
cur = cur[p];
|
|
1446
|
+
} else {
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return cur;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Load and flatten a YAML config file.
|
|
1454
|
+
*/
|
|
1455
|
+
loadConfigFile() {
|
|
1456
|
+
let content;
|
|
1457
|
+
try {
|
|
1458
|
+
content = fs2.readFileSync(this.configPath, "utf-8");
|
|
1459
|
+
} catch (err) {
|
|
1460
|
+
if (err instanceof Error && "code" in err && err.code === "ENOENT") {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
warn(
|
|
1464
|
+
`Configuration file '${this.configPath}' is malformed, using defaults.`
|
|
1465
|
+
);
|
|
1466
|
+
return null;
|
|
1467
|
+
}
|
|
1468
|
+
let parsed;
|
|
1469
|
+
try {
|
|
1470
|
+
parsed = yaml2.load(content);
|
|
1471
|
+
} catch {
|
|
1472
|
+
warn(
|
|
1473
|
+
`Configuration file '${this.configPath}' is malformed, using defaults.`
|
|
1474
|
+
);
|
|
1475
|
+
return null;
|
|
1476
|
+
}
|
|
1477
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1478
|
+
warn(
|
|
1479
|
+
`Configuration file '${this.configPath}' is malformed, using defaults.`
|
|
1480
|
+
);
|
|
1481
|
+
return null;
|
|
1482
|
+
}
|
|
1483
|
+
this._rawConfig = parsed;
|
|
1484
|
+
return this.flattenDict(parsed);
|
|
1485
|
+
}
|
|
1486
|
+
/**
|
|
1487
|
+
* Flatten nested dict to dot-notation keys.
|
|
1488
|
+
*/
|
|
1489
|
+
flattenDict(d, prefix = "") {
|
|
1490
|
+
const result = {};
|
|
1491
|
+
for (const [key, value] of Object.entries(d)) {
|
|
1492
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
1493
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
1494
|
+
Object.assign(
|
|
1495
|
+
result,
|
|
1496
|
+
this.flattenDict(value, fullKey)
|
|
1497
|
+
);
|
|
1498
|
+
} else {
|
|
1499
|
+
result[fullKey] = value;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
return result;
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
});
|
|
557
1507
|
|
|
558
1508
|
// src/shell.ts
|
|
559
|
-
init_esm_shims();
|
|
560
|
-
init_errors();
|
|
561
|
-
import { readFileSync as readFileSync2 } from "fs";
|
|
562
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
563
|
-
import * as path3 from "path";
|
|
564
1509
|
import { spawnSync } from "child_process";
|
|
565
1510
|
import { Command, Help, Option } from "commander";
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
1511
|
+
function makeFunctionName(progName) {
|
|
1512
|
+
return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
|
|
1513
|
+
}
|
|
1514
|
+
function shellQuote(s) {
|
|
1515
|
+
return "'" + s.replace(/'/g, "'\\''") + "'";
|
|
1516
|
+
}
|
|
1517
|
+
function enumerateApcliSubcommands(apcliGroup) {
|
|
1518
|
+
if (!apcliGroup) return [];
|
|
1519
|
+
return apcliGroup.commands.filter((c) => !isCmdHidden(c)).map((c) => c.name());
|
|
1520
|
+
}
|
|
1521
|
+
function enumerateRootCommands(program) {
|
|
1522
|
+
return program.commands.filter((c) => !isCmdHidden(c)).map((c) => c.name());
|
|
1523
|
+
}
|
|
1524
|
+
function findApcliGroup(program) {
|
|
1525
|
+
return program.commands.find((c) => c.name() === "apcli");
|
|
1526
|
+
}
|
|
1527
|
+
function isCmdHidden(cmd) {
|
|
1528
|
+
const withHiddenFn = cmd;
|
|
1529
|
+
const withHiddenField = cmd;
|
|
1530
|
+
if (typeof withHiddenFn.hidden === "function") return !!withHiddenFn.hidden();
|
|
1531
|
+
return !!withHiddenField._hidden;
|
|
1532
|
+
}
|
|
1533
|
+
function generateBashCompletion(progName, program) {
|
|
1534
|
+
const fn = makeFunctionName(progName);
|
|
1535
|
+
const quoted = shellQuote(progName);
|
|
1536
|
+
const apcliGroup = program ? findApcliGroup(program) : void 0;
|
|
1537
|
+
const apcliVisible = apcliGroup !== void 0 && !isCmdHidden(apcliGroup);
|
|
1538
|
+
const rootCmds = program ? enumerateRootCommands(program).filter(
|
|
1539
|
+
(n) => n !== "apcli" || apcliVisible
|
|
1540
|
+
) : [];
|
|
1541
|
+
const apcliCmds = apcliVisible ? enumerateApcliSubcommands(apcliGroup) : [];
|
|
1542
|
+
const rootOpts = rootCmds.join(" ");
|
|
1543
|
+
const apcliOpts = apcliCmds.join(" ");
|
|
1544
|
+
let body = `${fn}() {
|
|
1545
|
+
local cur prev opts
|
|
1546
|
+
COMPREPLY=()
|
|
1547
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
1548
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
1549
|
+
|
|
1550
|
+
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
|
1551
|
+
opts="${rootOpts}"
|
|
1552
|
+
COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
|
|
1553
|
+
return 0
|
|
1554
|
+
fi
|
|
1555
|
+
`;
|
|
1556
|
+
if (apcliVisible) {
|
|
1557
|
+
body += `
|
|
1558
|
+
if [[ \${COMP_CWORD} -eq 2 ]]; then
|
|
1559
|
+
if [[ "\${COMP_WORDS[1]}" == "apcli" ]]; then
|
|
1560
|
+
local apcli_cmds="${apcliOpts}"
|
|
1561
|
+
COMPREPLY=( $(compgen -W "\${apcli_cmds}" -- \${cur}) )
|
|
1562
|
+
return 0
|
|
1563
|
+
fi
|
|
1564
|
+
fi
|
|
1565
|
+
`;
|
|
1566
|
+
}
|
|
1567
|
+
body += `}
|
|
1568
|
+
complete -F ${fn} ${quoted}
|
|
1569
|
+
`;
|
|
1570
|
+
return body;
|
|
1571
|
+
}
|
|
1572
|
+
function generateZshCompletion(progName, program) {
|
|
1573
|
+
const fn = makeFunctionName(progName);
|
|
1574
|
+
const quoted = shellQuote(progName);
|
|
1575
|
+
const apcliGroup = program ? findApcliGroup(program) : void 0;
|
|
1576
|
+
const apcliVisible = apcliGroup !== void 0 && !isCmdHidden(apcliGroup);
|
|
1577
|
+
const rootCmds = program ? enumerateRootCommands(program).filter(
|
|
1578
|
+
(n) => n !== "apcli" || apcliVisible
|
|
1579
|
+
) : [];
|
|
1580
|
+
const apcliCmds = apcliVisible ? enumerateApcliSubcommands(apcliGroup) : [];
|
|
1581
|
+
const rootEntries = rootCmds.map((n) => ` '${n}:${n}'`).join("\n");
|
|
1582
|
+
const apcliEntries = apcliCmds.map((n) => ` '${n}:${n}'`).join("\n");
|
|
1583
|
+
return `#compdef ${progName}
|
|
1584
|
+
|
|
1585
|
+
${fn}() {
|
|
1586
|
+
local -a commands
|
|
1587
|
+
commands=(
|
|
1588
|
+
` + (rootEntries ? rootEntries + "\n" : "") + ` )
|
|
1589
|
+
local -a apcli_cmds
|
|
1590
|
+
apcli_cmds=(
|
|
1591
|
+
` + (apcliEntries ? apcliEntries + "\n" : "") + ` )
|
|
1592
|
+
|
|
1593
|
+
_arguments -C \\
|
|
1594
|
+
'1:command:->command' \\
|
|
1595
|
+
'*::arg:->args'
|
|
1596
|
+
|
|
1597
|
+
case "$state" in
|
|
1598
|
+
command)
|
|
1599
|
+
_describe -t commands '${progName} commands' commands
|
|
1600
|
+
;;
|
|
1601
|
+
args)
|
|
1602
|
+
case "\${words[1]}" in
|
|
1603
|
+
apcli)
|
|
1604
|
+
_describe -t apcli_cmds '${progName} apcli commands' apcli_cmds
|
|
1605
|
+
;;
|
|
1606
|
+
esac
|
|
1607
|
+
;;
|
|
1608
|
+
esac
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
compdef ${fn} ${quoted}
|
|
1612
|
+
`;
|
|
1613
|
+
}
|
|
1614
|
+
function generateFishCompletion(progName, program) {
|
|
1615
|
+
const quoted = shellQuote(progName);
|
|
1616
|
+
const apcliGroup = program ? findApcliGroup(program) : void 0;
|
|
1617
|
+
const apcliVisible = apcliGroup !== void 0 && !isCmdHidden(apcliGroup);
|
|
1618
|
+
const rootCmds = program ? enumerateRootCommands(program).filter(
|
|
1619
|
+
(n) => n !== "apcli" || apcliVisible
|
|
1620
|
+
) : [];
|
|
1621
|
+
const apcliCmds = apcliVisible ? enumerateApcliSubcommands(apcliGroup) : [];
|
|
1622
|
+
const lines = [];
|
|
1623
|
+
lines.push(`# Fish completions for ${progName}`);
|
|
1624
|
+
for (const name of rootCmds) {
|
|
1625
|
+
lines.push(
|
|
1626
|
+
`complete -c ${quoted} -n "__fish_use_subcommand" -a ${name} -d "${name}"`
|
|
1627
|
+
);
|
|
1628
|
+
}
|
|
1629
|
+
if (apcliVisible && apcliCmds.length > 0) {
|
|
1630
|
+
lines.push("");
|
|
1631
|
+
for (const name of apcliCmds) {
|
|
1632
|
+
lines.push(
|
|
1633
|
+
`complete -c ${quoted} -n "__fish_seen_subcommand_from apcli" -a ${name} -d "${name}"`
|
|
1634
|
+
);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
return lines.join("\n") + "\n";
|
|
572
1638
|
}
|
|
573
1639
|
function roffEscape(s) {
|
|
574
1640
|
return s.replace(/\\/g, "\\\\").replace(/-/g, "\\-").replace(/'/g, "\\(aq");
|
|
575
1641
|
}
|
|
576
|
-
function buildProgramManPage(program, progName, version, description,
|
|
1642
|
+
function buildProgramManPage(program, progName, version, description, docsUrl2) {
|
|
577
1643
|
const help = new Help();
|
|
578
1644
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
579
1645
|
const s = [];
|
|
@@ -663,18 +1729,18 @@ ${meaning}`);
|
|
|
663
1729
|
}
|
|
664
1730
|
s.push(".SH SEE ALSO");
|
|
665
1731
|
s.push(`\\fB${progName} \\-\\-help \\-\\-verbose\\fR for full option list.`);
|
|
666
|
-
if (
|
|
1732
|
+
if (docsUrl2) {
|
|
667
1733
|
s.push(`.PP
|
|
668
|
-
Full documentation at \\fI${roffEscape(
|
|
1734
|
+
Full documentation at \\fI${roffEscape(docsUrl2)}\\fR`);
|
|
669
1735
|
}
|
|
670
1736
|
return s.join("\n");
|
|
671
1737
|
}
|
|
672
|
-
function configureManHelp(program, progName, version, description,
|
|
1738
|
+
function configureManHelp(program, progName, version, description, docsUrl2) {
|
|
673
1739
|
const manOpt = new Option("--man", "Output man page in roff format (use with --help)").hideHelp();
|
|
674
1740
|
program.addOption(manOpt);
|
|
675
1741
|
program.addHelpText("beforeAll", () => {
|
|
676
1742
|
if (program.opts().man) {
|
|
677
|
-
const roff = buildProgramManPage(program, progName, version, description,
|
|
1743
|
+
const roff = buildProgramManPage(program, progName, version, description, docsUrl2) + "\n";
|
|
678
1744
|
if (process.stdout.isTTY) {
|
|
679
1745
|
const pagers = [
|
|
680
1746
|
{ cmd: "mandoc", args: ["-a"] },
|
|
@@ -710,119 +1776,777 @@ function configureManHelp(program, progName, version, description, docsUrl) {
|
|
|
710
1776
|
return "";
|
|
711
1777
|
});
|
|
712
1778
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
function
|
|
719
|
-
const
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
`
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
const merged = opts.input ? await collectInput(opts.input, {}, false) : {};
|
|
728
|
-
if (!executor.validate) {
|
|
729
|
-
process.stderr.write("Error: Executor does not support validate.\n");
|
|
730
|
-
process.exit(1);
|
|
1779
|
+
function findRootProgram(host) {
|
|
1780
|
+
let cur = host;
|
|
1781
|
+
while (cur.parent) cur = cur.parent;
|
|
1782
|
+
return cur;
|
|
1783
|
+
}
|
|
1784
|
+
function registerCompletionCommand(host) {
|
|
1785
|
+
const completionCmd = new Command("completion").description("Generate a shell completion script and print it to stdout.").argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
|
|
1786
|
+
const validShells = ["bash", "zsh", "fish"];
|
|
1787
|
+
if (!validShells.includes(shell)) {
|
|
1788
|
+
process.stderr.write(
|
|
1789
|
+
`Error: Unknown shell '${shell}'. Expected: bash, zsh, or fish.
|
|
1790
|
+
`
|
|
1791
|
+
);
|
|
1792
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
731
1793
|
}
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
1794
|
+
const root = findRootProgram(host);
|
|
1795
|
+
const resolved = root.name() || "apcore-cli";
|
|
1796
|
+
const generators = {
|
|
1797
|
+
bash: () => generateBashCompletion(resolved, root),
|
|
1798
|
+
zsh: () => generateZshCompletion(resolved, root),
|
|
1799
|
+
fish: () => generateFishCompletion(resolved, root)
|
|
1800
|
+
};
|
|
1801
|
+
process.stdout.write(generators[shell]());
|
|
735
1802
|
});
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// src/system-cmd.ts
|
|
740
|
-
init_esm_shims();
|
|
741
|
-
import { Command as Command3 } from "commander";
|
|
742
|
-
async function callSystemModule(executor, moduleId, inputs) {
|
|
743
|
-
if (executor.call) {
|
|
744
|
-
return executor.call(moduleId, inputs);
|
|
745
|
-
}
|
|
746
|
-
return executor.execute(moduleId, inputs);
|
|
1803
|
+
host.addCommand(completionCmd);
|
|
747
1804
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
return;
|
|
1805
|
+
var init_shell = __esm({
|
|
1806
|
+
"src/shell.ts"() {
|
|
1807
|
+
"use strict";
|
|
1808
|
+
init_esm_shims();
|
|
1809
|
+
init_errors();
|
|
754
1810
|
}
|
|
755
|
-
|
|
756
|
-
process.stdout.write(`Health Overview (${total} modules)
|
|
1811
|
+
});
|
|
757
1812
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
const parts = [];
|
|
773
|
-
for (const key of ["healthy", "degraded", "error"]) {
|
|
774
|
-
const count = summary[key];
|
|
775
|
-
if (count) parts.push(`${count} ${key}`);
|
|
776
|
-
}
|
|
777
|
-
process.stdout.write(`
|
|
778
|
-
Summary: ${parts.join(", ") || "no data"}
|
|
779
|
-
`);
|
|
1813
|
+
// src/security/audit.ts
|
|
1814
|
+
var audit_exports = {};
|
|
1815
|
+
__export(audit_exports, {
|
|
1816
|
+
AuditLogger: () => AuditLogger,
|
|
1817
|
+
canonicalizeForHash: () => canonicalizeForHash,
|
|
1818
|
+
getAuditLogger: () => getAuditLogger,
|
|
1819
|
+
setAuditLogger: () => setAuditLogger
|
|
1820
|
+
});
|
|
1821
|
+
import * as crypto from "crypto";
|
|
1822
|
+
import * as fs3 from "fs";
|
|
1823
|
+
import * as os from "os";
|
|
1824
|
+
import * as path3 from "path";
|
|
1825
|
+
function setAuditLogger(auditLogger) {
|
|
1826
|
+
_auditLogger = auditLogger;
|
|
780
1827
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const
|
|
788
|
-
const
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
process.stdout.write(`Calls: ${total.toLocaleString()} total | ${errors.toLocaleString()} errors | ${(rate * 100).toFixed(1)}% error rate
|
|
792
|
-
`);
|
|
793
|
-
process.stdout.write(`Latency: ${avg.toFixed(0)}ms avg | ${p99.toFixed(0)}ms p99
|
|
794
|
-
`);
|
|
795
|
-
const recent = result.recent_errors ?? [];
|
|
796
|
-
if (recent.length > 0) {
|
|
797
|
-
process.stdout.write(`
|
|
798
|
-
Recent Errors (top ${recent.length}):
|
|
799
|
-
`);
|
|
800
|
-
for (const e of recent) {
|
|
801
|
-
const count = e.count ?? "?";
|
|
802
|
-
const last = e.last_occurred ?? "?";
|
|
803
|
-
process.stdout.write(` ${String(e.code ?? "?").padEnd(24)} x${count} (last: ${last})
|
|
804
|
-
`);
|
|
805
|
-
}
|
|
1828
|
+
function getAuditLogger() {
|
|
1829
|
+
return _auditLogger;
|
|
1830
|
+
}
|
|
1831
|
+
function canonicalizeForHash(value) {
|
|
1832
|
+
if (value === null || typeof value !== "object") return value;
|
|
1833
|
+
if (Array.isArray(value)) return value.map(canonicalizeForHash);
|
|
1834
|
+
const src = value;
|
|
1835
|
+
const sorted = {};
|
|
1836
|
+
for (const key of Object.keys(src).sort()) {
|
|
1837
|
+
sorted[key] = canonicalizeForHash(src[key]);
|
|
806
1838
|
}
|
|
1839
|
+
return sorted;
|
|
807
1840
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1841
|
+
var _auditLogger, AuditLogger;
|
|
1842
|
+
var init_audit = __esm({
|
|
1843
|
+
"src/security/audit.ts"() {
|
|
1844
|
+
"use strict";
|
|
1845
|
+
init_esm_shims();
|
|
1846
|
+
init_logger();
|
|
1847
|
+
_auditLogger = null;
|
|
1848
|
+
AuditLogger = class _AuditLogger {
|
|
1849
|
+
static DEFAULT_PATH = path3.join(
|
|
1850
|
+
os.homedir(),
|
|
1851
|
+
".apcore-cli",
|
|
1852
|
+
"audit.jsonl"
|
|
1853
|
+
);
|
|
1854
|
+
logPath;
|
|
1855
|
+
writeFailureWarned = false;
|
|
1856
|
+
constructor(path5) {
|
|
1857
|
+
this.logPath = path5 ?? _AuditLogger.DEFAULT_PATH;
|
|
1858
|
+
this.ensureDirectory();
|
|
1859
|
+
}
|
|
1860
|
+
ensureDirectory() {
|
|
1861
|
+
const dir = path3.dirname(this.logPath);
|
|
1862
|
+
try {
|
|
1863
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1864
|
+
try {
|
|
1865
|
+
fs3.chmodSync(dir, 448);
|
|
1866
|
+
} catch {
|
|
1867
|
+
}
|
|
1868
|
+
} catch {
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
logExecution(moduleId, inputData, status, exitCode, durationMs) {
|
|
1872
|
+
const entry = {
|
|
1873
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1874
|
+
user: this.getUser(),
|
|
1875
|
+
module_id: moduleId,
|
|
1876
|
+
input_hash: this.hashInput(inputData),
|
|
1877
|
+
status,
|
|
1878
|
+
exit_code: exitCode,
|
|
1879
|
+
duration_ms: durationMs
|
|
1880
|
+
};
|
|
1881
|
+
try {
|
|
1882
|
+
fs3.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
|
|
1883
|
+
try {
|
|
1884
|
+
fs3.chmodSync(this.logPath, 384);
|
|
1885
|
+
} catch {
|
|
1886
|
+
}
|
|
1887
|
+
} catch (err) {
|
|
1888
|
+
if (!this.writeFailureWarned) {
|
|
1889
|
+
this.writeFailureWarned = true;
|
|
1890
|
+
warn(`Could not write audit log: ${err}`);
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
hashInput(inputData) {
|
|
1895
|
+
const salt = crypto.randomBytes(16);
|
|
1896
|
+
const payload = JSON.stringify(canonicalizeForHash(inputData));
|
|
1897
|
+
return crypto.createHash("sha256").update(Buffer.concat([salt, Buffer.from(payload, "utf-8")])).digest("hex");
|
|
1898
|
+
}
|
|
1899
|
+
getUser() {
|
|
1900
|
+
try {
|
|
1901
|
+
return os.userInfo().username;
|
|
1902
|
+
} catch {
|
|
1903
|
+
return process.env.USER ?? process.env.USERNAME ?? "unknown";
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
};
|
|
815
1907
|
}
|
|
816
|
-
|
|
1908
|
+
});
|
|
817
1909
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
1910
|
+
// src/security/config-encryptor.ts
|
|
1911
|
+
import * as crypto2 from "crypto";
|
|
1912
|
+
import * as os2 from "os";
|
|
1913
|
+
async function getKeytar() {
|
|
1914
|
+
if (keytarModule) return keytarModule;
|
|
1915
|
+
try {
|
|
1916
|
+
keytarModule = await import("keytar");
|
|
1917
|
+
return keytarModule;
|
|
1918
|
+
} catch {
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
}
|
|
1922
|
+
var PBKDF2_ITERATIONS, V1_STATIC_SALT, keytarModule, ConfigEncryptor;
|
|
1923
|
+
var init_config_encryptor = __esm({
|
|
1924
|
+
"src/security/config-encryptor.ts"() {
|
|
1925
|
+
"use strict";
|
|
1926
|
+
init_esm_shims();
|
|
1927
|
+
init_errors();
|
|
1928
|
+
init_logger();
|
|
1929
|
+
PBKDF2_ITERATIONS = 6e5;
|
|
1930
|
+
V1_STATIC_SALT = Buffer.from("apcore-cli-config-v1");
|
|
1931
|
+
keytarModule = null;
|
|
1932
|
+
ConfigEncryptor = class _ConfigEncryptor {
|
|
1933
|
+
static SERVICE_NAME = "apcore-cli";
|
|
1934
|
+
// One-shot flag so the "obfuscation only" warning fires exactly once
|
|
1935
|
+
// per process instead of once per encrypt/decrypt call.
|
|
1936
|
+
static weakFallbackWarned = false;
|
|
1937
|
+
/**
|
|
1938
|
+
* Encrypt and store a configuration value.
|
|
1939
|
+
*
|
|
1940
|
+
* Cross-SDK contract (D10-003, 2026-04-26): when the OS keyring is
|
|
1941
|
+
* detected as available but `setPassword` then throws (locked keyring,
|
|
1942
|
+
* transient backend failure, permission revoked, etc.), the error is
|
|
1943
|
+
* propagated wrapped in a `ConfigDecryptionError`. Previously TS
|
|
1944
|
+
* caught the exception and silently fell through to AES file encryption
|
|
1945
|
+
* — a quiet downgrade that surprised users who expected a hard failure.
|
|
1946
|
+
* Python lets the keyring exception propagate raw; Rust returns
|
|
1947
|
+
* `ConfigDecryptionError::KeyringError`. The fall-through to AES is
|
|
1948
|
+
* still reached when `getKeytar()` returns `null` (keyring
|
|
1949
|
+
* genuinely unavailable on this platform / install).
|
|
1950
|
+
*/
|
|
1951
|
+
async store(key, value) {
|
|
1952
|
+
const keytar = await getKeytar();
|
|
1953
|
+
if (keytar) {
|
|
1954
|
+
try {
|
|
1955
|
+
await keytar.setPassword(_ConfigEncryptor.SERVICE_NAME, key, value);
|
|
1956
|
+
return `keyring:${key}`;
|
|
1957
|
+
} catch (err) {
|
|
1958
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1959
|
+
throw new ConfigDecryptionError(
|
|
1960
|
+
`Failed to store '${key}' in OS keyring: ${detail}. Unset APCORE_CLI_CONFIG_PASSPHRASE-aware backends or unlock the keyring before retrying.`
|
|
1961
|
+
);
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
warn("OS keyring unavailable. Using file-based encryption.");
|
|
1965
|
+
const ciphertext = this.aesEncrypt(value);
|
|
1966
|
+
return `enc:v2:${ciphertext.toString("base64")}`;
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Retrieve and decrypt a configuration value.
|
|
1970
|
+
*/
|
|
1971
|
+
async retrieve(configValue, key) {
|
|
1972
|
+
if (configValue.startsWith("keyring:")) {
|
|
1973
|
+
const keytar = await getKeytar();
|
|
1974
|
+
if (!keytar) {
|
|
1975
|
+
throw new ConfigDecryptionError(
|
|
1976
|
+
`Keyring module not available to retrieve '${key}'.`
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
try {
|
|
1980
|
+
const refKey = configValue.slice("keyring:".length);
|
|
1981
|
+
const result = await keytar.getPassword(
|
|
1982
|
+
_ConfigEncryptor.SERVICE_NAME,
|
|
1983
|
+
refKey
|
|
1984
|
+
);
|
|
1985
|
+
if (result === null || result === void 0) {
|
|
1986
|
+
throw new ConfigDecryptionError(
|
|
1987
|
+
`Keyring entry not found for '${refKey}'.`
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
return result;
|
|
1991
|
+
} catch (err) {
|
|
1992
|
+
if (err instanceof ConfigDecryptionError) throw err;
|
|
1993
|
+
throw new ConfigDecryptionError(
|
|
1994
|
+
`Failed to retrieve from keyring: ${err}`
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
if (configValue.startsWith("enc:v2:")) {
|
|
1999
|
+
const data = Buffer.from(configValue.slice("enc:v2:".length), "base64");
|
|
2000
|
+
try {
|
|
2001
|
+
return this.aesDecrypt(data);
|
|
2002
|
+
} catch {
|
|
2003
|
+
throw new ConfigDecryptionError(
|
|
2004
|
+
`Failed to decrypt configuration value '${key}'. Re-configure with 'apcore-cli config set ${key}'.`
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
if (configValue.startsWith("enc:")) {
|
|
2009
|
+
const data = Buffer.from(configValue.slice("enc:".length), "base64");
|
|
2010
|
+
try {
|
|
2011
|
+
return this.aesDecryptV1(data);
|
|
2012
|
+
} catch {
|
|
2013
|
+
throw new ConfigDecryptionError(
|
|
2014
|
+
`Failed to decrypt configuration value '${key}'. Re-configure with 'apcore-cli config set ${key}'.`
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
return configValue;
|
|
2019
|
+
}
|
|
2020
|
+
// Derive an AES-256 key with a provided salt (v2 format).
|
|
2021
|
+
//
|
|
2022
|
+
// Order of preference:
|
|
2023
|
+
// 1. APCORE_CLI_CONFIG_PASSPHRASE env var — a real secret supplied by
|
|
2024
|
+
// the user; produces a key an attacker with filesystem read cannot
|
|
2025
|
+
// reconstruct without also knowing the passphrase.
|
|
2026
|
+
// 2. hostname + username — obfuscation-only derivation for backward
|
|
2027
|
+
// compatibility. Emits a loud stderr warning on first use so
|
|
2028
|
+
// operators know the stored value is NOT protected against a
|
|
2029
|
+
// filesystem-read attacker.
|
|
2030
|
+
deriveKey(salt) {
|
|
2031
|
+
const passphrase = process.env.APCORE_CLI_CONFIG_PASSPHRASE;
|
|
2032
|
+
if (passphrase && passphrase.length > 0) {
|
|
2033
|
+
return crypto2.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, 32, "sha256");
|
|
2034
|
+
}
|
|
2035
|
+
if (!_ConfigEncryptor.weakFallbackWarned) {
|
|
2036
|
+
warn(
|
|
2037
|
+
"APCORE_CLI_CONFIG_PASSPHRASE is not set. The `enc:v2:` fallback uses a key derived from hostname+username (non-secret inputs) and is OBFUSCATION ONLY \u2014 an attacker with filesystem read access can reconstruct the key. Set APCORE_CLI_CONFIG_PASSPHRASE or ensure the OS keyring is available for real encryption."
|
|
2038
|
+
);
|
|
2039
|
+
_ConfigEncryptor.weakFallbackWarned = true;
|
|
2040
|
+
}
|
|
2041
|
+
const hostname2 = os2.hostname();
|
|
2042
|
+
const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
|
|
2043
|
+
const material = `${hostname2}:${username}`;
|
|
2044
|
+
return crypto2.pbkdf2Sync(material, salt, PBKDF2_ITERATIONS, 32, "sha256");
|
|
2045
|
+
}
|
|
2046
|
+
aesEncrypt(plaintext) {
|
|
2047
|
+
const salt = crypto2.randomBytes(16);
|
|
2048
|
+
const key = this.deriveKey(salt);
|
|
2049
|
+
const nonce = crypto2.randomBytes(12);
|
|
2050
|
+
const cipher = crypto2.createCipheriv("aes-256-gcm", key, nonce);
|
|
2051
|
+
const ct = Buffer.concat([
|
|
2052
|
+
cipher.update(plaintext, "utf-8"),
|
|
2053
|
+
cipher.final()
|
|
2054
|
+
]);
|
|
2055
|
+
const tag = cipher.getAuthTag();
|
|
2056
|
+
return Buffer.concat([salt, nonce, tag, ct]);
|
|
2057
|
+
}
|
|
2058
|
+
aesDecrypt(data) {
|
|
2059
|
+
const salt = data.subarray(0, 16);
|
|
2060
|
+
const nonce = data.subarray(16, 28);
|
|
2061
|
+
const tag = data.subarray(28, 44);
|
|
2062
|
+
const ct = data.subarray(44);
|
|
2063
|
+
const key = this.deriveKey(salt);
|
|
2064
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
|
|
2065
|
+
decipher.setAuthTag(tag);
|
|
2066
|
+
return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf-8");
|
|
2067
|
+
}
|
|
2068
|
+
/** Decrypt legacy v1-format values: nonce(12)+tag(16)+ct, static salt. */
|
|
2069
|
+
aesDecryptV1(data) {
|
|
2070
|
+
const nonce = data.subarray(0, 12);
|
|
2071
|
+
const tag = data.subarray(12, 28);
|
|
2072
|
+
const ct = data.subarray(28);
|
|
2073
|
+
const hostname2 = os2.hostname();
|
|
2074
|
+
const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
|
|
2075
|
+
const passphrase = process.env.APCORE_CLI_CONFIG_PASSPHRASE;
|
|
2076
|
+
const materials = passphrase ? [passphrase, `${hostname2}:${username}`] : [`${hostname2}:${username}`];
|
|
2077
|
+
for (const material of materials) {
|
|
2078
|
+
for (const iterations of [6e5, 1e5]) {
|
|
2079
|
+
try {
|
|
2080
|
+
const key = crypto2.pbkdf2Sync(material, V1_STATIC_SALT, iterations, 32, "sha256");
|
|
2081
|
+
const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
|
|
2082
|
+
decipher.setAuthTag(tag);
|
|
2083
|
+
return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf-8");
|
|
2084
|
+
} catch {
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
throw new Error("v1 decryption failed with all material+iteration combinations");
|
|
2089
|
+
}
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
});
|
|
2093
|
+
|
|
2094
|
+
// src/security/auth.ts
|
|
2095
|
+
var AuthProvider;
|
|
2096
|
+
var init_auth = __esm({
|
|
2097
|
+
"src/security/auth.ts"() {
|
|
2098
|
+
"use strict";
|
|
2099
|
+
init_esm_shims();
|
|
2100
|
+
init_errors();
|
|
2101
|
+
init_config_encryptor();
|
|
2102
|
+
AuthProvider = class {
|
|
2103
|
+
config;
|
|
2104
|
+
encryptor;
|
|
2105
|
+
constructor(config, encryptor) {
|
|
2106
|
+
this.config = config;
|
|
2107
|
+
this.encryptor = encryptor ?? new ConfigEncryptor();
|
|
2108
|
+
}
|
|
2109
|
+
/**
|
|
2110
|
+
* Retrieve the API key from the configured sources.
|
|
2111
|
+
* Handles keyring: and enc: prefixes via ConfigEncryptor.
|
|
2112
|
+
*/
|
|
2113
|
+
async getApiKey() {
|
|
2114
|
+
const result = this.config.resolve(
|
|
2115
|
+
"auth.api_key",
|
|
2116
|
+
"--api-key",
|
|
2117
|
+
"APCORE_AUTH_API_KEY"
|
|
2118
|
+
);
|
|
2119
|
+
if (result === null || result === void 0) {
|
|
2120
|
+
return null;
|
|
2121
|
+
}
|
|
2122
|
+
const strResult = String(result);
|
|
2123
|
+
if (strResult.startsWith("keyring:") || strResult.startsWith("enc:")) {
|
|
2124
|
+
try {
|
|
2125
|
+
return await this.encryptor.retrieve(strResult, "auth.api_key");
|
|
2126
|
+
} catch (err) {
|
|
2127
|
+
if (err instanceof ConfigDecryptionError) {
|
|
2128
|
+
throw new AuthenticationError(
|
|
2129
|
+
"Failed to decrypt stored API key. Re-configure with 'apcore-cli config set auth.api_key'."
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
throw err;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
return strResult;
|
|
2136
|
+
}
|
|
2137
|
+
/**
|
|
2138
|
+
* Add authentication headers to an outgoing request.
|
|
2139
|
+
*
|
|
2140
|
+
* Cross-SDK contract (D10-002, 2026-04-26): the input `headers` object
|
|
2141
|
+
* is mutated **in place** and the same reference is returned. Callers
|
|
2142
|
+
* that share the headers reference (the documented pattern in
|
|
2143
|
+
* apcore-cli/docs/features/security.md §AuthProvider) can read
|
|
2144
|
+
* `headers.Authorization` after the call without re-binding the
|
|
2145
|
+
* return value. Python and Rust both mutate-and-return; TS previously
|
|
2146
|
+
* spread into a new object, which silently broke shared-reference
|
|
2147
|
+
* callers.
|
|
2148
|
+
*/
|
|
2149
|
+
async authenticateRequest(headers) {
|
|
2150
|
+
const key = await this.getApiKey();
|
|
2151
|
+
if (!key) {
|
|
2152
|
+
throw new AuthenticationError(
|
|
2153
|
+
"Remote registry requires authentication. Set --api-key, APCORE_AUTH_API_KEY, or auth.api_key in config."
|
|
2154
|
+
);
|
|
2155
|
+
}
|
|
2156
|
+
if (/[\r\n]/.test(key)) {
|
|
2157
|
+
throw new AuthenticationError(
|
|
2158
|
+
"Malformed API key: contains invalid characters (CR/LF). Re-configure with 'apcore-cli config set auth.api_key'."
|
|
2159
|
+
);
|
|
2160
|
+
}
|
|
2161
|
+
headers.Authorization = `Bearer ${key.trim()}`;
|
|
2162
|
+
return headers;
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Handle an HTTP response status code for auth-related errors.
|
|
2166
|
+
*/
|
|
2167
|
+
handleResponse(statusCode) {
|
|
2168
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
2169
|
+
throw new AuthenticationError(
|
|
2170
|
+
"Authentication failed. Verify your API key."
|
|
2171
|
+
);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
};
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
|
|
2178
|
+
// src/security/index.ts
|
|
2179
|
+
var security_exports = {};
|
|
2180
|
+
__export(security_exports, {
|
|
2181
|
+
AuditLogger: () => AuditLogger,
|
|
2182
|
+
AuthProvider: () => AuthProvider,
|
|
2183
|
+
ConfigEncryptor: () => ConfigEncryptor,
|
|
2184
|
+
Sandbox: () => Sandbox,
|
|
2185
|
+
getAuditLogger: () => getAuditLogger,
|
|
2186
|
+
setAuditLogger: () => setAuditLogger
|
|
2187
|
+
});
|
|
2188
|
+
var init_security = __esm({
|
|
2189
|
+
"src/security/index.ts"() {
|
|
2190
|
+
"use strict";
|
|
2191
|
+
init_esm_shims();
|
|
2192
|
+
init_audit();
|
|
2193
|
+
init_auth();
|
|
2194
|
+
init_config_encryptor();
|
|
2195
|
+
init_sandbox();
|
|
2196
|
+
}
|
|
2197
|
+
});
|
|
2198
|
+
|
|
2199
|
+
// src/discovery.ts
|
|
2200
|
+
import { Command as Command2, Option as Option2 } from "commander";
|
|
2201
|
+
function validateTag(tag) {
|
|
2202
|
+
if (!TAG_PATTERN.test(tag)) {
|
|
2203
|
+
process.stderr.write(
|
|
2204
|
+
`Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
|
|
2205
|
+
`
|
|
2206
|
+
);
|
|
2207
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
function collectTag(value, previous) {
|
|
2211
|
+
return previous.concat([value]);
|
|
2212
|
+
}
|
|
2213
|
+
function collectAnnotation(value, previous) {
|
|
2214
|
+
return previous.concat([value]);
|
|
2215
|
+
}
|
|
2216
|
+
function getAnnotationFlag(moduleDef, flag) {
|
|
2217
|
+
const annotations = moduleDef.annotations;
|
|
2218
|
+
if (!annotations || typeof annotations !== "object") return false;
|
|
2219
|
+
const ann = annotations;
|
|
2220
|
+
const map = {
|
|
2221
|
+
"destructive": "destructive",
|
|
2222
|
+
"requires-approval": "requires_approval",
|
|
2223
|
+
"readonly": "readonly",
|
|
2224
|
+
"streaming": "streaming",
|
|
2225
|
+
"cacheable": "cacheable",
|
|
2226
|
+
"idempotent": "idempotent",
|
|
2227
|
+
"paginated": "paginated"
|
|
2228
|
+
};
|
|
2229
|
+
const attr = map[flag] ?? flag;
|
|
2230
|
+
return ann[attr] === true;
|
|
2231
|
+
}
|
|
2232
|
+
function registerListCommand(apcliGroup, registry, exposureFilter) {
|
|
2233
|
+
const listCmd = new Command2("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--flat", "Show flat list (no grouping).", false).option("--format <format>", "Output format.", void 0).option("-s, --search <query>", "Filter by substring match on ID and description.").addOption(
|
|
2234
|
+
new Option2("--status <status>", "Filter by module status.").choices(["enabled", "disabled", "all"]).default("enabled")
|
|
2235
|
+
).option("-a, --annotation <flag>", "Filter by annotation flag (AND logic). Repeatable.", collectAnnotation, []).addOption(
|
|
2236
|
+
new Option2("--sort <field>", "Sort order.").choices(["id", "calls", "errors", "latency"]).default("id")
|
|
2237
|
+
).option("--reverse", "Reverse sort order.", false).option("--deprecated", "Include deprecated modules.", false).option("--deps", "Show dependency count column.", false).addOption(
|
|
2238
|
+
new Option2("--exposure <mode>", "Filter by exposure status.").choices(["exposed", "hidden", "all"]).default("exposed")
|
|
2239
|
+
).action((opts) => {
|
|
2240
|
+
for (const t of opts.tag) {
|
|
2241
|
+
validateTag(t);
|
|
2242
|
+
}
|
|
2243
|
+
let modules = [];
|
|
2244
|
+
for (const m of registry.listModules()) {
|
|
2245
|
+
modules.push(m);
|
|
2246
|
+
}
|
|
2247
|
+
if (opts.tag.length > 0) {
|
|
2248
|
+
const filterTags = new Set(opts.tag);
|
|
2249
|
+
modules = modules.filter((m) => {
|
|
2250
|
+
const mTags = m.tags ?? [];
|
|
2251
|
+
return [...filterTags].every((t) => mTags.includes(t));
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
if (opts.search) {
|
|
2255
|
+
const query = opts.search.toLowerCase();
|
|
2256
|
+
modules = modules.filter(
|
|
2257
|
+
(m) => (m.id ?? "").toLowerCase().includes(query) || (m.description ?? "").toLowerCase().includes(query)
|
|
2258
|
+
);
|
|
2259
|
+
}
|
|
2260
|
+
if (opts.status === "enabled") {
|
|
2261
|
+
modules = modules.filter((m) => {
|
|
2262
|
+
const enabled = m.enabled;
|
|
2263
|
+
return enabled !== false;
|
|
2264
|
+
});
|
|
2265
|
+
} else if (opts.status === "disabled") {
|
|
2266
|
+
modules = modules.filter((m) => {
|
|
2267
|
+
const enabled = m.enabled;
|
|
2268
|
+
return enabled === false;
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
if (!opts.deprecated) {
|
|
2272
|
+
modules = modules.filter((m) => {
|
|
2273
|
+
const deprecated = m.deprecated;
|
|
2274
|
+
return deprecated !== true;
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
if (opts.annotation.length > 0) {
|
|
2278
|
+
for (const annFlag of opts.annotation) {
|
|
2279
|
+
modules = modules.filter((m) => getAnnotationFlag(m, annFlag));
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (opts.sort === "calls" || opts.sort === "errors" || opts.sort === "latency") {
|
|
2283
|
+
process.stderr.write(
|
|
2284
|
+
`Warning: Usage data not available; sorting by id. Sort by ${opts.sort} requires system.usage modules.
|
|
2285
|
+
`
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
|
|
2289
|
+
if (opts.reverse) {
|
|
2290
|
+
modules.reverse();
|
|
2291
|
+
}
|
|
2292
|
+
let showExposureCol = false;
|
|
2293
|
+
if (exposureFilter && opts.exposure !== "all") {
|
|
2294
|
+
if (opts.exposure === "exposed") {
|
|
2295
|
+
modules = modules.filter((m) => exposureFilter.isExposed(m.id ?? ""));
|
|
2296
|
+
} else if (opts.exposure === "hidden") {
|
|
2297
|
+
modules = modules.filter((m) => !exposureFilter.isExposed(m.id ?? ""));
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
if (opts.exposure === "all" && exposureFilter) {
|
|
2301
|
+
showExposureCol = true;
|
|
2302
|
+
}
|
|
2303
|
+
const fmt = resolveFormat(opts.format);
|
|
2304
|
+
const filterTagsArg = opts.tag.length > 0 ? opts.tag : void 0;
|
|
2305
|
+
formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
|
|
2306
|
+
});
|
|
2307
|
+
apcliGroup.addCommand(listCmd);
|
|
2308
|
+
}
|
|
2309
|
+
function registerDescribeCommand(apcliGroup, registry) {
|
|
2310
|
+
const describeCmd = new Command2("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").option("--format <format>", "Output format.", void 0).action((moduleId, opts) => {
|
|
2311
|
+
validateModuleId(moduleId);
|
|
2312
|
+
const moduleDef = registry.getModule(moduleId);
|
|
2313
|
+
if (!moduleDef) {
|
|
2314
|
+
process.stderr.write(
|
|
2315
|
+
`Error: Module '${moduleId}' not found.
|
|
2316
|
+
`
|
|
2317
|
+
);
|
|
2318
|
+
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
2319
|
+
}
|
|
2320
|
+
const fmt = resolveFormat(opts.format);
|
|
2321
|
+
formatModuleDetail(moduleDef, fmt);
|
|
2322
|
+
});
|
|
2323
|
+
apcliGroup.addCommand(describeCmd);
|
|
2324
|
+
}
|
|
2325
|
+
function registerExecCommand(apcliGroup, registry, executor) {
|
|
2326
|
+
const execCmd = new Command2("exec").description("Execute a module by ID with JSON input.").argument("<module-id>", "Module ID to execute").option("--format <format>", "Output format (json, table, csv, yaml, jsonl).").option("--fields <fields>", "Comma-separated dot-paths to select from the result.").option(
|
|
2327
|
+
"--input <json>",
|
|
2328
|
+
"JSON object passed as input to the module. Use '-' to read JSON from stdin."
|
|
2329
|
+
).option("-y, --yes", "Auto-approve if the module declares requires_approval.", false).option(
|
|
2330
|
+
"--approval-timeout <seconds>",
|
|
2331
|
+
"Seconds to wait for interactive approval.",
|
|
2332
|
+
parseInt
|
|
2333
|
+
).option("--sandbox", "Run module in an isolated subprocess with restricted env.", false).option("--strategy <name>", "Execution strategy (standard, parallel, sequential, etc.).").option("--trace", "Enable pipeline trace output.", false).option("--dry-run", "Validate inputs without executing the module.", false).option("--stream", "Stream output as JSONL instead of buffering.", false).action(async (moduleId, opts) => {
|
|
2334
|
+
validateModuleId(moduleId);
|
|
2335
|
+
const moduleDef = registry.getModule(moduleId);
|
|
2336
|
+
if (!moduleDef) {
|
|
2337
|
+
process.stderr.write(`Error: Module '${moduleId}' not found.
|
|
2338
|
+
`);
|
|
2339
|
+
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
2340
|
+
}
|
|
2341
|
+
let merged = {};
|
|
2342
|
+
if (opts.input === "-") {
|
|
2343
|
+
merged = await collectInput("-", {}, false);
|
|
2344
|
+
} else if (opts.input !== void 0) {
|
|
2345
|
+
try {
|
|
2346
|
+
const parsed = JSON.parse(opts.input);
|
|
2347
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2348
|
+
process.stderr.write("Error: --input JSON must be an object.\n");
|
|
2349
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2350
|
+
}
|
|
2351
|
+
merged = parsed;
|
|
2352
|
+
} catch (err) {
|
|
2353
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2354
|
+
process.stderr.write(`Error: --input is not valid JSON: ${msg}
|
|
2355
|
+
`);
|
|
2356
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
const startTime = performance.now();
|
|
2360
|
+
try {
|
|
2361
|
+
await checkApproval(moduleDef, opts.yes, opts.approvalTimeout);
|
|
2362
|
+
if (opts.dryRun) {
|
|
2363
|
+
if (executor.validate) {
|
|
2364
|
+
const preflight = await executor.validate(moduleId, merged);
|
|
2365
|
+
formatPreflightResult(preflight, opts.format);
|
|
2366
|
+
} else {
|
|
2367
|
+
process.stdout.write(JSON.stringify({ valid: true }) + "\n");
|
|
2368
|
+
}
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
let result;
|
|
2372
|
+
if (opts.strategy && executor.callWithTrace) {
|
|
2373
|
+
const [res] = await executor.callWithTrace(moduleId, merged, { strategy: opts.strategy });
|
|
2374
|
+
result = res;
|
|
2375
|
+
} else {
|
|
2376
|
+
const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
|
|
2377
|
+
const sandbox = new Sandbox2(opts.sandbox);
|
|
2378
|
+
result = await sandbox.execute(moduleId, merged, executor);
|
|
2379
|
+
}
|
|
2380
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
2381
|
+
const fmt = resolveFormat(opts.format);
|
|
2382
|
+
formatExecResult(result, fmt, opts.fields);
|
|
2383
|
+
const auditLogger = getAuditLogger();
|
|
2384
|
+
if (auditLogger) {
|
|
2385
|
+
auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
|
|
2386
|
+
}
|
|
2387
|
+
} catch (err) {
|
|
2388
|
+
const exitCode = exitCodeForError(err);
|
|
2389
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
2390
|
+
try {
|
|
2391
|
+
const auditLogger = getAuditLogger();
|
|
2392
|
+
if (auditLogger) {
|
|
2393
|
+
auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
|
|
2394
|
+
}
|
|
2395
|
+
} catch {
|
|
2396
|
+
}
|
|
2397
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
|
|
2398
|
+
`);
|
|
2399
|
+
process.exit(exitCode);
|
|
2400
|
+
}
|
|
2401
|
+
});
|
|
2402
|
+
apcliGroup.addCommand(execCmd);
|
|
2403
|
+
}
|
|
2404
|
+
function registerValidateCommand(cli, registry, executor) {
|
|
2405
|
+
const validateCmd = new Command2("validate").description("Run preflight checks without executing a module.").argument("<module-id>", "Module ID to validate").option("--input <source>", "JSON input file or '-' for stdin.").option("--format <format>", "Output format.").action(async (moduleId, opts) => {
|
|
2406
|
+
validateModuleId(moduleId);
|
|
2407
|
+
const moduleDef = registry.getModule(moduleId);
|
|
2408
|
+
if (!moduleDef) {
|
|
2409
|
+
process.stderr.write(`Error: Module '${moduleId}' not found.
|
|
2410
|
+
`);
|
|
2411
|
+
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
2412
|
+
}
|
|
2413
|
+
const merged = opts.input ? await collectInput(opts.input, {}, false) : {};
|
|
2414
|
+
if (!executor.validate) {
|
|
2415
|
+
process.stderr.write("Error: Executor does not support validate.\n");
|
|
2416
|
+
process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
|
|
2417
|
+
}
|
|
2418
|
+
try {
|
|
2419
|
+
const preflight = await executor.validate(moduleId, merged);
|
|
2420
|
+
formatPreflightResult(preflight, opts.format);
|
|
2421
|
+
process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
|
|
2422
|
+
} catch (err) {
|
|
2423
|
+
const exitCode = exitCodeForError(err);
|
|
2424
|
+
try {
|
|
2425
|
+
const auditLogger = getAuditLogger();
|
|
2426
|
+
if (auditLogger) {
|
|
2427
|
+
auditLogger.logExecution(moduleId, merged, "error", exitCode, 0);
|
|
2428
|
+
}
|
|
2429
|
+
} catch {
|
|
2430
|
+
}
|
|
2431
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
|
|
2432
|
+
`);
|
|
2433
|
+
process.exit(exitCode);
|
|
2434
|
+
}
|
|
2435
|
+
});
|
|
2436
|
+
cli.addCommand(validateCmd);
|
|
2437
|
+
}
|
|
2438
|
+
var TAG_PATTERN;
|
|
2439
|
+
var init_discovery = __esm({
|
|
2440
|
+
"src/discovery.ts"() {
|
|
2441
|
+
"use strict";
|
|
2442
|
+
init_esm_shims();
|
|
2443
|
+
init_approval();
|
|
2444
|
+
init_errors();
|
|
2445
|
+
init_main();
|
|
2446
|
+
init_output();
|
|
2447
|
+
init_audit();
|
|
2448
|
+
TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
2449
|
+
}
|
|
2450
|
+
});
|
|
2451
|
+
|
|
2452
|
+
// src/system-cmd.ts
|
|
2453
|
+
import { Command as Command3 } from "commander";
|
|
2454
|
+
async function callSystemModule(executor, moduleId, inputs) {
|
|
2455
|
+
if (executor.call) {
|
|
2456
|
+
return executor.call(moduleId, inputs);
|
|
2457
|
+
}
|
|
2458
|
+
return executor.execute(moduleId, inputs);
|
|
2459
|
+
}
|
|
2460
|
+
function emitResult(jsonPayload, fmt, ttyRender) {
|
|
2461
|
+
if (fmt === "json" || !process.stdout.isTTY) {
|
|
2462
|
+
process.stdout.write(JSON.stringify(jsonPayload, null, 2) + "\n");
|
|
2463
|
+
} else {
|
|
2464
|
+
ttyRender();
|
|
2465
|
+
}
|
|
2466
|
+
}
|
|
2467
|
+
function emitErrorAndExit(e) {
|
|
2468
|
+
process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
|
|
2469
|
+
`);
|
|
2470
|
+
process.exit(exitCodeForError(e));
|
|
2471
|
+
}
|
|
2472
|
+
function formatHealthSummaryTty(result) {
|
|
2473
|
+
const summary = result.summary ?? {};
|
|
2474
|
+
const modules = result.modules ?? [];
|
|
2475
|
+
if (modules.length === 0) {
|
|
2476
|
+
process.stdout.write("No modules found.\n");
|
|
2477
|
+
return;
|
|
2478
|
+
}
|
|
2479
|
+
const total = summary.total_modules ?? modules.length;
|
|
2480
|
+
process.stdout.write(`Health Overview (${total} modules)
|
|
2481
|
+
|
|
2482
|
+
`);
|
|
2483
|
+
process.stdout.write(` ${"Module".padEnd(28)} ${"Status".padEnd(12)} ${"Error Rate".padEnd(12)} Top Error
|
|
2484
|
+
`);
|
|
2485
|
+
process.stdout.write(` ${"-".repeat(28)} ${"-".repeat(12)} ${"-".repeat(12)} ${"-".repeat(20)}
|
|
2486
|
+
`);
|
|
2487
|
+
for (const m of modules) {
|
|
2488
|
+
const top = m.top_error;
|
|
2489
|
+
const topStr = top ? `${top.code} (${top.count ?? "?"})` : "\u2014";
|
|
2490
|
+
const rate = `${((m.error_rate ?? 0) * 100).toFixed(1)}%`;
|
|
2491
|
+
process.stdout.write(
|
|
2492
|
+
` ${String(m.module_id).padEnd(28)} ${String(m.status).padEnd(12)} ${rate.padEnd(12)} ${topStr}
|
|
2493
|
+
`
|
|
2494
|
+
);
|
|
2495
|
+
}
|
|
2496
|
+
const parts = [];
|
|
2497
|
+
for (const key of ["healthy", "degraded", "error"]) {
|
|
2498
|
+
const count = summary[key];
|
|
2499
|
+
if (count) parts.push(`${count} ${key}`);
|
|
2500
|
+
}
|
|
2501
|
+
process.stdout.write(`
|
|
2502
|
+
Summary: ${parts.join(", ") || "no data"}
|
|
2503
|
+
`);
|
|
2504
|
+
}
|
|
2505
|
+
function formatHealthModuleTty(result) {
|
|
2506
|
+
process.stdout.write(`Module: ${result.module_id ?? "?"}
|
|
2507
|
+
`);
|
|
2508
|
+
process.stdout.write(`Status: ${result.status ?? "unknown"}
|
|
2509
|
+
`);
|
|
2510
|
+
const total = result.total_calls ?? 0;
|
|
2511
|
+
const errors = result.error_count ?? 0;
|
|
2512
|
+
const rate = result.error_rate ?? 0;
|
|
2513
|
+
const avg = result.avg_latency_ms ?? 0;
|
|
2514
|
+
const p99 = result.p99_latency_ms ?? 0;
|
|
2515
|
+
process.stdout.write(`Calls: ${total.toLocaleString()} total | ${errors.toLocaleString()} errors | ${(rate * 100).toFixed(1)}% error rate
|
|
2516
|
+
`);
|
|
2517
|
+
process.stdout.write(`Latency: ${avg.toFixed(0)}ms avg | ${p99.toFixed(0)}ms p99
|
|
2518
|
+
`);
|
|
2519
|
+
const recent = result.recent_errors ?? [];
|
|
2520
|
+
if (recent.length > 0) {
|
|
2521
|
+
process.stdout.write(`
|
|
2522
|
+
Recent Errors (top ${recent.length}):
|
|
2523
|
+
`);
|
|
2524
|
+
for (const e of recent) {
|
|
2525
|
+
const count = e.count ?? "?";
|
|
2526
|
+
const last = e.last_occurred ?? "?";
|
|
2527
|
+
process.stdout.write(` ${String(e.code ?? "?").padEnd(24)} x${count} (last: ${last})
|
|
2528
|
+
`);
|
|
2529
|
+
}
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
function formatUsageSummaryTty(result) {
|
|
2533
|
+
const modules = result.modules ?? [];
|
|
2534
|
+
const period = result.period ?? "?";
|
|
2535
|
+
if (modules.length === 0) {
|
|
2536
|
+
process.stdout.write(`No usage data for period ${period}.
|
|
2537
|
+
`);
|
|
2538
|
+
return;
|
|
2539
|
+
}
|
|
2540
|
+
process.stdout.write(`Usage Summary (last ${period})
|
|
2541
|
+
|
|
2542
|
+
`);
|
|
2543
|
+
process.stdout.write(` ${"Module".padEnd(24)} ${"Calls".padStart(8)} ${"Errors".padStart(8)} ${"Avg Latency".padStart(12)} ${"Trend".padStart(10)}
|
|
2544
|
+
`);
|
|
2545
|
+
process.stdout.write(` ${"-".repeat(24)} ${"-".repeat(8)} ${"-".repeat(8)} ${"-".repeat(12)} ${"-".repeat(10)}
|
|
2546
|
+
`);
|
|
2547
|
+
for (const m of modules) {
|
|
2548
|
+
const avg = `${(m.avg_latency_ms ?? 0).toFixed(0)}ms`;
|
|
2549
|
+
process.stdout.write(
|
|
826
2550
|
` ${String(m.module_id).padEnd(24)} ${String(m.call_count ?? 0).padStart(8)} ${String(m.error_count ?? 0).padStart(8)} ${avg.padStart(12)} ${String(m.trend ?? "").padStart(10)}
|
|
827
2551
|
`
|
|
828
2552
|
);
|
|
@@ -833,17 +2557,7 @@ function formatUsageSummaryTty(result) {
|
|
|
833
2557
|
Total: ${totalCalls.toLocaleString()} calls | ${totalErrors.toLocaleString()} errors
|
|
834
2558
|
`);
|
|
835
2559
|
}
|
|
836
|
-
|
|
837
|
-
try {
|
|
838
|
-
if (executor.validate) {
|
|
839
|
-
await executor.validate("system.health.summary", {});
|
|
840
|
-
} else {
|
|
841
|
-
await callSystemModule(executor, "system.health.summary", { include_healthy: true });
|
|
842
|
-
}
|
|
843
|
-
} catch {
|
|
844
|
-
debug("System modules not available; skipping system command registration.");
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
2560
|
+
function registerHealthCommand(apcliGroup, executor) {
|
|
847
2561
|
const healthCmd = new Command3("health").description("Show module health status. Optionally specify a module ID for details.").argument("[module-id]", "Module ID for detailed health").option("--threshold <number>", "Error rate threshold (default: 0.01).", parseFloat, 0.01).option("--all", "Include healthy modules.", false).option("--errors <count>", "Max recent errors (module detail only).", parseInt, 10).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
|
|
848
2562
|
const fmt = resolveFormat(opts.format);
|
|
849
2563
|
try {
|
|
@@ -852,29 +2566,21 @@ async function registerSystemCommands(cli, executor) {
|
|
|
852
2566
|
module_id: moduleId,
|
|
853
2567
|
error_limit: opts.errors
|
|
854
2568
|
});
|
|
855
|
-
|
|
856
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
857
|
-
} else {
|
|
858
|
-
formatHealthModuleTty(result);
|
|
859
|
-
}
|
|
2569
|
+
emitResult(result, fmt, () => formatHealthModuleTty(result));
|
|
860
2570
|
} else {
|
|
861
2571
|
const result = await callSystemModule(executor, "system.health.summary", {
|
|
862
2572
|
error_rate_threshold: opts.threshold,
|
|
863
2573
|
include_healthy: opts.all
|
|
864
2574
|
});
|
|
865
|
-
|
|
866
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
867
|
-
} else {
|
|
868
|
-
formatHealthSummaryTty(result);
|
|
869
|
-
}
|
|
2575
|
+
emitResult(result, fmt, () => formatHealthSummaryTty(result));
|
|
870
2576
|
}
|
|
871
2577
|
} catch (e) {
|
|
872
|
-
|
|
873
|
-
`);
|
|
874
|
-
process.exit(1);
|
|
2578
|
+
emitErrorAndExit(e);
|
|
875
2579
|
}
|
|
876
2580
|
});
|
|
877
|
-
|
|
2581
|
+
apcliGroup.addCommand(healthCmd);
|
|
2582
|
+
}
|
|
2583
|
+
function registerUsageCommand(apcliGroup, executor) {
|
|
878
2584
|
const usageCmd = new Command3("usage").description("Show module usage statistics. Optionally specify a module ID for details.").argument("[module-id]", "Module ID for detailed usage").option("--period <period>", "Time window: 1h, 24h, 7d, 30d.", "24h").option("--format <format>", "Output format.").action(async (moduleId, opts) => {
|
|
879
2585
|
const fmt = resolveFormat(opts.format);
|
|
880
2586
|
try {
|
|
@@ -889,24 +2595,21 @@ async function registerSystemCommands(cli, executor) {
|
|
|
889
2595
|
period: opts.period
|
|
890
2596
|
});
|
|
891
2597
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
}
|
|
2598
|
+
emitResult(result, fmt, () => {
|
|
2599
|
+
if (moduleId) {
|
|
2600
|
+
formatExecResult(result, fmt);
|
|
2601
|
+
} else {
|
|
2602
|
+
formatUsageSummaryTty(result);
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
899
2605
|
} catch (e) {
|
|
900
|
-
|
|
901
|
-
`);
|
|
902
|
-
process.exit(1);
|
|
2606
|
+
emitErrorAndExit(e);
|
|
903
2607
|
}
|
|
904
2608
|
});
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
2609
|
+
apcliGroup.addCommand(usageCmd);
|
|
2610
|
+
}
|
|
2611
|
+
function registerEnableCommand(apcliGroup, executor) {
|
|
2612
|
+
const enableCmd = new Command3("enable").description("Enable a disabled module at runtime.").argument("<module-id>", "Module ID to enable").requiredOption("--reason <reason>", "Reason for enabling (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
|
|
910
2613
|
const fmt = resolveFormat(opts.format);
|
|
911
2614
|
try {
|
|
912
2615
|
const result = await callSystemModule(executor, "system.control.toggle_feature", {
|
|
@@ -914,24 +2617,19 @@ async function registerSystemCommands(cli, executor) {
|
|
|
914
2617
|
enabled: true,
|
|
915
2618
|
reason: opts.reason
|
|
916
2619
|
});
|
|
917
|
-
|
|
918
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
919
|
-
} else {
|
|
2620
|
+
emitResult(result, fmt, () => {
|
|
920
2621
|
process.stdout.write(`Module '${moduleId}' enabled.
|
|
921
2622
|
Reason: ${opts.reason}
|
|
922
2623
|
`);
|
|
923
|
-
}
|
|
2624
|
+
});
|
|
924
2625
|
} catch (e) {
|
|
925
|
-
|
|
926
|
-
`);
|
|
927
|
-
process.exit(1);
|
|
2626
|
+
emitErrorAndExit(e);
|
|
928
2627
|
}
|
|
929
2628
|
});
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
2629
|
+
apcliGroup.addCommand(enableCmd);
|
|
2630
|
+
}
|
|
2631
|
+
function registerDisableCommand(apcliGroup, executor) {
|
|
2632
|
+
const disableCmd = new Command3("disable").description("Disable a module at runtime (calls are rejected until re-enabled).").argument("<module-id>", "Module ID to disable").requiredOption("--reason <reason>", "Reason for disabling (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
|
|
935
2633
|
const fmt = resolveFormat(opts.format);
|
|
936
2634
|
try {
|
|
937
2635
|
const result = await callSystemModule(executor, "system.control.toggle_feature", {
|
|
@@ -939,33 +2637,26 @@ async function registerSystemCommands(cli, executor) {
|
|
|
939
2637
|
enabled: false,
|
|
940
2638
|
reason: opts.reason
|
|
941
2639
|
});
|
|
942
|
-
|
|
943
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
944
|
-
} else {
|
|
2640
|
+
emitResult(result, fmt, () => {
|
|
945
2641
|
process.stdout.write(`Module '${moduleId}' disabled.
|
|
946
2642
|
Reason: ${opts.reason}
|
|
947
2643
|
`);
|
|
948
|
-
}
|
|
2644
|
+
});
|
|
949
2645
|
} catch (e) {
|
|
950
|
-
|
|
951
|
-
`);
|
|
952
|
-
process.exit(1);
|
|
2646
|
+
emitErrorAndExit(e);
|
|
953
2647
|
}
|
|
954
2648
|
});
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
}
|
|
2649
|
+
apcliGroup.addCommand(disableCmd);
|
|
2650
|
+
}
|
|
2651
|
+
function registerReloadCommand(apcliGroup, executor) {
|
|
2652
|
+
const reloadCmd = new Command3("reload").description("Hot-reload a module from disk.").argument("<module-id>", "Module ID to reload").requiredOption("--reason <reason>", "Reason for reload (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
|
|
960
2653
|
const fmt = resolveFormat(opts.format);
|
|
961
2654
|
try {
|
|
962
2655
|
const result = await callSystemModule(executor, "system.control.reload_module", {
|
|
963
2656
|
module_id: moduleId,
|
|
964
2657
|
reason: opts.reason
|
|
965
2658
|
});
|
|
966
|
-
|
|
967
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
968
|
-
} else {
|
|
2659
|
+
emitResult(result, fmt, () => {
|
|
969
2660
|
const prev = result.previous_version ?? "?";
|
|
970
2661
|
const newVer = result.new_version ?? "?";
|
|
971
2662
|
const dur = result.reload_duration_ms ?? "?";
|
|
@@ -975,34 +2666,30 @@ async function registerSystemCommands(cli, executor) {
|
|
|
975
2666
|
`);
|
|
976
2667
|
process.stdout.write(` Duration: ${dur}ms
|
|
977
2668
|
`);
|
|
978
|
-
}
|
|
2669
|
+
});
|
|
979
2670
|
} catch (e) {
|
|
980
|
-
|
|
981
|
-
`);
|
|
982
|
-
process.exit(1);
|
|
2671
|
+
emitErrorAndExit(e);
|
|
983
2672
|
}
|
|
984
2673
|
});
|
|
985
|
-
|
|
2674
|
+
apcliGroup.addCommand(reloadCmd);
|
|
2675
|
+
}
|
|
2676
|
+
function registerConfigCommand(apcliGroup, executor) {
|
|
986
2677
|
const configGroup = new Command3("config").description("Read or update runtime configuration.");
|
|
987
2678
|
const configGetCmd = new Command3("get").description("Read a configuration value by dot-path key.").argument("<key>", "Configuration key (dot-path)").option("--format <format>", "Output format.", "table").action(async (key, opts) => {
|
|
988
2679
|
const fmt = resolveFormat(opts.format);
|
|
989
2680
|
try {
|
|
990
2681
|
const result = await callSystemModule(executor, "system.config.get", { key });
|
|
991
2682
|
const value = result?.value ?? result;
|
|
992
|
-
|
|
993
|
-
process.stdout.write(JSON.stringify({ key, value }, null, 2) + "\n");
|
|
994
|
-
} else {
|
|
2683
|
+
emitResult({ key, value }, fmt, () => {
|
|
995
2684
|
process.stdout.write(`${key} = ${JSON.stringify(value)}
|
|
996
2685
|
`);
|
|
997
|
-
}
|
|
2686
|
+
});
|
|
998
2687
|
} catch (e) {
|
|
999
|
-
|
|
1000
|
-
`);
|
|
1001
|
-
process.exit(1);
|
|
2688
|
+
emitErrorAndExit(e);
|
|
1002
2689
|
}
|
|
1003
2690
|
});
|
|
1004
2691
|
configGroup.addCommand(configGetCmd);
|
|
1005
|
-
const configSetCmd = new Command3("set").description("Update a runtime configuration value (
|
|
2692
|
+
const configSetCmd = new Command3("set").description("Update a runtime configuration value (audit-logged; server-side approval gate applies).").argument("<key>", "Configuration key (dot-path)").argument("<value>", "New value").requiredOption("--reason <reason>", "Reason for config change (required for audit).").option("--format <format>", "Output format.").action(async (key, value, opts) => {
|
|
1006
2693
|
const fmt = resolveFormat(opts.format);
|
|
1007
2694
|
let parsedValue;
|
|
1008
2695
|
try {
|
|
@@ -1016,9 +2703,7 @@ async function registerSystemCommands(cli, executor) {
|
|
|
1016
2703
|
value: parsedValue,
|
|
1017
2704
|
reason: opts.reason
|
|
1018
2705
|
});
|
|
1019
|
-
|
|
1020
|
-
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
1021
|
-
} else {
|
|
2706
|
+
emitResult(result, fmt, () => {
|
|
1022
2707
|
const old = result.old_value ?? "?";
|
|
1023
2708
|
const newVal = result.new_value ?? "?";
|
|
1024
2709
|
process.stdout.write(`Config updated: ${key}
|
|
@@ -1027,257 +2712,915 @@ async function registerSystemCommands(cli, executor) {
|
|
|
1027
2712
|
`);
|
|
1028
2713
|
process.stdout.write(` Reason: ${opts.reason}
|
|
1029
2714
|
`);
|
|
1030
|
-
}
|
|
2715
|
+
});
|
|
1031
2716
|
} catch (e) {
|
|
1032
|
-
|
|
1033
|
-
`);
|
|
1034
|
-
process.exit(1);
|
|
2717
|
+
emitErrorAndExit(e);
|
|
1035
2718
|
}
|
|
1036
2719
|
});
|
|
1037
2720
|
configGroup.addCommand(configSetCmd);
|
|
1038
|
-
|
|
2721
|
+
apcliGroup.addCommand(configGroup);
|
|
1039
2722
|
}
|
|
2723
|
+
var init_system_cmd = __esm({
|
|
2724
|
+
"src/system-cmd.ts"() {
|
|
2725
|
+
"use strict";
|
|
2726
|
+
init_esm_shims();
|
|
2727
|
+
init_errors();
|
|
2728
|
+
init_output();
|
|
2729
|
+
}
|
|
2730
|
+
});
|
|
1040
2731
|
|
|
1041
2732
|
// src/strategy.ts
|
|
1042
|
-
init_esm_shims();
|
|
1043
2733
|
import { Command as Command4, Option as Option3 } from "commander";
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
"middleware_after",
|
|
1067
|
-
"return_result"
|
|
1068
|
-
],
|
|
1069
|
-
testing: [
|
|
1070
|
-
"context_creation",
|
|
1071
|
-
"module_lookup",
|
|
1072
|
-
"middleware_before",
|
|
1073
|
-
"input_validation",
|
|
1074
|
-
"execute",
|
|
1075
|
-
"output_validation",
|
|
1076
|
-
"middleware_after",
|
|
1077
|
-
"return_result"
|
|
1078
|
-
],
|
|
1079
|
-
performance: [
|
|
1080
|
-
"context_creation",
|
|
1081
|
-
"call_chain_guard",
|
|
1082
|
-
"module_lookup",
|
|
1083
|
-
"acl_check",
|
|
1084
|
-
"approval_gate",
|
|
1085
|
-
"input_validation",
|
|
1086
|
-
"execute",
|
|
1087
|
-
"output_validation",
|
|
1088
|
-
"return_result"
|
|
1089
|
-
],
|
|
1090
|
-
minimal: [
|
|
1091
|
-
"context_creation",
|
|
1092
|
-
"module_lookup",
|
|
1093
|
-
"execute",
|
|
1094
|
-
"return_result"
|
|
1095
|
-
]
|
|
1096
|
-
};
|
|
2734
|
+
function lookupStrategyInfo(executor, strategyName) {
|
|
2735
|
+
if (typeof executor.describePipeline === "function") {
|
|
2736
|
+
try {
|
|
2737
|
+
const current = executor.describePipeline();
|
|
2738
|
+
if (current && current.name === strategyName) {
|
|
2739
|
+
return { info: current, isCurrent: true };
|
|
2740
|
+
}
|
|
2741
|
+
} catch {
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
const ctor = executor.constructor;
|
|
2745
|
+
if (ctor && typeof ctor.listStrategies === "function") {
|
|
2746
|
+
try {
|
|
2747
|
+
const all = ctor.listStrategies();
|
|
2748
|
+
const info = all.find((s) => s.name === strategyName) ?? null;
|
|
2749
|
+
return { info, isCurrent: false };
|
|
2750
|
+
} catch {
|
|
2751
|
+
return { info: null, isCurrent: false };
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
return { info: null, isCurrent: false };
|
|
2755
|
+
}
|
|
1097
2756
|
function registerPipelineCommand(cli, executor) {
|
|
1098
2757
|
const pipelineCmd = new Command4("describe-pipeline").description("Show the execution pipeline steps for a strategy.").addOption(
|
|
1099
2758
|
new Option3("--strategy <name>", "Strategy to describe (default: standard).").choices(["standard", "internal", "testing", "performance", "minimal"]).default("standard")
|
|
1100
2759
|
).option("--format <format>", "Output format.").action((opts) => {
|
|
1101
2760
|
const fmt = resolveFormat(opts.format);
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
const fn = ex._resolve_strategy_name ?? ex._resolveStrategyName;
|
|
1107
|
-
strategyObj = fn(opts.strategy);
|
|
1108
|
-
} catch {
|
|
1109
|
-
strategyObj = null;
|
|
1110
|
-
}
|
|
1111
|
-
}
|
|
1112
|
-
if (!strategyObj) {
|
|
1113
|
-
const steps = PRESET_STEPS[opts.strategy] ?? [];
|
|
1114
|
-
const pureSteps = /* @__PURE__ */ new Set([
|
|
1115
|
-
"context_creation",
|
|
1116
|
-
"call_chain_guard",
|
|
1117
|
-
"module_lookup",
|
|
1118
|
-
"acl_check",
|
|
1119
|
-
"input_validation"
|
|
1120
|
-
]);
|
|
1121
|
-
const nonRemovable = /* @__PURE__ */ new Set([
|
|
1122
|
-
"context_creation",
|
|
1123
|
-
"module_lookup",
|
|
1124
|
-
"execute",
|
|
1125
|
-
"return_result"
|
|
1126
|
-
]);
|
|
2761
|
+
const { info, isCurrent } = lookupStrategyInfo(executor, opts.strategy);
|
|
2762
|
+
if (info) {
|
|
2763
|
+
const strategySteps = isCurrent ? executor.currentStrategy?.steps ?? [] : [];
|
|
2764
|
+
const header = `Pipeline: ${info.name} (${info.stepCount} steps)`;
|
|
1127
2765
|
if (fmt === "json" || !process.stdout.isTTY) {
|
|
1128
2766
|
const payload = {
|
|
1129
|
-
strategy:
|
|
1130
|
-
step_count:
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
2767
|
+
strategy: info.name,
|
|
2768
|
+
step_count: info.stepCount,
|
|
2769
|
+
description: info.description,
|
|
2770
|
+
steps: info.stepNames.map((name, i) => {
|
|
2771
|
+
const stepMeta = strategySteps[i];
|
|
2772
|
+
return {
|
|
2773
|
+
index: i + 1,
|
|
2774
|
+
name,
|
|
2775
|
+
pure: stepMeta?.pure ?? false,
|
|
2776
|
+
removable: stepMeta?.removable ?? true,
|
|
2777
|
+
timeout_ms: stepMeta?.timeoutMs ?? null
|
|
2778
|
+
};
|
|
2779
|
+
})
|
|
1137
2780
|
};
|
|
1138
2781
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
1139
2782
|
} else {
|
|
1140
|
-
process.stdout.write(
|
|
2783
|
+
process.stdout.write(`${header}
|
|
1141
2784
|
|
|
1142
2785
|
`);
|
|
1143
2786
|
process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
|
|
1144
2787
|
`);
|
|
1145
2788
|
process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
|
|
1146
2789
|
`);
|
|
1147
|
-
for (let i = 0; i <
|
|
1148
|
-
const
|
|
1149
|
-
const
|
|
1150
|
-
|
|
2790
|
+
for (let i = 0; i < info.stepNames.length; i++) {
|
|
2791
|
+
const stepMeta = strategySteps[i];
|
|
2792
|
+
const pure = stepMeta?.pure ? "yes" : "no";
|
|
2793
|
+
const removable = stepMeta?.removable !== false ? "yes" : "no";
|
|
2794
|
+
const timeout = stepMeta?.timeoutMs ? `${stepMeta.timeoutMs}ms` : "\u2014";
|
|
2795
|
+
process.stdout.write(` ${String(i + 1).padEnd(4)} ${info.stepNames[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} ${timeout}
|
|
1151
2796
|
`);
|
|
1152
2797
|
}
|
|
1153
2798
|
}
|
|
1154
2799
|
return;
|
|
1155
2800
|
}
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
2801
|
+
const steps = PRESET_STEPS[opts.strategy] ?? [];
|
|
2802
|
+
const pureSteps = /* @__PURE__ */ new Set([
|
|
2803
|
+
"context_creation",
|
|
2804
|
+
"call_chain_guard",
|
|
2805
|
+
"module_lookup",
|
|
2806
|
+
"acl_check",
|
|
2807
|
+
"input_validation"
|
|
2808
|
+
]);
|
|
2809
|
+
const nonRemovable = /* @__PURE__ */ new Set([
|
|
2810
|
+
"context_creation",
|
|
2811
|
+
"module_lookup",
|
|
2812
|
+
"execute",
|
|
2813
|
+
"return_result"
|
|
2814
|
+
]);
|
|
1162
2815
|
if (fmt === "json" || !process.stdout.isTTY) {
|
|
1163
2816
|
const payload = {
|
|
1164
2817
|
strategy: opts.strategy,
|
|
1165
|
-
step_count:
|
|
1166
|
-
steps:
|
|
2818
|
+
step_count: steps.length,
|
|
2819
|
+
steps: steps.map((s, i) => ({
|
|
2820
|
+
index: i + 1,
|
|
2821
|
+
name: s,
|
|
2822
|
+
pure: pureSteps.has(s),
|
|
2823
|
+
removable: !nonRemovable.has(s)
|
|
2824
|
+
}))
|
|
1167
2825
|
};
|
|
1168
2826
|
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
1169
2827
|
} else {
|
|
1170
|
-
process.stdout.write(`Pipeline: ${opts.strategy} (${
|
|
2828
|
+
process.stdout.write(`Pipeline: ${opts.strategy} (${steps.length} steps)
|
|
1171
2829
|
|
|
1172
2830
|
`);
|
|
1173
2831
|
process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
|
|
1174
2832
|
`);
|
|
1175
2833
|
process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
|
|
1176
2834
|
`);
|
|
1177
|
-
for (let i = 0; i <
|
|
1178
|
-
const
|
|
1179
|
-
const
|
|
1180
|
-
|
|
1181
|
-
const timeout = s.timeout_ms !== null ? `${s.timeout_ms}ms` : "\u2014";
|
|
1182
|
-
process.stdout.write(` ${String(i + 1).padEnd(4)} ${s.name.padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} ${timeout}
|
|
2835
|
+
for (let i = 0; i < steps.length; i++) {
|
|
2836
|
+
const pure = pureSteps.has(steps[i]) ? "yes" : "no";
|
|
2837
|
+
const removable = nonRemovable.has(steps[i]) ? "no" : "yes";
|
|
2838
|
+
process.stdout.write(` ${String(i + 1).padEnd(4)} ${steps[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} \u2014
|
|
1183
2839
|
`);
|
|
1184
2840
|
}
|
|
1185
|
-
}
|
|
2841
|
+
}
|
|
2842
|
+
});
|
|
2843
|
+
cli.addCommand(pipelineCmd);
|
|
2844
|
+
}
|
|
2845
|
+
var PRESET_STEPS;
|
|
2846
|
+
var init_strategy = __esm({
|
|
2847
|
+
"src/strategy.ts"() {
|
|
2848
|
+
"use strict";
|
|
2849
|
+
init_esm_shims();
|
|
2850
|
+
init_output();
|
|
2851
|
+
PRESET_STEPS = {
|
|
2852
|
+
standard: [
|
|
2853
|
+
"context_creation",
|
|
2854
|
+
"call_chain_guard",
|
|
2855
|
+
"module_lookup",
|
|
2856
|
+
"acl_check",
|
|
2857
|
+
"approval_gate",
|
|
2858
|
+
"middleware_before",
|
|
2859
|
+
"input_validation",
|
|
2860
|
+
"execute",
|
|
2861
|
+
"output_validation",
|
|
2862
|
+
"middleware_after",
|
|
2863
|
+
"return_result"
|
|
2864
|
+
],
|
|
2865
|
+
internal: [
|
|
2866
|
+
"context_creation",
|
|
2867
|
+
"call_chain_guard",
|
|
2868
|
+
"module_lookup",
|
|
2869
|
+
"middleware_before",
|
|
2870
|
+
"input_validation",
|
|
2871
|
+
"execute",
|
|
2872
|
+
"output_validation",
|
|
2873
|
+
"middleware_after",
|
|
2874
|
+
"return_result"
|
|
2875
|
+
],
|
|
2876
|
+
testing: [
|
|
2877
|
+
"context_creation",
|
|
2878
|
+
"module_lookup",
|
|
2879
|
+
"middleware_before",
|
|
2880
|
+
"input_validation",
|
|
2881
|
+
"execute",
|
|
2882
|
+
"output_validation",
|
|
2883
|
+
"middleware_after",
|
|
2884
|
+
"return_result"
|
|
2885
|
+
],
|
|
2886
|
+
performance: [
|
|
2887
|
+
"context_creation",
|
|
2888
|
+
"call_chain_guard",
|
|
2889
|
+
"module_lookup",
|
|
2890
|
+
"acl_check",
|
|
2891
|
+
"approval_gate",
|
|
2892
|
+
"input_validation",
|
|
2893
|
+
"execute",
|
|
2894
|
+
"output_validation",
|
|
2895
|
+
"return_result"
|
|
2896
|
+
],
|
|
2897
|
+
minimal: [
|
|
2898
|
+
"context_creation",
|
|
2899
|
+
"module_lookup",
|
|
2900
|
+
"execute",
|
|
2901
|
+
"return_result"
|
|
2902
|
+
]
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
|
|
2907
|
+
// src/builtin-group.ts
|
|
2908
|
+
var RESERVED_GROUP_NAMES, VALID_USER_MODES, APCLI_SUBCOMMAND_NAMES, ApcliGroup;
|
|
2909
|
+
var init_builtin_group = __esm({
|
|
2910
|
+
"src/builtin-group.ts"() {
|
|
2911
|
+
"use strict";
|
|
2912
|
+
init_esm_shims();
|
|
2913
|
+
init_errors();
|
|
2914
|
+
init_logger();
|
|
2915
|
+
RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set(["apcli"]);
|
|
2916
|
+
VALID_USER_MODES = /* @__PURE__ */ new Set([
|
|
2917
|
+
"all",
|
|
2918
|
+
"none",
|
|
2919
|
+
"include",
|
|
2920
|
+
"exclude"
|
|
2921
|
+
]);
|
|
2922
|
+
APCLI_SUBCOMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
2923
|
+
"list",
|
|
2924
|
+
"describe",
|
|
2925
|
+
"exec",
|
|
2926
|
+
"validate",
|
|
2927
|
+
"init",
|
|
2928
|
+
"health",
|
|
2929
|
+
"usage",
|
|
2930
|
+
"enable",
|
|
2931
|
+
"disable",
|
|
2932
|
+
"reload",
|
|
2933
|
+
"config",
|
|
2934
|
+
"completion",
|
|
2935
|
+
"describe-pipeline"
|
|
2936
|
+
]);
|
|
2937
|
+
ApcliGroup = class _ApcliGroup {
|
|
2938
|
+
_mode;
|
|
2939
|
+
_include;
|
|
2940
|
+
_exclude;
|
|
2941
|
+
_disableEnv;
|
|
2942
|
+
_registryInjected;
|
|
2943
|
+
_fromCliConfig;
|
|
2944
|
+
constructor(init) {
|
|
2945
|
+
this._mode = init.mode;
|
|
2946
|
+
this._include = init.include;
|
|
2947
|
+
this._exclude = init.exclude;
|
|
2948
|
+
this._disableEnv = init.disableEnv;
|
|
2949
|
+
this._registryInjected = init.registryInjected;
|
|
2950
|
+
this._fromCliConfig = init.fromCliConfig;
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Tier 1 constructor — config came from `createCli({ apcli })`.
|
|
2954
|
+
*
|
|
2955
|
+
* A non-auto mode from this tier wins over env var and yaml.
|
|
2956
|
+
*/
|
|
2957
|
+
static fromCliConfig(config, opts) {
|
|
2958
|
+
return _ApcliGroup._build(
|
|
2959
|
+
config,
|
|
2960
|
+
opts,
|
|
2961
|
+
/*fromCliConfig*/
|
|
2962
|
+
true
|
|
2963
|
+
);
|
|
2964
|
+
}
|
|
2965
|
+
/**
|
|
2966
|
+
* Tier 3 constructor — config came from `apcore.yaml`.
|
|
2967
|
+
*
|
|
2968
|
+
* Env var (Tier 2) may override the yaml-supplied mode.
|
|
2969
|
+
*/
|
|
2970
|
+
static fromYaml(config, opts) {
|
|
2971
|
+
return _ApcliGroup._build(
|
|
2972
|
+
config,
|
|
2973
|
+
opts,
|
|
2974
|
+
/*fromCliConfig*/
|
|
2975
|
+
false
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Non-panicking Tier 3 factory (A-001 parity with Rust's `try_from_yaml`).
|
|
2980
|
+
* Returns `[instance, null]` on success or `[null, errorMessage]` on invalid input.
|
|
2981
|
+
* Use this in programmatic contexts where throwing/exiting is unwanted.
|
|
2982
|
+
*/
|
|
2983
|
+
static tryFromYaml(config, opts) {
|
|
2984
|
+
if (config !== null && config !== void 0 && typeof config !== "boolean" && typeof config !== "object") {
|
|
2985
|
+
return [null, `apcore.yaml 'apcli:' must be a bool, object, or null; got ${typeof config}`];
|
|
2986
|
+
}
|
|
2987
|
+
if (config !== null && config !== void 0 && typeof config === "object" && !Array.isArray(config)) {
|
|
2988
|
+
const mode = config["mode"];
|
|
2989
|
+
if (mode !== void 0 && mode !== null) {
|
|
2990
|
+
const validModes = ["all", "none", "include", "exclude"];
|
|
2991
|
+
if (typeof mode !== "string" || !validModes.includes(mode)) {
|
|
2992
|
+
return [null, `Invalid apcli mode: '${mode}'. Must be one of: all, none, include, exclude.`];
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
return [_ApcliGroup.fromYaml(config, opts), null];
|
|
2997
|
+
}
|
|
2998
|
+
// -------------------------------------------------------------------------
|
|
2999
|
+
// Internal builder — shared by both factories
|
|
3000
|
+
// -------------------------------------------------------------------------
|
|
3001
|
+
static _build(config, opts, fromCliConfig) {
|
|
3002
|
+
if (config === true) {
|
|
3003
|
+
return new _ApcliGroup({
|
|
3004
|
+
mode: "all",
|
|
3005
|
+
include: [],
|
|
3006
|
+
exclude: [],
|
|
3007
|
+
disableEnv: false,
|
|
3008
|
+
registryInjected: opts.registryInjected,
|
|
3009
|
+
fromCliConfig
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
if (config === false) {
|
|
3013
|
+
return new _ApcliGroup({
|
|
3014
|
+
mode: "none",
|
|
3015
|
+
include: [],
|
|
3016
|
+
exclude: [],
|
|
3017
|
+
disableEnv: false,
|
|
3018
|
+
registryInjected: opts.registryInjected,
|
|
3019
|
+
fromCliConfig
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
if (config === void 0 || config === null) {
|
|
3023
|
+
return new _ApcliGroup({
|
|
3024
|
+
mode: "auto",
|
|
3025
|
+
include: [],
|
|
3026
|
+
exclude: [],
|
|
3027
|
+
disableEnv: false,
|
|
3028
|
+
registryInjected: opts.registryInjected,
|
|
3029
|
+
fromCliConfig
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3032
|
+
if (typeof config !== "object" || Array.isArray(config)) {
|
|
3033
|
+
process.stderr.write(
|
|
3034
|
+
`Error: apcli config must be a boolean or object; got ${Array.isArray(config) ? "array" : typeof config}.
|
|
3035
|
+
`
|
|
3036
|
+
);
|
|
3037
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3038
|
+
}
|
|
3039
|
+
const cfg = config;
|
|
3040
|
+
let mode;
|
|
3041
|
+
if (cfg.mode === void 0 || cfg.mode === null) {
|
|
3042
|
+
mode = "auto";
|
|
3043
|
+
} else if (typeof cfg.mode !== "string") {
|
|
3044
|
+
process.stderr.write(
|
|
3045
|
+
`Error: apcli.mode must be a string; got ${typeof cfg.mode}. Expected one of all|none|include|exclude.
|
|
3046
|
+
`
|
|
3047
|
+
);
|
|
3048
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3049
|
+
} else if (!VALID_USER_MODES.has(cfg.mode)) {
|
|
3050
|
+
process.stderr.write(
|
|
3051
|
+
`Error: apcli.mode '${cfg.mode}' is invalid. Expected one of all|none|include|exclude.
|
|
3052
|
+
`
|
|
3053
|
+
);
|
|
3054
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3055
|
+
} else {
|
|
3056
|
+
mode = cfg.mode;
|
|
3057
|
+
}
|
|
3058
|
+
const include = _ApcliGroup._normalizeList(cfg.include, "include");
|
|
3059
|
+
const exclude = _ApcliGroup._normalizeList(cfg.exclude, "exclude");
|
|
3060
|
+
const rawDisableEnv = cfg.disableEnv !== void 0 ? cfg.disableEnv : cfg["disable_env"];
|
|
3061
|
+
let disableEnv = false;
|
|
3062
|
+
if (rawDisableEnv !== void 0) {
|
|
3063
|
+
if (typeof rawDisableEnv === "boolean") {
|
|
3064
|
+
disableEnv = rawDisableEnv;
|
|
3065
|
+
} else {
|
|
3066
|
+
warn(
|
|
3067
|
+
`apcli.disable_env must be boolean; got ${typeof rawDisableEnv}. Treating as false.`
|
|
3068
|
+
);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
return new _ApcliGroup({
|
|
3072
|
+
mode,
|
|
3073
|
+
include,
|
|
3074
|
+
exclude,
|
|
3075
|
+
disableEnv,
|
|
3076
|
+
registryInjected: opts.registryInjected,
|
|
3077
|
+
fromCliConfig
|
|
3078
|
+
});
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* Normalize an include/exclude list. Non-array → warn and return [].
|
|
3082
|
+
*
|
|
3083
|
+
* Unknown but well-formed entries emit a WARNING (spec §7 error table,
|
|
3084
|
+
* T-APCLI-25) but are retained in the returned list for forward-compat —
|
|
3085
|
+
* if apcore-cli later adds a subcommand named `foo`, existing configs
|
|
3086
|
+
* continue to work without a config change. At runtime, unknown names
|
|
3087
|
+
* simply never match any registered subcommand.
|
|
3088
|
+
*/
|
|
3089
|
+
static _normalizeList(raw, label) {
|
|
3090
|
+
if (raw === void 0 || raw === null) return [];
|
|
3091
|
+
if (!Array.isArray(raw)) {
|
|
3092
|
+
warn(`apcli.${label} must be a list; got ${typeof raw}. Ignoring.`);
|
|
3093
|
+
return [];
|
|
3094
|
+
}
|
|
3095
|
+
const out = [];
|
|
3096
|
+
for (const entry of raw) {
|
|
3097
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
3098
|
+
if (!APCLI_SUBCOMMAND_NAMES.has(entry)) {
|
|
3099
|
+
warn(
|
|
3100
|
+
`Unknown apcli subcommand '${entry}' in ${label} list \u2014 ignoring.`
|
|
3101
|
+
);
|
|
3102
|
+
}
|
|
3103
|
+
out.push(entry);
|
|
3104
|
+
} else {
|
|
3105
|
+
warn(`apcli.${label} contains non-string entry; skipping.`);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
return out;
|
|
3109
|
+
}
|
|
3110
|
+
// -------------------------------------------------------------------------
|
|
3111
|
+
// Public API
|
|
3112
|
+
// -------------------------------------------------------------------------
|
|
3113
|
+
/**
|
|
3114
|
+
* Resolve effective visibility mode after applying tier precedence.
|
|
3115
|
+
*
|
|
3116
|
+
* Returns one of `"all" | "none" | "include" | "exclude"` — never `"auto"`.
|
|
3117
|
+
*
|
|
3118
|
+
* Tier order (spec §4.4):
|
|
3119
|
+
* 1. CliConfig non-auto wins outright.
|
|
3120
|
+
* 2. `APCORE_CLI_APCLI` env var (unless sealed by disableEnv).
|
|
3121
|
+
* 3. yaml non-auto.
|
|
3122
|
+
* 4. Auto-detect from registryInjected.
|
|
3123
|
+
*/
|
|
3124
|
+
resolveVisibility() {
|
|
3125
|
+
if (this._fromCliConfig && this._mode !== "auto") {
|
|
3126
|
+
return this._mode;
|
|
3127
|
+
}
|
|
3128
|
+
if (!this._disableEnv) {
|
|
3129
|
+
const envMode = this._parseEnv(process.env.APCORE_CLI_APCLI);
|
|
3130
|
+
if (envMode !== null) {
|
|
3131
|
+
return envMode;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
if (this._mode !== "auto") {
|
|
3135
|
+
return this._mode;
|
|
3136
|
+
}
|
|
3137
|
+
return this._registryInjected ? "none" : "all";
|
|
3138
|
+
}
|
|
3139
|
+
/**
|
|
3140
|
+
* True iff `subcommand` passes the include/exclude filter.
|
|
3141
|
+
*
|
|
3142
|
+
* Callers MUST first check {@link resolveVisibility} — this method throws
|
|
3143
|
+
* under modes `"all"` or `"none"` (caller bug per spec §4.6).
|
|
3144
|
+
*/
|
|
3145
|
+
isSubcommandIncluded(subcommand) {
|
|
3146
|
+
const mode = this.resolveVisibility();
|
|
3147
|
+
if (mode === "include") return this._include.includes(subcommand);
|
|
3148
|
+
if (mode === "exclude") return !this._exclude.includes(subcommand);
|
|
3149
|
+
throw new Error(
|
|
3150
|
+
`isSubcommandIncluded called under mode '${mode}'; caller should bypass.`
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
/** True iff the `apcli` group itself should appear in root `--help`. */
|
|
3154
|
+
isGroupVisible() {
|
|
3155
|
+
return this.resolveVisibility() !== "none";
|
|
3156
|
+
}
|
|
3157
|
+
// -------------------------------------------------------------------------
|
|
3158
|
+
// Env parser (Tier 2) — co-located per spec §4.4
|
|
3159
|
+
// -------------------------------------------------------------------------
|
|
3160
|
+
/**
|
|
3161
|
+
* Parse APCORE_CLI_APCLI. Case-insensitive.
|
|
3162
|
+
*
|
|
3163
|
+
* - `show` / `1` / `true` → `"all"`
|
|
3164
|
+
* - `hide` / `0` / `false` → `"none"`
|
|
3165
|
+
* - Empty / unset → `null`
|
|
3166
|
+
* - Anything else → warn and return `null`
|
|
3167
|
+
*/
|
|
3168
|
+
_parseEnv(raw) {
|
|
3169
|
+
if (raw === void 0 || raw === "") return null;
|
|
3170
|
+
const normalized = raw.toLowerCase();
|
|
3171
|
+
if (normalized === "show" || normalized === "1" || normalized === "true") {
|
|
3172
|
+
return "all";
|
|
3173
|
+
}
|
|
3174
|
+
if (normalized === "hide" || normalized === "0" || normalized === "false") {
|
|
3175
|
+
return "none";
|
|
3176
|
+
}
|
|
3177
|
+
warn(
|
|
3178
|
+
`Unknown APCORE_CLI_APCLI value '${raw}', ignoring. Expected: show, hide, 1, 0, true, false.`
|
|
3179
|
+
);
|
|
3180
|
+
return null;
|
|
3181
|
+
}
|
|
3182
|
+
};
|
|
3183
|
+
}
|
|
3184
|
+
});
|
|
3185
|
+
|
|
3186
|
+
// src/exposure.ts
|
|
3187
|
+
function escapeRegex(str) {
|
|
3188
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3189
|
+
}
|
|
3190
|
+
function compilePattern(pattern) {
|
|
3191
|
+
const sentinel = "\0GLOB\0";
|
|
3192
|
+
const escaped = pattern.replaceAll("**", sentinel);
|
|
3193
|
+
const parts = escaped.split("*");
|
|
3194
|
+
const regexParts = parts.map((p) => {
|
|
3195
|
+
const restored = p.replaceAll(sentinel, "**");
|
|
3196
|
+
return escapeRegex(restored);
|
|
1186
3197
|
});
|
|
1187
|
-
|
|
3198
|
+
let regex = regexParts.join("[^.]*");
|
|
3199
|
+
regex = regex.replaceAll("\\*\\*", ".+");
|
|
3200
|
+
return new RegExp(`^${regex}$`);
|
|
1188
3201
|
}
|
|
3202
|
+
var ExposureFilter;
|
|
3203
|
+
var init_exposure = __esm({
|
|
3204
|
+
"src/exposure.ts"() {
|
|
3205
|
+
"use strict";
|
|
3206
|
+
init_esm_shims();
|
|
3207
|
+
init_logger();
|
|
3208
|
+
ExposureFilter = class _ExposureFilter {
|
|
3209
|
+
static VALID_MODES = ["all", "include", "exclude", "none"];
|
|
3210
|
+
_mode;
|
|
3211
|
+
_compiledInclude;
|
|
3212
|
+
_compiledExclude;
|
|
3213
|
+
constructor(mode = "all", include, exclude) {
|
|
3214
|
+
if (!_ExposureFilter.VALID_MODES.includes(mode)) {
|
|
3215
|
+
process.stderr.write(
|
|
3216
|
+
`Warning: Unknown ExposureFilter mode '${mode}' \u2014 defaulting to 'none'. Valid modes: ${_ExposureFilter.VALID_MODES.join(", ")}.
|
|
3217
|
+
`
|
|
3218
|
+
);
|
|
3219
|
+
mode = "none";
|
|
3220
|
+
}
|
|
3221
|
+
this._mode = mode;
|
|
3222
|
+
const dedup = (arr) => [...new Set(arr)];
|
|
3223
|
+
this._compiledInclude = dedup(include ?? []).map(compilePattern);
|
|
3224
|
+
this._compiledExclude = dedup(exclude ?? []).map(compilePattern);
|
|
3225
|
+
}
|
|
3226
|
+
/** Return true if the module should be exposed as a CLI command. */
|
|
3227
|
+
isExposed(moduleId) {
|
|
3228
|
+
if (this._mode === "all") return true;
|
|
3229
|
+
if (this._mode === "include") {
|
|
3230
|
+
return this._compiledInclude.some((rx) => rx.test(moduleId));
|
|
3231
|
+
}
|
|
3232
|
+
if (this._mode === "exclude") {
|
|
3233
|
+
return !this._compiledExclude.some((rx) => rx.test(moduleId));
|
|
3234
|
+
}
|
|
3235
|
+
return false;
|
|
3236
|
+
}
|
|
3237
|
+
/** Partition moduleIds into [exposed, hidden] lists. */
|
|
3238
|
+
filterModules(moduleIds) {
|
|
3239
|
+
const exposed = [];
|
|
3240
|
+
const hidden = [];
|
|
3241
|
+
for (const mid of moduleIds) {
|
|
3242
|
+
(this.isExposed(mid) ? exposed : hidden).push(mid);
|
|
3243
|
+
}
|
|
3244
|
+
return [exposed, hidden];
|
|
3245
|
+
}
|
|
3246
|
+
/**
|
|
3247
|
+
* Create an ExposureFilter from a parsed config dict.
|
|
3248
|
+
*
|
|
3249
|
+
* Expected: `{ expose: { mode: "include", include: ["admin.*"] } }`
|
|
3250
|
+
*/
|
|
3251
|
+
static fromConfig(config) {
|
|
3252
|
+
const expose = config.expose ?? {};
|
|
3253
|
+
if (typeof expose !== "object" || expose === null || Array.isArray(expose)) {
|
|
3254
|
+
warn("Invalid 'expose' config (expected dict), using mode: all.");
|
|
3255
|
+
return new _ExposureFilter();
|
|
3256
|
+
}
|
|
3257
|
+
const exposeObj = expose;
|
|
3258
|
+
const mode = exposeObj.mode ?? "all";
|
|
3259
|
+
if (!["all", "include", "exclude"].includes(mode)) {
|
|
3260
|
+
throw new Error(
|
|
3261
|
+
`Invalid expose mode: '${mode}'. Must be one of: all, include, exclude.`
|
|
3262
|
+
);
|
|
3263
|
+
}
|
|
3264
|
+
let include = exposeObj.include ?? [];
|
|
3265
|
+
if (!Array.isArray(include)) {
|
|
3266
|
+
warn("Invalid 'expose.include' (expected list), ignoring.");
|
|
3267
|
+
include = [];
|
|
3268
|
+
}
|
|
3269
|
+
let exclude = exposeObj.exclude ?? [];
|
|
3270
|
+
if (!Array.isArray(exclude)) {
|
|
3271
|
+
warn("Invalid 'expose.exclude' (expected list), ignoring.");
|
|
3272
|
+
exclude = [];
|
|
3273
|
+
}
|
|
3274
|
+
const filterList = (arr, label) => {
|
|
3275
|
+
const result = [];
|
|
3276
|
+
for (const p of arr) {
|
|
3277
|
+
if (!p) {
|
|
3278
|
+
warn(`Empty pattern in expose.${label}, skipping.`);
|
|
3279
|
+
} else {
|
|
3280
|
+
result.push(String(p));
|
|
3281
|
+
}
|
|
3282
|
+
}
|
|
3283
|
+
return result;
|
|
3284
|
+
};
|
|
3285
|
+
return new _ExposureFilter(
|
|
3286
|
+
mode,
|
|
3287
|
+
filterList(include, "include"),
|
|
3288
|
+
filterList(exclude, "exclude")
|
|
3289
|
+
);
|
|
3290
|
+
}
|
|
3291
|
+
};
|
|
3292
|
+
}
|
|
3293
|
+
});
|
|
1189
3294
|
|
|
1190
|
-
// src/
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
"
|
|
1195
|
-
"
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
"
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
3295
|
+
// src/canonical-help.ts
|
|
3296
|
+
function resolveHelpText(cmd, section) {
|
|
3297
|
+
const bag = cmd._helpText;
|
|
3298
|
+
const v = bag?.[section];
|
|
3299
|
+
if (typeof v === "function") return v({ error: false, command: cmd });
|
|
3300
|
+
return v ?? "";
|
|
3301
|
+
}
|
|
3302
|
+
function uppercasePlaceholders(flags) {
|
|
3303
|
+
return flags.replace(/<([a-zA-Z0-9_-]+)>/g, (_, name) => `<${name.toUpperCase()}>`).replace(/\[([a-zA-Z0-9_-]+)\]/g, (_, name) => `[${name.toUpperCase()}]`);
|
|
3304
|
+
}
|
|
3305
|
+
function optionTerm(opt) {
|
|
3306
|
+
const flags = uppercasePlaceholders(opt.flags);
|
|
3307
|
+
if (!opt.short && flags.startsWith("--")) return " " + flags;
|
|
3308
|
+
return flags;
|
|
3309
|
+
}
|
|
3310
|
+
function optionDescription(opt) {
|
|
3311
|
+
let desc = opt.description;
|
|
3312
|
+
const d = opt.defaultValue;
|
|
3313
|
+
if (d !== void 0 && d !== false && d !== "" && d !== null) {
|
|
3314
|
+
desc = `${desc} [default: ${String(d)}]`;
|
|
3315
|
+
}
|
|
3316
|
+
return desc;
|
|
3317
|
+
}
|
|
3318
|
+
function reorderHelpVersionLast(opts) {
|
|
3319
|
+
const helpOpts = [];
|
|
3320
|
+
const versionOpts = [];
|
|
3321
|
+
const rest = [];
|
|
3322
|
+
for (const o of opts) {
|
|
3323
|
+
if (o.long === "--help") helpOpts.push(o);
|
|
3324
|
+
else if (o.long === "--version") versionOpts.push(o);
|
|
3325
|
+
else rest.push(o);
|
|
3326
|
+
}
|
|
3327
|
+
return [...rest, ...helpOpts, ...versionOpts];
|
|
3328
|
+
}
|
|
3329
|
+
function canonicalFormatHelp(cmd, helper) {
|
|
3330
|
+
const sections = [];
|
|
3331
|
+
const beforeAll = resolveHelpText(cmd, "beforeAll");
|
|
3332
|
+
if (beforeAll) sections.push(beforeAll);
|
|
3333
|
+
const desc = cmd.description();
|
|
3334
|
+
if (desc) sections.push(desc);
|
|
3335
|
+
const before = resolveHelpText(cmd, "before");
|
|
3336
|
+
if (before) sections.push(before);
|
|
3337
|
+
const visibleOpts = reorderHelpVersionLast(helper.visibleOptions(cmd));
|
|
3338
|
+
const visibleCmds = helper.visibleCommands(cmd);
|
|
3339
|
+
const args = cmd.registeredArguments ?? [];
|
|
3340
|
+
let usage = `Usage: ${cmd.name()}`;
|
|
3341
|
+
if (visibleOpts.length > 0) usage += " [OPTIONS]";
|
|
3342
|
+
for (const a of args) {
|
|
3343
|
+
const n = a.name().toUpperCase();
|
|
3344
|
+
usage += a.required ? ` <${n}>` : ` [${n}]`;
|
|
3345
|
+
}
|
|
3346
|
+
if (visibleCmds.length > 0) usage += " [COMMAND]";
|
|
3347
|
+
sections.push(usage);
|
|
3348
|
+
if (visibleCmds.length > 0) {
|
|
3349
|
+
const terms = visibleCmds.map((c) => c.name());
|
|
3350
|
+
const w = Math.max(...terms.map((t) => t.length));
|
|
3351
|
+
const lines = ["Commands:"];
|
|
3352
|
+
visibleCmds.forEach((sub, i) => {
|
|
3353
|
+
lines.push(` ${terms[i].padEnd(w)} ${sub.description()}`);
|
|
3354
|
+
});
|
|
3355
|
+
sections.push(lines.join("\n"));
|
|
3356
|
+
}
|
|
3357
|
+
if (visibleOpts.length > 0) {
|
|
3358
|
+
const terms = visibleOpts.map(optionTerm);
|
|
3359
|
+
const w = Math.max(...terms.map((t) => t.length));
|
|
3360
|
+
const lines = ["Options:"];
|
|
3361
|
+
visibleOpts.forEach((opt, i) => {
|
|
3362
|
+
lines.push(` ${terms[i].padEnd(w)} ${optionDescription(opt)}`);
|
|
3363
|
+
});
|
|
3364
|
+
sections.push(lines.join("\n"));
|
|
3365
|
+
}
|
|
3366
|
+
const after = resolveHelpText(cmd, "after");
|
|
3367
|
+
if (after) sections.push(after);
|
|
3368
|
+
const afterAll = resolveHelpText(cmd, "afterAll");
|
|
3369
|
+
if (afterAll) sections.push(afterAll);
|
|
3370
|
+
return sections.join("\n\n") + "\n";
|
|
3371
|
+
}
|
|
3372
|
+
var init_canonical_help = __esm({
|
|
3373
|
+
"src/canonical-help.ts"() {
|
|
3374
|
+
"use strict";
|
|
3375
|
+
init_esm_shims();
|
|
3376
|
+
}
|
|
3377
|
+
});
|
|
1209
3378
|
|
|
1210
3379
|
// src/main.ts
|
|
1211
|
-
var
|
|
1212
|
-
|
|
3380
|
+
var main_exports = {};
|
|
3381
|
+
__export(main_exports, {
|
|
3382
|
+
applyToolkitIntegration: () => applyToolkitIntegration,
|
|
3383
|
+
buildModuleCommand: () => buildModuleCommand,
|
|
3384
|
+
clearBindingDisplayMap: () => clearBindingDisplayMap,
|
|
3385
|
+
collectInput: () => collectInput,
|
|
3386
|
+
createCli: () => createCli,
|
|
3387
|
+
docsUrl: () => docsUrl,
|
|
3388
|
+
emitErrorJson: () => emitErrorJson,
|
|
3389
|
+
emitErrorTty: () => emitErrorTty,
|
|
3390
|
+
lookupBindingDisplay: () => lookupBindingDisplay,
|
|
3391
|
+
main: () => main,
|
|
3392
|
+
reconvertEnumValues: () => reconvertEnumValues,
|
|
3393
|
+
resolveIntOption: () => resolveIntOption,
|
|
3394
|
+
resolveStringOption: () => resolveStringOption,
|
|
3395
|
+
setDocsUrl: () => setDocsUrl,
|
|
3396
|
+
setVerboseHelp: () => setVerboseHelp,
|
|
3397
|
+
validateModuleId: () => validateModuleId,
|
|
3398
|
+
verboseHelp: () => verboseHelp
|
|
3399
|
+
});
|
|
3400
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3401
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3402
|
+
import * as path4 from "path";
|
|
3403
|
+
import { Command as Command5, CommanderError, Option as Option4 } from "commander";
|
|
3404
|
+
function setVerboseHelp(verbose) {
|
|
3405
|
+
verboseHelp = verbose;
|
|
3406
|
+
}
|
|
3407
|
+
function setDocsUrl(url) {
|
|
3408
|
+
docsUrl = url;
|
|
3409
|
+
}
|
|
1213
3410
|
function hasVerboseFlag() {
|
|
1214
3411
|
return process.argv.includes("--verbose");
|
|
1215
3412
|
}
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
3413
|
+
function resolveIntOption(cliValue, envValue, defaultValue) {
|
|
3414
|
+
if (cliValue !== void 0 && Number.isFinite(cliValue) && cliValue > 0) {
|
|
3415
|
+
return cliValue;
|
|
3416
|
+
}
|
|
3417
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
3418
|
+
const parsed = parseInt(envValue, 10);
|
|
3419
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
3420
|
+
return parsed;
|
|
3421
|
+
}
|
|
3422
|
+
process.stderr.write(
|
|
3423
|
+
`Warning: invalid integer env value '${envValue}'; using default ${defaultValue}.
|
|
3424
|
+
`
|
|
3425
|
+
);
|
|
3426
|
+
}
|
|
3427
|
+
return defaultValue;
|
|
3428
|
+
}
|
|
3429
|
+
function resolveStringOption(cliValue, envValue) {
|
|
3430
|
+
if (typeof cliValue === "string" && cliValue !== "") {
|
|
3431
|
+
return cliValue;
|
|
3432
|
+
}
|
|
3433
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
3434
|
+
return envValue;
|
|
3435
|
+
}
|
|
3436
|
+
return void 0;
|
|
3437
|
+
}
|
|
3438
|
+
function emitErrorJson(e, exitCode) {
|
|
3439
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
3440
|
+
const errRecord = err;
|
|
3441
|
+
const code = errRecord.code ?? "UNKNOWN";
|
|
3442
|
+
const payload = {
|
|
3443
|
+
error: true,
|
|
3444
|
+
code,
|
|
3445
|
+
message: err.message,
|
|
3446
|
+
exit_code: exitCode
|
|
3447
|
+
};
|
|
3448
|
+
for (const field of ["details", "suggestion", "ai_guidance", "retryable", "user_fixable"]) {
|
|
3449
|
+
const val = errRecord[field];
|
|
3450
|
+
if (val !== void 0 && val !== null) {
|
|
3451
|
+
payload[field] = val;
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
process.stderr.write(JSON.stringify(payload) + "\n");
|
|
3455
|
+
}
|
|
3456
|
+
function emitErrorTty(e, exitCode) {
|
|
3457
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
3458
|
+
const errRecord = err;
|
|
3459
|
+
const code = errRecord.code;
|
|
3460
|
+
const header = code ? `Error [${code}]: ${err.message}` : `Error: ${err.message}`;
|
|
3461
|
+
process.stderr.write(header + "\n");
|
|
3462
|
+
const details = errRecord.details;
|
|
3463
|
+
if (details && typeof details === "object" && !Array.isArray(details)) {
|
|
3464
|
+
process.stderr.write("\n Details:\n");
|
|
3465
|
+
for (const [k, v] of Object.entries(details)) {
|
|
3466
|
+
process.stderr.write(` ${k}: ${v}
|
|
3467
|
+
`);
|
|
3468
|
+
}
|
|
3469
|
+
}
|
|
3470
|
+
const suggestion = errRecord.suggestion;
|
|
3471
|
+
if (suggestion) {
|
|
3472
|
+
process.stderr.write(`
|
|
3473
|
+
Suggestion: ${suggestion}
|
|
3474
|
+
`);
|
|
3475
|
+
}
|
|
3476
|
+
const retryable = errRecord.retryable;
|
|
3477
|
+
if (retryable !== void 0 && retryable !== null) {
|
|
3478
|
+
const label = retryable ? "Yes" : "No (same input will fail again)";
|
|
3479
|
+
process.stderr.write(` Retryable: ${label}
|
|
3480
|
+
`);
|
|
3481
|
+
}
|
|
3482
|
+
process.stderr.write(`
|
|
3483
|
+
Exit code: ${exitCode}
|
|
3484
|
+
`);
|
|
1221
3485
|
}
|
|
1222
3486
|
function createCli(extensionsDirOrOpts, progName, verbose = false) {
|
|
1223
3487
|
let extensionsDir;
|
|
1224
3488
|
let registry;
|
|
1225
3489
|
let executor;
|
|
1226
3490
|
let extraCommands;
|
|
3491
|
+
let app;
|
|
3492
|
+
let expose;
|
|
3493
|
+
let apcliOption;
|
|
1227
3494
|
if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
|
|
1228
3495
|
extensionsDir = extensionsDirOrOpts.extensionsDir;
|
|
1229
3496
|
progName = extensionsDirOrOpts.progName ?? progName;
|
|
1230
3497
|
verbose = extensionsDirOrOpts.verbose ?? verbose;
|
|
3498
|
+
app = extensionsDirOrOpts.app;
|
|
1231
3499
|
registry = extensionsDirOrOpts.registry;
|
|
1232
3500
|
executor = extensionsDirOrOpts.executor;
|
|
1233
3501
|
extraCommands = extensionsDirOrOpts.extraCommands;
|
|
3502
|
+
expose = extensionsDirOrOpts.expose;
|
|
3503
|
+
apcliOption = extensionsDirOrOpts.apcli;
|
|
1234
3504
|
} else {
|
|
1235
3505
|
extensionsDir = extensionsDirOrOpts;
|
|
1236
3506
|
}
|
|
1237
3507
|
verboseHelp = verbose;
|
|
1238
3508
|
registerConfigNamespace();
|
|
3509
|
+
try {
|
|
3510
|
+
const auditLogger = new AuditLogger();
|
|
3511
|
+
setAuditLogger(auditLogger);
|
|
3512
|
+
} catch {
|
|
3513
|
+
}
|
|
1239
3514
|
const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
|
|
1240
3515
|
const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
|
|
1241
3516
|
setLogLevel(cliLogLevel);
|
|
1242
|
-
|
|
3517
|
+
if (app && (registry || executor)) {
|
|
3518
|
+
process.stderr.write("Error: app is mutually exclusive with registry/executor\n");
|
|
3519
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3520
|
+
}
|
|
3521
|
+
if (app) {
|
|
3522
|
+
registry = app.registry;
|
|
3523
|
+
executor = app.executor;
|
|
3524
|
+
}
|
|
1243
3525
|
if (executor && !registry) {
|
|
1244
|
-
|
|
3526
|
+
process.stderr.write("Error: executor requires registry \u2014 pass both or neither\n");
|
|
3527
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3528
|
+
}
|
|
3529
|
+
if (executor && typeof executor.setApprovalHandler === "function") {
|
|
3530
|
+
try {
|
|
3531
|
+
const handler = new CliApprovalHandler(
|
|
3532
|
+
/*autoApprove*/
|
|
3533
|
+
false
|
|
3534
|
+
);
|
|
3535
|
+
executor.setApprovalHandler(handler);
|
|
3536
|
+
} catch {
|
|
3537
|
+
}
|
|
3538
|
+
}
|
|
3539
|
+
const registryInjected = registry !== void 0;
|
|
3540
|
+
const program = new Command5(resolvedProgName).exitOverride().version(VERSION, "-V, --version", "Print version").helpOption("-h, --help", "Print help").addHelpCommand("help [command]", "Print this message or the help of the given subcommand(s)").description("apcore CLI \u2014 execute apcore modules from the command line").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in apcore options)");
|
|
3541
|
+
program.configureHelp({ formatHelp: canonicalFormatHelp });
|
|
3542
|
+
if (!registryInjected) {
|
|
3543
|
+
program.option("--extensions-dir <path>", "Path to extensions directory");
|
|
3544
|
+
program.option("--commands-dir <path>", "Path to convention-based commands directory");
|
|
3545
|
+
program.option("--binding <path>", "Path to binding.yaml for display overlay");
|
|
3546
|
+
}
|
|
3547
|
+
let apcliCfg;
|
|
3548
|
+
if (apcliOption instanceof ApcliGroup) {
|
|
3549
|
+
apcliCfg = apcliOption;
|
|
3550
|
+
} else if (apcliOption !== void 0) {
|
|
3551
|
+
apcliCfg = ApcliGroup.fromCliConfig(apcliOption, { registryInjected });
|
|
3552
|
+
} else {
|
|
3553
|
+
let yamlVal = null;
|
|
3554
|
+
try {
|
|
3555
|
+
const resolver = new ConfigResolver();
|
|
3556
|
+
yamlVal = resolver.resolveObject("apcli");
|
|
3557
|
+
} catch {
|
|
3558
|
+
yamlVal = null;
|
|
3559
|
+
}
|
|
3560
|
+
apcliCfg = ApcliGroup.fromYaml(yamlVal, { registryInjected });
|
|
1245
3561
|
}
|
|
3562
|
+
const apcliGroup = program.command("apcli", { hidden: !apcliCfg.isGroupVisible() }).description("apcore-cli built-in commands");
|
|
1246
3563
|
if (registry) {
|
|
1247
3564
|
program._registry = registry;
|
|
1248
3565
|
if (executor) {
|
|
1249
3566
|
program._executor = executor;
|
|
1250
|
-
registerValidateCommand(program, registry, executor);
|
|
1251
|
-
void registerSystemCommands(program, executor);
|
|
1252
|
-
registerPipelineCommand(program, executor);
|
|
1253
3567
|
}
|
|
1254
3568
|
} else {
|
|
1255
3569
|
const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
|
|
1256
3570
|
void resolvedExtDir;
|
|
1257
3571
|
}
|
|
3572
|
+
let exposureFilter;
|
|
3573
|
+
try {
|
|
3574
|
+
if (expose instanceof ExposureFilter) {
|
|
3575
|
+
exposureFilter = expose;
|
|
3576
|
+
} else if (typeof expose === "object" && expose !== null) {
|
|
3577
|
+
exposureFilter = ExposureFilter.fromConfig({ expose });
|
|
3578
|
+
} else {
|
|
3579
|
+
exposureFilter = new ExposureFilter();
|
|
3580
|
+
}
|
|
3581
|
+
} catch (err) {
|
|
3582
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3583
|
+
process.stderr.write(`Error: invalid 'expose' option \u2014 ${msg}
|
|
3584
|
+
`);
|
|
3585
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3586
|
+
}
|
|
3587
|
+
_registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter);
|
|
3588
|
+
_registerDeprecationShims(program, apcliGroup, registryInjected, resolvedProgName);
|
|
1258
3589
|
program.addHelpText("after", [
|
|
1259
3590
|
"",
|
|
1260
3591
|
"Use --help --verbose to show all options (including built-in apcore options).",
|
|
1261
3592
|
"Use --help --man to display a formatted man page."
|
|
1262
3593
|
].join("\n"));
|
|
1263
|
-
registerInitCommand(program);
|
|
1264
3594
|
configureManHelp(program, resolvedProgName, VERSION);
|
|
1265
3595
|
if (extraCommands && extraCommands.length > 0) {
|
|
1266
|
-
const existingNames = /* @__PURE__ */ new Set([
|
|
1267
|
-
...BUILTIN_COMMANDS,
|
|
1268
|
-
...program.commands.map((c) => c.name())
|
|
1269
|
-
]);
|
|
1270
3596
|
for (const cmd of extraCommands) {
|
|
1271
3597
|
const cmdName = cmd.name();
|
|
1272
|
-
if (
|
|
3598
|
+
if (RESERVED_GROUP_NAMES.has(cmdName)) {
|
|
1273
3599
|
process.stderr.write(
|
|
1274
|
-
`
|
|
3600
|
+
`Error: extraCommands name '${cmdName}' is reserved
|
|
1275
3601
|
`
|
|
1276
3602
|
);
|
|
1277
|
-
|
|
3603
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3604
|
+
}
|
|
3605
|
+
const existing = program.commands.find((c) => c.name() === cmdName);
|
|
3606
|
+
if (existing) {
|
|
3607
|
+
const isShim = existing.__isDeprecationShim === true;
|
|
3608
|
+
if (isShim) {
|
|
3609
|
+
warn(
|
|
3610
|
+
`extraCommands '${cmdName}' overrides the deprecation shim for the same name. The shim will be removed.`
|
|
3611
|
+
);
|
|
3612
|
+
const cmds = program.commands;
|
|
3613
|
+
const idx = cmds.indexOf(existing);
|
|
3614
|
+
if (idx >= 0) cmds.splice(idx, 1);
|
|
3615
|
+
} else {
|
|
3616
|
+
process.stderr.write(
|
|
3617
|
+
`Error: extraCommands name '${cmdName}' collides with an existing command
|
|
3618
|
+
`
|
|
3619
|
+
);
|
|
3620
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3621
|
+
}
|
|
1278
3622
|
}
|
|
1279
3623
|
program.addCommand(cmd);
|
|
1280
|
-
existingNames.add(cmdName);
|
|
1281
3624
|
}
|
|
1282
3625
|
}
|
|
1283
3626
|
program.hook("preAction", async (thisCommand) => {
|
|
@@ -1288,25 +3631,132 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
|
|
|
1288
3631
|
});
|
|
1289
3632
|
return program;
|
|
1290
3633
|
}
|
|
3634
|
+
function _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter) {
|
|
3635
|
+
const emitUnwiredError = () => {
|
|
3636
|
+
process.stderr.write(
|
|
3637
|
+
"Error: no apcore-js registry wired. In standalone mode, pass --extensions-dir <path> to enable module discovery.\n"
|
|
3638
|
+
);
|
|
3639
|
+
process.exit(EXIT_CODES.CONFIG_INVALID);
|
|
3640
|
+
};
|
|
3641
|
+
const effectiveRegistry = registry ?? {
|
|
3642
|
+
listModules: () => emitUnwiredError(),
|
|
3643
|
+
getModule: () => emitUnwiredError()
|
|
3644
|
+
};
|
|
3645
|
+
const TABLE = [
|
|
3646
|
+
{ name: "list", requiresExecutor: false, register: (g) => registerListCommand(g, effectiveRegistry, exposureFilter) },
|
|
3647
|
+
{ name: "describe", requiresExecutor: false, register: (g) => registerDescribeCommand(g, effectiveRegistry) },
|
|
3648
|
+
{ name: "exec", requiresExecutor: true, register: (g, _r, ex) => registerExecCommand(g, effectiveRegistry, ex) },
|
|
3649
|
+
{ name: "validate", requiresExecutor: true, register: (g, _r, ex) => registerValidateCommand(g, effectiveRegistry, ex) },
|
|
3650
|
+
{ name: "init", requiresExecutor: false, register: (g) => registerInitCommand(g) },
|
|
3651
|
+
{ name: "health", requiresExecutor: true, register: (g, _r, ex) => registerHealthCommand(g, ex) },
|
|
3652
|
+
{ name: "usage", requiresExecutor: true, register: (g, _r, ex) => registerUsageCommand(g, ex) },
|
|
3653
|
+
{ name: "enable", requiresExecutor: true, register: (g, _r, ex) => registerEnableCommand(g, ex) },
|
|
3654
|
+
{ name: "disable", requiresExecutor: true, register: (g, _r, ex) => registerDisableCommand(g, ex) },
|
|
3655
|
+
{ name: "reload", requiresExecutor: true, register: (g, _r, ex) => registerReloadCommand(g, ex) },
|
|
3656
|
+
{ name: "config", requiresExecutor: true, register: (g, _r, ex) => registerConfigCommand(g, ex) },
|
|
3657
|
+
{ name: "completion", requiresExecutor: false, register: (g) => registerCompletionCommand(g) },
|
|
3658
|
+
{ name: "describe-pipeline", requiresExecutor: true, register: (g, _r, ex) => registerPipelineCommand(g, ex) }
|
|
3659
|
+
];
|
|
3660
|
+
const mode = apcliCfg.resolveVisibility();
|
|
3661
|
+
for (const entry of TABLE) {
|
|
3662
|
+
let shouldRegister;
|
|
3663
|
+
if (mode === "all" || mode === "none") {
|
|
3664
|
+
shouldRegister = true;
|
|
3665
|
+
} else {
|
|
3666
|
+
shouldRegister = _ALWAYS_REGISTERED.has(entry.name) || apcliCfg.isSubcommandIncluded(entry.name);
|
|
3667
|
+
}
|
|
3668
|
+
if (!shouldRegister) continue;
|
|
3669
|
+
if (entry.requiresExecutor && !executor) {
|
|
3670
|
+
if (_ALWAYS_REGISTERED.has(entry.name)) {
|
|
3671
|
+
warn(
|
|
3672
|
+
`apcli.${entry.name} is in _ALWAYS_REGISTERED but no executor is wired \u2014 subcommand unavailable. Pass executor to createCli() or avoid ${entry.name} invocations.`
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
3675
|
+
continue;
|
|
3676
|
+
}
|
|
3677
|
+
entry.register(apcliGroup, registry, executor);
|
|
3678
|
+
}
|
|
3679
|
+
}
|
|
3680
|
+
function _registerDeprecationShims(root, apcliGroup, registryInjected, cliName) {
|
|
3681
|
+
if (registryInjected) return;
|
|
3682
|
+
for (const name of _DEPRECATED_ROOT_COMMANDS) {
|
|
3683
|
+
const apcliSub = apcliGroup.commands.find((c) => c.name() === name);
|
|
3684
|
+
if (!apcliSub) continue;
|
|
3685
|
+
if (root.commands.some((c) => c.name() === name)) continue;
|
|
3686
|
+
const shim = root.command(name).description(`[DEPRECATED] Use '${cliName} apcli ${name}' instead.`).allowUnknownOption(true).allowExcessArguments(true).helpOption(false);
|
|
3687
|
+
shim.__isDeprecationShim = true;
|
|
3688
|
+
shim.action(async function() {
|
|
3689
|
+
process.stderr.write(
|
|
3690
|
+
`WARNING: '${name}' as a root-level command is deprecated. Use '${cliName} apcli ${name}' instead.
|
|
3691
|
+
Will be removed in v0.8. See: https://aiperceivable.github.io/apcore-cli/features/builtin-group/#11-migration
|
|
3692
|
+
`
|
|
3693
|
+
);
|
|
3694
|
+
const tail = _collectShimForwardArgs(this);
|
|
3695
|
+
await apcliSub.parseAsync(tail, { from: "user" });
|
|
3696
|
+
});
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
function _collectShimForwardArgs(shim) {
|
|
3700
|
+
const shimArgs = (shim.args ?? []).slice();
|
|
3701
|
+
if (shimArgs.length > 0) return shimArgs;
|
|
3702
|
+
const shimName = shim.name();
|
|
3703
|
+
const idx = process.argv.indexOf(shimName);
|
|
3704
|
+
if (idx < 0) return [];
|
|
3705
|
+
return process.argv.slice(idx + 1);
|
|
3706
|
+
}
|
|
3707
|
+
function lookupBindingDisplay(moduleId) {
|
|
3708
|
+
return bindingDisplayMap.get(moduleId);
|
|
3709
|
+
}
|
|
3710
|
+
function clearBindingDisplayMap() {
|
|
3711
|
+
bindingDisplayMap.clear();
|
|
3712
|
+
}
|
|
1291
3713
|
async function applyToolkitIntegration(commandsDir, bindingPath) {
|
|
1292
3714
|
if (!commandsDir && !bindingPath) {
|
|
1293
3715
|
return;
|
|
1294
3716
|
}
|
|
3717
|
+
let toolkit;
|
|
1295
3718
|
try {
|
|
1296
3719
|
const toolkitModule = "apcore-toolkit";
|
|
1297
|
-
|
|
3720
|
+
toolkit = await import(
|
|
1298
3721
|
/* @vite-ignore */
|
|
1299
3722
|
toolkitModule
|
|
1300
3723
|
);
|
|
1301
|
-
|
|
1302
|
-
|
|
3724
|
+
} catch {
|
|
3725
|
+
warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
|
|
3726
|
+
return;
|
|
3727
|
+
}
|
|
3728
|
+
if (commandsDir) {
|
|
3729
|
+
warn("Convention scanning not available in the TypeScript toolkit");
|
|
3730
|
+
}
|
|
3731
|
+
if (bindingPath) {
|
|
3732
|
+
try {
|
|
3733
|
+
await loadBindingDisplayOverlay(toolkit, bindingPath);
|
|
3734
|
+
} catch (err) {
|
|
3735
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3736
|
+
warn(`apcore-toolkit: failed to load binding '${bindingPath}': ${msg}`);
|
|
1303
3737
|
}
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
async function loadBindingDisplayOverlay(toolkit, bindingPath) {
|
|
3741
|
+
const BindingLoaderCtor = toolkit.BindingLoader;
|
|
3742
|
+
const DisplayResolverCtor = toolkit.DisplayResolver;
|
|
3743
|
+
if (!BindingLoaderCtor || !DisplayResolverCtor) {
|
|
3744
|
+
return;
|
|
3745
|
+
}
|
|
3746
|
+
const loader = new BindingLoaderCtor();
|
|
3747
|
+
const scanned = loader.load(bindingPath);
|
|
3748
|
+
const resolver = new DisplayResolverCtor();
|
|
3749
|
+
const resolved = resolver.resolve(scanned, { bindingPath });
|
|
3750
|
+
for (const mod of resolved) {
|
|
3751
|
+
if (!mod || typeof mod !== "object") continue;
|
|
3752
|
+
const entry = mod;
|
|
3753
|
+
const id = typeof entry.moduleId === "string" ? entry.moduleId : null;
|
|
3754
|
+
if (!id) continue;
|
|
3755
|
+
const meta = entry.metadata ?? {};
|
|
3756
|
+
const display = meta.display;
|
|
3757
|
+
if (display && typeof display === "object" && !Array.isArray(display)) {
|
|
3758
|
+
bindingDisplayMap.set(id, display);
|
|
1307
3759
|
}
|
|
1308
|
-
} catch {
|
|
1309
|
-
console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
|
|
1310
3760
|
}
|
|
1311
3761
|
}
|
|
1312
3762
|
function main(progName) {
|
|
@@ -1326,10 +3776,302 @@ function main(progName) {
|
|
|
1326
3776
|
process.exit(code);
|
|
1327
3777
|
}
|
|
1328
3778
|
}
|
|
3779
|
+
function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName, verbose = verboseHelp) {
|
|
3780
|
+
const moduleId = moduleDef.id;
|
|
3781
|
+
let resolvedSchema = {};
|
|
3782
|
+
let schemaOptions = [];
|
|
3783
|
+
const display = getDisplay(moduleDef);
|
|
3784
|
+
const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
|
|
3785
|
+
const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
|
|
3786
|
+
const cmdHelp = cliDisplay.description ?? moduleDef.description;
|
|
3787
|
+
const inputSchema = moduleDef.inputSchema;
|
|
3788
|
+
if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
|
|
3789
|
+
try {
|
|
3790
|
+
resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
|
|
3791
|
+
} catch {
|
|
3792
|
+
resolvedSchema = inputSchema;
|
|
3793
|
+
}
|
|
3794
|
+
schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
|
|
3795
|
+
}
|
|
3796
|
+
const cmd = new Command5(effectiveCmdName).description(cmdHelp);
|
|
3797
|
+
const inputOpt = new Option4("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
|
|
3798
|
+
const yesOpt = new Option4("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
|
|
3799
|
+
const largeInputOpt = new Option4("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
|
|
3800
|
+
const formatOpt = new Option4("--format <format>", "Output format: json, table, csv, yaml, jsonl.").choices(["json", "table", "csv", "yaml", "jsonl"]);
|
|
3801
|
+
const fieldsOpt = new Option4("--fields <fields>", "Comma-separated dot-paths to select from the result (e.g., 'status,data.count').");
|
|
3802
|
+
const sandboxOpt = new Option4("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
|
|
3803
|
+
const dryRunOpt = new Option4("--dry-run", "Run preflight checks without executing the module. Shows validation results.").default(false);
|
|
3804
|
+
const traceOpt = new Option4("--trace", "Show execution pipeline trace with per-step timing after the result.").default(false);
|
|
3805
|
+
const streamOpt = new Option4("--stream", "Stream module output as JSONL (one JSON object per line, flushed immediately).").default(false);
|
|
3806
|
+
const strategyOpt = new Option4("--strategy <name>", "Execution pipeline strategy: standard (default), internal, testing, performance.").choices(["standard", "internal", "testing", "performance", "minimal"]);
|
|
3807
|
+
const approvalTimeoutOpt = new Option4("--approval-timeout <seconds>", "Override approval prompt timeout in seconds (default: 60).").argParser(parseInt);
|
|
3808
|
+
const approvalTokenOpt = new Option4("--approval-token <token>", "Resume a pending approval with the given token (for async approval flows).");
|
|
3809
|
+
if (!verbose) {
|
|
3810
|
+
inputOpt.hideHelp();
|
|
3811
|
+
yesOpt.hideHelp();
|
|
3812
|
+
largeInputOpt.hideHelp();
|
|
3813
|
+
formatOpt.hideHelp();
|
|
3814
|
+
fieldsOpt.hideHelp();
|
|
3815
|
+
dryRunOpt.hideHelp();
|
|
3816
|
+
traceOpt.hideHelp();
|
|
3817
|
+
streamOpt.hideHelp();
|
|
3818
|
+
strategyOpt.hideHelp();
|
|
3819
|
+
approvalTimeoutOpt.hideHelp();
|
|
3820
|
+
approvalTokenOpt.hideHelp();
|
|
3821
|
+
}
|
|
3822
|
+
cmd.addOption(inputOpt);
|
|
3823
|
+
cmd.addOption(yesOpt);
|
|
3824
|
+
cmd.addOption(largeInputOpt);
|
|
3825
|
+
cmd.addOption(formatOpt);
|
|
3826
|
+
cmd.addOption(fieldsOpt);
|
|
3827
|
+
cmd.addOption(sandboxOpt);
|
|
3828
|
+
cmd.addOption(dryRunOpt);
|
|
3829
|
+
cmd.addOption(traceOpt);
|
|
3830
|
+
cmd.addOption(streamOpt);
|
|
3831
|
+
cmd.addOption(strategyOpt);
|
|
3832
|
+
cmd.addOption(approvalTimeoutOpt);
|
|
3833
|
+
cmd.addOption(approvalTokenOpt);
|
|
3834
|
+
const footerParts = [];
|
|
3835
|
+
if (!verbose) {
|
|
3836
|
+
footerParts.push("Use --verbose to show all options (including built-in apcore options).");
|
|
3837
|
+
}
|
|
3838
|
+
if (docsUrl) {
|
|
3839
|
+
footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
|
|
3840
|
+
}
|
|
3841
|
+
if (footerParts.length > 0) {
|
|
3842
|
+
cmd.addHelpText("after", "\n" + footerParts.join("\n") + "\n");
|
|
3843
|
+
}
|
|
3844
|
+
for (const opt of schemaOptions) {
|
|
3845
|
+
if (opt.parseArg) {
|
|
3846
|
+
cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
|
|
3847
|
+
} else {
|
|
3848
|
+
cmd.option(opt.flags, opt.description, opt.defaultValue);
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
cmd.action(async (options) => {
|
|
3852
|
+
const stdinFlag = options.input;
|
|
3853
|
+
const autoApprove = options.yes;
|
|
3854
|
+
const largeInput = options.largeInput;
|
|
3855
|
+
const outputFormat = options.format;
|
|
3856
|
+
const outputFields = options.fields;
|
|
3857
|
+
const sandboxEnabled = options.sandbox;
|
|
3858
|
+
const dryRun = options.dryRun;
|
|
3859
|
+
const traceFlag = options.trace;
|
|
3860
|
+
const streamFlag = options.stream;
|
|
3861
|
+
const strategyName = resolveStringOption(options.strategy, process.env.APCORE_CLI_STRATEGY);
|
|
3862
|
+
const approvalTimeout = resolveIntOption(
|
|
3863
|
+
options.approvalTimeout,
|
|
3864
|
+
process.env.APCORE_CLI_APPROVAL_TIMEOUT,
|
|
3865
|
+
60
|
|
3866
|
+
);
|
|
3867
|
+
const approvalToken = options.approvalToken;
|
|
3868
|
+
const schemaKwargs = {};
|
|
3869
|
+
const builtinKeys = /* @__PURE__ */ new Set([
|
|
3870
|
+
"input",
|
|
3871
|
+
"yes",
|
|
3872
|
+
"largeInput",
|
|
3873
|
+
"format",
|
|
3874
|
+
"fields",
|
|
3875
|
+
"sandbox",
|
|
3876
|
+
"verbose",
|
|
3877
|
+
"dryRun",
|
|
3878
|
+
"trace",
|
|
3879
|
+
"stream",
|
|
3880
|
+
"strategy",
|
|
3881
|
+
"approvalTimeout",
|
|
3882
|
+
"approvalToken"
|
|
3883
|
+
]);
|
|
3884
|
+
for (const [k, v] of Object.entries(options)) {
|
|
3885
|
+
if (!builtinKeys.has(k)) {
|
|
3886
|
+
schemaKwargs[k] = v;
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
let merged = {};
|
|
3890
|
+
const startTime = performance.now();
|
|
3891
|
+
try {
|
|
3892
|
+
merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
|
|
3893
|
+
const reconverted = reconvertEnumValues(merged, schemaOptions);
|
|
3894
|
+
merged = reconverted;
|
|
3895
|
+
if (dryRun) {
|
|
3896
|
+
if (!executor.validate) {
|
|
3897
|
+
process.stderr.write("Error: Executor does not support validate.\n");
|
|
3898
|
+
process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
|
|
3899
|
+
}
|
|
3900
|
+
const preflight = await executor.validate(moduleId, merged);
|
|
3901
|
+
formatPreflightResult(preflight, outputFormat);
|
|
3902
|
+
if (traceFlag) {
|
|
3903
|
+
const pureSteps = /* @__PURE__ */ new Set([
|
|
3904
|
+
"context_creation",
|
|
3905
|
+
"call_chain_guard",
|
|
3906
|
+
"module_lookup",
|
|
3907
|
+
"acl_check",
|
|
3908
|
+
"input_validation"
|
|
3909
|
+
]);
|
|
3910
|
+
const allSteps = [
|
|
3911
|
+
"context_creation",
|
|
3912
|
+
"call_chain_guard",
|
|
3913
|
+
"module_lookup",
|
|
3914
|
+
"acl_check",
|
|
3915
|
+
"approval_gate",
|
|
3916
|
+
"middleware_before",
|
|
3917
|
+
"input_validation",
|
|
3918
|
+
"execute",
|
|
3919
|
+
"output_validation",
|
|
3920
|
+
"middleware_after",
|
|
3921
|
+
"return_result"
|
|
3922
|
+
];
|
|
3923
|
+
process.stderr.write("\nPipeline preview (dry-run):\n");
|
|
3924
|
+
for (const s of allSteps) {
|
|
3925
|
+
if (pureSteps.has(s)) {
|
|
3926
|
+
process.stderr.write(` \u2713 ${s.padEnd(24)} (pure \u2014 would execute)
|
|
3927
|
+
`);
|
|
3928
|
+
} else {
|
|
3929
|
+
process.stderr.write(` \u25CB ${s.padEnd(24)} (impure \u2014 skipped in dry-run)
|
|
3930
|
+
`);
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
|
|
3935
|
+
}
|
|
3936
|
+
if (approvalToken) {
|
|
3937
|
+
merged._approval_token = approvalToken;
|
|
3938
|
+
}
|
|
3939
|
+
await checkApproval(moduleDef, autoApprove, approvalTimeout);
|
|
3940
|
+
if (streamFlag) {
|
|
3941
|
+
if (resolveFormat(outputFormat) === "table") {
|
|
3942
|
+
process.stderr.write("Warning: Streaming mode always outputs JSONL; --format table is ignored.\n");
|
|
3943
|
+
}
|
|
3944
|
+
const annotations = moduleDef.annotations;
|
|
3945
|
+
const isStreaming = annotations?.streaming === true;
|
|
3946
|
+
if (!isStreaming) {
|
|
3947
|
+
process.stderr.write(
|
|
3948
|
+
`Warning: Module '${moduleId}' does not declare streaming support. Falling back to standard execution.
|
|
3949
|
+
`
|
|
3950
|
+
);
|
|
3951
|
+
}
|
|
3952
|
+
if (isStreaming && executor.stream) {
|
|
3953
|
+
let chunks = 0;
|
|
3954
|
+
for await (const chunk of executor.stream(moduleId, merged)) {
|
|
3955
|
+
chunks++;
|
|
3956
|
+
process.stdout.write(JSON.stringify(chunk) + "\n");
|
|
3957
|
+
if (process.stderr.isTTY) {
|
|
3958
|
+
process.stderr.write(`\rStreaming ${moduleId}... (${chunks} chunks)`);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
if (process.stderr.isTTY) {
|
|
3962
|
+
process.stderr.write("\n");
|
|
3963
|
+
}
|
|
3964
|
+
const durationMs2 = Math.round(performance.now() - startTime);
|
|
3965
|
+
const { getAuditLogger: getAuditLogger3 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
3966
|
+
const auditLogger2 = getAuditLogger3();
|
|
3967
|
+
if (auditLogger2) {
|
|
3968
|
+
auditLogger2.logExecution(moduleId, merged, "success", 0, durationMs2);
|
|
3969
|
+
}
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
if (traceFlag && executor.callWithTrace) {
|
|
3974
|
+
const [result2, trace] = await executor.callWithTrace(
|
|
3975
|
+
moduleId,
|
|
3976
|
+
merged,
|
|
3977
|
+
strategyName ? { strategy: strategyName } : void 0
|
|
3978
|
+
);
|
|
3979
|
+
const durationMs2 = Math.round(performance.now() - startTime);
|
|
3980
|
+
const resolved = resolveFormat(outputFormat);
|
|
3981
|
+
if (resolved === "json" || !process.stdout.isTTY) {
|
|
3982
|
+
const traceData = {
|
|
3983
|
+
strategy: trace.strategyName,
|
|
3984
|
+
total_duration_ms: trace.totalDurationMs,
|
|
3985
|
+
success: trace.success,
|
|
3986
|
+
steps: trace.steps.map((s) => ({
|
|
3987
|
+
name: s.name,
|
|
3988
|
+
duration_ms: s.durationMs,
|
|
3989
|
+
skipped: s.skipped,
|
|
3990
|
+
...s.skipped ? { skip_reason: s.skipReason ?? null } : {}
|
|
3991
|
+
}))
|
|
3992
|
+
};
|
|
3993
|
+
let output;
|
|
3994
|
+
if (typeof result2 === "object" && result2 !== null && !Array.isArray(result2)) {
|
|
3995
|
+
output = { ...result2, _trace: traceData };
|
|
3996
|
+
} else {
|
|
3997
|
+
output = { result: result2, _trace: traceData };
|
|
3998
|
+
}
|
|
3999
|
+
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
4000
|
+
} else {
|
|
4001
|
+
formatExecResult(result2, outputFormat, outputFields);
|
|
4002
|
+
const stepCount = trace.steps.length;
|
|
4003
|
+
process.stderr.write(
|
|
4004
|
+
`
|
|
4005
|
+
Pipeline Trace (strategy: ${trace.strategyName}, ${stepCount} steps, ${trace.totalDurationMs.toFixed(1)}ms)
|
|
4006
|
+
`
|
|
4007
|
+
);
|
|
4008
|
+
for (const s of trace.steps) {
|
|
4009
|
+
if (s.skipped) {
|
|
4010
|
+
const reason = s.skipReason ?? "n/a";
|
|
4011
|
+
process.stderr.write(` \u25CB ${s.name.padEnd(24)} ${"\u2014".padStart(8)} skipped (${reason})
|
|
4012
|
+
`);
|
|
4013
|
+
} else {
|
|
4014
|
+
process.stderr.write(` \u2713 ${s.name.padEnd(24)} ${(s.durationMs.toFixed(1) + "ms").padStart(8)}
|
|
4015
|
+
`);
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
const { getAuditLogger: getAL2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4020
|
+
const al2 = getAL2();
|
|
4021
|
+
if (al2) {
|
|
4022
|
+
al2.logExecution(moduleId, merged, "success", 0, durationMs2);
|
|
4023
|
+
}
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
let result;
|
|
4027
|
+
if (strategyName && executor.callWithTrace) {
|
|
4028
|
+
const [res] = await executor.callWithTrace(
|
|
4029
|
+
moduleId,
|
|
4030
|
+
merged,
|
|
4031
|
+
{ strategy: strategyName }
|
|
4032
|
+
);
|
|
4033
|
+
result = res;
|
|
4034
|
+
if (strategyName !== "standard" && process.stderr.isTTY) {
|
|
4035
|
+
process.stderr.write(`Warning: Using '${strategyName}' strategy.
|
|
4036
|
+
`);
|
|
4037
|
+
}
|
|
4038
|
+
} else {
|
|
4039
|
+
const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
|
|
4040
|
+
const sandbox = new Sandbox2(sandboxEnabled);
|
|
4041
|
+
result = await sandbox.execute(moduleId, merged, executor);
|
|
4042
|
+
}
|
|
4043
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
4044
|
+
formatExecResult(result, outputFormat, outputFields);
|
|
4045
|
+
const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4046
|
+
const auditLogger = getAuditLogger2();
|
|
4047
|
+
if (auditLogger) {
|
|
4048
|
+
auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
|
|
4049
|
+
}
|
|
4050
|
+
} catch (err) {
|
|
4051
|
+
const exitCode = exitCodeForError(err);
|
|
4052
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
4053
|
+
try {
|
|
4054
|
+
const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4055
|
+
const auditLogger = getAuditLogger2();
|
|
4056
|
+
if (auditLogger) {
|
|
4057
|
+
auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
|
|
4058
|
+
}
|
|
4059
|
+
} catch {
|
|
4060
|
+
}
|
|
4061
|
+
if (outputFormat === "json" || !process.stderr.isTTY) {
|
|
4062
|
+
emitErrorJson(err, exitCode);
|
|
4063
|
+
} else {
|
|
4064
|
+
emitErrorTty(err, exitCode);
|
|
4065
|
+
}
|
|
4066
|
+
process.exit(exitCode);
|
|
4067
|
+
}
|
|
4068
|
+
});
|
|
4069
|
+
return cmd;
|
|
4070
|
+
}
|
|
1329
4071
|
function validateModuleId(moduleId) {
|
|
1330
|
-
if (moduleId.length >
|
|
4072
|
+
if (moduleId.length > 192) {
|
|
1331
4073
|
process.stderr.write(
|
|
1332
|
-
`Error: Invalid module ID format: '${moduleId}'. Maximum length is
|
|
4074
|
+
`Error: Invalid module ID format: '${moduleId}'. Maximum length is 192 characters.
|
|
1333
4075
|
`
|
|
1334
4076
|
);
|
|
1335
4077
|
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
@@ -1352,45 +4094,57 @@ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
|
|
|
1352
4094
|
if (!stdinFlag) {
|
|
1353
4095
|
return cliKwargsNonNull;
|
|
1354
4096
|
}
|
|
4097
|
+
let raw;
|
|
4098
|
+
let source;
|
|
1355
4099
|
if (stdinFlag === "-") {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
"Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
|
|
1361
|
-
);
|
|
1362
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
1363
|
-
}
|
|
1364
|
-
if (!raw) {
|
|
1365
|
-
return cliKwargsNonNull;
|
|
1366
|
-
}
|
|
1367
|
-
let stdinData;
|
|
4100
|
+
raw = await readStdin();
|
|
4101
|
+
source = "STDIN";
|
|
4102
|
+
} else {
|
|
4103
|
+
source = `file '${stdinFlag}'`;
|
|
1368
4104
|
try {
|
|
1369
|
-
|
|
1370
|
-
} catch {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
4105
|
+
raw = readFileSync2(stdinFlag, "utf-8");
|
|
4106
|
+
} catch (err) {
|
|
4107
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4108
|
+
process.stderr.write(`Error: Could not read input ${source}: ${msg}
|
|
4109
|
+
`);
|
|
1374
4110
|
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
1375
4111
|
}
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
4112
|
+
}
|
|
4113
|
+
const rawSize = Buffer.byteLength(raw, "utf-8");
|
|
4114
|
+
if (rawSize > 10485760 && !largeInput) {
|
|
4115
|
+
process.stderr.write(
|
|
4116
|
+
`Error: ${source} input exceeds 10MB limit. Use --large-input to override.
|
|
1379
4117
|
`
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
4118
|
+
);
|
|
4119
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4120
|
+
}
|
|
4121
|
+
if (!raw) {
|
|
4122
|
+
return cliKwargsNonNull;
|
|
4123
|
+
}
|
|
4124
|
+
let parsed;
|
|
4125
|
+
try {
|
|
4126
|
+
parsed = JSON.parse(raw);
|
|
4127
|
+
} catch {
|
|
4128
|
+
process.stderr.write(`Error: ${source} does not contain valid JSON.
|
|
4129
|
+
`);
|
|
4130
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
1384
4131
|
}
|
|
1385
|
-
|
|
4132
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
4133
|
+
process.stderr.write(
|
|
4134
|
+
`Error: ${source} JSON must be an object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.
|
|
4135
|
+
`
|
|
4136
|
+
);
|
|
4137
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4138
|
+
}
|
|
4139
|
+
return { ...parsed, ...cliKwargsNonNull };
|
|
1386
4140
|
}
|
|
1387
4141
|
function readStdin() {
|
|
1388
|
-
return new Promise((
|
|
4142
|
+
return new Promise((resolve2, reject) => {
|
|
1389
4143
|
const chunks = [];
|
|
1390
4144
|
const onData = (chunk) => chunks.push(chunk);
|
|
1391
4145
|
const onEnd = () => {
|
|
1392
4146
|
cleanup();
|
|
1393
|
-
|
|
4147
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
1394
4148
|
};
|
|
1395
4149
|
const onError = (err) => {
|
|
1396
4150
|
cleanup();
|
|
@@ -1407,7 +4161,97 @@ function readStdin() {
|
|
|
1407
4161
|
process.stdin.resume();
|
|
1408
4162
|
});
|
|
1409
4163
|
}
|
|
4164
|
+
function reconvertEnumValues(kwargs, options) {
|
|
4165
|
+
const result = { ...kwargs };
|
|
4166
|
+
for (const opt of options) {
|
|
4167
|
+
if (!opt.enumOriginalTypes) continue;
|
|
4168
|
+
const paramName = opt.name;
|
|
4169
|
+
if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
|
|
4170
|
+
continue;
|
|
4171
|
+
}
|
|
4172
|
+
const strVal = String(result[paramName]);
|
|
4173
|
+
const origType = opt.enumOriginalTypes[strVal];
|
|
4174
|
+
if (origType === "int") {
|
|
4175
|
+
result[paramName] = parseInt(strVal, 10);
|
|
4176
|
+
} else if (origType === "float") {
|
|
4177
|
+
result[paramName] = parseFloat(strVal);
|
|
4178
|
+
} else if (origType === "bool") {
|
|
4179
|
+
result[paramName] = strVal.toLowerCase() === "true";
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
return result;
|
|
4183
|
+
}
|
|
4184
|
+
var __dirname2, verboseHelp, docsUrl, VERSION, _ALWAYS_REGISTERED, _DEPRECATED_ROOT_COMMANDS, bindingDisplayMap;
|
|
4185
|
+
var init_main = __esm({
|
|
4186
|
+
"src/main.ts"() {
|
|
4187
|
+
"use strict";
|
|
4188
|
+
init_esm_shims();
|
|
4189
|
+
init_errors();
|
|
4190
|
+
init_ref_resolver();
|
|
4191
|
+
init_schema_parser();
|
|
4192
|
+
init_approval();
|
|
4193
|
+
init_output();
|
|
4194
|
+
init_logger();
|
|
4195
|
+
init_init_cmd();
|
|
4196
|
+
init_display_helpers();
|
|
4197
|
+
init_config();
|
|
4198
|
+
init_approval();
|
|
4199
|
+
init_shell();
|
|
4200
|
+
init_discovery();
|
|
4201
|
+
init_system_cmd();
|
|
4202
|
+
init_strategy();
|
|
4203
|
+
init_builtin_group();
|
|
4204
|
+
init_exposure();
|
|
4205
|
+
init_audit();
|
|
4206
|
+
init_canonical_help();
|
|
4207
|
+
__dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
|
|
4208
|
+
verboseHelp = false;
|
|
4209
|
+
docsUrl = null;
|
|
4210
|
+
VERSION = "0.0.0";
|
|
4211
|
+
try {
|
|
4212
|
+
const pkg = JSON.parse(readFileSync2(path4.resolve(__dirname2, "../package.json"), "utf-8"));
|
|
4213
|
+
VERSION = pkg.version;
|
|
4214
|
+
} catch {
|
|
4215
|
+
}
|
|
4216
|
+
_ALWAYS_REGISTERED = /* @__PURE__ */ new Set(["exec"]);
|
|
4217
|
+
_DEPRECATED_ROOT_COMMANDS = [
|
|
4218
|
+
"list",
|
|
4219
|
+
"describe",
|
|
4220
|
+
"exec",
|
|
4221
|
+
"init",
|
|
4222
|
+
"validate",
|
|
4223
|
+
"health",
|
|
4224
|
+
"usage",
|
|
4225
|
+
"enable",
|
|
4226
|
+
"disable",
|
|
4227
|
+
"reload",
|
|
4228
|
+
"config",
|
|
4229
|
+
"completion",
|
|
4230
|
+
"describe-pipeline"
|
|
4231
|
+
];
|
|
4232
|
+
bindingDisplayMap = /* @__PURE__ */ new Map();
|
|
4233
|
+
}
|
|
4234
|
+
});
|
|
1410
4235
|
|
|
1411
4236
|
// bin/apcore-cli.ts
|
|
1412
|
-
|
|
4237
|
+
init_esm_shims();
|
|
4238
|
+
var sandboxIdx = process.argv.indexOf("--internal-sandbox-runner");
|
|
4239
|
+
if (sandboxIdx !== -1) {
|
|
4240
|
+
const moduleId = process.argv[sandboxIdx + 1];
|
|
4241
|
+
if (!moduleId) {
|
|
4242
|
+
process.stderr.write("--internal-sandbox-runner requires a module_id argument.\n");
|
|
4243
|
+
process.exit(1);
|
|
4244
|
+
}
|
|
4245
|
+
Promise.resolve().then(() => (init_sandbox(), sandbox_exports)).then(({ runSandboxRunner: runSandboxRunner2 }) => {
|
|
4246
|
+
runSandboxRunner2(moduleId).catch((err) => {
|
|
4247
|
+
process.stderr.write(`sandbox runner fatal: ${err}
|
|
4248
|
+
`);
|
|
4249
|
+
process.exit(1);
|
|
4250
|
+
});
|
|
4251
|
+
});
|
|
4252
|
+
} else {
|
|
4253
|
+
Promise.resolve().then(() => (init_main(), main_exports)).then(({ main: main2 }) => {
|
|
4254
|
+
main2("apcore-cli");
|
|
4255
|
+
});
|
|
4256
|
+
}
|
|
1413
4257
|
//# sourceMappingURL=apcore-cli.js.map
|