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