apcore-cli 0.9.1 → 0.10.1
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 +53 -0
- package/dist/bin/apcore-cli.js +1606 -1579
- package/dist/bin/apcore-cli.js.map +1 -1
- package/dist/index.d.ts +43 -10
- package/dist/index.js +2268 -2251
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -652,7 +652,7 @@ var init_sandbox = __esm({
|
|
|
652
652
|
*/
|
|
653
653
|
async execute(moduleId, inputData, executor) {
|
|
654
654
|
if (!this.enabled) {
|
|
655
|
-
return executor.
|
|
655
|
+
return executor.call(moduleId, inputData);
|
|
656
656
|
}
|
|
657
657
|
return this._sandboxedExecute(moduleId, inputData);
|
|
658
658
|
}
|
|
@@ -781,7 +781,7 @@ init_errors();
|
|
|
781
781
|
import { readFileSync as readFileSync3 } from "fs";
|
|
782
782
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
783
783
|
import * as path5 from "path";
|
|
784
|
-
import { Command as
|
|
784
|
+
import { Command as Command6, CommanderError, Option as Option4 } from "commander";
|
|
785
785
|
|
|
786
786
|
// src/ref-resolver.ts
|
|
787
787
|
init_esm_shims();
|
|
@@ -954,9 +954,17 @@ function mapType(propName, propSchema) {
|
|
|
954
954
|
array: "string"
|
|
955
955
|
};
|
|
956
956
|
if (!schemaType) {
|
|
957
|
+
warn(`No type specified for property '${propName}', defaulting to string.`);
|
|
957
958
|
return "string";
|
|
958
959
|
}
|
|
959
|
-
|
|
960
|
+
const mapped = typeMap[schemaType];
|
|
961
|
+
if (mapped === void 0) {
|
|
962
|
+
warn(
|
|
963
|
+
`Unknown schema type '${schemaType}' for property '${propName}', defaulting to string.`
|
|
964
|
+
);
|
|
965
|
+
return "string";
|
|
966
|
+
}
|
|
967
|
+
return mapped;
|
|
960
968
|
}
|
|
961
969
|
function extractHelp(propSchema, maxLength = 1e3) {
|
|
962
970
|
let text = propSchema["x-llm-description"];
|
|
@@ -1155,7 +1163,7 @@ var CliApprovalHandler = class {
|
|
|
1155
1163
|
const message = extra.approval_message ?? `Module '${moduleId}' requires approval to execute.`;
|
|
1156
1164
|
process.stderr.write(message + "\n");
|
|
1157
1165
|
try {
|
|
1158
|
-
await promptWithTimeout({
|
|
1166
|
+
await promptWithTimeout({ moduleId }, this.timeout);
|
|
1159
1167
|
return { status: "approved", approved_by: "tty_user" };
|
|
1160
1168
|
} catch {
|
|
1161
1169
|
return { status: "rejected", reason: "User rejected or timed out" };
|
|
@@ -1178,7 +1186,7 @@ async function checkApproval(moduleDef, autoApprove, timeout) {
|
|
|
1178
1186
|
if (!requiresApproval) {
|
|
1179
1187
|
return;
|
|
1180
1188
|
}
|
|
1181
|
-
const moduleId = moduleDef.
|
|
1189
|
+
const moduleId = moduleDef.moduleId;
|
|
1182
1190
|
if (autoApprove) {
|
|
1183
1191
|
return;
|
|
1184
1192
|
}
|
|
@@ -1202,7 +1210,7 @@ async function checkApproval(moduleDef, autoApprove, timeout) {
|
|
|
1202
1210
|
}
|
|
1203
1211
|
async function promptWithTimeout(moduleDef, timeout) {
|
|
1204
1212
|
timeout = Math.max(1, Math.min(timeout, 3600));
|
|
1205
|
-
const moduleId = moduleDef.
|
|
1213
|
+
const moduleId = moduleDef.moduleId;
|
|
1206
1214
|
const annotations = moduleDef.annotations;
|
|
1207
1215
|
const message = (annotations ? getAnnotation(annotations, "approval_message") : void 0) ?? `Module '${moduleId}' requires approval to execute.`;
|
|
1208
1216
|
process.stderr.write(message + "\n");
|
|
@@ -1248,7 +1256,7 @@ function descriptorToScanned(m) {
|
|
|
1248
1256
|
const metadata = m.metadata ?? {};
|
|
1249
1257
|
const display = metadata["display"] ?? null;
|
|
1250
1258
|
return {
|
|
1251
|
-
moduleId: m.
|
|
1259
|
+
moduleId: m.moduleId,
|
|
1252
1260
|
description: m.description ?? "",
|
|
1253
1261
|
inputSchema: m.inputSchema ?? {},
|
|
1254
1262
|
outputSchema: m.outputSchema ?? {},
|
|
@@ -1304,13 +1312,13 @@ async function formatModuleList(modules, format, filterTags, showDeps = false, e
|
|
|
1304
1312
|
if (showDeps) headers.push("Deps");
|
|
1305
1313
|
if (exposureFilter) headers.push("Exposure");
|
|
1306
1314
|
const rows = modules.map((m) => {
|
|
1307
|
-
const base = [m.
|
|
1315
|
+
const base = [m.moduleId, truncate(m.description, 80), (m.tags ?? []).join(", ")];
|
|
1308
1316
|
if (showDeps) {
|
|
1309
1317
|
const deps = m.dependencies;
|
|
1310
1318
|
base.push(String(Array.isArray(deps) ? deps.length : 0));
|
|
1311
1319
|
}
|
|
1312
1320
|
if (exposureFilter) {
|
|
1313
|
-
base.push(exposureFilter.isExposed(m.
|
|
1321
|
+
base.push(exposureFilter.isExposed(m.moduleId ?? "") ? "\u2713" : "\u2014");
|
|
1314
1322
|
}
|
|
1315
1323
|
return base;
|
|
1316
1324
|
});
|
|
@@ -1318,7 +1326,7 @@ async function formatModuleList(modules, format, filterTags, showDeps = false, e
|
|
|
1318
1326
|
} else if (format === "json") {
|
|
1319
1327
|
const result = modules.map((m) => {
|
|
1320
1328
|
const entry = {
|
|
1321
|
-
id: m.
|
|
1329
|
+
id: m.moduleId,
|
|
1322
1330
|
description: m.description,
|
|
1323
1331
|
tags: m.tags ?? []
|
|
1324
1332
|
};
|
|
@@ -1327,7 +1335,7 @@ async function formatModuleList(modules, format, filterTags, showDeps = false, e
|
|
|
1327
1335
|
entry.dependency_count = Array.isArray(deps) ? deps.length : 0;
|
|
1328
1336
|
}
|
|
1329
1337
|
if (exposureFilter) {
|
|
1330
|
-
entry.exposed = exposureFilter.isExposed(m.
|
|
1338
|
+
entry.exposed = exposureFilter.isExposed(m.moduleId ?? "");
|
|
1331
1339
|
}
|
|
1332
1340
|
return entry;
|
|
1333
1341
|
});
|
|
@@ -1359,7 +1367,7 @@ function annotationsToDict(annotations) {
|
|
|
1359
1367
|
async function formatModuleDetail(moduleDef, format) {
|
|
1360
1368
|
if (format === "table") {
|
|
1361
1369
|
process.stdout.write(`
|
|
1362
|
-
Module: ${moduleDef.
|
|
1370
|
+
Module: ${moduleDef.moduleId}
|
|
1363
1371
|
`);
|
|
1364
1372
|
process.stdout.write(`
|
|
1365
1373
|
Description:
|
|
@@ -1407,7 +1415,7 @@ Tags: ${tags.join(", ")}
|
|
|
1407
1415
|
}
|
|
1408
1416
|
} else if (format === "json") {
|
|
1409
1417
|
const result = {
|
|
1410
|
-
id: moduleDef.
|
|
1418
|
+
id: moduleDef.moduleId,
|
|
1411
1419
|
description: moduleDef.description
|
|
1412
1420
|
};
|
|
1413
1421
|
if (moduleDef.inputSchema) result.input_schema = moduleDef.inputSchema;
|
|
@@ -1756,7 +1764,7 @@ function getDisplay(descriptor) {
|
|
|
1756
1764
|
if (display && typeof display === "object" && !Array.isArray(display)) {
|
|
1757
1765
|
return display;
|
|
1758
1766
|
}
|
|
1759
|
-
const overlay = lookupBindingDisplay(descriptor.
|
|
1767
|
+
const overlay = lookupBindingDisplay(descriptor.moduleId);
|
|
1760
1768
|
return overlay ?? {};
|
|
1761
1769
|
}
|
|
1762
1770
|
|
|
@@ -1811,9 +1819,9 @@ var LEGACY_TO_NAMESPACE = Object.fromEntries(
|
|
|
1811
1819
|
function registerConfigNamespace() {
|
|
1812
1820
|
try {
|
|
1813
1821
|
const nodeRequire = createRequire(import.meta.url);
|
|
1814
|
-
const { Config } = nodeRequire("apcore-js");
|
|
1815
|
-
if (typeof
|
|
1816
|
-
|
|
1822
|
+
const { Config: Config2 } = nodeRequire("apcore-js");
|
|
1823
|
+
if (typeof Config2?.registerNamespace === "function") {
|
|
1824
|
+
Config2.registerNamespace({
|
|
1817
1825
|
name: "apcore-cli",
|
|
1818
1826
|
envPrefix: "APCORE_CLI",
|
|
1819
1827
|
defaults: NAMESPACE_DEFAULTS
|
|
@@ -2274,2482 +2282,2491 @@ function registerCompletionCommand(host) {
|
|
|
2274
2282
|
|
|
2275
2283
|
// src/discovery.ts
|
|
2276
2284
|
init_esm_shims();
|
|
2277
|
-
import { Command as
|
|
2278
|
-
init_errors();
|
|
2279
|
-
init_audit();
|
|
2285
|
+
import { Command as Command3, Option as Option2 } from "commander";
|
|
2280
2286
|
|
|
2281
|
-
// src/
|
|
2287
|
+
// src/cli.ts
|
|
2282
2288
|
init_esm_shims();
|
|
2283
|
-
import
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
"
|
|
2290
|
-
"30d": 30 * 24 * 60 * 60 * 1e3
|
|
2291
|
-
};
|
|
2292
|
-
var DEFAULT_AUDIT_PATH = path4.join(
|
|
2293
|
-
os2.homedir(),
|
|
2294
|
-
".apcore-cli",
|
|
2295
|
-
"audit.jsonl"
|
|
2296
|
-
);
|
|
2297
|
-
function computeSummary(options = {}) {
|
|
2298
|
-
const auditPath = options.auditPath ?? DEFAULT_AUDIT_PATH;
|
|
2299
|
-
const period = options.period ?? "24h";
|
|
2300
|
-
const cutoff = (options.now ?? /* @__PURE__ */ new Date()).getTime() - PERIOD_TO_MS[period];
|
|
2301
|
-
if (!fs4.existsSync(auditPath)) {
|
|
2302
|
-
return /* @__PURE__ */ new Map();
|
|
2303
|
-
}
|
|
2304
|
-
let raw;
|
|
2305
|
-
try {
|
|
2306
|
-
raw = fs4.readFileSync(auditPath, "utf-8");
|
|
2307
|
-
} catch {
|
|
2308
|
-
return /* @__PURE__ */ new Map();
|
|
2309
|
-
}
|
|
2310
|
-
const counts = /* @__PURE__ */ new Map();
|
|
2311
|
-
const errors = /* @__PURE__ */ new Map();
|
|
2312
|
-
const latencySum = /* @__PURE__ */ new Map();
|
|
2313
|
-
for (const line of raw.split("\n")) {
|
|
2314
|
-
const trimmed = line.trim();
|
|
2315
|
-
if (!trimmed) continue;
|
|
2316
|
-
let entry;
|
|
2317
|
-
try {
|
|
2318
|
-
entry = JSON.parse(trimmed);
|
|
2319
|
-
} catch {
|
|
2320
|
-
continue;
|
|
2321
|
-
}
|
|
2322
|
-
const ts = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
|
|
2323
|
-
if (Number.isNaN(ts) || ts < cutoff) continue;
|
|
2324
|
-
const moduleId = typeof entry.module_id === "string" ? entry.module_id : null;
|
|
2325
|
-
if (!moduleId) continue;
|
|
2326
|
-
counts.set(moduleId, (counts.get(moduleId) ?? 0) + 1);
|
|
2327
|
-
if (entry.status === "error") {
|
|
2328
|
-
errors.set(moduleId, (errors.get(moduleId) ?? 0) + 1);
|
|
2329
|
-
}
|
|
2330
|
-
const duration = entry.duration_ms;
|
|
2331
|
-
if (typeof duration === "number") {
|
|
2332
|
-
latencySum.set(moduleId, (latencySum.get(moduleId) ?? 0) + duration);
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
const out = /* @__PURE__ */ new Map();
|
|
2336
|
-
for (const [id, calls] of counts) {
|
|
2337
|
-
out.set(id, {
|
|
2338
|
-
module_id: id,
|
|
2339
|
-
calls,
|
|
2340
|
-
errors: errors.get(id) ?? 0,
|
|
2341
|
-
latency_ms: calls > 0 ? (latencySum.get(id) ?? 0) / calls : 0
|
|
2342
|
-
});
|
|
2343
|
-
}
|
|
2344
|
-
return out;
|
|
2289
|
+
import { Command as Command2 } from "commander";
|
|
2290
|
+
|
|
2291
|
+
// src/exposure.ts
|
|
2292
|
+
init_esm_shims();
|
|
2293
|
+
init_logger();
|
|
2294
|
+
function escapeRegex(str) {
|
|
2295
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2345
2296
|
}
|
|
2346
|
-
function
|
|
2347
|
-
const
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
modules.sort((a, b) => (a.id ?? a.module_id ?? "").localeCompare(b.id ?? b.module_id ?? ""));
|
|
2354
|
-
if (reverse) modules.reverse();
|
|
2355
|
-
return { used: false };
|
|
2356
|
-
}
|
|
2357
|
-
const key = (m) => {
|
|
2358
|
-
const id = m.id ?? m.module_id ?? "";
|
|
2359
|
-
const s = summary.get(id);
|
|
2360
|
-
if (!s) return 0;
|
|
2361
|
-
return field === "latency" ? s.latency_ms : field === "calls" ? s.calls : s.errors;
|
|
2362
|
-
};
|
|
2363
|
-
modules.sort((a, b) => {
|
|
2364
|
-
const diff = key(a) - key(b);
|
|
2365
|
-
if (diff !== 0) return reverse ? -diff : diff;
|
|
2366
|
-
return (a.id ?? a.module_id ?? "").localeCompare(b.id ?? b.module_id ?? "");
|
|
2297
|
+
function compilePattern(pattern) {
|
|
2298
|
+
const sentinel = "\0GLOB\0";
|
|
2299
|
+
const escaped = pattern.replaceAll("**", sentinel);
|
|
2300
|
+
const parts = escaped.split("*");
|
|
2301
|
+
const regexParts = parts.map((p) => {
|
|
2302
|
+
const restored = p.replaceAll(sentinel, "**");
|
|
2303
|
+
return escapeRegex(restored);
|
|
2367
2304
|
});
|
|
2368
|
-
|
|
2305
|
+
let regex = regexParts.join("[^.]*");
|
|
2306
|
+
regex = regex.replaceAll("\\*\\*", ".+");
|
|
2307
|
+
return new RegExp(`^${regex}$`);
|
|
2369
2308
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2309
|
+
var ExposureFilter = class _ExposureFilter {
|
|
2310
|
+
static VALID_MODES = ["all", "include", "exclude", "none"];
|
|
2311
|
+
_mode;
|
|
2312
|
+
_compiledInclude;
|
|
2313
|
+
_compiledExclude;
|
|
2314
|
+
constructor(mode = "all", include, exclude) {
|
|
2315
|
+
if (!_ExposureFilter.VALID_MODES.includes(mode)) {
|
|
2316
|
+
process.stderr.write(
|
|
2317
|
+
`Warning: Unknown ExposureFilter mode '${mode}' \u2014 defaulting to 'none'. Valid modes: ${_ExposureFilter.VALID_MODES.join(", ")}.
|
|
2377
2318
|
`
|
|
2378
|
-
|
|
2379
|
-
|
|
2319
|
+
);
|
|
2320
|
+
mode = "none";
|
|
2321
|
+
}
|
|
2322
|
+
this._mode = mode;
|
|
2323
|
+
const dedup = (arr) => [...new Set(arr)];
|
|
2324
|
+
this._compiledInclude = dedup(include ?? []).map(compilePattern);
|
|
2325
|
+
this._compiledExclude = dedup(exclude ?? []).map(compilePattern);
|
|
2380
2326
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
return previous.concat([value]);
|
|
2387
|
-
}
|
|
2388
|
-
function getAnnotationFlag(moduleDef, flag) {
|
|
2389
|
-
const annotations = moduleDef.annotations;
|
|
2390
|
-
if (!annotations || typeof annotations !== "object") return false;
|
|
2391
|
-
const ann = annotations;
|
|
2392
|
-
const map = {
|
|
2393
|
-
"destructive": "destructive",
|
|
2394
|
-
"requires-approval": "requires_approval",
|
|
2395
|
-
"readonly": "readonly",
|
|
2396
|
-
"streaming": "streaming",
|
|
2397
|
-
"cacheable": "cacheable",
|
|
2398
|
-
"idempotent": "idempotent",
|
|
2399
|
-
"paginated": "paginated"
|
|
2400
|
-
};
|
|
2401
|
-
const attr = map[flag] ?? flag;
|
|
2402
|
-
return ann[attr] === true;
|
|
2403
|
-
}
|
|
2404
|
-
function registerListCommand(apcliGroup, registry, exposureFilter) {
|
|
2405
|
-
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(
|
|
2406
|
-
new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
|
|
2407
|
-
).option("-s, --search <query>", "Filter by substring match on ID and description.").addOption(
|
|
2408
|
-
new Option2("--status <status>", "Filter by module status.").choices(["enabled", "disabled", "all"]).default("enabled")
|
|
2409
|
-
).option("-a, --annotation <flag>", "Filter by annotation flag (AND logic). Repeatable.", collectAnnotation, []).addOption(
|
|
2410
|
-
new Option2("--sort <field>", "Sort order.").choices(["id", "calls", "errors", "latency"]).default("id")
|
|
2411
|
-
).option("--reverse", "Reverse sort order.", false).option("--deprecated", "Include deprecated modules.", false).option("--deps", "Show dependency count column.", false).addOption(
|
|
2412
|
-
new Option2("--exposure <mode>", "Filter by exposure status.").choices(["exposed", "hidden", "all"]).default("exposed")
|
|
2413
|
-
).action((opts) => {
|
|
2414
|
-
for (const t of opts.tag) {
|
|
2415
|
-
validateTag(t);
|
|
2327
|
+
/** Return true if the module should be exposed as a CLI command. */
|
|
2328
|
+
isExposed(moduleId) {
|
|
2329
|
+
if (this._mode === "all") return true;
|
|
2330
|
+
if (this._mode === "include") {
|
|
2331
|
+
return this._compiledInclude.some((rx) => rx.test(moduleId));
|
|
2416
2332
|
}
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
modules.push(m);
|
|
2333
|
+
if (this._mode === "exclude") {
|
|
2334
|
+
return !this._compiledExclude.some((rx) => rx.test(moduleId));
|
|
2420
2335
|
}
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2336
|
+
return false;
|
|
2337
|
+
}
|
|
2338
|
+
/** Partition moduleIds into [exposed, hidden] lists. */
|
|
2339
|
+
filterModules(moduleIds) {
|
|
2340
|
+
const exposed = [];
|
|
2341
|
+
const hidden = [];
|
|
2342
|
+
for (const mid of moduleIds) {
|
|
2343
|
+
(this.isExposed(mid) ? exposed : hidden).push(mid);
|
|
2427
2344
|
}
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2345
|
+
return [exposed, hidden];
|
|
2346
|
+
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Create an ExposureFilter from a parsed config dict.
|
|
2349
|
+
*
|
|
2350
|
+
* Expected: `{ expose: { mode: "include", include: ["admin.*"] } }`
|
|
2351
|
+
*/
|
|
2352
|
+
static fromConfig(config) {
|
|
2353
|
+
const expose = config.expose ?? {};
|
|
2354
|
+
if (typeof expose !== "object" || expose === null || Array.isArray(expose)) {
|
|
2355
|
+
warn("Invalid 'expose' config (expected dict), using mode: all.");
|
|
2356
|
+
return new _ExposureFilter();
|
|
2433
2357
|
}
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
modules = modules.filter((m) => {
|
|
2441
|
-
const enabled = m.enabled;
|
|
2442
|
-
return enabled === false;
|
|
2443
|
-
});
|
|
2358
|
+
const exposeObj = expose;
|
|
2359
|
+
const mode = exposeObj.mode ?? "all";
|
|
2360
|
+
if (!["all", "include", "exclude"].includes(mode)) {
|
|
2361
|
+
throw new Error(
|
|
2362
|
+
`Invalid expose mode: '${mode}'. Must be one of: all, include, exclude.`
|
|
2363
|
+
);
|
|
2444
2364
|
}
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
});
|
|
2365
|
+
let include = exposeObj.include ?? [];
|
|
2366
|
+
if (!Array.isArray(include)) {
|
|
2367
|
+
warn("Invalid 'expose.include' (expected list), ignoring.");
|
|
2368
|
+
include = [];
|
|
2450
2369
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2370
|
+
let exclude = exposeObj.exclude ?? [];
|
|
2371
|
+
if (!Array.isArray(exclude)) {
|
|
2372
|
+
warn("Invalid 'expose.exclude' (expected list), ignoring.");
|
|
2373
|
+
exclude = [];
|
|
2455
2374
|
}
|
|
2456
|
-
|
|
2457
|
-
const
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
`
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
} else {
|
|
2465
|
-
modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
|
|
2466
|
-
if (opts.reverse) {
|
|
2467
|
-
modules.reverse();
|
|
2375
|
+
const filterList = (arr, label) => {
|
|
2376
|
+
const result = [];
|
|
2377
|
+
for (const p of arr) {
|
|
2378
|
+
if (!p) {
|
|
2379
|
+
warn(`Empty pattern in expose.${label}, skipping.`);
|
|
2380
|
+
} else {
|
|
2381
|
+
result.push(String(p));
|
|
2382
|
+
}
|
|
2468
2383
|
}
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
if (opts.exposure === "all" && exposureFilter) {
|
|
2479
|
-
showExposureCol = true;
|
|
2480
|
-
}
|
|
2481
|
-
const fmt = resolveFormat(opts.format);
|
|
2482
|
-
const filterTagsArg = opts.tag.length > 0 ? opts.tag : void 0;
|
|
2483
|
-
void formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
|
|
2484
|
-
});
|
|
2485
|
-
apcliGroup.addCommand(listCmd);
|
|
2486
|
-
}
|
|
2487
|
-
function registerDescribeCommand(apcliGroup, registry) {
|
|
2488
|
-
const describeCmd = new Command2("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").addOption(
|
|
2489
|
-
new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
|
|
2490
|
-
).action((moduleId, opts) => {
|
|
2491
|
-
validateModuleId(moduleId);
|
|
2492
|
-
const moduleDef = registry.getModule(moduleId);
|
|
2493
|
-
if (!moduleDef) {
|
|
2494
|
-
process.stderr.write(
|
|
2495
|
-
`Error: Module '${moduleId}' not found.
|
|
2496
|
-
`
|
|
2497
|
-
);
|
|
2498
|
-
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
2499
|
-
}
|
|
2500
|
-
const fmt = resolveFormat(opts.format);
|
|
2501
|
-
void formatModuleDetail(moduleDef, fmt);
|
|
2502
|
-
});
|
|
2503
|
-
apcliGroup.addCommand(describeCmd);
|
|
2504
|
-
}
|
|
2505
|
-
function registerExecCommand(apcliGroup, registry, executor) {
|
|
2506
|
-
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(
|
|
2507
|
-
"--input <json>",
|
|
2508
|
-
"JSON object passed as input to the module. Use '-' to read JSON from stdin."
|
|
2509
|
-
).option("-y, --yes", "Auto-approve if the module declares requires_approval.", false).option(
|
|
2510
|
-
"--approval-timeout <seconds>",
|
|
2511
|
-
"Seconds to wait for interactive approval.",
|
|
2512
|
-
parseInt
|
|
2513
|
-
).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) => {
|
|
2514
|
-
validateModuleId(moduleId);
|
|
2515
|
-
const moduleDef = registry.getModule(moduleId);
|
|
2516
|
-
if (!moduleDef) {
|
|
2517
|
-
process.stderr.write(`Error: Module '${moduleId}' not found.
|
|
2518
|
-
`);
|
|
2519
|
-
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
2520
|
-
}
|
|
2521
|
-
let merged = {};
|
|
2522
|
-
if (opts.input === "-") {
|
|
2523
|
-
merged = await collectInput("-", {}, false);
|
|
2524
|
-
} else if (opts.input !== void 0) {
|
|
2525
|
-
try {
|
|
2526
|
-
const parsed = JSON.parse(opts.input);
|
|
2527
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
2528
|
-
process.stderr.write("Error: --input JSON must be an object.\n");
|
|
2529
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2530
|
-
}
|
|
2531
|
-
merged = parsed;
|
|
2532
|
-
} catch (err) {
|
|
2533
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2534
|
-
process.stderr.write(`Error: --input is not valid JSON: ${msg}
|
|
2535
|
-
`);
|
|
2536
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2537
|
-
}
|
|
2538
|
-
}
|
|
2539
|
-
const startTime = performance.now();
|
|
2540
|
-
try {
|
|
2541
|
-
await checkApproval(moduleDef, opts.yes, opts.approvalTimeout);
|
|
2542
|
-
if (opts.dryRun) {
|
|
2543
|
-
if (executor.validate) {
|
|
2544
|
-
const preflight = await executor.validate(moduleId, merged);
|
|
2545
|
-
formatPreflightResult(preflight, opts.format);
|
|
2546
|
-
} else {
|
|
2547
|
-
process.stdout.write(JSON.stringify({ valid: true }) + "\n");
|
|
2548
|
-
}
|
|
2549
|
-
return;
|
|
2550
|
-
}
|
|
2551
|
-
let result;
|
|
2552
|
-
if ((opts.trace || opts.strategy) && executor.callWithTrace) {
|
|
2553
|
-
const [res] = await executor.callWithTrace(moduleId, merged, { strategy: opts.strategy });
|
|
2554
|
-
result = res;
|
|
2555
|
-
} else {
|
|
2556
|
-
const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
|
|
2557
|
-
const sandbox = new Sandbox2(opts.sandbox);
|
|
2558
|
-
result = await sandbox.execute(moduleId, merged, executor);
|
|
2559
|
-
}
|
|
2560
|
-
const durationMs = Math.round(performance.now() - startTime);
|
|
2561
|
-
const fmt = resolveFormat(opts.format);
|
|
2562
|
-
formatExecResult(result, fmt, opts.fields);
|
|
2563
|
-
const auditLogger = getAuditLogger();
|
|
2564
|
-
if (auditLogger) {
|
|
2565
|
-
auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
|
|
2566
|
-
}
|
|
2567
|
-
} catch (err) {
|
|
2568
|
-
const exitCode = exitCodeForError(err);
|
|
2569
|
-
const durationMs = Math.round(performance.now() - startTime);
|
|
2570
|
-
try {
|
|
2571
|
-
const auditLogger = getAuditLogger();
|
|
2572
|
-
if (auditLogger) {
|
|
2573
|
-
auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
|
|
2574
|
-
}
|
|
2575
|
-
} catch {
|
|
2576
|
-
}
|
|
2577
|
-
process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
|
|
2578
|
-
`);
|
|
2579
|
-
process.exit(exitCode);
|
|
2580
|
-
}
|
|
2581
|
-
});
|
|
2582
|
-
apcliGroup.addCommand(execCmd);
|
|
2583
|
-
}
|
|
2584
|
-
function registerValidateCommand(cli, registry, executor) {
|
|
2585
|
-
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) => {
|
|
2586
|
-
validateModuleId(moduleId);
|
|
2587
|
-
const moduleDef = registry.getModule(moduleId);
|
|
2588
|
-
if (!moduleDef) {
|
|
2589
|
-
process.stderr.write(`Error: Module '${moduleId}' not found.
|
|
2590
|
-
`);
|
|
2591
|
-
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
2592
|
-
}
|
|
2593
|
-
const merged = opts.input ? await collectInput(opts.input, {}, false) : {};
|
|
2594
|
-
if (!executor.validate) {
|
|
2595
|
-
process.stderr.write("Error: Executor does not support validate.\n");
|
|
2596
|
-
process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
|
|
2597
|
-
}
|
|
2598
|
-
try {
|
|
2599
|
-
const preflight = await executor.validate(moduleId, merged);
|
|
2600
|
-
formatPreflightResult(preflight, opts.format);
|
|
2601
|
-
process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
|
|
2602
|
-
} catch (err) {
|
|
2603
|
-
const exitCode = exitCodeForError(err);
|
|
2604
|
-
try {
|
|
2605
|
-
const auditLogger = getAuditLogger();
|
|
2606
|
-
if (auditLogger) {
|
|
2607
|
-
auditLogger.logExecution(moduleId, merged, "error", exitCode, 0);
|
|
2608
|
-
}
|
|
2609
|
-
} catch {
|
|
2610
|
-
}
|
|
2611
|
-
process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
|
|
2612
|
-
`);
|
|
2613
|
-
process.exit(exitCode);
|
|
2614
|
-
}
|
|
2615
|
-
});
|
|
2616
|
-
cli.addCommand(validateCmd);
|
|
2617
|
-
}
|
|
2384
|
+
return result;
|
|
2385
|
+
};
|
|
2386
|
+
return new _ExposureFilter(
|
|
2387
|
+
mode,
|
|
2388
|
+
filterList(include, "include"),
|
|
2389
|
+
filterList(exclude, "exclude")
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
2392
|
+
};
|
|
2618
2393
|
|
|
2619
|
-
// src/
|
|
2394
|
+
// src/cli.ts
|
|
2395
|
+
init_logger();
|
|
2396
|
+
|
|
2397
|
+
// src/builtin-group.ts
|
|
2620
2398
|
init_esm_shims();
|
|
2621
|
-
import { Command as Command3 } from "commander";
|
|
2622
2399
|
init_errors();
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
}
|
|
2629
|
-
function emitResult(jsonPayload, fmt, ttyRender) {
|
|
2630
|
-
if (fmt === "json" || !process.stdout.isTTY) {
|
|
2631
|
-
process.stdout.write(JSON.stringify(jsonPayload, null, 2) + "\n");
|
|
2632
|
-
} else {
|
|
2633
|
-
ttyRender();
|
|
2400
|
+
init_logger();
|
|
2401
|
+
var ApcliGroupError = class extends Error {
|
|
2402
|
+
constructor(message) {
|
|
2403
|
+
super(message);
|
|
2404
|
+
this.name = "ApcliGroupError";
|
|
2634
2405
|
}
|
|
2406
|
+
};
|
|
2407
|
+
var DEFAULT_BUILTIN_GROUP_NAME = "apcli";
|
|
2408
|
+
var RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set([DEFAULT_BUILTIN_GROUP_NAME]);
|
|
2409
|
+
var _effectiveReservedNames = RESERVED_GROUP_NAMES;
|
|
2410
|
+
function getReservedGroupNames() {
|
|
2411
|
+
return _effectiveReservedNames;
|
|
2635
2412
|
}
|
|
2636
|
-
function
|
|
2637
|
-
|
|
2638
|
-
`);
|
|
2639
|
-
process.exit(exitCodeForError(e));
|
|
2640
|
-
}
|
|
2641
|
-
async function requireApprovalForSystemCommand(moduleId, autoApprove) {
|
|
2642
|
-
const syntheticModuleDef = {
|
|
2643
|
-
id: moduleId,
|
|
2644
|
-
name: moduleId,
|
|
2645
|
-
description: `system command: ${moduleId}`,
|
|
2646
|
-
annotations: { requires_approval: true }
|
|
2647
|
-
};
|
|
2648
|
-
await checkApproval(syntheticModuleDef, autoApprove, void 0);
|
|
2413
|
+
function setReservedGroupNames(names) {
|
|
2414
|
+
_effectiveReservedNames = names;
|
|
2649
2415
|
}
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
return;
|
|
2656
|
-
}
|
|
2657
|
-
const total = summary.total_modules ?? modules.length;
|
|
2658
|
-
process.stdout.write(`Health Overview (${total} modules)
|
|
2659
|
-
|
|
2660
|
-
`);
|
|
2661
|
-
process.stdout.write(` ${"Module".padEnd(28)} ${"Status".padEnd(12)} ${"Error Rate".padEnd(12)} Top Error
|
|
2662
|
-
`);
|
|
2663
|
-
process.stdout.write(` ${"-".repeat(28)} ${"-".repeat(12)} ${"-".repeat(12)} ${"-".repeat(20)}
|
|
2664
|
-
`);
|
|
2665
|
-
for (const m of modules) {
|
|
2666
|
-
const top = m.top_error;
|
|
2667
|
-
const topStr = top ? `${top.code} (${top.count ?? "?"})` : "\u2014";
|
|
2668
|
-
const rate = `${((m.error_rate ?? 0) * 100).toFixed(1)}%`;
|
|
2669
|
-
process.stdout.write(
|
|
2670
|
-
` ${String(m.module_id).padEnd(28)} ${String(m.status).padEnd(12)} ${rate.padEnd(12)} ${topStr}
|
|
2671
|
-
`
|
|
2416
|
+
var _NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
|
|
2417
|
+
function _validateBuiltinGroupName(name) {
|
|
2418
|
+
if (!name || !_NAME_REGEX.test(name)) {
|
|
2419
|
+
throw new ApcliGroupError(
|
|
2420
|
+
`builtinGroupName ${JSON.stringify(name)} must match /^[a-z][a-z0-9_-]*$/ (non-empty, lowercase, alphanumeric + '_' / '-', leading letter).`
|
|
2672
2421
|
);
|
|
2673
2422
|
}
|
|
2674
|
-
const parts = [];
|
|
2675
|
-
for (const key of ["healthy", "degraded", "error"]) {
|
|
2676
|
-
const count = summary[key];
|
|
2677
|
-
if (count) parts.push(`${count} ${key}`);
|
|
2678
|
-
}
|
|
2679
|
-
process.stdout.write(`
|
|
2680
|
-
Summary: ${parts.join(", ") || "no data"}
|
|
2681
|
-
`);
|
|
2682
|
-
}
|
|
2683
|
-
function formatHealthModuleTty(result) {
|
|
2684
|
-
process.stdout.write(`Module: ${result.module_id ?? "?"}
|
|
2685
|
-
`);
|
|
2686
|
-
process.stdout.write(`Status: ${result.status ?? "unknown"}
|
|
2687
|
-
`);
|
|
2688
|
-
const total = result.total_calls ?? 0;
|
|
2689
|
-
const errors = result.error_count ?? 0;
|
|
2690
|
-
const rate = result.error_rate ?? 0;
|
|
2691
|
-
const avg = result.avg_latency_ms ?? 0;
|
|
2692
|
-
const p99 = result.p99_latency_ms ?? 0;
|
|
2693
|
-
process.stdout.write(`Calls: ${total.toLocaleString()} total | ${errors.toLocaleString()} errors | ${(rate * 100).toFixed(1)}% error rate
|
|
2694
|
-
`);
|
|
2695
|
-
process.stdout.write(`Latency: ${avg.toFixed(0)}ms avg | ${p99.toFixed(0)}ms p99
|
|
2696
|
-
`);
|
|
2697
|
-
const recent = result.recent_errors ?? [];
|
|
2698
|
-
if (recent.length > 0) {
|
|
2699
|
-
process.stdout.write(`
|
|
2700
|
-
Recent Errors (top ${recent.length}):
|
|
2701
|
-
`);
|
|
2702
|
-
for (const e of recent) {
|
|
2703
|
-
const count = e.count ?? "?";
|
|
2704
|
-
const last = e.last_occurred ?? "?";
|
|
2705
|
-
process.stdout.write(` ${String(e.code ?? "?").padEnd(24)} x${count} (last: ${last})
|
|
2706
|
-
`);
|
|
2707
|
-
}
|
|
2708
|
-
}
|
|
2709
2423
|
}
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2424
|
+
var VALID_USER_MODES = /* @__PURE__ */ new Set([
|
|
2425
|
+
"all",
|
|
2426
|
+
"none",
|
|
2427
|
+
"include",
|
|
2428
|
+
"exclude"
|
|
2429
|
+
]);
|
|
2430
|
+
var APCLI_SUBCOMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
2431
|
+
"list",
|
|
2432
|
+
"describe",
|
|
2433
|
+
"exec",
|
|
2434
|
+
"validate",
|
|
2435
|
+
"init",
|
|
2436
|
+
"health",
|
|
2437
|
+
"usage",
|
|
2438
|
+
"enable",
|
|
2439
|
+
"disable",
|
|
2440
|
+
"reload",
|
|
2441
|
+
"config",
|
|
2442
|
+
"completion",
|
|
2443
|
+
"describe-pipeline"
|
|
2444
|
+
]);
|
|
2445
|
+
var ApcliGroup = class _ApcliGroup {
|
|
2446
|
+
_mode;
|
|
2447
|
+
_include;
|
|
2448
|
+
_exclude;
|
|
2449
|
+
_disableEnv;
|
|
2450
|
+
_registryInjected;
|
|
2451
|
+
_fromCliConfig;
|
|
2452
|
+
_name;
|
|
2453
|
+
constructor(init) {
|
|
2454
|
+
this._mode = init.mode;
|
|
2455
|
+
this._include = init.include;
|
|
2456
|
+
this._exclude = init.exclude;
|
|
2457
|
+
this._disableEnv = init.disableEnv;
|
|
2458
|
+
this._registryInjected = init.registryInjected;
|
|
2459
|
+
this._fromCliConfig = init.fromCliConfig;
|
|
2460
|
+
this._name = init.name;
|
|
2717
2461
|
}
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
`
|
|
2721
|
-
|
|
2722
|
-
`)
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2462
|
+
/**
|
|
2463
|
+
* Resolved name for the built-in command group (default `"apcli"`).
|
|
2464
|
+
* Overridable via createCli's `builtinGroupName` option for downstream
|
|
2465
|
+
* branded CLIs that want a custom namespace. Cross-SDK parity with
|
|
2466
|
+
* Python `ApcliGroup.name` (2026-05-08).
|
|
2467
|
+
*/
|
|
2468
|
+
get name() {
|
|
2469
|
+
return this._name;
|
|
2470
|
+
}
|
|
2471
|
+
/**
|
|
2472
|
+
* Tier 1 constructor — config came from `createCli({ apcli })`.
|
|
2473
|
+
*
|
|
2474
|
+
* A non-auto mode from this tier wins over env var and yaml.
|
|
2475
|
+
*/
|
|
2476
|
+
static fromCliConfig(config, opts) {
|
|
2477
|
+
return _ApcliGroup._build(
|
|
2478
|
+
config,
|
|
2479
|
+
opts,
|
|
2480
|
+
/*fromCliConfig*/
|
|
2481
|
+
true
|
|
2730
2482
|
);
|
|
2731
2483
|
}
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
const result = await callSystemModule(executor, "system.health.summary", {
|
|
2750
|
-
error_rate_threshold: opts.threshold,
|
|
2751
|
-
include_healthy: opts.all
|
|
2752
|
-
});
|
|
2753
|
-
emitResult(result, fmt, () => formatHealthSummaryTty(result));
|
|
2754
|
-
}
|
|
2755
|
-
} catch (e) {
|
|
2756
|
-
emitErrorAndExit(e);
|
|
2484
|
+
/**
|
|
2485
|
+
* Tier 3 constructor — config came from `apcore.yaml`.
|
|
2486
|
+
*
|
|
2487
|
+
* Env var (Tier 2) may override the yaml-supplied mode.
|
|
2488
|
+
*/
|
|
2489
|
+
static fromYaml(config, opts) {
|
|
2490
|
+
if (config !== null && config !== void 0 && typeof config !== "boolean" && (typeof config !== "object" || Array.isArray(config))) {
|
|
2491
|
+
const got = Array.isArray(config) ? "array" : typeof config;
|
|
2492
|
+
warn(
|
|
2493
|
+
`apcore.yaml apcli has unexpected type ${got}; using auto-detect.`
|
|
2494
|
+
);
|
|
2495
|
+
return _ApcliGroup._build(
|
|
2496
|
+
void 0,
|
|
2497
|
+
opts,
|
|
2498
|
+
/*fromCliConfig*/
|
|
2499
|
+
false
|
|
2500
|
+
);
|
|
2757
2501
|
}
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2502
|
+
return _ApcliGroup._build(
|
|
2503
|
+
config,
|
|
2504
|
+
opts,
|
|
2505
|
+
/*fromCliConfig*/
|
|
2506
|
+
false
|
|
2507
|
+
);
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Non-panicking Tier 3 factory (A-001 parity with Rust's `try_from_yaml`).
|
|
2511
|
+
* Returns `[instance, null]` on success or `[null, errorMessage]` on invalid input.
|
|
2512
|
+
* Use this in programmatic contexts where throwing/exiting is unwanted.
|
|
2513
|
+
*/
|
|
2514
|
+
static tryFromYaml(config, opts) {
|
|
2515
|
+
if (config !== null && config !== void 0 && typeof config !== "boolean" && (typeof config !== "object" || Array.isArray(config))) {
|
|
2516
|
+
const got = Array.isArray(config) ? "array" : typeof config;
|
|
2517
|
+
return [
|
|
2518
|
+
null,
|
|
2519
|
+
`apcore.yaml 'apcli:' must be a bool, object, or null; got ${got}`
|
|
2520
|
+
];
|
|
2521
|
+
}
|
|
2522
|
+
if (config !== null && config !== void 0 && typeof config === "object" && !Array.isArray(config)) {
|
|
2523
|
+
const mode = config["mode"];
|
|
2524
|
+
if (mode !== void 0 && mode !== null) {
|
|
2525
|
+
const validModes = ["all", "none", "include", "exclude"];
|
|
2526
|
+
if (typeof mode !== "string" || !validModes.includes(mode)) {
|
|
2527
|
+
return [null, `Invalid apcli mode: '${mode}'. Must be one of: all, none, include, exclude.`];
|
|
2781
2528
|
}
|
|
2782
|
-
}
|
|
2783
|
-
} catch (e) {
|
|
2784
|
-
emitErrorAndExit(e);
|
|
2529
|
+
}
|
|
2785
2530
|
}
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2531
|
+
return [_ApcliGroup.fromYaml(config, opts), null];
|
|
2532
|
+
}
|
|
2533
|
+
// -------------------------------------------------------------------------
|
|
2534
|
+
// Internal builder — shared by both factories
|
|
2535
|
+
// -------------------------------------------------------------------------
|
|
2536
|
+
static _build(config, opts, fromCliConfig) {
|
|
2537
|
+
const name = opts.name ?? DEFAULT_BUILTIN_GROUP_NAME;
|
|
2538
|
+
_validateBuiltinGroupName(name);
|
|
2539
|
+
if (config === true) {
|
|
2540
|
+
return new _ApcliGroup({
|
|
2541
|
+
mode: "all",
|
|
2542
|
+
include: [],
|
|
2543
|
+
exclude: [],
|
|
2544
|
+
disableEnv: false,
|
|
2545
|
+
registryInjected: opts.registryInjected,
|
|
2546
|
+
fromCliConfig,
|
|
2547
|
+
name
|
|
2803
2548
|
});
|
|
2804
|
-
} catch (e) {
|
|
2805
|
-
emitErrorAndExit(e);
|
|
2806
2549
|
}
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
module_id: moduleId,
|
|
2817
|
-
enabled: false,
|
|
2818
|
-
reason: opts.reason
|
|
2819
|
-
});
|
|
2820
|
-
emitResult(result, fmt, () => {
|
|
2821
|
-
process.stdout.write(`Module '${moduleId}' disabled.
|
|
2822
|
-
Reason: ${opts.reason}
|
|
2823
|
-
`);
|
|
2550
|
+
if (config === false) {
|
|
2551
|
+
return new _ApcliGroup({
|
|
2552
|
+
mode: "none",
|
|
2553
|
+
include: [],
|
|
2554
|
+
exclude: [],
|
|
2555
|
+
disableEnv: false,
|
|
2556
|
+
registryInjected: opts.registryInjected,
|
|
2557
|
+
fromCliConfig,
|
|
2558
|
+
name
|
|
2824
2559
|
});
|
|
2825
|
-
} catch (e) {
|
|
2826
|
-
emitErrorAndExit(e);
|
|
2827
2560
|
}
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
module_id: moduleId,
|
|
2838
|
-
reason: opts.reason
|
|
2839
|
-
});
|
|
2840
|
-
emitResult(result, fmt, () => {
|
|
2841
|
-
const prev = result.previous_version ?? "?";
|
|
2842
|
-
const newVer = result.new_version ?? "?";
|
|
2843
|
-
const dur = result.reload_duration_ms ?? "?";
|
|
2844
|
-
process.stdout.write(`Module '${moduleId}' reloaded.
|
|
2845
|
-
`);
|
|
2846
|
-
process.stdout.write(` Version: ${prev} -> ${newVer}
|
|
2847
|
-
`);
|
|
2848
|
-
process.stdout.write(` Duration: ${dur}ms
|
|
2849
|
-
`);
|
|
2561
|
+
if (config === void 0 || config === null) {
|
|
2562
|
+
return new _ApcliGroup({
|
|
2563
|
+
mode: "auto",
|
|
2564
|
+
include: [],
|
|
2565
|
+
exclude: [],
|
|
2566
|
+
disableEnv: false,
|
|
2567
|
+
registryInjected: opts.registryInjected,
|
|
2568
|
+
fromCliConfig,
|
|
2569
|
+
name
|
|
2850
2570
|
});
|
|
2851
|
-
} catch (e) {
|
|
2852
|
-
emitErrorAndExit(e);
|
|
2853
2571
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
}
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
const fmt = resolveFormat(opts.format);
|
|
2861
|
-
try {
|
|
2862
|
-
const result = await callSystemModule(executor, "system.config.get", { key });
|
|
2863
|
-
const value = result?.value ?? result;
|
|
2864
|
-
emitResult({ key, value }, fmt, () => {
|
|
2865
|
-
process.stdout.write(`${key} = ${JSON.stringify(value)}
|
|
2866
|
-
`);
|
|
2867
|
-
});
|
|
2868
|
-
} catch (e) {
|
|
2869
|
-
emitErrorAndExit(e);
|
|
2870
|
-
}
|
|
2871
|
-
});
|
|
2872
|
-
configGroup.addCommand(configGetCmd);
|
|
2873
|
-
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) => {
|
|
2874
|
-
const fmt = resolveFormat(opts.format);
|
|
2875
|
-
let parsedValue;
|
|
2876
|
-
try {
|
|
2877
|
-
parsedValue = JSON.parse(value);
|
|
2878
|
-
} catch {
|
|
2879
|
-
parsedValue = value;
|
|
2572
|
+
if (typeof config !== "object" || Array.isArray(config)) {
|
|
2573
|
+
process.stderr.write(
|
|
2574
|
+
`Error: apcli config must be a boolean or object; got ${Array.isArray(config) ? "array" : typeof config}.
|
|
2575
|
+
`
|
|
2576
|
+
);
|
|
2577
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2880
2578
|
}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
`
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
emitErrorAndExit(e);
|
|
2579
|
+
const cfg = config;
|
|
2580
|
+
let mode;
|
|
2581
|
+
if (cfg.mode === void 0 || cfg.mode === null) {
|
|
2582
|
+
mode = "auto";
|
|
2583
|
+
} else if (typeof cfg.mode !== "string") {
|
|
2584
|
+
process.stderr.write(
|
|
2585
|
+
`Error: apcli.mode must be a string; got ${typeof cfg.mode}. Expected one of all|none|include|exclude.
|
|
2586
|
+
`
|
|
2587
|
+
);
|
|
2588
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2589
|
+
} else if (!VALID_USER_MODES.has(cfg.mode)) {
|
|
2590
|
+
process.stderr.write(
|
|
2591
|
+
`Error: apcli.mode '${cfg.mode}' is invalid. Expected one of all|none|include|exclude.
|
|
2592
|
+
`
|
|
2593
|
+
);
|
|
2594
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2595
|
+
} else {
|
|
2596
|
+
mode = cfg.mode;
|
|
2900
2597
|
}
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
const current = executor.describePipeline();
|
|
2913
|
-
if (current && current.name === strategyName) {
|
|
2914
|
-
return { info: current, isCurrent: true };
|
|
2598
|
+
const include = _ApcliGroup._normalizeList(cfg.include, "include");
|
|
2599
|
+
const exclude = _ApcliGroup._normalizeList(cfg.exclude, "exclude");
|
|
2600
|
+
const rawDisableEnv = cfg.disableEnv !== void 0 ? cfg.disableEnv : cfg["disable_env"];
|
|
2601
|
+
let disableEnv = false;
|
|
2602
|
+
if (rawDisableEnv !== void 0) {
|
|
2603
|
+
if (typeof rawDisableEnv === "boolean") {
|
|
2604
|
+
disableEnv = rawDisableEnv;
|
|
2605
|
+
} else {
|
|
2606
|
+
warn(
|
|
2607
|
+
`apcli.disable_env must be boolean; got ${typeof rawDisableEnv}. Treating as false.`
|
|
2608
|
+
);
|
|
2915
2609
|
}
|
|
2916
|
-
} catch {
|
|
2917
2610
|
}
|
|
2611
|
+
return new _ApcliGroup({
|
|
2612
|
+
mode,
|
|
2613
|
+
include,
|
|
2614
|
+
exclude,
|
|
2615
|
+
disableEnv,
|
|
2616
|
+
registryInjected: opts.registryInjected,
|
|
2617
|
+
fromCliConfig,
|
|
2618
|
+
name
|
|
2619
|
+
});
|
|
2918
2620
|
}
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2621
|
+
/**
|
|
2622
|
+
* Normalize an include/exclude list. Non-array → warn and return [].
|
|
2623
|
+
*
|
|
2624
|
+
* Unknown but well-formed entries emit a WARNING (spec §7 error table,
|
|
2625
|
+
* T-APCLI-25) but are retained in the returned list for forward-compat —
|
|
2626
|
+
* if apcore-cli later adds a subcommand named `foo`, existing configs
|
|
2627
|
+
* continue to work without a config change. At runtime, unknown names
|
|
2628
|
+
* simply never match any registered subcommand.
|
|
2629
|
+
*/
|
|
2630
|
+
static _normalizeList(raw, label) {
|
|
2631
|
+
if (raw === void 0 || raw === null) return [];
|
|
2632
|
+
if (!Array.isArray(raw)) {
|
|
2633
|
+
warn(`apcli.${label} must be a list; got ${typeof raw}. Ignoring.`);
|
|
2634
|
+
return [];
|
|
2927
2635
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
"module_lookup",
|
|
2936
|
-
"acl_check",
|
|
2937
|
-
"approval_gate",
|
|
2938
|
-
"middleware_before",
|
|
2939
|
-
"input_validation",
|
|
2940
|
-
"execute",
|
|
2941
|
-
"output_validation",
|
|
2942
|
-
"middleware_after",
|
|
2943
|
-
"return_result"
|
|
2944
|
-
],
|
|
2945
|
-
internal: [
|
|
2946
|
-
"context_creation",
|
|
2947
|
-
"call_chain_guard",
|
|
2948
|
-
"module_lookup",
|
|
2949
|
-
"middleware_before",
|
|
2950
|
-
"input_validation",
|
|
2951
|
-
"execute",
|
|
2952
|
-
"output_validation",
|
|
2953
|
-
"middleware_after",
|
|
2954
|
-
"return_result"
|
|
2955
|
-
],
|
|
2956
|
-
testing: [
|
|
2957
|
-
"context_creation",
|
|
2958
|
-
"module_lookup",
|
|
2959
|
-
"middleware_before",
|
|
2960
|
-
"input_validation",
|
|
2961
|
-
"execute",
|
|
2962
|
-
"output_validation",
|
|
2963
|
-
"middleware_after",
|
|
2964
|
-
"return_result"
|
|
2965
|
-
],
|
|
2966
|
-
performance: [
|
|
2967
|
-
"context_creation",
|
|
2968
|
-
"call_chain_guard",
|
|
2969
|
-
"module_lookup",
|
|
2970
|
-
"acl_check",
|
|
2971
|
-
"approval_gate",
|
|
2972
|
-
"input_validation",
|
|
2973
|
-
"execute",
|
|
2974
|
-
"output_validation",
|
|
2975
|
-
"return_result"
|
|
2976
|
-
],
|
|
2977
|
-
minimal: [
|
|
2978
|
-
"context_creation",
|
|
2979
|
-
"module_lookup",
|
|
2980
|
-
"execute",
|
|
2981
|
-
"return_result"
|
|
2982
|
-
]
|
|
2983
|
-
};
|
|
2984
|
-
function registerPipelineCommand(cli, executor) {
|
|
2985
|
-
const pipelineCmd = new Command4("describe-pipeline").description("Show the execution pipeline steps for a strategy.").addOption(
|
|
2986
|
-
new Option3("--strategy <name>", "Strategy to describe (default: standard).").choices(["standard", "internal", "testing", "performance", "minimal"]).default("standard")
|
|
2987
|
-
).option("--format <format>", "Output format.").action((opts) => {
|
|
2988
|
-
const fmt = resolveFormat(opts.format);
|
|
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)`;
|
|
2993
|
-
if (fmt === "json" || !process.stdout.isTTY) {
|
|
2994
|
-
const payload = {
|
|
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
|
-
})
|
|
3008
|
-
};
|
|
3009
|
-
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
3010
|
-
} else {
|
|
3011
|
-
process.stdout.write(`${header}
|
|
3012
|
-
|
|
3013
|
-
`);
|
|
3014
|
-
process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
|
|
3015
|
-
`);
|
|
3016
|
-
process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
|
|
3017
|
-
`);
|
|
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}
|
|
3024
|
-
`);
|
|
2636
|
+
const out = [];
|
|
2637
|
+
for (const entry of raw) {
|
|
2638
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
2639
|
+
if (!APCLI_SUBCOMMAND_NAMES.has(entry)) {
|
|
2640
|
+
warn(
|
|
2641
|
+
`Unknown apcli subcommand '${entry}' in ${label} list \u2014 ignoring.`
|
|
2642
|
+
);
|
|
3025
2643
|
}
|
|
2644
|
+
out.push(entry);
|
|
2645
|
+
} else {
|
|
2646
|
+
warn(`apcli.${label} contains non-string entry; skipping.`);
|
|
3026
2647
|
}
|
|
3027
|
-
return;
|
|
3028
2648
|
}
|
|
3029
|
-
|
|
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
|
-
]);
|
|
3043
|
-
if (fmt === "json" || !process.stdout.isTTY) {
|
|
3044
|
-
const payload = {
|
|
3045
|
-
strategy: opts.strategy,
|
|
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
|
-
}))
|
|
3053
|
-
};
|
|
3054
|
-
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
3055
|
-
} else {
|
|
3056
|
-
process.stdout.write(`Pipeline: ${opts.strategy} (${steps.length} steps)
|
|
3057
|
-
|
|
3058
|
-
`);
|
|
3059
|
-
process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
|
|
3060
|
-
`);
|
|
3061
|
-
process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
|
|
3062
|
-
`);
|
|
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
|
|
3067
|
-
`);
|
|
3068
|
-
}
|
|
3069
|
-
}
|
|
3070
|
-
});
|
|
3071
|
-
cli.addCommand(pipelineCmd);
|
|
3072
|
-
}
|
|
3073
|
-
|
|
3074
|
-
// src/builtin-group.ts
|
|
3075
|
-
init_esm_shims();
|
|
3076
|
-
init_errors();
|
|
3077
|
-
init_logger();
|
|
3078
|
-
var ApcliGroupError = class extends Error {
|
|
3079
|
-
constructor(message) {
|
|
3080
|
-
super(message);
|
|
3081
|
-
this.name = "ApcliGroupError";
|
|
3082
|
-
}
|
|
3083
|
-
};
|
|
3084
|
-
var DEFAULT_BUILTIN_GROUP_NAME = "apcli";
|
|
3085
|
-
var RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set([DEFAULT_BUILTIN_GROUP_NAME]);
|
|
3086
|
-
var _effectiveReservedNames = RESERVED_GROUP_NAMES;
|
|
3087
|
-
function getReservedGroupNames() {
|
|
3088
|
-
return _effectiveReservedNames;
|
|
3089
|
-
}
|
|
3090
|
-
function setReservedGroupNames(names) {
|
|
3091
|
-
_effectiveReservedNames = names;
|
|
3092
|
-
}
|
|
3093
|
-
var _NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
|
|
3094
|
-
function _validateBuiltinGroupName(name) {
|
|
3095
|
-
if (!name || !_NAME_REGEX.test(name)) {
|
|
3096
|
-
throw new ApcliGroupError(
|
|
3097
|
-
`builtinGroupName ${JSON.stringify(name)} must match /^[a-z][a-z0-9_-]*$/ (non-empty, lowercase, alphanumeric + '_' / '-', leading letter).`
|
|
3098
|
-
);
|
|
3099
|
-
}
|
|
3100
|
-
}
|
|
3101
|
-
var VALID_USER_MODES = /* @__PURE__ */ new Set([
|
|
3102
|
-
"all",
|
|
3103
|
-
"none",
|
|
3104
|
-
"include",
|
|
3105
|
-
"exclude"
|
|
3106
|
-
]);
|
|
3107
|
-
var APCLI_SUBCOMMAND_NAMES = /* @__PURE__ */ new Set([
|
|
3108
|
-
"list",
|
|
3109
|
-
"describe",
|
|
3110
|
-
"exec",
|
|
3111
|
-
"validate",
|
|
3112
|
-
"init",
|
|
3113
|
-
"health",
|
|
3114
|
-
"usage",
|
|
3115
|
-
"enable",
|
|
3116
|
-
"disable",
|
|
3117
|
-
"reload",
|
|
3118
|
-
"config",
|
|
3119
|
-
"completion",
|
|
3120
|
-
"describe-pipeline"
|
|
3121
|
-
]);
|
|
3122
|
-
var ApcliGroup = class _ApcliGroup {
|
|
3123
|
-
_mode;
|
|
3124
|
-
_include;
|
|
3125
|
-
_exclude;
|
|
3126
|
-
_disableEnv;
|
|
3127
|
-
_registryInjected;
|
|
3128
|
-
_fromCliConfig;
|
|
3129
|
-
_name;
|
|
3130
|
-
constructor(init) {
|
|
3131
|
-
this._mode = init.mode;
|
|
3132
|
-
this._include = init.include;
|
|
3133
|
-
this._exclude = init.exclude;
|
|
3134
|
-
this._disableEnv = init.disableEnv;
|
|
3135
|
-
this._registryInjected = init.registryInjected;
|
|
3136
|
-
this._fromCliConfig = init.fromCliConfig;
|
|
3137
|
-
this._name = init.name;
|
|
2649
|
+
return out;
|
|
3138
2650
|
}
|
|
2651
|
+
// -------------------------------------------------------------------------
|
|
2652
|
+
// Public API
|
|
2653
|
+
// -------------------------------------------------------------------------
|
|
3139
2654
|
/**
|
|
3140
|
-
*
|
|
3141
|
-
*
|
|
3142
|
-
*
|
|
3143
|
-
*
|
|
2655
|
+
* Resolve effective visibility mode after applying tier precedence.
|
|
2656
|
+
*
|
|
2657
|
+
* Returns one of `"all" | "none" | "include" | "exclude"` — never `"auto"`.
|
|
2658
|
+
*
|
|
2659
|
+
* Tier order (spec §4.4):
|
|
2660
|
+
* 1. CliConfig non-auto wins outright.
|
|
2661
|
+
* 2. `APCORE_CLI_APCLI` env var (unless sealed by disableEnv).
|
|
2662
|
+
* 3. yaml non-auto.
|
|
2663
|
+
* 4. Auto-detect from registryInjected.
|
|
3144
2664
|
*/
|
|
3145
|
-
|
|
3146
|
-
|
|
2665
|
+
resolveVisibility() {
|
|
2666
|
+
if (this._fromCliConfig && this._mode !== "auto") {
|
|
2667
|
+
return this._mode;
|
|
2668
|
+
}
|
|
2669
|
+
if (!this._disableEnv) {
|
|
2670
|
+
const envMode = this._parseEnv(process.env.APCORE_CLI_APCLI);
|
|
2671
|
+
if (envMode !== null) {
|
|
2672
|
+
return envMode;
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
if (this._mode !== "auto") {
|
|
2676
|
+
return this._mode;
|
|
2677
|
+
}
|
|
2678
|
+
return this._registryInjected ? "none" : "all";
|
|
3147
2679
|
}
|
|
3148
2680
|
/**
|
|
3149
|
-
*
|
|
2681
|
+
* True iff `subcommand` passes the include/exclude filter.
|
|
3150
2682
|
*
|
|
3151
|
-
*
|
|
2683
|
+
* Callers MUST first check {@link resolveVisibility} — this method throws
|
|
2684
|
+
* under modes `"all"` or `"none"` (caller bug per spec §4.6).
|
|
3152
2685
|
*/
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
2686
|
+
isSubcommandIncluded(subcommand) {
|
|
2687
|
+
const mode = this.resolveVisibility();
|
|
2688
|
+
if (mode === "include") return this._include.includes(subcommand);
|
|
2689
|
+
if (mode === "exclude") return !this._exclude.includes(subcommand);
|
|
2690
|
+
throw new Error(
|
|
2691
|
+
`isSubcommandIncluded called under mode '${mode}'; caller should bypass.`
|
|
3159
2692
|
);
|
|
3160
2693
|
}
|
|
2694
|
+
/** True iff the `apcli` group itself should appear in root `--help`. */
|
|
2695
|
+
isGroupVisible() {
|
|
2696
|
+
return this.resolveVisibility() !== "none";
|
|
2697
|
+
}
|
|
2698
|
+
// -------------------------------------------------------------------------
|
|
2699
|
+
// Env parser (Tier 2) — co-located per spec §4.4
|
|
2700
|
+
// -------------------------------------------------------------------------
|
|
3161
2701
|
/**
|
|
3162
|
-
*
|
|
2702
|
+
* Parse APCORE_CLI_APCLI. Case-insensitive.
|
|
3163
2703
|
*
|
|
3164
|
-
*
|
|
2704
|
+
* - `show` / `1` / `true` → `"all"`
|
|
2705
|
+
* - `hide` / `0` / `false` → `"none"`
|
|
2706
|
+
* - Empty / unset → `null`
|
|
2707
|
+
* - Anything else → warn and return `null`
|
|
3165
2708
|
*/
|
|
3166
|
-
|
|
3167
|
-
if (
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
return _ApcliGroup._build(
|
|
3173
|
-
void 0,
|
|
3174
|
-
opts,
|
|
3175
|
-
/*fromCliConfig*/
|
|
3176
|
-
false
|
|
3177
|
-
);
|
|
2709
|
+
_parseEnv(raw) {
|
|
2710
|
+
if (raw === void 0 || raw === "") return null;
|
|
2711
|
+
const normalized = raw.trim().toLowerCase();
|
|
2712
|
+
if (normalized === "") return null;
|
|
2713
|
+
if (normalized === "show" || normalized === "1" || normalized === "true") {
|
|
2714
|
+
return "all";
|
|
3178
2715
|
}
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
false
|
|
2716
|
+
if (normalized === "hide" || normalized === "0" || normalized === "false") {
|
|
2717
|
+
return "none";
|
|
2718
|
+
}
|
|
2719
|
+
warn(
|
|
2720
|
+
`Unknown APCORE_CLI_APCLI value '${raw}', ignoring. Expected: show, hide, 1, 0, true, false.`
|
|
3184
2721
|
);
|
|
2722
|
+
return null;
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2725
|
+
|
|
2726
|
+
// src/cli.ts
|
|
2727
|
+
init_errors();
|
|
2728
|
+
function listAllDefinitions(registry) {
|
|
2729
|
+
const defs = [];
|
|
2730
|
+
for (const id of registry.list()) {
|
|
2731
|
+
const def = registry.getDefinition(id);
|
|
2732
|
+
if (def) defs.push(def);
|
|
2733
|
+
}
|
|
2734
|
+
return defs;
|
|
2735
|
+
}
|
|
2736
|
+
function assertNotReserved(kind, name, moduleId) {
|
|
2737
|
+
if (!getReservedGroupNames().has(name)) return;
|
|
2738
|
+
let msg;
|
|
2739
|
+
if (kind === "group") {
|
|
2740
|
+
msg = `Error: Module '${moduleId}': display.cli.group '${name}' is reserved. Use a different CLI alias or set display.cli.group to another value.
|
|
2741
|
+
`;
|
|
2742
|
+
} else if (kind === "auto-group") {
|
|
2743
|
+
msg = `Error: Module '${moduleId}': auto-group '${name}' is reserved. Rename the module id or set display.cli.group to another value.
|
|
2744
|
+
`;
|
|
2745
|
+
} else {
|
|
2746
|
+
msg = `Error: Module '${moduleId}': top-level CLI name '${name}' is reserved. Use a different CLI alias.
|
|
2747
|
+
`;
|
|
2748
|
+
}
|
|
2749
|
+
process.stderr.write(msg);
|
|
2750
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2751
|
+
}
|
|
2752
|
+
var LazyModuleGroup = class {
|
|
2753
|
+
registry;
|
|
2754
|
+
executor;
|
|
2755
|
+
helpTextMaxLength;
|
|
2756
|
+
commandCache = /* @__PURE__ */ new Map();
|
|
2757
|
+
/** alias -> canonical module_id (populated lazily) */
|
|
2758
|
+
aliasMap = /* @__PURE__ */ new Map();
|
|
2759
|
+
/** module_id -> descriptor cache (populated during alias map build) */
|
|
2760
|
+
descriptorCache = /* @__PURE__ */ new Map();
|
|
2761
|
+
aliasMapBuilt = false;
|
|
2762
|
+
constructor(registry, executor, helpTextMaxLength = 1e3) {
|
|
2763
|
+
this.registry = registry;
|
|
2764
|
+
this.executor = executor;
|
|
2765
|
+
this.helpTextMaxLength = helpTextMaxLength;
|
|
3185
2766
|
}
|
|
3186
2767
|
/**
|
|
3187
|
-
*
|
|
3188
|
-
* Returns `[instance, null]` on success or `[null, errorMessage]` on invalid input.
|
|
3189
|
-
* Use this in programmatic contexts where throwing/exiting is unwanted.
|
|
2768
|
+
* Build alias->module_id map from display overlay metadata.
|
|
3190
2769
|
*/
|
|
3191
|
-
|
|
3192
|
-
if (
|
|
3193
|
-
|
|
3194
|
-
return [
|
|
3195
|
-
null,
|
|
3196
|
-
`apcore.yaml 'apcli:' must be a bool, object, or null; got ${got}`
|
|
3197
|
-
];
|
|
2770
|
+
buildAliasMap() {
|
|
2771
|
+
if (this.aliasMapBuilt) {
|
|
2772
|
+
return;
|
|
3198
2773
|
}
|
|
3199
|
-
|
|
3200
|
-
const
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
2774
|
+
try {
|
|
2775
|
+
for (const descriptor of listAllDefinitions(this.registry)) {
|
|
2776
|
+
const moduleId = descriptor.moduleId;
|
|
2777
|
+
this.descriptorCache.set(moduleId, descriptor);
|
|
2778
|
+
const display = getDisplay(descriptor);
|
|
2779
|
+
const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
|
|
2780
|
+
const cliAlias = cliDisplay.alias;
|
|
2781
|
+
if (cliAlias && cliAlias !== moduleId) {
|
|
2782
|
+
this.aliasMap.set(cliAlias, moduleId);
|
|
3205
2783
|
}
|
|
3206
2784
|
}
|
|
2785
|
+
this.aliasMapBuilt = true;
|
|
2786
|
+
} catch {
|
|
2787
|
+
warn("Failed to build alias map from registry");
|
|
3207
2788
|
}
|
|
3208
|
-
return [_ApcliGroup.fromYaml(config, opts), null];
|
|
3209
2789
|
}
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
mode: "all",
|
|
3219
|
-
include: [],
|
|
3220
|
-
exclude: [],
|
|
3221
|
-
disableEnv: false,
|
|
3222
|
-
registryInjected: opts.registryInjected,
|
|
3223
|
-
fromCliConfig,
|
|
3224
|
-
name
|
|
3225
|
-
});
|
|
2790
|
+
/**
|
|
2791
|
+
* List all available command names from the Registry.
|
|
2792
|
+
*/
|
|
2793
|
+
listCommands() {
|
|
2794
|
+
this.buildAliasMap();
|
|
2795
|
+
const reverse = /* @__PURE__ */ new Map();
|
|
2796
|
+
for (const [alias, moduleId] of this.aliasMap) {
|
|
2797
|
+
reverse.set(moduleId, alias);
|
|
3226
2798
|
}
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
2799
|
+
const moduleIds = listAllDefinitions(this.registry).map((m) => m.moduleId);
|
|
2800
|
+
const names = moduleIds.map((mid) => reverse.get(mid) ?? mid);
|
|
2801
|
+
return [...new Set(names)].sort();
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Get or lazily build a Commander Command for the given module.
|
|
2805
|
+
*/
|
|
2806
|
+
getCommand(cmdName) {
|
|
2807
|
+
if (this.commandCache.has(cmdName)) {
|
|
2808
|
+
return this.commandCache.get(cmdName);
|
|
3237
2809
|
}
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
disableEnv: false,
|
|
3244
|
-
registryInjected: opts.registryInjected,
|
|
3245
|
-
fromCliConfig,
|
|
3246
|
-
name
|
|
3247
|
-
});
|
|
2810
|
+
this.buildAliasMap();
|
|
2811
|
+
const moduleId = this.aliasMap.get(cmdName) ?? cmdName;
|
|
2812
|
+
let moduleDef = this.descriptorCache.get(moduleId);
|
|
2813
|
+
if (!moduleDef) {
|
|
2814
|
+
moduleDef = this.registry.getDefinition(moduleId) ?? void 0;
|
|
3248
2815
|
}
|
|
3249
|
-
if (
|
|
3250
|
-
|
|
3251
|
-
`Error: apcli config must be a boolean or object; got ${Array.isArray(config) ? "array" : typeof config}.
|
|
3252
|
-
`
|
|
3253
|
-
);
|
|
3254
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
2816
|
+
if (!moduleDef) {
|
|
2817
|
+
return null;
|
|
3255
2818
|
}
|
|
3256
|
-
const
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
2819
|
+
const cmd = buildModuleCommand(moduleDef, this.executor, this.helpTextMaxLength, cmdName);
|
|
2820
|
+
this.commandCache.set(cmdName, cmd);
|
|
2821
|
+
return cmd;
|
|
2822
|
+
}
|
|
2823
|
+
};
|
|
2824
|
+
var LazyGroup = class {
|
|
2825
|
+
members;
|
|
2826
|
+
_executor;
|
|
2827
|
+
_helpTextMaxLength;
|
|
2828
|
+
_cmdCache = /* @__PURE__ */ new Map();
|
|
2829
|
+
command;
|
|
2830
|
+
constructor(members, executor, name, helpTextMaxLength = 1e3) {
|
|
2831
|
+
this.members = members;
|
|
2832
|
+
this._executor = executor;
|
|
2833
|
+
this._helpTextMaxLength = helpTextMaxLength;
|
|
2834
|
+
this.command = new Command2(name).description(`${name} commands`);
|
|
2835
|
+
for (const [cmdName, [, descriptor]] of this.members) {
|
|
2836
|
+
const cmd = buildModuleCommand(
|
|
2837
|
+
descriptor,
|
|
2838
|
+
this._executor,
|
|
2839
|
+
this._helpTextMaxLength,
|
|
2840
|
+
cmdName
|
|
3270
2841
|
);
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
mode = cfg.mode;
|
|
2842
|
+
this._cmdCache.set(cmdName, cmd);
|
|
2843
|
+
this.command.addCommand(cmd);
|
|
3274
2844
|
}
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
} else {
|
|
3283
|
-
warn(
|
|
3284
|
-
`apcli.disable_env must be boolean; got ${typeof rawDisableEnv}. Treating as false.`
|
|
3285
|
-
);
|
|
3286
|
-
}
|
|
2845
|
+
}
|
|
2846
|
+
listCommands() {
|
|
2847
|
+
return [...this.members.keys()].sort();
|
|
2848
|
+
}
|
|
2849
|
+
getCommand(cmdName) {
|
|
2850
|
+
if (this._cmdCache.has(cmdName)) {
|
|
2851
|
+
return this._cmdCache.get(cmdName);
|
|
3287
2852
|
}
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
2853
|
+
const entry = this.members.get(cmdName);
|
|
2854
|
+
if (!entry) {
|
|
2855
|
+
return null;
|
|
2856
|
+
}
|
|
2857
|
+
const [, descriptor] = entry;
|
|
2858
|
+
const cmd = buildModuleCommand(
|
|
2859
|
+
descriptor,
|
|
2860
|
+
this._executor,
|
|
2861
|
+
this._helpTextMaxLength,
|
|
2862
|
+
cmdName
|
|
2863
|
+
);
|
|
2864
|
+
this._cmdCache.set(cmdName, cmd);
|
|
2865
|
+
return cmd;
|
|
2866
|
+
}
|
|
2867
|
+
};
|
|
2868
|
+
var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
|
|
2869
|
+
/** groupName -> { cmdName -> [moduleId, descriptor] } */
|
|
2870
|
+
groupMap = /* @__PURE__ */ new Map();
|
|
2871
|
+
/** cmdName -> [moduleId, descriptor] for top-level (ungrouped) modules */
|
|
2872
|
+
topLevelModules = /* @__PURE__ */ new Map();
|
|
2873
|
+
/** Cached LazyGroup instances */
|
|
2874
|
+
groupCache = /* @__PURE__ */ new Map();
|
|
2875
|
+
groupMapBuilt = false;
|
|
2876
|
+
/** Exposure filter (FE-12) — controls which modules appear as CLI commands */
|
|
2877
|
+
exposureFilter;
|
|
2878
|
+
/** Effective group depth (CLAUDE.md v0.6.0): constructor arg > APCORE_CLI_GROUP_DEPTH env > 1. */
|
|
2879
|
+
groupDepth;
|
|
2880
|
+
constructor(registry, executor, helpTextMaxLength = 1e3, exposureFilter, groupDepth) {
|
|
2881
|
+
super(registry, executor, helpTextMaxLength);
|
|
2882
|
+
this.exposureFilter = exposureFilter ?? new ExposureFilter();
|
|
2883
|
+
this.groupDepth = _GroupedModuleGroup.resolveGroupDepth(groupDepth);
|
|
3297
2884
|
}
|
|
3298
2885
|
/**
|
|
3299
|
-
*
|
|
3300
|
-
*
|
|
3301
|
-
* Unknown but well-formed entries emit a WARNING (spec §7 error table,
|
|
3302
|
-
* T-APCLI-25) but are retained in the returned list for forward-compat —
|
|
3303
|
-
* if apcore-cli later adds a subcommand named `foo`, existing configs
|
|
3304
|
-
* continue to work without a config change. At runtime, unknown names
|
|
3305
|
-
* simply never match any registered subcommand.
|
|
2886
|
+
* Resolve group depth from constructor arg > APCORE_CLI_GROUP_DEPTH env > default 1.
|
|
2887
|
+
* Invalid env values (non-integer, non-positive) fall through to the default.
|
|
3306
2888
|
*/
|
|
3307
|
-
static
|
|
3308
|
-
if (
|
|
3309
|
-
|
|
3310
|
-
warn(`apcli.${label} must be a list; got ${typeof raw}. Ignoring.`);
|
|
3311
|
-
return [];
|
|
2889
|
+
static resolveGroupDepth(explicit) {
|
|
2890
|
+
if (explicit !== void 0 && Number.isFinite(explicit) && explicit > 0) {
|
|
2891
|
+
return Math.floor(explicit);
|
|
3312
2892
|
}
|
|
3313
|
-
const
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
`Unknown apcli subcommand '${entry}' in ${label} list \u2014 ignoring.`
|
|
3319
|
-
);
|
|
3320
|
-
}
|
|
3321
|
-
out.push(entry);
|
|
3322
|
-
} else {
|
|
3323
|
-
warn(`apcli.${label} contains non-string entry; skipping.`);
|
|
2893
|
+
const raw = process.env.APCORE_CLI_GROUP_DEPTH;
|
|
2894
|
+
if (raw !== void 0 && raw !== "") {
|
|
2895
|
+
const parsed = parseInt(raw, 10);
|
|
2896
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
2897
|
+
return parsed;
|
|
3324
2898
|
}
|
|
3325
2899
|
}
|
|
3326
|
-
return
|
|
2900
|
+
return 1;
|
|
3327
2901
|
}
|
|
3328
|
-
// -------------------------------------------------------------------------
|
|
3329
|
-
// Public API
|
|
3330
|
-
// -------------------------------------------------------------------------
|
|
3331
2902
|
/**
|
|
3332
|
-
*
|
|
3333
|
-
*
|
|
3334
|
-
* Returns one of `"all" | "none" | "include" | "exclude"` — never `"auto"`.
|
|
2903
|
+
* Determine (groupName | null, commandName) for a module from its display overlay.
|
|
3335
2904
|
*
|
|
3336
|
-
*
|
|
3337
|
-
*
|
|
3338
|
-
*
|
|
3339
|
-
*
|
|
3340
|
-
* 4. Auto-detect from registryInjected.
|
|
2905
|
+
* @param groupDepth Number of dotted segments to consume as the group prefix.
|
|
2906
|
+
* Defaults to 1 (e.g., "math.add" → group="math", cmd="add").
|
|
2907
|
+
* Set to 2 for multi-level grouping (e.g., "math.trig.sin" →
|
|
2908
|
+
* group="math.trig", cmd="sin").
|
|
3341
2909
|
*/
|
|
3342
|
-
|
|
3343
|
-
if (
|
|
3344
|
-
|
|
2910
|
+
static resolveGroup(moduleId, descriptor, groupDepth = 1) {
|
|
2911
|
+
if (!moduleId) {
|
|
2912
|
+
warn("Empty module_id encountered in resolveGroup");
|
|
2913
|
+
return [null, ""];
|
|
3345
2914
|
}
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
2915
|
+
const display = getDisplay(descriptor);
|
|
2916
|
+
const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
|
|
2917
|
+
const explicitGroup = cliDisplay.group;
|
|
2918
|
+
if (typeof explicitGroup === "string" && explicitGroup !== "") {
|
|
2919
|
+
return [explicitGroup, cliDisplay.alias ?? moduleId];
|
|
3351
2920
|
}
|
|
3352
|
-
if (
|
|
3353
|
-
return
|
|
2921
|
+
if (explicitGroup === "") {
|
|
2922
|
+
return [null, cliDisplay.alias ?? moduleId];
|
|
3354
2923
|
}
|
|
3355
|
-
|
|
2924
|
+
const cliName = cliDisplay.alias ?? moduleId;
|
|
2925
|
+
if (cliName.includes(".")) {
|
|
2926
|
+
const parts = cliName.split(".");
|
|
2927
|
+
const depth = Math.max(1, Math.min(groupDepth, parts.length - 1));
|
|
2928
|
+
const group = parts.slice(0, depth).join(".");
|
|
2929
|
+
const cmd = parts.slice(depth).join(".");
|
|
2930
|
+
return [group, cmd];
|
|
2931
|
+
}
|
|
2932
|
+
return [null, cliName];
|
|
3356
2933
|
}
|
|
3357
2934
|
/**
|
|
3358
|
-
*
|
|
2935
|
+
* Build the group map from registry modules.
|
|
3359
2936
|
*
|
|
3360
|
-
*
|
|
3361
|
-
*
|
|
2937
|
+
* FE-13: hard-fails with exit 2 when a module resolves to the reserved
|
|
2938
|
+
* `apcli` namespace in any of three ways — explicit `display.cli.group`,
|
|
2939
|
+
* auto-grouped dotted prefix, or top-level alias/id. See spec §4.10.
|
|
3362
2940
|
*/
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
if (mode === "exclude") return !this._exclude.includes(subcommand);
|
|
3367
|
-
throw new Error(
|
|
3368
|
-
`isSubcommandIncluded called under mode '${mode}'; caller should bypass.`
|
|
3369
|
-
);
|
|
3370
|
-
}
|
|
3371
|
-
/** True iff the `apcli` group itself should appear in root `--help`. */
|
|
3372
|
-
isGroupVisible() {
|
|
3373
|
-
return this.resolveVisibility() !== "none";
|
|
3374
|
-
}
|
|
3375
|
-
// -------------------------------------------------------------------------
|
|
3376
|
-
// Env parser (Tier 2) — co-located per spec §4.4
|
|
3377
|
-
// -------------------------------------------------------------------------
|
|
3378
|
-
/**
|
|
3379
|
-
* Parse APCORE_CLI_APCLI. Case-insensitive.
|
|
3380
|
-
*
|
|
3381
|
-
* - `show` / `1` / `true` → `"all"`
|
|
3382
|
-
* - `hide` / `0` / `false` → `"none"`
|
|
3383
|
-
* - Empty / unset → `null`
|
|
3384
|
-
* - Anything else → warn and return `null`
|
|
3385
|
-
*/
|
|
3386
|
-
_parseEnv(raw) {
|
|
3387
|
-
if (raw === void 0 || raw === "") return null;
|
|
3388
|
-
const normalized = raw.trim().toLowerCase();
|
|
3389
|
-
if (normalized === "") return null;
|
|
3390
|
-
if (normalized === "show" || normalized === "1" || normalized === "true") {
|
|
3391
|
-
return "all";
|
|
3392
|
-
}
|
|
3393
|
-
if (normalized === "hide" || normalized === "0" || normalized === "false") {
|
|
3394
|
-
return "none";
|
|
3395
|
-
}
|
|
3396
|
-
warn(
|
|
3397
|
-
`Unknown APCORE_CLI_APCLI value '${raw}', ignoring. Expected: show, hide, 1, 0, true, false.`
|
|
3398
|
-
);
|
|
3399
|
-
return null;
|
|
3400
|
-
}
|
|
3401
|
-
};
|
|
3402
|
-
|
|
3403
|
-
// src/exposure.ts
|
|
3404
|
-
init_esm_shims();
|
|
3405
|
-
init_logger();
|
|
3406
|
-
function escapeRegex(str) {
|
|
3407
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3408
|
-
}
|
|
3409
|
-
function compilePattern(pattern) {
|
|
3410
|
-
const sentinel = "\0GLOB\0";
|
|
3411
|
-
const escaped = pattern.replaceAll("**", sentinel);
|
|
3412
|
-
const parts = escaped.split("*");
|
|
3413
|
-
const regexParts = parts.map((p) => {
|
|
3414
|
-
const restored = p.replaceAll(sentinel, "**");
|
|
3415
|
-
return escapeRegex(restored);
|
|
3416
|
-
});
|
|
3417
|
-
let regex = regexParts.join("[^.]*");
|
|
3418
|
-
regex = regex.replaceAll("\\*\\*", ".+");
|
|
3419
|
-
return new RegExp(`^${regex}$`);
|
|
3420
|
-
}
|
|
3421
|
-
var ExposureFilter = class _ExposureFilter {
|
|
3422
|
-
static VALID_MODES = ["all", "include", "exclude", "none"];
|
|
3423
|
-
_mode;
|
|
3424
|
-
_compiledInclude;
|
|
3425
|
-
_compiledExclude;
|
|
3426
|
-
constructor(mode = "all", include, exclude) {
|
|
3427
|
-
if (!_ExposureFilter.VALID_MODES.includes(mode)) {
|
|
3428
|
-
process.stderr.write(
|
|
3429
|
-
`Warning: Unknown ExposureFilter mode '${mode}' \u2014 defaulting to 'none'. Valid modes: ${_ExposureFilter.VALID_MODES.join(", ")}.
|
|
3430
|
-
`
|
|
3431
|
-
);
|
|
3432
|
-
mode = "none";
|
|
3433
|
-
}
|
|
3434
|
-
this._mode = mode;
|
|
3435
|
-
const dedup = (arr) => [...new Set(arr)];
|
|
3436
|
-
this._compiledInclude = dedup(include ?? []).map(compilePattern);
|
|
3437
|
-
this._compiledExclude = dedup(exclude ?? []).map(compilePattern);
|
|
3438
|
-
}
|
|
3439
|
-
/** Return true if the module should be exposed as a CLI command. */
|
|
3440
|
-
isExposed(moduleId) {
|
|
3441
|
-
if (this._mode === "all") return true;
|
|
3442
|
-
if (this._mode === "include") {
|
|
3443
|
-
return this._compiledInclude.some((rx) => rx.test(moduleId));
|
|
3444
|
-
}
|
|
3445
|
-
if (this._mode === "exclude") {
|
|
3446
|
-
return !this._compiledExclude.some((rx) => rx.test(moduleId));
|
|
2941
|
+
buildGroupMap() {
|
|
2942
|
+
if (this.groupMapBuilt) {
|
|
2943
|
+
return;
|
|
3447
2944
|
}
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
(this.isExposed(
|
|
2945
|
+
this.buildAliasMap();
|
|
2946
|
+
for (const descriptor of listAllDefinitions(this.registry)) {
|
|
2947
|
+
const moduleId = descriptor.moduleId;
|
|
2948
|
+
const cached = this.descriptorCache.get(moduleId);
|
|
2949
|
+
if (!cached) {
|
|
2950
|
+
continue;
|
|
2951
|
+
}
|
|
2952
|
+
if (!this.exposureFilter.isExposed(moduleId)) {
|
|
2953
|
+
continue;
|
|
2954
|
+
}
|
|
2955
|
+
const display = getDisplay(cached);
|
|
2956
|
+
const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
|
|
2957
|
+
const explicitGroup = typeof cliDisplay.group === "string" && cliDisplay.group !== "" ? cliDisplay.group : void 0;
|
|
2958
|
+
if (explicitGroup !== void 0) {
|
|
2959
|
+
assertNotReserved("group", explicitGroup, moduleId);
|
|
2960
|
+
}
|
|
2961
|
+
const [group, cmd] = _GroupedModuleGroup.resolveGroup(moduleId, cached, this.groupDepth);
|
|
2962
|
+
if (group !== null && explicitGroup === void 0) {
|
|
2963
|
+
assertNotReserved("auto-group", group, moduleId);
|
|
2964
|
+
}
|
|
2965
|
+
if (group === null) {
|
|
2966
|
+
assertNotReserved("top-level", cmd, moduleId);
|
|
2967
|
+
this.topLevelModules.set(cmd, [moduleId, cached]);
|
|
2968
|
+
} else if (!/^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)*$/.test(group)) {
|
|
2969
|
+
warn(
|
|
2970
|
+
`Module '${moduleId}': group name '${group}' is not shell-safe \u2014 treating as top-level.`
|
|
2971
|
+
);
|
|
2972
|
+
this.topLevelModules.set(cmd, [moduleId, cached]);
|
|
2973
|
+
} else {
|
|
2974
|
+
if (!this.groupMap.has(group)) {
|
|
2975
|
+
this.groupMap.set(group, /* @__PURE__ */ new Map());
|
|
2976
|
+
}
|
|
2977
|
+
this.groupMap.get(group).set(cmd, [moduleId, cached]);
|
|
2978
|
+
}
|
|
3456
2979
|
}
|
|
3457
|
-
|
|
2980
|
+
this.groupMapBuilt = true;
|
|
3458
2981
|
}
|
|
3459
2982
|
/**
|
|
3460
|
-
*
|
|
2983
|
+
* List all available command names: group names + top-level module names.
|
|
3461
2984
|
*
|
|
3462
|
-
*
|
|
2985
|
+
* FE-13: the built-in subcommand list is no longer folded in here — those
|
|
2986
|
+
* commands live under the `apcli` prefix and are registered directly by
|
|
2987
|
+
* `createCli`.
|
|
3463
2988
|
*/
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
2989
|
+
listCommands() {
|
|
2990
|
+
this.buildGroupMap();
|
|
2991
|
+
const reserved = getReservedGroupNames();
|
|
2992
|
+
const groupNames = [...this.groupMap.keys()].filter(
|
|
2993
|
+
(g) => !reserved.has(g)
|
|
2994
|
+
);
|
|
2995
|
+
const topNames = [...this.topLevelModules.keys()];
|
|
2996
|
+
return [.../* @__PURE__ */ new Set([...groupNames, ...topNames])].sort();
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Get a command by name: check builtins -> group cache -> group map -> top-level modules.
|
|
3000
|
+
*/
|
|
3001
|
+
getCommand(cmdName) {
|
|
3002
|
+
this.buildGroupMap();
|
|
3003
|
+
if (this.groupCache.has(cmdName)) {
|
|
3004
|
+
return this.groupCache.get(cmdName).command;
|
|
3469
3005
|
}
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3006
|
+
if (this.groupMap.has(cmdName)) {
|
|
3007
|
+
const lazyGrp = new LazyGroup(
|
|
3008
|
+
this.groupMap.get(cmdName),
|
|
3009
|
+
this.executor,
|
|
3010
|
+
cmdName,
|
|
3011
|
+
this.helpTextMaxLength
|
|
3475
3012
|
);
|
|
3013
|
+
this.groupCache.set(cmdName, lazyGrp);
|
|
3014
|
+
return lazyGrp.command;
|
|
3476
3015
|
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
include = [];
|
|
3481
|
-
}
|
|
3482
|
-
let exclude = exposeObj.exclude ?? [];
|
|
3483
|
-
if (!Array.isArray(exclude)) {
|
|
3484
|
-
warn("Invalid 'expose.exclude' (expected list), ignoring.");
|
|
3485
|
-
exclude = [];
|
|
3486
|
-
}
|
|
3487
|
-
const filterList = (arr, label) => {
|
|
3488
|
-
const result = [];
|
|
3489
|
-
for (const p of arr) {
|
|
3490
|
-
if (!p) {
|
|
3491
|
-
warn(`Empty pattern in expose.${label}, skipping.`);
|
|
3492
|
-
} else {
|
|
3493
|
-
result.push(String(p));
|
|
3494
|
-
}
|
|
3016
|
+
if (this.topLevelModules.has(cmdName)) {
|
|
3017
|
+
if (this.commandCache.has(cmdName)) {
|
|
3018
|
+
return this.commandCache.get(cmdName);
|
|
3495
3019
|
}
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3020
|
+
const [, descriptor] = this.topLevelModules.get(cmdName);
|
|
3021
|
+
const cmd = buildModuleCommand(
|
|
3022
|
+
descriptor,
|
|
3023
|
+
this.executor,
|
|
3024
|
+
this.helpTextMaxLength,
|
|
3025
|
+
cmdName
|
|
3026
|
+
);
|
|
3027
|
+
this.commandCache.set(cmdName, cmd);
|
|
3028
|
+
return cmd;
|
|
3029
|
+
}
|
|
3030
|
+
return null;
|
|
3031
|
+
}
|
|
3032
|
+
/** Expose groupMap for testing. */
|
|
3033
|
+
getGroupMap() {
|
|
3034
|
+
return this.groupMap;
|
|
3035
|
+
}
|
|
3036
|
+
/** Expose topLevelModules for testing. */
|
|
3037
|
+
getTopLevelModules() {
|
|
3038
|
+
return this.topLevelModules;
|
|
3039
|
+
}
|
|
3040
|
+
/** Expose groupMapBuilt for testing. */
|
|
3041
|
+
isGroupMapBuilt() {
|
|
3042
|
+
return this.groupMapBuilt;
|
|
3503
3043
|
}
|
|
3504
3044
|
};
|
|
3505
3045
|
|
|
3506
|
-
// src/
|
|
3046
|
+
// src/discovery.ts
|
|
3047
|
+
init_errors();
|
|
3507
3048
|
init_audit();
|
|
3508
3049
|
|
|
3509
|
-
// src/
|
|
3050
|
+
// src/system-usage.ts
|
|
3510
3051
|
init_esm_shims();
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
}
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
function
|
|
3526
|
-
|
|
3527
|
-
const
|
|
3528
|
-
|
|
3529
|
-
|
|
3052
|
+
import * as fs4 from "fs";
|
|
3053
|
+
import * as os2 from "os";
|
|
3054
|
+
import * as path4 from "path";
|
|
3055
|
+
var PERIOD_TO_MS = {
|
|
3056
|
+
"1h": 60 * 60 * 1e3,
|
|
3057
|
+
"24h": 24 * 60 * 60 * 1e3,
|
|
3058
|
+
"7d": 7 * 24 * 60 * 60 * 1e3,
|
|
3059
|
+
"30d": 30 * 24 * 60 * 60 * 1e3
|
|
3060
|
+
};
|
|
3061
|
+
var DEFAULT_AUDIT_PATH = path4.join(
|
|
3062
|
+
os2.homedir(),
|
|
3063
|
+
".apcore-cli",
|
|
3064
|
+
"audit.jsonl"
|
|
3065
|
+
);
|
|
3066
|
+
function computeSummary(options = {}) {
|
|
3067
|
+
const auditPath = options.auditPath ?? DEFAULT_AUDIT_PATH;
|
|
3068
|
+
const period = options.period ?? "24h";
|
|
3069
|
+
const cutoff = (options.now ?? /* @__PURE__ */ new Date()).getTime() - PERIOD_TO_MS[period];
|
|
3070
|
+
if (!fs4.existsSync(auditPath)) {
|
|
3071
|
+
return /* @__PURE__ */ new Map();
|
|
3530
3072
|
}
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
const rest = [];
|
|
3537
|
-
for (const o of opts) {
|
|
3538
|
-
if (o.long === "--help") helpOpts.push(o);
|
|
3539
|
-
else if (o.long === "--version") versionOpts.push(o);
|
|
3540
|
-
else rest.push(o);
|
|
3073
|
+
let raw;
|
|
3074
|
+
try {
|
|
3075
|
+
raw = fs4.readFileSync(auditPath, "utf-8");
|
|
3076
|
+
} catch {
|
|
3077
|
+
return /* @__PURE__ */ new Map();
|
|
3541
3078
|
}
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
const
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3079
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3080
|
+
const errors = /* @__PURE__ */ new Map();
|
|
3081
|
+
const latencySum = /* @__PURE__ */ new Map();
|
|
3082
|
+
for (const line of raw.split("\n")) {
|
|
3083
|
+
const trimmed = line.trim();
|
|
3084
|
+
if (!trimmed) continue;
|
|
3085
|
+
let entry;
|
|
3086
|
+
try {
|
|
3087
|
+
entry = JSON.parse(trimmed);
|
|
3088
|
+
} catch {
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
const ts = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
|
|
3092
|
+
if (Number.isNaN(ts) || ts < cutoff) continue;
|
|
3093
|
+
const moduleId = typeof entry.module_id === "string" ? entry.module_id : null;
|
|
3094
|
+
if (!moduleId) continue;
|
|
3095
|
+
counts.set(moduleId, (counts.get(moduleId) ?? 0) + 1);
|
|
3096
|
+
if (entry.status === "error") {
|
|
3097
|
+
errors.set(moduleId, (errors.get(moduleId) ?? 0) + 1);
|
|
3098
|
+
}
|
|
3099
|
+
const duration = entry.duration_ms;
|
|
3100
|
+
if (typeof duration === "number") {
|
|
3101
|
+
latencySum.set(moduleId, (latencySum.get(moduleId) ?? 0) + duration);
|
|
3102
|
+
}
|
|
3560
3103
|
}
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
lines.push(` ${terms[i].padEnd(w)} ${sub.description()}`);
|
|
3104
|
+
const out = /* @__PURE__ */ new Map();
|
|
3105
|
+
for (const [id, calls] of counts) {
|
|
3106
|
+
out.set(id, {
|
|
3107
|
+
module_id: id,
|
|
3108
|
+
calls,
|
|
3109
|
+
errors: errors.get(id) ?? 0,
|
|
3110
|
+
latency_ms: calls > 0 ? (latencySum.get(id) ?? 0) / calls : 0
|
|
3569
3111
|
});
|
|
3570
|
-
sections.push(lines.join("\n"));
|
|
3571
3112
|
}
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3113
|
+
return out;
|
|
3114
|
+
}
|
|
3115
|
+
function sortModulesByUsage(modules, field, options = {}) {
|
|
3116
|
+
const reverse = options.reverse ?? true;
|
|
3117
|
+
const summary = computeSummary({
|
|
3118
|
+
auditPath: options.auditPath,
|
|
3119
|
+
period: options.period
|
|
3120
|
+
});
|
|
3121
|
+
const idOf = (m) => m.moduleId ?? m.id ?? m.module_id ?? "";
|
|
3122
|
+
if (summary.size === 0) {
|
|
3123
|
+
modules.sort((a, b) => idOf(a).localeCompare(idOf(b)));
|
|
3124
|
+
if (reverse) modules.reverse();
|
|
3125
|
+
return { used: false };
|
|
3580
3126
|
}
|
|
3581
|
-
const
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3127
|
+
const key = (m) => {
|
|
3128
|
+
const id = idOf(m);
|
|
3129
|
+
const s = summary.get(id);
|
|
3130
|
+
if (!s) return 0;
|
|
3131
|
+
return field === "latency" ? s.latency_ms : field === "calls" ? s.calls : s.errors;
|
|
3132
|
+
};
|
|
3133
|
+
modules.sort((a, b) => {
|
|
3134
|
+
const diff = key(a) - key(b);
|
|
3135
|
+
if (diff !== 0) return reverse ? -diff : diff;
|
|
3136
|
+
return idOf(a).localeCompare(idOf(b));
|
|
3137
|
+
});
|
|
3138
|
+
return { used: true };
|
|
3586
3139
|
}
|
|
3587
3140
|
|
|
3588
|
-
// src/
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
var MAX_MODULE_ID_LENGTH = 192;
|
|
3593
|
-
function validateModuleId(moduleId) {
|
|
3594
|
-
if (moduleId.length > MAX_MODULE_ID_LENGTH) {
|
|
3595
|
-
process.stderr.write(
|
|
3596
|
-
`Error: Invalid module ID format: '${moduleId}'. Maximum length is ${MAX_MODULE_ID_LENGTH} characters.
|
|
3597
|
-
`
|
|
3598
|
-
);
|
|
3599
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3600
|
-
}
|
|
3601
|
-
if (!MODULE_ID_PATTERN.test(moduleId)) {
|
|
3141
|
+
// src/discovery.ts
|
|
3142
|
+
var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
3143
|
+
function validateTag(tag) {
|
|
3144
|
+
if (!TAG_PATTERN.test(tag)) {
|
|
3602
3145
|
process.stderr.write(
|
|
3603
|
-
`Error: Invalid
|
|
3146
|
+
`Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
|
|
3604
3147
|
`
|
|
3605
3148
|
);
|
|
3606
3149
|
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3607
3150
|
}
|
|
3608
3151
|
}
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
|
|
3612
|
-
var verboseHelp = false;
|
|
3613
|
-
function setAllOptionsHelp(allOptions) {
|
|
3614
|
-
verboseHelp = allOptions;
|
|
3615
|
-
}
|
|
3616
|
-
function setVerboseHelp(verbose) {
|
|
3617
|
-
setAllOptionsHelp(verbose);
|
|
3152
|
+
function collectTag(value, previous) {
|
|
3153
|
+
return previous.concat([value]);
|
|
3618
3154
|
}
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
docsUrl = url;
|
|
3155
|
+
function collectAnnotation(value, previous) {
|
|
3156
|
+
return previous.concat([value]);
|
|
3622
3157
|
}
|
|
3623
|
-
function
|
|
3624
|
-
|
|
3158
|
+
function getAnnotationFlag(moduleDef, flag) {
|
|
3159
|
+
const annotations = moduleDef.annotations;
|
|
3160
|
+
if (!annotations || typeof annotations !== "object") return false;
|
|
3161
|
+
const ann = annotations;
|
|
3162
|
+
const map = {
|
|
3163
|
+
"destructive": "destructive",
|
|
3164
|
+
"requires-approval": "requires_approval",
|
|
3165
|
+
"readonly": "readonly",
|
|
3166
|
+
"streaming": "streaming",
|
|
3167
|
+
"cacheable": "cacheable",
|
|
3168
|
+
"idempotent": "idempotent",
|
|
3169
|
+
"paginated": "paginated"
|
|
3170
|
+
};
|
|
3171
|
+
const attr = map[flag] ?? flag;
|
|
3172
|
+
return ann[attr] === true;
|
|
3625
3173
|
}
|
|
3626
|
-
function
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3174
|
+
function registerListCommand(apcliGroup, registry, exposureFilter) {
|
|
3175
|
+
const listCmd = new Command3("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(
|
|
3176
|
+
new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
|
|
3177
|
+
).option("-s, --search <query>", "Filter by substring match on ID and description.").addOption(
|
|
3178
|
+
new Option2("--status <status>", "Filter by module status.").choices(["enabled", "disabled", "all"]).default("enabled")
|
|
3179
|
+
).option("-a, --annotation <flag>", "Filter by annotation flag (AND logic). Repeatable.", collectAnnotation, []).addOption(
|
|
3180
|
+
new Option2("--sort <field>", "Sort order.").choices(["id", "calls", "errors", "latency"]).default("id")
|
|
3181
|
+
).option("--reverse", "Reverse sort order.", false).option("--deprecated", "Include deprecated modules.", false).option("--deps", "Show dependency count column.", false).addOption(
|
|
3182
|
+
new Option2("--exposure <mode>", "Filter by exposure status.").choices(["exposed", "hidden", "all"]).default("exposed")
|
|
3183
|
+
).action((opts) => {
|
|
3184
|
+
for (const t of opts.tag) {
|
|
3185
|
+
validateTag(t);
|
|
3634
3186
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
}
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3187
|
+
let modules = [];
|
|
3188
|
+
for (const m of listAllDefinitions(registry)) {
|
|
3189
|
+
modules.push(m);
|
|
3190
|
+
}
|
|
3191
|
+
if (opts.tag.length > 0) {
|
|
3192
|
+
const filterTags = new Set(opts.tag);
|
|
3193
|
+
modules = modules.filter((m) => {
|
|
3194
|
+
const mTags = m.tags ?? [];
|
|
3195
|
+
return [...filterTags].every((t) => mTags.includes(t));
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
if (opts.search) {
|
|
3199
|
+
const query = opts.search.toLowerCase();
|
|
3200
|
+
modules = modules.filter(
|
|
3201
|
+
(m) => (m.moduleId ?? "").toLowerCase().includes(query) || (m.description ?? "").toLowerCase().includes(query)
|
|
3202
|
+
);
|
|
3203
|
+
}
|
|
3204
|
+
if (opts.status === "enabled") {
|
|
3205
|
+
modules = modules.filter((m) => {
|
|
3206
|
+
const enabled = m.enabled;
|
|
3207
|
+
return enabled !== false;
|
|
3208
|
+
});
|
|
3209
|
+
} else if (opts.status === "disabled") {
|
|
3210
|
+
modules = modules.filter((m) => {
|
|
3211
|
+
const enabled = m.enabled;
|
|
3212
|
+
return enabled === false;
|
|
3213
|
+
});
|
|
3214
|
+
}
|
|
3215
|
+
if (!opts.deprecated) {
|
|
3216
|
+
modules = modules.filter((m) => {
|
|
3217
|
+
const deprecated = m.deprecated;
|
|
3218
|
+
return deprecated !== true;
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
if (opts.annotation.length > 0) {
|
|
3222
|
+
for (const annFlag of opts.annotation) {
|
|
3223
|
+
modules = modules.filter((m) => getAnnotationFlag(m, annFlag));
|
|
3664
3224
|
}
|
|
3665
3225
|
}
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
if (!expectedType) continue;
|
|
3674
|
-
const actualType = typeof val;
|
|
3675
|
-
if (expectedType === "string" && actualType !== "string") {
|
|
3676
|
-
return `'${field}' must be a string, got ${actualType}`;
|
|
3226
|
+
if (opts.sort === "calls" || opts.sort === "errors" || opts.sort === "latency") {
|
|
3227
|
+
const { used } = sortModulesByUsage(modules, opts.sort, { reverse: !opts.reverse });
|
|
3228
|
+
if (!used) {
|
|
3229
|
+
process.stderr.write(
|
|
3230
|
+
`note: no usage data available for --sort ${opts.sort}; sorted by id. Run some modules first to populate ~/.apcore-cli/audit.jsonl.
|
|
3231
|
+
`
|
|
3232
|
+
);
|
|
3677
3233
|
}
|
|
3678
|
-
|
|
3679
|
-
|
|
3234
|
+
} else {
|
|
3235
|
+
modules.sort((a, b) => (a.moduleId ?? "").localeCompare(b.moduleId ?? ""));
|
|
3236
|
+
if (opts.reverse) {
|
|
3237
|
+
modules.reverse();
|
|
3680
3238
|
}
|
|
3681
|
-
|
|
3682
|
-
|
|
3239
|
+
}
|
|
3240
|
+
let showExposureCol = false;
|
|
3241
|
+
if (exposureFilter && opts.exposure !== "all") {
|
|
3242
|
+
if (opts.exposure === "exposed") {
|
|
3243
|
+
modules = modules.filter((m) => exposureFilter.isExposed(m.moduleId ?? ""));
|
|
3244
|
+
} else if (opts.exposure === "hidden") {
|
|
3245
|
+
modules = modules.filter((m) => !exposureFilter.isExposed(m.moduleId ?? ""));
|
|
3683
3246
|
}
|
|
3684
3247
|
}
|
|
3685
|
-
|
|
3686
|
-
|
|
3248
|
+
if (opts.exposure === "all" && exposureFilter) {
|
|
3249
|
+
showExposureCol = true;
|
|
3250
|
+
}
|
|
3251
|
+
const fmt = resolveFormat(opts.format);
|
|
3252
|
+
const filterTagsArg = opts.tag.length > 0 ? opts.tag : void 0;
|
|
3253
|
+
void formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
|
|
3254
|
+
});
|
|
3255
|
+
apcliGroup.addCommand(listCmd);
|
|
3687
3256
|
}
|
|
3688
|
-
function
|
|
3689
|
-
const
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
if (val !== void 0 && val !== null) {
|
|
3701
|
-
payload[field] = val;
|
|
3257
|
+
function registerDescribeCommand(apcliGroup, registry) {
|
|
3258
|
+
const describeCmd = new Command3("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").addOption(
|
|
3259
|
+
new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
|
|
3260
|
+
).action((moduleId, opts) => {
|
|
3261
|
+
validateModuleId(moduleId);
|
|
3262
|
+
const moduleDef = registry.getDefinition(moduleId);
|
|
3263
|
+
if (!moduleDef) {
|
|
3264
|
+
process.stderr.write(
|
|
3265
|
+
`Error: Module '${moduleId}' not found.
|
|
3266
|
+
`
|
|
3267
|
+
);
|
|
3268
|
+
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
3702
3269
|
}
|
|
3703
|
-
|
|
3704
|
-
|
|
3270
|
+
const fmt = resolveFormat(opts.format);
|
|
3271
|
+
void formatModuleDetail(moduleDef, fmt);
|
|
3272
|
+
});
|
|
3273
|
+
apcliGroup.addCommand(describeCmd);
|
|
3705
3274
|
}
|
|
3706
|
-
function
|
|
3707
|
-
const
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3275
|
+
function registerExecCommand(apcliGroup, registry, executor) {
|
|
3276
|
+
const execCmd = new Command3("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(
|
|
3277
|
+
"--input <json>",
|
|
3278
|
+
"JSON object passed as input to the module. Use '-' to read JSON from stdin."
|
|
3279
|
+
).option("-y, --yes", "Auto-approve if the module declares requires_approval.", false).option(
|
|
3280
|
+
"--approval-timeout <seconds>",
|
|
3281
|
+
"Seconds to wait for interactive approval.",
|
|
3282
|
+
parseInt
|
|
3283
|
+
).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) => {
|
|
3284
|
+
validateModuleId(moduleId);
|
|
3285
|
+
const moduleDef = registry.getDefinition(moduleId);
|
|
3286
|
+
if (!moduleDef) {
|
|
3287
|
+
process.stderr.write(`Error: Module '${moduleId}' not found.
|
|
3717
3288
|
`);
|
|
3289
|
+
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
3718
3290
|
}
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
Exit code: ${exitCode}
|
|
3291
|
+
let merged = {};
|
|
3292
|
+
if (opts.input === "-") {
|
|
3293
|
+
merged = await collectInput("-", {}, false);
|
|
3294
|
+
} else if (opts.input !== void 0) {
|
|
3295
|
+
try {
|
|
3296
|
+
const parsed = JSON.parse(opts.input);
|
|
3297
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3298
|
+
process.stderr.write("Error: --input JSON must be an object.\n");
|
|
3299
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3300
|
+
}
|
|
3301
|
+
merged = parsed;
|
|
3302
|
+
} catch (err) {
|
|
3303
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3304
|
+
process.stderr.write(`Error: --input is not valid JSON: ${msg}
|
|
3734
3305
|
`);
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
let extensionsDir;
|
|
3738
|
-
let registry;
|
|
3739
|
-
let executor;
|
|
3740
|
-
let extraCommands;
|
|
3741
|
-
let app;
|
|
3742
|
-
let expose;
|
|
3743
|
-
let apcliOption;
|
|
3744
|
-
let appVersion;
|
|
3745
|
-
let appDescription;
|
|
3746
|
-
let allowedPrefixes;
|
|
3747
|
-
let builtinGroupName;
|
|
3748
|
-
if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
|
|
3749
|
-
extensionsDir = extensionsDirOrOpts.extensionsDir;
|
|
3750
|
-
progName = extensionsDirOrOpts.progName ?? progName;
|
|
3751
|
-
allOptions = extensionsDirOrOpts.allOptions ?? extensionsDirOrOpts.verbose ?? allOptions;
|
|
3752
|
-
app = extensionsDirOrOpts.app;
|
|
3753
|
-
registry = extensionsDirOrOpts.registry;
|
|
3754
|
-
executor = extensionsDirOrOpts.executor;
|
|
3755
|
-
extraCommands = extensionsDirOrOpts.extraCommands;
|
|
3756
|
-
expose = extensionsDirOrOpts.expose;
|
|
3757
|
-
apcliOption = extensionsDirOrOpts.apcli;
|
|
3758
|
-
appVersion = extensionsDirOrOpts.version;
|
|
3759
|
-
appDescription = extensionsDirOrOpts.description;
|
|
3760
|
-
builtinGroupName = extensionsDirOrOpts.builtinGroupName;
|
|
3761
|
-
allowedPrefixes = extensionsDirOrOpts.allowedPrefixes;
|
|
3762
|
-
} else {
|
|
3763
|
-
extensionsDir = extensionsDirOrOpts;
|
|
3764
|
-
}
|
|
3765
|
-
verboseHelp = allOptions;
|
|
3766
|
-
registerConfigNamespace();
|
|
3767
|
-
try {
|
|
3768
|
-
const auditLogger = new AuditLogger();
|
|
3769
|
-
setAuditLogger(auditLogger);
|
|
3770
|
-
} catch {
|
|
3771
|
-
}
|
|
3772
|
-
const resolvedProgName = progName ?? path5.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
|
|
3773
|
-
const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
|
|
3774
|
-
setLogLevel(cliLogLevel);
|
|
3775
|
-
if (app && (registry || executor)) {
|
|
3776
|
-
process.stderr.write("Error: app is mutually exclusive with registry/executor\n");
|
|
3777
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3778
|
-
}
|
|
3779
|
-
if (app) {
|
|
3780
|
-
registry = app.registry;
|
|
3781
|
-
executor = app.executor;
|
|
3782
|
-
}
|
|
3783
|
-
if (executor && !registry) {
|
|
3784
|
-
process.stderr.write("Error: executor requires registry \u2014 pass both or neither\n");
|
|
3785
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3786
|
-
}
|
|
3787
|
-
if (executor && typeof executor.setApprovalHandler === "function") {
|
|
3788
|
-
try {
|
|
3789
|
-
const handler = new CliApprovalHandler(
|
|
3790
|
-
/*autoApprove*/
|
|
3791
|
-
false
|
|
3792
|
-
);
|
|
3793
|
-
executor.setApprovalHandler(handler);
|
|
3794
|
-
} catch {
|
|
3306
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3307
|
+
}
|
|
3795
3308
|
}
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
}
|
|
3808
|
-
let apcliCfg;
|
|
3809
|
-
try {
|
|
3810
|
-
if (apcliOption instanceof ApcliGroup) {
|
|
3811
|
-
if (builtinGroupName !== void 0 && builtinGroupName !== "apcli" && apcliOption.name !== builtinGroupName) {
|
|
3812
|
-
throw new Error(
|
|
3813
|
-
`builtinGroupName=${JSON.stringify(builtinGroupName)} conflicts with the name on the supplied ApcliGroup (${JSON.stringify(apcliOption.name)}). Pass only one.`
|
|
3814
|
-
);
|
|
3309
|
+
const startTime = performance.now();
|
|
3310
|
+
try {
|
|
3311
|
+
await checkApproval(moduleDef, opts.yes, opts.approvalTimeout);
|
|
3312
|
+
if (opts.dryRun) {
|
|
3313
|
+
if (executor.validate) {
|
|
3314
|
+
const preflight = await executor.validate(moduleId, merged);
|
|
3315
|
+
formatPreflightResult(preflight, opts.format);
|
|
3316
|
+
} else {
|
|
3317
|
+
process.stdout.write(JSON.stringify({ valid: true }) + "\n");
|
|
3318
|
+
}
|
|
3319
|
+
return;
|
|
3815
3320
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3321
|
+
let result;
|
|
3322
|
+
if ((opts.trace || opts.strategy) && executor.callWithTrace) {
|
|
3323
|
+
const [res] = await executor.callWithTrace(moduleId, merged, { strategy: opts.strategy });
|
|
3324
|
+
result = res;
|
|
3325
|
+
} else {
|
|
3326
|
+
const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
|
|
3327
|
+
const sandbox = new Sandbox2(opts.sandbox);
|
|
3328
|
+
result = await sandbox.execute(moduleId, merged, executor);
|
|
3329
|
+
}
|
|
3330
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
3331
|
+
const fmt = resolveFormat(opts.format);
|
|
3332
|
+
formatExecResult(result, fmt, opts.fields);
|
|
3333
|
+
const auditLogger = getAuditLogger();
|
|
3334
|
+
if (auditLogger) {
|
|
3335
|
+
auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
|
|
3336
|
+
}
|
|
3337
|
+
} catch (err) {
|
|
3338
|
+
const exitCode = exitCodeForError(err);
|
|
3339
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
3824
3340
|
try {
|
|
3825
|
-
const
|
|
3826
|
-
|
|
3341
|
+
const auditLogger = getAuditLogger();
|
|
3342
|
+
if (auditLogger) {
|
|
3343
|
+
auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
|
|
3344
|
+
}
|
|
3827
3345
|
} catch {
|
|
3828
|
-
yamlVal = null;
|
|
3829
3346
|
}
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
});
|
|
3347
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
|
|
3348
|
+
`);
|
|
3349
|
+
process.exit(exitCode);
|
|
3834
3350
|
}
|
|
3835
|
-
}
|
|
3836
|
-
|
|
3351
|
+
});
|
|
3352
|
+
apcliGroup.addCommand(execCmd);
|
|
3353
|
+
}
|
|
3354
|
+
function registerValidateCommand(cli, registry, executor) {
|
|
3355
|
+
const validateCmd = new Command3("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) => {
|
|
3356
|
+
validateModuleId(moduleId);
|
|
3357
|
+
const moduleDef = registry.getDefinition(moduleId);
|
|
3358
|
+
if (!moduleDef) {
|
|
3359
|
+
process.stderr.write(`Error: Module '${moduleId}' not found.
|
|
3837
3360
|
`);
|
|
3838
|
-
|
|
3839
|
-
}
|
|
3840
|
-
setReservedGroupNames(/* @__PURE__ */ new Set([apcliCfg.name]));
|
|
3841
|
-
const apcliGroup = program.command(apcliCfg.name, { hidden: !apcliCfg.isGroupVisible() }).description("Built-in commands");
|
|
3842
|
-
if (registry) {
|
|
3843
|
-
program._registry = registry;
|
|
3844
|
-
if (executor) {
|
|
3845
|
-
program._executor = executor;
|
|
3361
|
+
process.exit(EXIT_CODES.MODULE_NOT_FOUND);
|
|
3846
3362
|
}
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
let exposureFilter;
|
|
3852
|
-
try {
|
|
3853
|
-
if (expose instanceof ExposureFilter) {
|
|
3854
|
-
exposureFilter = expose;
|
|
3855
|
-
} else if (typeof expose === "object" && expose !== null) {
|
|
3856
|
-
exposureFilter = ExposureFilter.fromConfig({ expose });
|
|
3857
|
-
} else {
|
|
3858
|
-
exposureFilter = new ExposureFilter();
|
|
3363
|
+
const merged = opts.input ? await collectInput(opts.input, {}, false) : {};
|
|
3364
|
+
if (!executor.validate) {
|
|
3365
|
+
process.stderr.write("Error: Executor does not support validate.\n");
|
|
3366
|
+
process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
|
|
3859
3367
|
}
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
configureManHelp(program, resolvedProgName, appVersion ?? VERSION);
|
|
3873
|
-
if (extraCommands && extraCommands.length > 0) {
|
|
3874
|
-
const _reservedForExtra = /* @__PURE__ */ new Set([apcliCfg.name]);
|
|
3875
|
-
for (const cmd of extraCommands) {
|
|
3876
|
-
const cmdName = cmd.name();
|
|
3877
|
-
if (_reservedForExtra.has(cmdName)) {
|
|
3878
|
-
process.stderr.write(
|
|
3879
|
-
`Error: extraCommands name '${cmdName}' is reserved
|
|
3880
|
-
`
|
|
3881
|
-
);
|
|
3882
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3883
|
-
}
|
|
3884
|
-
const existing = program.commands.find((c) => c.name() === cmdName);
|
|
3885
|
-
if (existing) {
|
|
3886
|
-
process.stderr.write(
|
|
3887
|
-
`Error: extraCommands name '${cmdName}' collides with an existing command
|
|
3888
|
-
`
|
|
3889
|
-
);
|
|
3890
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3368
|
+
try {
|
|
3369
|
+
const preflight = await executor.validate(moduleId, merged);
|
|
3370
|
+
formatPreflightResult(preflight, opts.format);
|
|
3371
|
+
process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
|
|
3372
|
+
} catch (err) {
|
|
3373
|
+
const exitCode = exitCodeForError(err);
|
|
3374
|
+
try {
|
|
3375
|
+
const auditLogger = getAuditLogger();
|
|
3376
|
+
if (auditLogger) {
|
|
3377
|
+
auditLogger.logExecution(moduleId, merged, "error", exitCode, 0);
|
|
3378
|
+
}
|
|
3379
|
+
} catch {
|
|
3891
3380
|
}
|
|
3892
|
-
|
|
3381
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
|
|
3382
|
+
`);
|
|
3383
|
+
process.exit(exitCode);
|
|
3893
3384
|
}
|
|
3894
|
-
}
|
|
3895
|
-
program.hook("preAction", async (thisCommand) => {
|
|
3896
|
-
const opts = thisCommand.opts();
|
|
3897
|
-
const commandsDir = opts.commandsDir;
|
|
3898
|
-
const bindingPath = opts.binding;
|
|
3899
|
-
await applyToolkitIntegration(commandsDir, bindingPath, { allowedPrefixes });
|
|
3900
3385
|
});
|
|
3901
|
-
|
|
3386
|
+
cli.addCommand(validateCmd);
|
|
3902
3387
|
}
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
{ name: "describe", requiresExecutor: false, register: (g) => registerDescribeCommand(g, effectiveRegistry) },
|
|
3918
|
-
{ name: "exec", requiresExecutor: true, register: (g, _r, ex) => registerExecCommand(g, effectiveRegistry, ex) },
|
|
3919
|
-
{ name: "validate", requiresExecutor: true, register: (g, _r, ex) => registerValidateCommand(g, effectiveRegistry, ex) },
|
|
3920
|
-
{ name: "init", requiresExecutor: false, register: (g) => registerInitCommand(g) },
|
|
3921
|
-
{ name: "health", requiresExecutor: true, register: (g, _r, ex) => registerHealthCommand(g, ex) },
|
|
3922
|
-
{ name: "usage", requiresExecutor: true, register: (g, _r, ex) => registerUsageCommand(g, ex) },
|
|
3923
|
-
{ name: "enable", requiresExecutor: true, register: (g, _r, ex) => registerEnableCommand(g, ex) },
|
|
3924
|
-
{ name: "disable", requiresExecutor: true, register: (g, _r, ex) => registerDisableCommand(g, ex) },
|
|
3925
|
-
{ name: "reload", requiresExecutor: true, register: (g, _r, ex) => registerReloadCommand(g, ex) },
|
|
3926
|
-
{ name: "config", requiresExecutor: true, register: (g, _r, ex) => registerConfigCommand(g, ex) },
|
|
3927
|
-
{ name: "completion", requiresExecutor: false, register: (g) => registerCompletionCommand(g) },
|
|
3928
|
-
{ name: "describe-pipeline", requiresExecutor: true, register: (g, _r, ex) => registerPipelineCommand(g, ex) }
|
|
3929
|
-
];
|
|
3930
|
-
const mode = apcliCfg.resolveVisibility();
|
|
3931
|
-
for (const entry of TABLE) {
|
|
3932
|
-
let shouldRegister;
|
|
3933
|
-
if (mode === "all" || mode === "none") {
|
|
3934
|
-
shouldRegister = true;
|
|
3935
|
-
} else {
|
|
3936
|
-
shouldRegister = _ALWAYS_REGISTERED.has(entry.name) || apcliCfg.isSubcommandIncluded(entry.name);
|
|
3937
|
-
}
|
|
3938
|
-
if (!shouldRegister) continue;
|
|
3939
|
-
if (entry.requiresExecutor && !executor) {
|
|
3940
|
-
if (_ALWAYS_REGISTERED.has(entry.name)) {
|
|
3941
|
-
warn(
|
|
3942
|
-
`apcli.${entry.name} is in _ALWAYS_REGISTERED but no executor is wired \u2014 subcommand unavailable. Pass executor to createCli() or avoid ${entry.name} invocations.`
|
|
3943
|
-
);
|
|
3944
|
-
}
|
|
3945
|
-
continue;
|
|
3946
|
-
}
|
|
3947
|
-
entry.register(apcliGroup, registry, executor);
|
|
3388
|
+
|
|
3389
|
+
// src/system-cmd.ts
|
|
3390
|
+
init_esm_shims();
|
|
3391
|
+
import { Command as Command4 } from "commander";
|
|
3392
|
+
import { Config } from "apcore-js";
|
|
3393
|
+
init_errors();
|
|
3394
|
+
async function callSystemModule(executor, moduleId, inputs) {
|
|
3395
|
+
return executor.call(moduleId, inputs);
|
|
3396
|
+
}
|
|
3397
|
+
function emitResult(jsonPayload, fmt, ttyRender) {
|
|
3398
|
+
if (fmt === "json" || !process.stdout.isTTY) {
|
|
3399
|
+
process.stdout.write(JSON.stringify(jsonPayload, null, 2) + "\n");
|
|
3400
|
+
} else {
|
|
3401
|
+
ttyRender();
|
|
3948
3402
|
}
|
|
3949
3403
|
}
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3404
|
+
function emitErrorAndExit(e) {
|
|
3405
|
+
process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
|
|
3406
|
+
`);
|
|
3407
|
+
process.exit(exitCodeForError(e));
|
|
3953
3408
|
}
|
|
3954
|
-
async function
|
|
3955
|
-
|
|
3409
|
+
async function requireApprovalForSystemCommand(moduleId, autoApprove) {
|
|
3410
|
+
const syntheticModuleDef = {
|
|
3411
|
+
moduleId,
|
|
3412
|
+
name: moduleId,
|
|
3413
|
+
description: `system command: ${moduleId}`,
|
|
3414
|
+
annotations: { requires_approval: true }
|
|
3415
|
+
};
|
|
3416
|
+
await checkApproval(syntheticModuleDef, autoApprove, void 0);
|
|
3417
|
+
}
|
|
3418
|
+
function formatHealthSummaryTty(result) {
|
|
3419
|
+
const summary = result.summary ?? {};
|
|
3420
|
+
const modules = result.modules ?? [];
|
|
3421
|
+
if (modules.length === 0) {
|
|
3422
|
+
process.stdout.write("No modules found.\n");
|
|
3956
3423
|
return;
|
|
3957
3424
|
}
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3425
|
+
const total = summary.total_modules ?? modules.length;
|
|
3426
|
+
process.stdout.write(`Health Overview (${total} modules)
|
|
3427
|
+
|
|
3428
|
+
`);
|
|
3429
|
+
process.stdout.write(` ${"Module".padEnd(28)} ${"Status".padEnd(12)} ${"Error Rate".padEnd(12)} Top Error
|
|
3430
|
+
`);
|
|
3431
|
+
process.stdout.write(` ${"-".repeat(28)} ${"-".repeat(12)} ${"-".repeat(12)} ${"-".repeat(20)}
|
|
3432
|
+
`);
|
|
3433
|
+
for (const m of modules) {
|
|
3434
|
+
const top = m.top_error;
|
|
3435
|
+
const topStr = top ? `${top.code} (${top.count ?? "?"})` : "\u2014";
|
|
3436
|
+
const rate = `${((m.error_rate ?? 0) * 100).toFixed(1)}%`;
|
|
3437
|
+
process.stdout.write(
|
|
3438
|
+
` ${String(m.module_id).padEnd(28)} ${String(m.status).padEnd(12)} ${rate.padEnd(12)} ${topStr}
|
|
3439
|
+
`
|
|
3964
3440
|
);
|
|
3965
|
-
} catch {
|
|
3966
|
-
warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
|
|
3967
|
-
return;
|
|
3968
3441
|
}
|
|
3969
|
-
|
|
3970
|
-
|
|
3442
|
+
const parts = [];
|
|
3443
|
+
for (const key of ["healthy", "degraded", "error"]) {
|
|
3444
|
+
const count = summary[key];
|
|
3445
|
+
if (count) parts.push(`${count} ${key}`);
|
|
3971
3446
|
}
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3447
|
+
process.stdout.write(`
|
|
3448
|
+
Summary: ${parts.join(", ") || "no data"}
|
|
3449
|
+
`);
|
|
3450
|
+
}
|
|
3451
|
+
function formatHealthModuleTty(result) {
|
|
3452
|
+
process.stdout.write(`Module: ${result.module_id ?? "?"}
|
|
3453
|
+
`);
|
|
3454
|
+
process.stdout.write(`Status: ${result.status ?? "unknown"}
|
|
3455
|
+
`);
|
|
3456
|
+
const total = result.total_calls ?? 0;
|
|
3457
|
+
const errors = result.error_count ?? 0;
|
|
3458
|
+
const rate = result.error_rate ?? 0;
|
|
3459
|
+
const avg = result.avg_latency_ms ?? 0;
|
|
3460
|
+
const p99 = result.p99_latency_ms ?? 0;
|
|
3461
|
+
process.stdout.write(`Calls: ${total.toLocaleString()} total | ${errors.toLocaleString()} errors | ${(rate * 100).toFixed(1)}% error rate
|
|
3462
|
+
`);
|
|
3463
|
+
process.stdout.write(`Latency: ${avg.toFixed(0)}ms avg | ${p99.toFixed(0)}ms p99
|
|
3464
|
+
`);
|
|
3465
|
+
const recent = result.recent_errors ?? [];
|
|
3466
|
+
if (recent.length > 0) {
|
|
3467
|
+
process.stdout.write(`
|
|
3468
|
+
Recent Errors (top ${recent.length}):
|
|
3469
|
+
`);
|
|
3470
|
+
for (const e of recent) {
|
|
3471
|
+
const count = e.count ?? "?";
|
|
3472
|
+
const last = e.last_occurred ?? "?";
|
|
3473
|
+
process.stdout.write(` ${String(e.code ?? "?").padEnd(24)} x${count} (last: ${last})
|
|
3474
|
+
`);
|
|
3978
3475
|
}
|
|
3979
3476
|
}
|
|
3980
3477
|
}
|
|
3981
|
-
|
|
3982
|
-
const
|
|
3983
|
-
const
|
|
3984
|
-
if (
|
|
3478
|
+
function formatUsageSummaryTty(result) {
|
|
3479
|
+
const modules = result.modules ?? [];
|
|
3480
|
+
const period = result.period ?? "?";
|
|
3481
|
+
if (modules.length === 0) {
|
|
3482
|
+
process.stdout.write(`No usage data for period ${period}.
|
|
3483
|
+
`);
|
|
3985
3484
|
return;
|
|
3986
3485
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
const id = typeof entry.moduleId === "string" ? entry.moduleId : null;
|
|
4001
|
-
if (!id) continue;
|
|
4002
|
-
if (!isTargetAllowed(entry.target)) {
|
|
4003
|
-
warn(
|
|
4004
|
-
`apcore-toolkit: dropped binding entry '${id}' \u2014 target '${String(entry.target)}' is outside allowedPrefixes`
|
|
4005
|
-
);
|
|
4006
|
-
continue;
|
|
4007
|
-
}
|
|
4008
|
-
const meta = entry.metadata ?? {};
|
|
4009
|
-
const display = meta.display;
|
|
4010
|
-
if (display && typeof display === "object" && !Array.isArray(display)) {
|
|
4011
|
-
bindingDisplayMap.set(id, display);
|
|
4012
|
-
}
|
|
3486
|
+
process.stdout.write(`Usage Summary (last ${period})
|
|
3487
|
+
|
|
3488
|
+
`);
|
|
3489
|
+
process.stdout.write(` ${"Module".padEnd(24)} ${"Calls".padStart(8)} ${"Errors".padStart(8)} ${"Avg Latency".padStart(12)} ${"Trend".padStart(10)}
|
|
3490
|
+
`);
|
|
3491
|
+
process.stdout.write(` ${"-".repeat(24)} ${"-".repeat(8)} ${"-".repeat(8)} ${"-".repeat(12)} ${"-".repeat(10)}
|
|
3492
|
+
`);
|
|
3493
|
+
for (const m of modules) {
|
|
3494
|
+
const avg = `${(m.avg_latency_ms ?? 0).toFixed(0)}ms`;
|
|
3495
|
+
process.stdout.write(
|
|
3496
|
+
` ${String(m.module_id).padEnd(24)} ${String(m.call_count ?? 0).padStart(8)} ${String(m.error_count ?? 0).padStart(8)} ${avg.padStart(12)} ${String(m.trend ?? "").padStart(10)}
|
|
3497
|
+
`
|
|
3498
|
+
);
|
|
4013
3499
|
}
|
|
3500
|
+
const totalCalls = result.total_calls ?? modules.reduce((s, m) => s + (m.call_count ?? 0), 0);
|
|
3501
|
+
const totalErrors = result.total_errors ?? modules.reduce((s, m) => s + (m.error_count ?? 0), 0);
|
|
3502
|
+
process.stdout.write(`
|
|
3503
|
+
Total: ${totalCalls.toLocaleString()} calls | ${totalErrors.toLocaleString()} errors
|
|
3504
|
+
`);
|
|
4014
3505
|
}
|
|
4015
|
-
function
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
3506
|
+
function registerHealthCommand(apcliGroup, executor) {
|
|
3507
|
+
const healthCmd = new Command4("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) => {
|
|
3508
|
+
const fmt = resolveFormat(opts.format);
|
|
3509
|
+
try {
|
|
3510
|
+
if (moduleId) {
|
|
3511
|
+
const result = await callSystemModule(executor, "system.health.module", {
|
|
3512
|
+
module_id: moduleId,
|
|
3513
|
+
error_limit: opts.errors
|
|
3514
|
+
});
|
|
3515
|
+
emitResult(result, fmt, () => formatHealthModuleTty(result));
|
|
3516
|
+
} else {
|
|
3517
|
+
const result = await callSystemModule(executor, "system.health.summary", {
|
|
3518
|
+
error_rate_threshold: opts.threshold,
|
|
3519
|
+
include_healthy: opts.all
|
|
3520
|
+
});
|
|
3521
|
+
emitResult(result, fmt, () => formatHealthSummaryTty(result));
|
|
3522
|
+
}
|
|
3523
|
+
} catch (e) {
|
|
3524
|
+
emitErrorAndExit(e);
|
|
4028
3525
|
}
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
3526
|
+
});
|
|
3527
|
+
apcliGroup.addCommand(healthCmd);
|
|
3528
|
+
}
|
|
3529
|
+
function registerUsageCommand(apcliGroup, executor) {
|
|
3530
|
+
const usageCmd = new Command4("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) => {
|
|
3531
|
+
const fmt = resolveFormat(opts.format);
|
|
3532
|
+
try {
|
|
3533
|
+
let result;
|
|
3534
|
+
if (moduleId) {
|
|
3535
|
+
result = await callSystemModule(executor, "system.usage.module", {
|
|
3536
|
+
module_id: moduleId,
|
|
3537
|
+
period: opts.period
|
|
3538
|
+
});
|
|
3539
|
+
} else {
|
|
3540
|
+
result = await callSystemModule(executor, "system.usage.summary", {
|
|
3541
|
+
period: opts.period
|
|
3542
|
+
});
|
|
3543
|
+
}
|
|
3544
|
+
emitResult(result, fmt, () => {
|
|
3545
|
+
if (moduleId) {
|
|
3546
|
+
formatExecResult(result, fmt);
|
|
3547
|
+
} else {
|
|
3548
|
+
formatUsageSummaryTty(result);
|
|
3549
|
+
}
|
|
3550
|
+
});
|
|
3551
|
+
} catch (e) {
|
|
3552
|
+
emitErrorAndExit(e);
|
|
3553
|
+
}
|
|
3554
|
+
});
|
|
3555
|
+
apcliGroup.addCommand(usageCmd);
|
|
3556
|
+
}
|
|
3557
|
+
function registerEnableCommand(apcliGroup, executor) {
|
|
3558
|
+
const enableCmd = new Command4("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) => {
|
|
3559
|
+
const fmt = resolveFormat(opts.format);
|
|
3560
|
+
try {
|
|
3561
|
+
await requireApprovalForSystemCommand("system.control.toggle_feature", opts.yes);
|
|
3562
|
+
const result = await callSystemModule(executor, "system.control.toggle_feature", {
|
|
3563
|
+
module_id: moduleId,
|
|
3564
|
+
enabled: true,
|
|
3565
|
+
reason: opts.reason
|
|
3566
|
+
});
|
|
3567
|
+
emitResult(result, fmt, () => {
|
|
3568
|
+
process.stdout.write(`Module '${moduleId}' enabled.
|
|
3569
|
+
Reason: ${opts.reason}
|
|
4032
3570
|
`);
|
|
3571
|
+
});
|
|
3572
|
+
} catch (e) {
|
|
3573
|
+
emitErrorAndExit(e);
|
|
4033
3574
|
}
|
|
4034
|
-
|
|
4035
|
-
|
|
3575
|
+
});
|
|
3576
|
+
apcliGroup.addCommand(enableCmd);
|
|
4036
3577
|
}
|
|
4037
|
-
function
|
|
4038
|
-
const
|
|
4039
|
-
|
|
4040
|
-
let schemaOptions = [];
|
|
4041
|
-
const display = getDisplay(moduleDef);
|
|
4042
|
-
const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
|
|
4043
|
-
const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
|
|
4044
|
-
const cmdHelp = cliDisplay.description ?? moduleDef.description;
|
|
4045
|
-
const inputSchema = moduleDef.inputSchema;
|
|
4046
|
-
if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
|
|
3578
|
+
function registerDisableCommand(apcliGroup, executor) {
|
|
3579
|
+
const disableCmd = new Command4("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) => {
|
|
3580
|
+
const fmt = resolveFormat(opts.format);
|
|
4047
3581
|
try {
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
3582
|
+
await requireApprovalForSystemCommand("system.control.toggle_feature", opts.yes);
|
|
3583
|
+
const result = await callSystemModule(executor, "system.control.toggle_feature", {
|
|
3584
|
+
module_id: moduleId,
|
|
3585
|
+
enabled: false,
|
|
3586
|
+
reason: opts.reason
|
|
3587
|
+
});
|
|
3588
|
+
emitResult(result, fmt, () => {
|
|
3589
|
+
process.stdout.write(`Module '${moduleId}' disabled.
|
|
3590
|
+
Reason: ${opts.reason}
|
|
4052
3591
|
`);
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
3592
|
+
});
|
|
3593
|
+
} catch (e) {
|
|
3594
|
+
emitErrorAndExit(e);
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
apcliGroup.addCommand(disableCmd);
|
|
3598
|
+
}
|
|
3599
|
+
function registerReloadCommand(apcliGroup, executor) {
|
|
3600
|
+
const reloadCmd = new Command4("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) => {
|
|
3601
|
+
const fmt = resolveFormat(opts.format);
|
|
3602
|
+
try {
|
|
3603
|
+
await requireApprovalForSystemCommand("system.control.reload_module", opts.yes);
|
|
3604
|
+
const result = await callSystemModule(executor, "system.control.reload_module", {
|
|
3605
|
+
module_id: moduleId,
|
|
3606
|
+
reason: opts.reason
|
|
3607
|
+
});
|
|
3608
|
+
emitResult(result, fmt, () => {
|
|
3609
|
+
const prev = result.previous_version ?? "?";
|
|
3610
|
+
const newVer = result.new_version ?? "?";
|
|
3611
|
+
const dur = result.reload_duration_ms ?? "?";
|
|
3612
|
+
process.stdout.write(`Module '${moduleId}' reloaded.
|
|
4057
3613
|
`);
|
|
4058
|
-
process.
|
|
3614
|
+
process.stdout.write(` Version: ${prev} -> ${newVer}
|
|
3615
|
+
`);
|
|
3616
|
+
process.stdout.write(` Duration: ${dur}ms
|
|
3617
|
+
`);
|
|
3618
|
+
});
|
|
3619
|
+
} catch (e) {
|
|
3620
|
+
emitErrorAndExit(e);
|
|
3621
|
+
}
|
|
3622
|
+
});
|
|
3623
|
+
apcliGroup.addCommand(reloadCmd);
|
|
3624
|
+
}
|
|
3625
|
+
function registerConfigCommand(apcliGroup, executor) {
|
|
3626
|
+
const configGroup = new Command4("config").description("Read or update runtime configuration.");
|
|
3627
|
+
const configGetCmd = new Command4("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) => {
|
|
3628
|
+
const fmt = resolveFormat(opts.format);
|
|
3629
|
+
try {
|
|
3630
|
+
const value = new Config().get(key);
|
|
3631
|
+
emitResult({ key, value }, fmt, () => {
|
|
3632
|
+
process.stdout.write(`${key} = ${JSON.stringify(value)}
|
|
3633
|
+
`);
|
|
3634
|
+
});
|
|
3635
|
+
} catch (e) {
|
|
3636
|
+
emitErrorAndExit(e);
|
|
3637
|
+
}
|
|
3638
|
+
});
|
|
3639
|
+
configGroup.addCommand(configGetCmd);
|
|
3640
|
+
const configSetCmd = new Command4("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) => {
|
|
3641
|
+
const fmt = resolveFormat(opts.format);
|
|
3642
|
+
let parsedValue;
|
|
3643
|
+
try {
|
|
3644
|
+
parsedValue = JSON.parse(value);
|
|
3645
|
+
} catch {
|
|
3646
|
+
parsedValue = value;
|
|
3647
|
+
}
|
|
3648
|
+
try {
|
|
3649
|
+
await requireApprovalForSystemCommand("system.control.update_config", opts.yes);
|
|
3650
|
+
const result = await callSystemModule(executor, "system.control.update_config", {
|
|
3651
|
+
key,
|
|
3652
|
+
value: parsedValue,
|
|
3653
|
+
reason: opts.reason
|
|
3654
|
+
});
|
|
3655
|
+
emitResult(result, fmt, () => {
|
|
3656
|
+
const old = result.old_value ?? "?";
|
|
3657
|
+
const newVal = result.new_value ?? "?";
|
|
3658
|
+
process.stdout.write(`Config updated: ${key}
|
|
3659
|
+
`);
|
|
3660
|
+
process.stdout.write(` ${JSON.stringify(old)} -> ${JSON.stringify(newVal)}
|
|
3661
|
+
`);
|
|
3662
|
+
process.stdout.write(` Reason: ${opts.reason}
|
|
3663
|
+
`);
|
|
3664
|
+
});
|
|
3665
|
+
} catch (e) {
|
|
3666
|
+
emitErrorAndExit(e);
|
|
3667
|
+
}
|
|
3668
|
+
});
|
|
3669
|
+
configGroup.addCommand(configSetCmd);
|
|
3670
|
+
apcliGroup.addCommand(configGroup);
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
// src/strategy.ts
|
|
3674
|
+
init_esm_shims();
|
|
3675
|
+
import { Command as Command5, Option as Option3 } from "commander";
|
|
3676
|
+
function lookupStrategyInfo(executor, strategyName) {
|
|
3677
|
+
if (typeof executor.describePipeline === "function") {
|
|
3678
|
+
try {
|
|
3679
|
+
const current = executor.describePipeline();
|
|
3680
|
+
if (current && current.name === strategyName) {
|
|
3681
|
+
return { info: current, isCurrent: true };
|
|
4059
3682
|
}
|
|
4060
|
-
|
|
3683
|
+
} catch {
|
|
4061
3684
|
}
|
|
4062
|
-
schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
|
|
4063
|
-
}
|
|
4064
|
-
const cmd = new Command5(effectiveCmdName).description(cmdHelp);
|
|
4065
|
-
const inputOpt = new Option4("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
|
|
4066
|
-
const yesOpt = new Option4("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
|
|
4067
|
-
const largeInputOpt = new Option4("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
|
|
4068
|
-
const formatOpt = new Option4("--format <format>", "Output format: json, table, csv, yaml, jsonl.").choices(["json", "table", "csv", "yaml", "jsonl"]);
|
|
4069
|
-
const fieldsOpt = new Option4("--fields <fields>", "Comma-separated dot-paths to select from the result (e.g., 'status,data.count').");
|
|
4070
|
-
const sandboxOpt = new Option4("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
|
|
4071
|
-
const dryRunOpt = new Option4("--dry-run", "Run preflight checks without executing the module. Shows validation results.").default(false);
|
|
4072
|
-
const traceOpt = new Option4("--trace", "Show execution pipeline trace with per-step timing after the result.").default(false);
|
|
4073
|
-
const streamOpt = new Option4("--stream", "Stream module output as JSONL (one JSON object per line, flushed immediately).").default(false);
|
|
4074
|
-
const strategyOpt = new Option4("--strategy <name>", "Execution pipeline strategy: standard (default), internal, testing, performance.").choices(["standard", "internal", "testing", "performance", "minimal"]);
|
|
4075
|
-
const approvalTimeoutOpt = new Option4("--approval-timeout <seconds>", "Override approval prompt timeout in seconds (default: 60).").argParser(parseInt);
|
|
4076
|
-
const approvalTokenOpt = new Option4("--approval-token <token>", "Resume a pending approval with the given token (for async approval flows).");
|
|
4077
|
-
if (!verbose) {
|
|
4078
|
-
inputOpt.hideHelp();
|
|
4079
|
-
yesOpt.hideHelp();
|
|
4080
|
-
largeInputOpt.hideHelp();
|
|
4081
|
-
formatOpt.hideHelp();
|
|
4082
|
-
fieldsOpt.hideHelp();
|
|
4083
|
-
dryRunOpt.hideHelp();
|
|
4084
|
-
traceOpt.hideHelp();
|
|
4085
|
-
streamOpt.hideHelp();
|
|
4086
|
-
strategyOpt.hideHelp();
|
|
4087
|
-
approvalTimeoutOpt.hideHelp();
|
|
4088
|
-
approvalTokenOpt.hideHelp();
|
|
4089
|
-
}
|
|
4090
|
-
cmd.addOption(inputOpt);
|
|
4091
|
-
cmd.addOption(yesOpt);
|
|
4092
|
-
cmd.addOption(largeInputOpt);
|
|
4093
|
-
cmd.addOption(formatOpt);
|
|
4094
|
-
cmd.addOption(fieldsOpt);
|
|
4095
|
-
cmd.addOption(sandboxOpt);
|
|
4096
|
-
cmd.addOption(dryRunOpt);
|
|
4097
|
-
cmd.addOption(traceOpt);
|
|
4098
|
-
cmd.addOption(streamOpt);
|
|
4099
|
-
cmd.addOption(strategyOpt);
|
|
4100
|
-
cmd.addOption(approvalTimeoutOpt);
|
|
4101
|
-
cmd.addOption(approvalTokenOpt);
|
|
4102
|
-
const footerParts = [];
|
|
4103
|
-
if (!verbose) {
|
|
4104
|
-
footerParts.push("Use --all-options to show all options (including built-in options).");
|
|
4105
|
-
}
|
|
4106
|
-
if (docsUrl) {
|
|
4107
|
-
footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
|
|
4108
3685
|
}
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
opt.defaultValue
|
|
4118
|
-
)
|
|
4119
|
-
);
|
|
4120
|
-
cmd.addOption(new Option4(`--no-${flagBase}`).hideHelp());
|
|
4121
|
-
} else if (opt.parseArg) {
|
|
4122
|
-
cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
|
|
4123
|
-
} else {
|
|
4124
|
-
cmd.option(opt.flags, opt.description, opt.defaultValue);
|
|
3686
|
+
const ctor = executor.constructor;
|
|
3687
|
+
if (ctor && typeof ctor.listStrategies === "function") {
|
|
3688
|
+
try {
|
|
3689
|
+
const all = ctor.listStrategies();
|
|
3690
|
+
const info = all.find((s) => s.name === strategyName) ?? null;
|
|
3691
|
+
return { info, isCurrent: false };
|
|
3692
|
+
} catch {
|
|
3693
|
+
return { info: null, isCurrent: false };
|
|
4125
3694
|
}
|
|
4126
3695
|
}
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
3696
|
+
return { info: null, isCurrent: false };
|
|
3697
|
+
}
|
|
3698
|
+
var PRESET_STEPS = {
|
|
3699
|
+
standard: [
|
|
3700
|
+
"context_creation",
|
|
3701
|
+
"call_chain_guard",
|
|
3702
|
+
"module_lookup",
|
|
3703
|
+
"acl_check",
|
|
3704
|
+
"approval_gate",
|
|
3705
|
+
"middleware_before",
|
|
3706
|
+
"input_validation",
|
|
3707
|
+
"execute",
|
|
3708
|
+
"output_validation",
|
|
3709
|
+
"middleware_after",
|
|
3710
|
+
"return_result"
|
|
3711
|
+
],
|
|
3712
|
+
internal: [
|
|
3713
|
+
"context_creation",
|
|
3714
|
+
"call_chain_guard",
|
|
3715
|
+
"module_lookup",
|
|
3716
|
+
"middleware_before",
|
|
3717
|
+
"input_validation",
|
|
3718
|
+
"execute",
|
|
3719
|
+
"output_validation",
|
|
3720
|
+
"middleware_after",
|
|
3721
|
+
"return_result"
|
|
3722
|
+
],
|
|
3723
|
+
testing: [
|
|
3724
|
+
"context_creation",
|
|
3725
|
+
"module_lookup",
|
|
3726
|
+
"middleware_before",
|
|
3727
|
+
"input_validation",
|
|
3728
|
+
"execute",
|
|
3729
|
+
"output_validation",
|
|
3730
|
+
"middleware_after",
|
|
3731
|
+
"return_result"
|
|
3732
|
+
],
|
|
3733
|
+
performance: [
|
|
3734
|
+
"context_creation",
|
|
3735
|
+
"call_chain_guard",
|
|
3736
|
+
"module_lookup",
|
|
3737
|
+
"acl_check",
|
|
3738
|
+
"approval_gate",
|
|
3739
|
+
"input_validation",
|
|
3740
|
+
"execute",
|
|
3741
|
+
"output_validation",
|
|
3742
|
+
"return_result"
|
|
3743
|
+
],
|
|
3744
|
+
minimal: [
|
|
3745
|
+
"context_creation",
|
|
3746
|
+
"module_lookup",
|
|
3747
|
+
"execute",
|
|
3748
|
+
"return_result"
|
|
3749
|
+
]
|
|
3750
|
+
};
|
|
3751
|
+
function registerPipelineCommand(cli, executor) {
|
|
3752
|
+
const pipelineCmd = new Command5("describe-pipeline").description("Show the execution pipeline steps for a strategy.").addOption(
|
|
3753
|
+
new Option3("--strategy <name>", "Strategy to describe (default: standard).").choices(["standard", "internal", "testing", "performance", "minimal"]).default("standard")
|
|
3754
|
+
).option("--format <format>", "Output format.").action((opts) => {
|
|
3755
|
+
const fmt = resolveFormat(opts.format);
|
|
3756
|
+
const { info, isCurrent } = lookupStrategyInfo(executor, opts.strategy);
|
|
3757
|
+
if (info) {
|
|
3758
|
+
const strategySteps = isCurrent ? executor.currentStrategy?.steps ?? [] : [];
|
|
3759
|
+
const header = `Pipeline: ${info.name} (${info.stepCount} steps)`;
|
|
3760
|
+
if (fmt === "json" || !process.stdout.isTTY) {
|
|
3761
|
+
const payload = {
|
|
3762
|
+
strategy: info.name,
|
|
3763
|
+
step_count: info.stepCount,
|
|
3764
|
+
description: info.description,
|
|
3765
|
+
steps: info.stepNames.map((name, i) => {
|
|
3766
|
+
const stepMeta = strategySteps[i];
|
|
3767
|
+
return {
|
|
3768
|
+
index: i + 1,
|
|
3769
|
+
name,
|
|
3770
|
+
pure: stepMeta?.pure ?? false,
|
|
3771
|
+
removable: stepMeta?.removable ?? true,
|
|
3772
|
+
timeout_ms: stepMeta?.timeoutMs ?? null
|
|
3773
|
+
};
|
|
3774
|
+
})
|
|
3775
|
+
};
|
|
3776
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
3777
|
+
} else {
|
|
3778
|
+
process.stdout.write(`${header}
|
|
3779
|
+
|
|
4194
3780
|
`);
|
|
4195
|
-
|
|
4196
|
-
}
|
|
4197
|
-
}
|
|
4198
|
-
process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
|
|
4199
|
-
}
|
|
4200
|
-
if (resolvedSchema.properties) {
|
|
4201
|
-
const validationErr = validateInputSchema(resolvedSchema, merged);
|
|
4202
|
-
if (validationErr) {
|
|
4203
|
-
throw new SchemaValidationError(`Validation failed: ${validationErr}`);
|
|
4204
|
-
}
|
|
4205
|
-
}
|
|
4206
|
-
if (approvalToken) {
|
|
4207
|
-
merged._approval_token = approvalToken;
|
|
4208
|
-
}
|
|
4209
|
-
await checkApproval(moduleDef, autoApprove, approvalTimeout);
|
|
4210
|
-
if (streamFlag) {
|
|
4211
|
-
if (resolveFormat(outputFormat) === "table") {
|
|
4212
|
-
process.stderr.write("Warning: Streaming mode always outputs JSONL; --format table is ignored.\n");
|
|
4213
|
-
}
|
|
4214
|
-
const annotations = moduleDef.annotations;
|
|
4215
|
-
const isStreaming = annotations?.streaming === true;
|
|
4216
|
-
if (!isStreaming) {
|
|
4217
|
-
process.stderr.write(
|
|
4218
|
-
`Warning: Module '${moduleId}' does not declare streaming support. Falling back to standard execution.
|
|
4219
|
-
`
|
|
4220
|
-
);
|
|
4221
|
-
}
|
|
4222
|
-
if (isStreaming && executor.stream) {
|
|
4223
|
-
let chunks = 0;
|
|
4224
|
-
for await (const chunk of executor.stream(moduleId, merged)) {
|
|
4225
|
-
chunks++;
|
|
4226
|
-
process.stdout.write(JSON.stringify(chunk) + "\n");
|
|
4227
|
-
if (process.stderr.isTTY) {
|
|
4228
|
-
process.stderr.write(`\rStreaming ${moduleId}... (${chunks} chunks)`);
|
|
4229
|
-
}
|
|
4230
|
-
}
|
|
4231
|
-
if (process.stderr.isTTY) {
|
|
4232
|
-
process.stderr.write("\n");
|
|
4233
|
-
}
|
|
4234
|
-
const durationMs2 = Math.round(performance.now() - startTime);
|
|
4235
|
-
const { getAuditLogger: getAuditLogger3 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4236
|
-
const auditLogger2 = getAuditLogger3();
|
|
4237
|
-
if (auditLogger2) {
|
|
4238
|
-
auditLogger2.logExecution(moduleId, merged, "success", 0, durationMs2);
|
|
4239
|
-
}
|
|
4240
|
-
return;
|
|
4241
|
-
}
|
|
4242
|
-
}
|
|
4243
|
-
if (traceFlag && executor.callWithTrace) {
|
|
4244
|
-
const [result2, trace] = await executor.callWithTrace(
|
|
4245
|
-
moduleId,
|
|
4246
|
-
merged,
|
|
4247
|
-
strategyName ? { strategy: strategyName } : void 0
|
|
4248
|
-
);
|
|
4249
|
-
const durationMs2 = Math.round(performance.now() - startTime);
|
|
4250
|
-
const resolved = resolveFormat(outputFormat);
|
|
4251
|
-
if (resolved === "json" || !process.stdout.isTTY) {
|
|
4252
|
-
const traceData = {
|
|
4253
|
-
strategy: trace.strategyName,
|
|
4254
|
-
total_duration_ms: trace.totalDurationMs,
|
|
4255
|
-
success: trace.success,
|
|
4256
|
-
steps: trace.steps.map((s) => ({
|
|
4257
|
-
name: s.name,
|
|
4258
|
-
duration_ms: s.durationMs,
|
|
4259
|
-
skipped: s.skipped,
|
|
4260
|
-
...s.skipped ? { skip_reason: s.skipReason ?? null } : {}
|
|
4261
|
-
}))
|
|
4262
|
-
};
|
|
4263
|
-
let output;
|
|
4264
|
-
if (typeof result2 === "object" && result2 !== null && !Array.isArray(result2)) {
|
|
4265
|
-
output = { ...result2, _trace: traceData };
|
|
4266
|
-
} else {
|
|
4267
|
-
output = { result: result2, _trace: traceData };
|
|
4268
|
-
}
|
|
4269
|
-
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
4270
|
-
} else {
|
|
4271
|
-
formatExecResult(result2, outputFormat, outputFields);
|
|
4272
|
-
const stepCount = trace.steps.length;
|
|
4273
|
-
process.stderr.write(
|
|
4274
|
-
`
|
|
4275
|
-
Pipeline Trace (strategy: ${trace.strategyName}, ${stepCount} steps, ${trace.totalDurationMs.toFixed(1)}ms)
|
|
4276
|
-
`
|
|
4277
|
-
);
|
|
4278
|
-
for (const s of trace.steps) {
|
|
4279
|
-
if (s.skipped) {
|
|
4280
|
-
const reason = s.skipReason ?? "n/a";
|
|
4281
|
-
process.stderr.write(` \u25CB ${s.name.padEnd(24)} ${"\u2014".padStart(8)} skipped (${reason})
|
|
3781
|
+
process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
|
|
4282
3782
|
`);
|
|
4283
|
-
|
|
4284
|
-
process.stderr.write(` \u2713 ${s.name.padEnd(24)} ${(s.durationMs.toFixed(1) + "ms").padStart(8)}
|
|
3783
|
+
process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
|
|
4285
3784
|
`);
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
al2.logExecution(moduleId, merged, "success", 0, durationMs2);
|
|
4293
|
-
}
|
|
4294
|
-
return;
|
|
4295
|
-
}
|
|
4296
|
-
let result;
|
|
4297
|
-
if (strategyName && executor.callWithTrace) {
|
|
4298
|
-
const [res] = await executor.callWithTrace(
|
|
4299
|
-
moduleId,
|
|
4300
|
-
merged,
|
|
4301
|
-
{ strategy: strategyName }
|
|
4302
|
-
);
|
|
4303
|
-
result = res;
|
|
4304
|
-
if (strategyName !== "standard" && process.stderr.isTTY) {
|
|
4305
|
-
process.stderr.write(`Warning: Using '${strategyName}' strategy.
|
|
3785
|
+
for (let i = 0; i < info.stepNames.length; i++) {
|
|
3786
|
+
const stepMeta = strategySteps[i];
|
|
3787
|
+
const pure = stepMeta?.pure ? "yes" : "no";
|
|
3788
|
+
const removable = stepMeta?.removable !== false ? "yes" : "no";
|
|
3789
|
+
const timeout = stepMeta?.timeoutMs ? `${stepMeta.timeoutMs}ms` : "\u2014";
|
|
3790
|
+
process.stdout.write(` ${String(i + 1).padEnd(4)} ${info.stepNames[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} ${timeout}
|
|
4306
3791
|
`);
|
|
4307
3792
|
}
|
|
4308
|
-
} else {
|
|
4309
|
-
const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
|
|
4310
|
-
const sandbox = new Sandbox2(sandboxEnabled);
|
|
4311
|
-
result = await sandbox.execute(moduleId, merged, executor);
|
|
4312
|
-
}
|
|
4313
|
-
const durationMs = Math.round(performance.now() - startTime);
|
|
4314
|
-
formatExecResult(result, outputFormat, outputFields);
|
|
4315
|
-
const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4316
|
-
const auditLogger = getAuditLogger2();
|
|
4317
|
-
if (auditLogger) {
|
|
4318
|
-
auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
|
|
4319
|
-
}
|
|
4320
|
-
} catch (err) {
|
|
4321
|
-
const exitCode = exitCodeForError(err);
|
|
4322
|
-
const durationMs = Math.round(performance.now() - startTime);
|
|
4323
|
-
try {
|
|
4324
|
-
const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4325
|
-
const auditLogger = getAuditLogger2();
|
|
4326
|
-
if (auditLogger) {
|
|
4327
|
-
auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
|
|
4328
|
-
}
|
|
4329
|
-
} catch {
|
|
4330
3793
|
}
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
3794
|
+
return;
|
|
3795
|
+
}
|
|
3796
|
+
const steps = PRESET_STEPS[opts.strategy] ?? [];
|
|
3797
|
+
const pureSteps = /* @__PURE__ */ new Set([
|
|
3798
|
+
"context_creation",
|
|
3799
|
+
"call_chain_guard",
|
|
3800
|
+
"module_lookup",
|
|
3801
|
+
"acl_check",
|
|
3802
|
+
"input_validation"
|
|
3803
|
+
]);
|
|
3804
|
+
const nonRemovable = /* @__PURE__ */ new Set([
|
|
3805
|
+
"context_creation",
|
|
3806
|
+
"module_lookup",
|
|
3807
|
+
"execute",
|
|
3808
|
+
"return_result"
|
|
3809
|
+
]);
|
|
3810
|
+
if (fmt === "json" || !process.stdout.isTTY) {
|
|
3811
|
+
const payload = {
|
|
3812
|
+
strategy: opts.strategy,
|
|
3813
|
+
step_count: steps.length,
|
|
3814
|
+
steps: steps.map((s, i) => ({
|
|
3815
|
+
index: i + 1,
|
|
3816
|
+
name: s,
|
|
3817
|
+
pure: pureSteps.has(s),
|
|
3818
|
+
removable: !nonRemovable.has(s)
|
|
3819
|
+
}))
|
|
3820
|
+
};
|
|
3821
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
|
|
3822
|
+
} else {
|
|
3823
|
+
process.stdout.write(`Pipeline: ${opts.strategy} (${steps.length} steps)
|
|
3824
|
+
|
|
3825
|
+
`);
|
|
3826
|
+
process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
|
|
3827
|
+
`);
|
|
3828
|
+
process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
|
|
3829
|
+
`);
|
|
3830
|
+
for (let i = 0; i < steps.length; i++) {
|
|
3831
|
+
const pure = pureSteps.has(steps[i]) ? "yes" : "no";
|
|
3832
|
+
const removable = nonRemovable.has(steps[i]) ? "no" : "yes";
|
|
3833
|
+
process.stdout.write(` ${String(i + 1).padEnd(4)} ${steps[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} \u2014
|
|
3834
|
+
`);
|
|
4335
3835
|
}
|
|
4336
|
-
process.exit(exitCode);
|
|
4337
3836
|
}
|
|
4338
3837
|
});
|
|
4339
|
-
|
|
3838
|
+
cli.addCommand(pipelineCmd);
|
|
4340
3839
|
}
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
3840
|
+
|
|
3841
|
+
// src/main.ts
|
|
3842
|
+
import { BindingLoader, DisplayResolver } from "apcore-toolkit";
|
|
3843
|
+
init_audit();
|
|
3844
|
+
|
|
3845
|
+
// src/canonical-help.ts
|
|
3846
|
+
init_esm_shims();
|
|
3847
|
+
function resolveHelpText(cmd, section) {
|
|
3848
|
+
const bag = cmd._helpText;
|
|
3849
|
+
const v = bag?.[section];
|
|
3850
|
+
if (typeof v === "function") return v({ error: false, command: cmd });
|
|
3851
|
+
return v ?? "";
|
|
3852
|
+
}
|
|
3853
|
+
function uppercasePlaceholders(flags) {
|
|
3854
|
+
return flags.replace(/<([a-zA-Z0-9_-]+)>/g, (_, name) => `<${name.toUpperCase()}>`).replace(/\[([a-zA-Z0-9_-]+)\]/g, (_, name) => `[${name.toUpperCase()}]`);
|
|
3855
|
+
}
|
|
3856
|
+
function optionTerm(opt) {
|
|
3857
|
+
const flags = uppercasePlaceholders(opt.flags);
|
|
3858
|
+
if (!opt.short && flags.startsWith("--")) return " " + flags;
|
|
3859
|
+
return flags;
|
|
3860
|
+
}
|
|
3861
|
+
function optionDescription(opt) {
|
|
3862
|
+
let desc = opt.description;
|
|
3863
|
+
const d = opt.defaultValue;
|
|
3864
|
+
if (d !== void 0 && d !== false && d !== "" && d !== null) {
|
|
3865
|
+
desc = `${desc} [default: ${String(d)}]`;
|
|
3866
|
+
}
|
|
3867
|
+
return desc;
|
|
3868
|
+
}
|
|
3869
|
+
function reorderHelpVersionLast(opts) {
|
|
3870
|
+
const helpOpts = [];
|
|
3871
|
+
const versionOpts = [];
|
|
3872
|
+
const rest = [];
|
|
3873
|
+
for (const o of opts) {
|
|
3874
|
+
if (o.long === "--help") helpOpts.push(o);
|
|
3875
|
+
else if (o.long === "--version") versionOpts.push(o);
|
|
3876
|
+
else rest.push(o);
|
|
3877
|
+
}
|
|
3878
|
+
return [...rest, ...helpOpts, ...versionOpts];
|
|
3879
|
+
}
|
|
3880
|
+
function canonicalFormatHelp(cmd, helper) {
|
|
3881
|
+
const sections = [];
|
|
3882
|
+
const beforeAll = resolveHelpText(cmd, "beforeAll");
|
|
3883
|
+
if (beforeAll) sections.push(beforeAll);
|
|
3884
|
+
const desc = cmd.description();
|
|
3885
|
+
if (desc) sections.push(desc);
|
|
3886
|
+
const before = resolveHelpText(cmd, "before");
|
|
3887
|
+
if (before) sections.push(before);
|
|
3888
|
+
const visibleOpts = reorderHelpVersionLast(helper.visibleOptions(cmd));
|
|
3889
|
+
const visibleCmds = helper.visibleCommands(cmd);
|
|
3890
|
+
const args = cmd.registeredArguments ?? [];
|
|
3891
|
+
let usage = `Usage: ${cmd.name()}`;
|
|
3892
|
+
if (visibleOpts.length > 0) usage += " [OPTIONS]";
|
|
3893
|
+
for (const a of args) {
|
|
3894
|
+
const n = a.name().toUpperCase();
|
|
3895
|
+
usage += a.required ? ` <${n}>` : ` [${n}]`;
|
|
3896
|
+
}
|
|
3897
|
+
if (visibleCmds.length > 0) usage += " [COMMAND]";
|
|
3898
|
+
sections.push(usage);
|
|
3899
|
+
if (visibleCmds.length > 0) {
|
|
3900
|
+
const terms = visibleCmds.map((c) => c.name());
|
|
3901
|
+
const w = Math.max(...terms.map((t) => t.length));
|
|
3902
|
+
const lines = ["Commands:"];
|
|
3903
|
+
visibleCmds.forEach((sub, i) => {
|
|
3904
|
+
lines.push(` ${terms[i].padEnd(w)} ${sub.description()}`);
|
|
3905
|
+
});
|
|
3906
|
+
sections.push(lines.join("\n"));
|
|
3907
|
+
}
|
|
3908
|
+
if (visibleOpts.length > 0) {
|
|
3909
|
+
const terms = visibleOpts.map(optionTerm);
|
|
3910
|
+
const w = Math.max(...terms.map((t) => t.length));
|
|
3911
|
+
const lines = ["Options:"];
|
|
3912
|
+
visibleOpts.forEach((opt, i) => {
|
|
3913
|
+
lines.push(` ${terms[i].padEnd(w)} ${optionDescription(opt)}`);
|
|
3914
|
+
});
|
|
3915
|
+
sections.push(lines.join("\n"));
|
|
3916
|
+
}
|
|
3917
|
+
const after = resolveHelpText(cmd, "after");
|
|
3918
|
+
if (after) sections.push(after);
|
|
3919
|
+
const afterAll = resolveHelpText(cmd, "afterAll");
|
|
3920
|
+
if (afterAll) sections.push(afterAll);
|
|
3921
|
+
return sections.join("\n\n") + "\n";
|
|
3922
|
+
}
|
|
3923
|
+
|
|
3924
|
+
// src/validate.ts
|
|
3925
|
+
init_esm_shims();
|
|
3926
|
+
init_errors();
|
|
3927
|
+
var MODULE_ID_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/;
|
|
3928
|
+
var MAX_MODULE_ID_LENGTH = 192;
|
|
3929
|
+
function validateModuleId(moduleId) {
|
|
3930
|
+
if (moduleId.length > MAX_MODULE_ID_LENGTH) {
|
|
3931
|
+
process.stderr.write(
|
|
3932
|
+
`Error: Invalid module ID format: '${moduleId}'. Maximum length is ${MAX_MODULE_ID_LENGTH} characters.
|
|
3933
|
+
`
|
|
3934
|
+
);
|
|
3935
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3936
|
+
}
|
|
3937
|
+
if (!MODULE_ID_PATTERN.test(moduleId)) {
|
|
3938
|
+
process.stderr.write(
|
|
3939
|
+
`Error: Invalid module ID format: '${moduleId}'.
|
|
3940
|
+
`
|
|
3941
|
+
);
|
|
3942
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
// src/main.ts
|
|
3947
|
+
var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
|
|
3948
|
+
var verboseHelp = false;
|
|
3949
|
+
function setAllOptionsHelp(allOptions) {
|
|
3950
|
+
verboseHelp = allOptions;
|
|
3951
|
+
}
|
|
3952
|
+
function setVerboseHelp(verbose) {
|
|
3953
|
+
setAllOptionsHelp(verbose);
|
|
3954
|
+
}
|
|
3955
|
+
var docsUrl = null;
|
|
3956
|
+
function setDocsUrl(url) {
|
|
3957
|
+
docsUrl = url;
|
|
3958
|
+
}
|
|
3959
|
+
function hasVerboseFlag() {
|
|
3960
|
+
return process.argv.includes("--all-options");
|
|
3961
|
+
}
|
|
3962
|
+
function resolveIntOption(cliValue, envValue, defaultValue) {
|
|
3963
|
+
if (cliValue !== void 0 && Number.isFinite(cliValue) && cliValue > 0) {
|
|
3964
|
+
return cliValue;
|
|
3965
|
+
}
|
|
3966
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
3967
|
+
const parsed = parseInt(envValue, 10);
|
|
3968
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
3969
|
+
return parsed;
|
|
3970
|
+
}
|
|
3971
|
+
process.stderr.write(
|
|
3972
|
+
`Warning: invalid integer env value '${envValue}'; using default ${defaultValue}.
|
|
3973
|
+
`
|
|
3974
|
+
);
|
|
3975
|
+
}
|
|
3976
|
+
return defaultValue;
|
|
3977
|
+
}
|
|
3978
|
+
function resolveStringOption(cliValue, envValue) {
|
|
3979
|
+
if (typeof cliValue === "string" && cliValue !== "") {
|
|
3980
|
+
return cliValue;
|
|
3981
|
+
}
|
|
3982
|
+
if (envValue !== void 0 && envValue !== "") {
|
|
3983
|
+
return envValue;
|
|
3984
|
+
}
|
|
3985
|
+
return void 0;
|
|
3986
|
+
}
|
|
3987
|
+
var VERSION = "0.0.0";
|
|
3988
|
+
try {
|
|
3989
|
+
const pkg = JSON.parse(readFileSync3(path5.resolve(__dirname2, "../package.json"), "utf-8"));
|
|
3990
|
+
VERSION = pkg.version;
|
|
3991
|
+
} catch {
|
|
3992
|
+
}
|
|
3993
|
+
function validateInputSchema(schema, input) {
|
|
3994
|
+
const required = schema.required;
|
|
3995
|
+
if (required && Array.isArray(required)) {
|
|
3996
|
+
for (const field of required) {
|
|
3997
|
+
const val = input[field];
|
|
3998
|
+
if (val === null || val === void 0) {
|
|
3999
|
+
return `'${field}' is required`;
|
|
4000
|
+
}
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
const properties = schema.properties;
|
|
4004
|
+
if (properties) {
|
|
4005
|
+
for (const [field, propSchema] of Object.entries(properties)) {
|
|
4006
|
+
const val = input[field];
|
|
4007
|
+
if (val === null || val === void 0) continue;
|
|
4008
|
+
const expectedType = propSchema.type;
|
|
4009
|
+
if (!expectedType) continue;
|
|
4010
|
+
const actualType = typeof val;
|
|
4011
|
+
if (expectedType === "string" && actualType !== "string") {
|
|
4012
|
+
return `'${field}' must be a string, got ${actualType}`;
|
|
4013
|
+
}
|
|
4014
|
+
if ((expectedType === "integer" || expectedType === "number") && actualType !== "number") {
|
|
4015
|
+
return `'${field}' must be a number, got ${actualType}`;
|
|
4016
|
+
}
|
|
4017
|
+
if (expectedType === "boolean" && actualType !== "boolean") {
|
|
4018
|
+
return `'${field}' must be a boolean, got ${actualType}`;
|
|
4019
|
+
}
|
|
4346
4020
|
}
|
|
4347
4021
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4022
|
+
return null;
|
|
4023
|
+
}
|
|
4024
|
+
function emitErrorJson(e, exitCode) {
|
|
4025
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
4026
|
+
const errRecord = err;
|
|
4027
|
+
const code = errRecord.code ?? "UNKNOWN";
|
|
4028
|
+
const payload = {
|
|
4029
|
+
error: true,
|
|
4030
|
+
code,
|
|
4031
|
+
message: err.message,
|
|
4032
|
+
exit_code: exitCode
|
|
4033
|
+
};
|
|
4034
|
+
for (const field of ["details", "suggestion", "ai_guidance", "retryable", "user_fixable"]) {
|
|
4035
|
+
const val = errRecord[field];
|
|
4036
|
+
if (val !== void 0 && val !== null) {
|
|
4037
|
+
payload[field] = val;
|
|
4038
|
+
}
|
|
4350
4039
|
}
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4040
|
+
process.stderr.write(JSON.stringify(payload) + "\n");
|
|
4041
|
+
}
|
|
4042
|
+
function emitErrorTty(e, exitCode) {
|
|
4043
|
+
const err = e instanceof Error ? e : new Error(String(e));
|
|
4044
|
+
const errRecord = err;
|
|
4045
|
+
const code = errRecord.code;
|
|
4046
|
+
const header = code ? `Error [${code}]: ${err.message}` : `Error: ${err.message}`;
|
|
4047
|
+
process.stderr.write(header + "\n");
|
|
4048
|
+
const details = errRecord.details;
|
|
4049
|
+
if (details && typeof details === "object" && !Array.isArray(details)) {
|
|
4050
|
+
process.stderr.write("\n Details:\n");
|
|
4051
|
+
for (const [k, v] of Object.entries(details)) {
|
|
4052
|
+
process.stderr.write(` ${k}: ${v}
|
|
4363
4053
|
`);
|
|
4364
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4365
4054
|
}
|
|
4366
4055
|
}
|
|
4367
|
-
const
|
|
4368
|
-
if (
|
|
4369
|
-
process.stderr.write(
|
|
4370
|
-
|
|
4371
|
-
`
|
|
4372
|
-
);
|
|
4373
|
-
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4056
|
+
const suggestion = errRecord.suggestion;
|
|
4057
|
+
if (suggestion) {
|
|
4058
|
+
process.stderr.write(`
|
|
4059
|
+
Suggestion: ${suggestion}
|
|
4060
|
+
`);
|
|
4374
4061
|
}
|
|
4375
|
-
|
|
4376
|
-
|
|
4062
|
+
const retryable = errRecord.retryable;
|
|
4063
|
+
if (retryable !== void 0 && retryable !== null) {
|
|
4064
|
+
const label = retryable ? "Yes" : "No (same input will fail again)";
|
|
4065
|
+
process.stderr.write(` Retryable: ${label}
|
|
4066
|
+
`);
|
|
4377
4067
|
}
|
|
4378
|
-
|
|
4068
|
+
process.stderr.write(`
|
|
4069
|
+
Exit code: ${exitCode}
|
|
4070
|
+
`);
|
|
4071
|
+
}
|
|
4072
|
+
function createCli(extensionsDirOrOpts, progName, allOptions = false) {
|
|
4073
|
+
let extensionsDir;
|
|
4074
|
+
let registry;
|
|
4075
|
+
let executor;
|
|
4076
|
+
let extraCommands;
|
|
4077
|
+
let app;
|
|
4078
|
+
let expose;
|
|
4079
|
+
let apcliOption;
|
|
4080
|
+
let appVersion;
|
|
4081
|
+
let appDescription;
|
|
4082
|
+
let allowedPrefixes;
|
|
4083
|
+
let builtinGroupName;
|
|
4084
|
+
if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
|
|
4085
|
+
extensionsDir = extensionsDirOrOpts.extensionsDir;
|
|
4086
|
+
progName = extensionsDirOrOpts.progName ?? progName;
|
|
4087
|
+
allOptions = extensionsDirOrOpts.allOptions ?? extensionsDirOrOpts.verbose ?? allOptions;
|
|
4088
|
+
app = extensionsDirOrOpts.app;
|
|
4089
|
+
registry = extensionsDirOrOpts.registry;
|
|
4090
|
+
executor = extensionsDirOrOpts.executor;
|
|
4091
|
+
extraCommands = extensionsDirOrOpts.extraCommands;
|
|
4092
|
+
expose = extensionsDirOrOpts.expose;
|
|
4093
|
+
apcliOption = extensionsDirOrOpts.apcli;
|
|
4094
|
+
appVersion = extensionsDirOrOpts.version;
|
|
4095
|
+
appDescription = extensionsDirOrOpts.description;
|
|
4096
|
+
builtinGroupName = extensionsDirOrOpts.builtinGroupName;
|
|
4097
|
+
allowedPrefixes = extensionsDirOrOpts.allowedPrefixes;
|
|
4098
|
+
} else {
|
|
4099
|
+
extensionsDir = extensionsDirOrOpts;
|
|
4100
|
+
}
|
|
4101
|
+
verboseHelp = allOptions;
|
|
4102
|
+
registerConfigNamespace();
|
|
4379
4103
|
try {
|
|
4380
|
-
|
|
4104
|
+
const auditLogger = new AuditLogger();
|
|
4105
|
+
setAuditLogger(auditLogger);
|
|
4381
4106
|
} catch {
|
|
4382
|
-
|
|
4383
|
-
|
|
4107
|
+
}
|
|
4108
|
+
const resolvedProgName = progName ?? path5.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
|
|
4109
|
+
const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
|
|
4110
|
+
setLogLevel(cliLogLevel);
|
|
4111
|
+
if (app && (registry || executor)) {
|
|
4112
|
+
process.stderr.write("Error: app is mutually exclusive with registry/executor\n");
|
|
4384
4113
|
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4385
4114
|
}
|
|
4386
|
-
if (
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4115
|
+
if (app) {
|
|
4116
|
+
registry = app.registry;
|
|
4117
|
+
executor = app.executor;
|
|
4118
|
+
}
|
|
4119
|
+
if (executor && !registry) {
|
|
4120
|
+
process.stderr.write("Error: executor requires registry \u2014 pass both or neither\n");
|
|
4391
4121
|
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4392
4122
|
}
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
4402
|
-
};
|
|
4403
|
-
const onError = (err) => {
|
|
4404
|
-
cleanup();
|
|
4405
|
-
reject(err);
|
|
4406
|
-
};
|
|
4407
|
-
const cleanup = () => {
|
|
4408
|
-
process.stdin.removeListener("data", onData);
|
|
4409
|
-
process.stdin.removeListener("end", onEnd);
|
|
4410
|
-
process.stdin.removeListener("error", onError);
|
|
4411
|
-
};
|
|
4412
|
-
process.stdin.on("data", onData);
|
|
4413
|
-
process.stdin.on("end", onEnd);
|
|
4414
|
-
process.stdin.on("error", onError);
|
|
4415
|
-
process.stdin.resume();
|
|
4416
|
-
});
|
|
4417
|
-
}
|
|
4418
|
-
function reconvertEnumValues(kwargs, options) {
|
|
4419
|
-
const result = { ...kwargs };
|
|
4420
|
-
for (const opt of options) {
|
|
4421
|
-
if (!opt.enumOriginalTypes) continue;
|
|
4422
|
-
const paramName = opt.name;
|
|
4423
|
-
if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
|
|
4424
|
-
continue;
|
|
4425
|
-
}
|
|
4426
|
-
const strVal = String(result[paramName]);
|
|
4427
|
-
const origType = opt.enumOriginalTypes[strVal];
|
|
4428
|
-
if (origType === "int") {
|
|
4429
|
-
result[paramName] = parseInt(strVal, 10);
|
|
4430
|
-
} else if (origType === "float") {
|
|
4431
|
-
result[paramName] = parseFloat(strVal);
|
|
4432
|
-
} else if (origType === "bool") {
|
|
4433
|
-
result[paramName] = strVal.toLowerCase() === "true";
|
|
4123
|
+
if (executor && typeof executor.setApprovalHandler === "function") {
|
|
4124
|
+
try {
|
|
4125
|
+
const handler = new CliApprovalHandler(
|
|
4126
|
+
/*autoApprove*/
|
|
4127
|
+
false
|
|
4128
|
+
);
|
|
4129
|
+
executor.setApprovalHandler(handler);
|
|
4130
|
+
} catch {
|
|
4434
4131
|
}
|
|
4435
4132
|
}
|
|
4436
|
-
|
|
4437
|
-
}
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
init_esm_shims();
|
|
4441
|
-
import { Command as Command6 } from "commander";
|
|
4442
|
-
init_logger();
|
|
4443
|
-
init_errors();
|
|
4444
|
-
function assertNotReserved(kind, name, moduleId) {
|
|
4445
|
-
if (!getReservedGroupNames().has(name)) return;
|
|
4446
|
-
let msg;
|
|
4447
|
-
if (kind === "group") {
|
|
4448
|
-
msg = `Error: Module '${moduleId}': display.cli.group '${name}' is reserved. Use a different CLI alias or set display.cli.group to another value.
|
|
4449
|
-
`;
|
|
4450
|
-
} else if (kind === "auto-group") {
|
|
4451
|
-
msg = `Error: Module '${moduleId}': auto-group '${name}' is reserved. Rename the module id or set display.cli.group to another value.
|
|
4452
|
-
`;
|
|
4453
|
-
} else {
|
|
4454
|
-
msg = `Error: Module '${moduleId}': top-level CLI name '${name}' is reserved. Use a different CLI alias.
|
|
4455
|
-
`;
|
|
4133
|
+
const registryInjected = registry !== void 0;
|
|
4134
|
+
const program = new Command6(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("--all-options", "Show all options in help output (including built-in options)");
|
|
4135
|
+
if (appVersion) {
|
|
4136
|
+
program.version(appVersion, "-V, --version", "Print version");
|
|
4456
4137
|
}
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
executor;
|
|
4463
|
-
helpTextMaxLength;
|
|
4464
|
-
commandCache = /* @__PURE__ */ new Map();
|
|
4465
|
-
/** alias -> canonical module_id (populated lazily) */
|
|
4466
|
-
aliasMap = /* @__PURE__ */ new Map();
|
|
4467
|
-
/** module_id -> descriptor cache (populated during alias map build) */
|
|
4468
|
-
descriptorCache = /* @__PURE__ */ new Map();
|
|
4469
|
-
aliasMapBuilt = false;
|
|
4470
|
-
constructor(registry, executor, helpTextMaxLength = 1e3) {
|
|
4471
|
-
this.registry = registry;
|
|
4472
|
-
this.executor = executor;
|
|
4473
|
-
this.helpTextMaxLength = helpTextMaxLength;
|
|
4138
|
+
program.configureHelp({ formatHelp: canonicalFormatHelp });
|
|
4139
|
+
if (!registryInjected) {
|
|
4140
|
+
program.option("--extensions-dir <path>", "Path to extensions directory");
|
|
4141
|
+
program.option("--commands-dir <path>", "Path to convention-based commands directory");
|
|
4142
|
+
program.option("--binding <path>", "Path to binding.yaml for display overlay");
|
|
4474
4143
|
}
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4144
|
+
let apcliCfg;
|
|
4145
|
+
try {
|
|
4146
|
+
if (apcliOption instanceof ApcliGroup) {
|
|
4147
|
+
if (builtinGroupName !== void 0 && builtinGroupName !== "apcli" && apcliOption.name !== builtinGroupName) {
|
|
4148
|
+
throw new Error(
|
|
4149
|
+
`builtinGroupName=${JSON.stringify(builtinGroupName)} conflicts with the name on the supplied ApcliGroup (${JSON.stringify(apcliOption.name)}). Pass only one.`
|
|
4150
|
+
);
|
|
4151
|
+
}
|
|
4152
|
+
apcliCfg = apcliOption;
|
|
4153
|
+
} else if (apcliOption !== void 0) {
|
|
4154
|
+
apcliCfg = ApcliGroup.fromCliConfig(apcliOption, {
|
|
4155
|
+
registryInjected,
|
|
4156
|
+
name: builtinGroupName
|
|
4157
|
+
});
|
|
4158
|
+
} else {
|
|
4159
|
+
let yamlVal = null;
|
|
4160
|
+
try {
|
|
4161
|
+
const resolver = new ConfigResolver();
|
|
4162
|
+
yamlVal = resolver.resolveObject("apcli");
|
|
4163
|
+
} catch {
|
|
4164
|
+
yamlVal = null;
|
|
4165
|
+
}
|
|
4166
|
+
apcliCfg = ApcliGroup.fromYaml(yamlVal, {
|
|
4167
|
+
registryInjected,
|
|
4168
|
+
name: builtinGroupName
|
|
4169
|
+
});
|
|
4170
|
+
}
|
|
4171
|
+
} catch (e) {
|
|
4172
|
+
process.stderr.write(`Error: ${e instanceof Error ? e.message : String(e)}
|
|
4173
|
+
`);
|
|
4174
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4175
|
+
}
|
|
4176
|
+
setReservedGroupNames(/* @__PURE__ */ new Set([apcliCfg.name]));
|
|
4177
|
+
const apcliGroup = program.command(apcliCfg.name, { hidden: !apcliCfg.isGroupVisible() }).description("Built-in commands");
|
|
4178
|
+
if (registry) {
|
|
4179
|
+
program._registry = registry;
|
|
4180
|
+
if (executor) {
|
|
4181
|
+
program._executor = executor;
|
|
4481
4182
|
}
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
} catch {
|
|
4495
|
-
warn("Failed to build alias map from registry");
|
|
4183
|
+
} else {
|
|
4184
|
+
const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
|
|
4185
|
+
void resolvedExtDir;
|
|
4186
|
+
}
|
|
4187
|
+
let exposureFilter;
|
|
4188
|
+
try {
|
|
4189
|
+
if (expose instanceof ExposureFilter) {
|
|
4190
|
+
exposureFilter = expose;
|
|
4191
|
+
} else if (typeof expose === "object" && expose !== null) {
|
|
4192
|
+
exposureFilter = ExposureFilter.fromConfig({ expose });
|
|
4193
|
+
} else {
|
|
4194
|
+
exposureFilter = new ExposureFilter();
|
|
4496
4195
|
}
|
|
4196
|
+
} catch (err) {
|
|
4197
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4198
|
+
process.stderr.write(`Error: invalid 'expose' option \u2014 ${msg}
|
|
4199
|
+
`);
|
|
4200
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4497
4201
|
}
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4202
|
+
_registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter);
|
|
4203
|
+
program.addHelpText("after", [
|
|
4204
|
+
"",
|
|
4205
|
+
"Use --help --all-options to show all options (including built-in options).",
|
|
4206
|
+
"Use --help --man to display a formatted man page."
|
|
4207
|
+
].join("\n"));
|
|
4208
|
+
configureManHelp(program, resolvedProgName, appVersion ?? VERSION);
|
|
4209
|
+
if (extraCommands && extraCommands.length > 0) {
|
|
4210
|
+
const _reservedForExtra = /* @__PURE__ */ new Set([apcliCfg.name]);
|
|
4211
|
+
for (const cmd of extraCommands) {
|
|
4212
|
+
const cmdName = cmd.name();
|
|
4213
|
+
if (_reservedForExtra.has(cmdName)) {
|
|
4214
|
+
process.stderr.write(
|
|
4215
|
+
`Error: extraCommands name '${cmdName}' is reserved
|
|
4216
|
+
`
|
|
4217
|
+
);
|
|
4218
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4219
|
+
}
|
|
4220
|
+
const existing = program.commands.find((c) => c.name() === cmdName);
|
|
4221
|
+
if (existing) {
|
|
4222
|
+
process.stderr.write(
|
|
4223
|
+
`Error: extraCommands name '${cmdName}' collides with an existing command
|
|
4224
|
+
`
|
|
4225
|
+
);
|
|
4226
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4227
|
+
}
|
|
4228
|
+
program.addCommand(cmd);
|
|
4506
4229
|
}
|
|
4507
|
-
const moduleIds = this.registry.listModules().map((m) => m.id);
|
|
4508
|
-
const names = moduleIds.map((mid) => reverse.get(mid) ?? mid);
|
|
4509
|
-
return [...new Set(names)].sort();
|
|
4510
4230
|
}
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
|
|
4516
|
-
|
|
4231
|
+
program.hook("preAction", async (thisCommand) => {
|
|
4232
|
+
const opts = thisCommand.opts();
|
|
4233
|
+
const commandsDir = opts.commandsDir;
|
|
4234
|
+
const bindingPath = opts.binding;
|
|
4235
|
+
await applyToolkitIntegration(commandsDir, bindingPath, { allowedPrefixes });
|
|
4236
|
+
});
|
|
4237
|
+
return program;
|
|
4238
|
+
}
|
|
4239
|
+
var _ALWAYS_REGISTERED = /* @__PURE__ */ new Set(["exec"]);
|
|
4240
|
+
function _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter) {
|
|
4241
|
+
const emitUnwiredError = () => {
|
|
4242
|
+
process.stderr.write(
|
|
4243
|
+
"Error: no module registry wired. In standalone mode, pass --extensions-dir <path> to enable module discovery.\n"
|
|
4244
|
+
);
|
|
4245
|
+
process.exit(EXIT_CODES.CONFIG_INVALID);
|
|
4246
|
+
};
|
|
4247
|
+
const effectiveRegistry = registry ?? {
|
|
4248
|
+
list: () => emitUnwiredError(),
|
|
4249
|
+
getDefinition: () => emitUnwiredError()
|
|
4250
|
+
};
|
|
4251
|
+
const TABLE = [
|
|
4252
|
+
{ name: "list", requiresExecutor: false, register: (g) => registerListCommand(g, effectiveRegistry, exposureFilter) },
|
|
4253
|
+
{ name: "describe", requiresExecutor: false, register: (g) => registerDescribeCommand(g, effectiveRegistry) },
|
|
4254
|
+
{ name: "exec", requiresExecutor: true, register: (g, _r, ex) => registerExecCommand(g, effectiveRegistry, ex) },
|
|
4255
|
+
{ name: "validate", requiresExecutor: true, register: (g, _r, ex) => registerValidateCommand(g, effectiveRegistry, ex) },
|
|
4256
|
+
{ name: "init", requiresExecutor: false, register: (g) => registerInitCommand(g) },
|
|
4257
|
+
{ name: "health", requiresExecutor: true, register: (g, _r, ex) => registerHealthCommand(g, ex) },
|
|
4258
|
+
{ name: "usage", requiresExecutor: true, register: (g, _r, ex) => registerUsageCommand(g, ex) },
|
|
4259
|
+
{ name: "enable", requiresExecutor: true, register: (g, _r, ex) => registerEnableCommand(g, ex) },
|
|
4260
|
+
{ name: "disable", requiresExecutor: true, register: (g, _r, ex) => registerDisableCommand(g, ex) },
|
|
4261
|
+
{ name: "reload", requiresExecutor: true, register: (g, _r, ex) => registerReloadCommand(g, ex) },
|
|
4262
|
+
{ name: "config", requiresExecutor: true, register: (g, _r, ex) => registerConfigCommand(g, ex) },
|
|
4263
|
+
{ name: "completion", requiresExecutor: false, register: (g) => registerCompletionCommand(g) },
|
|
4264
|
+
{ name: "describe-pipeline", requiresExecutor: true, register: (g, _r, ex) => registerPipelineCommand(g, ex) }
|
|
4265
|
+
];
|
|
4266
|
+
const _SYSTEM_COMMANDS = /* @__PURE__ */ new Set(["health", "usage", "enable", "disable", "reload", "config"]);
|
|
4267
|
+
const systemModulesAvailable = (() => {
|
|
4268
|
+
if (!executor) return false;
|
|
4269
|
+
const reg = executor.registry ?? registry;
|
|
4270
|
+
if (!reg) return false;
|
|
4271
|
+
try {
|
|
4272
|
+
return reg.getDefinition("system.health.summary") != null;
|
|
4273
|
+
} catch {
|
|
4274
|
+
return false;
|
|
4517
4275
|
}
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4276
|
+
})();
|
|
4277
|
+
const mode = apcliCfg.resolveVisibility();
|
|
4278
|
+
for (const entry of TABLE) {
|
|
4279
|
+
let shouldRegister;
|
|
4280
|
+
if (mode === "all" || mode === "none") {
|
|
4281
|
+
shouldRegister = true;
|
|
4282
|
+
} else {
|
|
4283
|
+
shouldRegister = _ALWAYS_REGISTERED.has(entry.name) || apcliCfg.isSubcommandIncluded(entry.name);
|
|
4523
4284
|
}
|
|
4524
|
-
if (!
|
|
4525
|
-
|
|
4285
|
+
if (!shouldRegister) continue;
|
|
4286
|
+
if (_SYSTEM_COMMANDS.has(entry.name) && !systemModulesAvailable) continue;
|
|
4287
|
+
if (entry.requiresExecutor && !executor) {
|
|
4288
|
+
if (_ALWAYS_REGISTERED.has(entry.name)) {
|
|
4289
|
+
warn(
|
|
4290
|
+
`apcli.${entry.name} is in _ALWAYS_REGISTERED but no executor is wired \u2014 subcommand unavailable. Pass executor to createCli() or avoid ${entry.name} invocations.`
|
|
4291
|
+
);
|
|
4292
|
+
}
|
|
4293
|
+
continue;
|
|
4526
4294
|
}
|
|
4527
|
-
|
|
4528
|
-
this.commandCache.set(cmdName, cmd);
|
|
4529
|
-
return cmd;
|
|
4295
|
+
entry.register(apcliGroup, registry, executor);
|
|
4530
4296
|
}
|
|
4531
|
-
}
|
|
4532
|
-
var
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
this.members = members;
|
|
4540
|
-
this._executor = executor;
|
|
4541
|
-
this._helpTextMaxLength = helpTextMaxLength;
|
|
4542
|
-
this.command = new Command6(name).description(`${name} commands`);
|
|
4543
|
-
for (const [cmdName, [, descriptor]] of this.members) {
|
|
4544
|
-
const cmd = buildModuleCommand(
|
|
4545
|
-
descriptor,
|
|
4546
|
-
this._executor,
|
|
4547
|
-
this._helpTextMaxLength,
|
|
4548
|
-
cmdName
|
|
4549
|
-
);
|
|
4550
|
-
this._cmdCache.set(cmdName, cmd);
|
|
4551
|
-
this.command.addCommand(cmd);
|
|
4552
|
-
}
|
|
4297
|
+
}
|
|
4298
|
+
var bindingDisplayMap = /* @__PURE__ */ new Map();
|
|
4299
|
+
function lookupBindingDisplay(moduleId) {
|
|
4300
|
+
return bindingDisplayMap.get(moduleId);
|
|
4301
|
+
}
|
|
4302
|
+
async function applyToolkitIntegration(commandsDir, bindingPath, options = {}) {
|
|
4303
|
+
if (!commandsDir && !bindingPath) {
|
|
4304
|
+
return;
|
|
4553
4305
|
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4306
|
+
if (commandsDir) {
|
|
4307
|
+
warn("Convention scanning not available in the TypeScript toolkit");
|
|
4556
4308
|
}
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
}
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
return null;
|
|
4309
|
+
if (bindingPath) {
|
|
4310
|
+
try {
|
|
4311
|
+
await loadBindingDisplayOverlay(bindingPath, options.allowedPrefixes);
|
|
4312
|
+
} catch (err) {
|
|
4313
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4314
|
+
warn(`apcore-toolkit: failed to load binding '${bindingPath}': ${msg}`);
|
|
4564
4315
|
}
|
|
4565
|
-
const [, descriptor] = entry;
|
|
4566
|
-
const cmd = buildModuleCommand(
|
|
4567
|
-
descriptor,
|
|
4568
|
-
this._executor,
|
|
4569
|
-
this._helpTextMaxLength,
|
|
4570
|
-
cmdName
|
|
4571
|
-
);
|
|
4572
|
-
this._cmdCache.set(cmdName, cmd);
|
|
4573
|
-
return cmd;
|
|
4574
4316
|
}
|
|
4575
|
-
}
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4317
|
+
}
|
|
4318
|
+
async function loadBindingDisplayOverlay(bindingPath, allowedPrefixes) {
|
|
4319
|
+
const loader = new BindingLoader();
|
|
4320
|
+
const scanned = loader.load(bindingPath);
|
|
4321
|
+
const resolver = new DisplayResolver();
|
|
4322
|
+
const resolved = resolver.resolve(scanned, { bindingPath });
|
|
4323
|
+
const prefixes = allowedPrefixes && allowedPrefixes.length > 0 ? allowedPrefixes : null;
|
|
4324
|
+
const isTargetAllowed = (target) => {
|
|
4325
|
+
if (!prefixes) return true;
|
|
4326
|
+
if (typeof target !== "string" || target.length === 0) return true;
|
|
4327
|
+
return prefixes.some((p) => target.startsWith(p));
|
|
4328
|
+
};
|
|
4329
|
+
for (const mod of resolved) {
|
|
4330
|
+
if (!mod || typeof mod !== "object") continue;
|
|
4331
|
+
const entry = mod;
|
|
4332
|
+
const id = typeof entry.moduleId === "string" ? entry.moduleId : null;
|
|
4333
|
+
if (!id) continue;
|
|
4334
|
+
if (!isTargetAllowed(entry.target)) {
|
|
4335
|
+
warn(
|
|
4336
|
+
`apcore-toolkit: dropped binding entry '${id}' \u2014 target '${String(entry.target)}' is outside allowedPrefixes`
|
|
4337
|
+
);
|
|
4338
|
+
continue;
|
|
4339
|
+
}
|
|
4340
|
+
const meta = entry.metadata ?? {};
|
|
4341
|
+
const display = meta.display;
|
|
4342
|
+
if (display && typeof display === "object" && !Array.isArray(display)) {
|
|
4343
|
+
bindingDisplayMap.set(id, display);
|
|
4344
|
+
}
|
|
4592
4345
|
}
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4346
|
+
}
|
|
4347
|
+
function main(progName) {
|
|
4348
|
+
verboseHelp = hasVerboseFlag();
|
|
4349
|
+
const program = createCli({
|
|
4350
|
+
progName,
|
|
4351
|
+
allOptions: verboseHelp,
|
|
4352
|
+
version: VERSION,
|
|
4353
|
+
description: `${progName ?? "apcore-cli"} \u2014 execute modules from the command line`
|
|
4354
|
+
});
|
|
4355
|
+
try {
|
|
4356
|
+
program.parse(process.argv);
|
|
4357
|
+
} catch (error) {
|
|
4358
|
+
if (error instanceof CommanderError) {
|
|
4359
|
+
process.exit(error.exitCode);
|
|
4360
|
+
}
|
|
4361
|
+
const code = exitCodeForError(error);
|
|
4362
|
+
if (error instanceof Error) {
|
|
4363
|
+
process.stderr.write(`Error: ${error.message}
|
|
4364
|
+
`);
|
|
4600
4365
|
}
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4366
|
+
process.exit(code);
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName, verbose = verboseHelp) {
|
|
4370
|
+
const moduleId = moduleDef.moduleId;
|
|
4371
|
+
let resolvedSchema = {};
|
|
4372
|
+
let schemaOptions = [];
|
|
4373
|
+
const display = getDisplay(moduleDef);
|
|
4374
|
+
const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
|
|
4375
|
+
const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
|
|
4376
|
+
const cmdHelp = cliDisplay.description ?? moduleDef.description;
|
|
4377
|
+
const inputSchema = moduleDef.inputSchema;
|
|
4378
|
+
if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
|
|
4379
|
+
try {
|
|
4380
|
+
resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
|
|
4381
|
+
} catch (err) {
|
|
4382
|
+
if (err instanceof MaxDepthExceededError || err instanceof CircularRefError) {
|
|
4383
|
+
process.stderr.write(`Error: ${err.message}
|
|
4384
|
+
`);
|
|
4385
|
+
process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
|
|
4386
|
+
}
|
|
4387
|
+
if (err instanceof UnresolvableRefError) {
|
|
4388
|
+
process.stderr.write(`Error: ${err.message}
|
|
4389
|
+
`);
|
|
4390
|
+
process.exit(EXIT_CODES.SCHEMA_VALIDATION_ERROR);
|
|
4606
4391
|
}
|
|
4392
|
+
resolvedSchema = inputSchema;
|
|
4607
4393
|
}
|
|
4608
|
-
|
|
4394
|
+
schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
|
|
4609
4395
|
}
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4396
|
+
const cmd = new Command6(effectiveCmdName).description(cmdHelp);
|
|
4397
|
+
const inputOpt = new Option4("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
|
|
4398
|
+
const yesOpt = new Option4("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
|
|
4399
|
+
const largeInputOpt = new Option4("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
|
|
4400
|
+
const formatOpt = new Option4("--format <format>", "Output format: json, table, csv, yaml, jsonl.").choices(["json", "table", "csv", "yaml", "jsonl"]);
|
|
4401
|
+
const fieldsOpt = new Option4("--fields <fields>", "Comma-separated dot-paths to select from the result (e.g., 'status,data.count').");
|
|
4402
|
+
const sandboxOpt = new Option4("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
|
|
4403
|
+
const dryRunOpt = new Option4("--dry-run", "Run preflight checks without executing the module. Shows validation results.").default(false);
|
|
4404
|
+
const traceOpt = new Option4("--trace", "Show execution pipeline trace with per-step timing after the result.").default(false);
|
|
4405
|
+
const streamOpt = new Option4("--stream", "Stream module output as JSONL (one JSON object per line, flushed immediately).").default(false);
|
|
4406
|
+
const strategyOpt = new Option4("--strategy <name>", "Execution pipeline strategy: standard (default), internal, testing, performance.").choices(["standard", "internal", "testing", "performance", "minimal"]);
|
|
4407
|
+
const approvalTimeoutOpt = new Option4("--approval-timeout <seconds>", "Override approval prompt timeout in seconds (default: 60).").argParser(parseInt);
|
|
4408
|
+
const approvalTokenOpt = new Option4("--approval-token <token>", "Resume a pending approval with the given token (for async approval flows).");
|
|
4409
|
+
if (!verbose) {
|
|
4410
|
+
inputOpt.hideHelp();
|
|
4411
|
+
yesOpt.hideHelp();
|
|
4412
|
+
largeInputOpt.hideHelp();
|
|
4413
|
+
formatOpt.hideHelp();
|
|
4414
|
+
fieldsOpt.hideHelp();
|
|
4415
|
+
dryRunOpt.hideHelp();
|
|
4416
|
+
traceOpt.hideHelp();
|
|
4417
|
+
streamOpt.hideHelp();
|
|
4418
|
+
strategyOpt.hideHelp();
|
|
4419
|
+
approvalTimeoutOpt.hideHelp();
|
|
4420
|
+
approvalTokenOpt.hideHelp();
|
|
4421
|
+
}
|
|
4422
|
+
cmd.addOption(inputOpt);
|
|
4423
|
+
cmd.addOption(yesOpt);
|
|
4424
|
+
cmd.addOption(largeInputOpt);
|
|
4425
|
+
cmd.addOption(formatOpt);
|
|
4426
|
+
cmd.addOption(fieldsOpt);
|
|
4427
|
+
cmd.addOption(sandboxOpt);
|
|
4428
|
+
cmd.addOption(dryRunOpt);
|
|
4429
|
+
cmd.addOption(traceOpt);
|
|
4430
|
+
cmd.addOption(streamOpt);
|
|
4431
|
+
cmd.addOption(strategyOpt);
|
|
4432
|
+
cmd.addOption(approvalTimeoutOpt);
|
|
4433
|
+
cmd.addOption(approvalTokenOpt);
|
|
4434
|
+
const footerParts = [];
|
|
4435
|
+
if (!verbose) {
|
|
4436
|
+
footerParts.push("Use --all-options to show all options (including built-in options).");
|
|
4437
|
+
}
|
|
4438
|
+
if (docsUrl) {
|
|
4439
|
+
footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
|
|
4440
|
+
}
|
|
4441
|
+
if (footerParts.length > 0) {
|
|
4442
|
+
cmd.addHelpText("after", "\n" + footerParts.join("\n") + "\n");
|
|
4443
|
+
}
|
|
4444
|
+
for (const opt of schemaOptions) {
|
|
4445
|
+
if (opt.isBooleanFlag) {
|
|
4446
|
+
const flagBase = opt.name.replace(/_/g, "-");
|
|
4447
|
+
cmd.addOption(
|
|
4448
|
+
new Option4(`--${flagBase}`, opt.description).default(
|
|
4449
|
+
opt.defaultValue
|
|
4450
|
+
)
|
|
4451
|
+
);
|
|
4452
|
+
cmd.addOption(new Option4(`--no-${flagBase}`).hideHelp());
|
|
4453
|
+
} else if (opt.parseArg) {
|
|
4454
|
+
cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
|
|
4455
|
+
} else {
|
|
4456
|
+
cmd.option(opt.flags, opt.description, opt.defaultValue);
|
|
4639
4457
|
}
|
|
4640
|
-
return [null, cliName];
|
|
4641
4458
|
}
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4459
|
+
cmd.action(async (options) => {
|
|
4460
|
+
const stdinFlag = options.input;
|
|
4461
|
+
const autoApprove = options.yes;
|
|
4462
|
+
const largeInput = options.largeInput;
|
|
4463
|
+
const outputFormat = options.format;
|
|
4464
|
+
const outputFields = options.fields;
|
|
4465
|
+
const sandboxEnabled = options.sandbox;
|
|
4466
|
+
const dryRun = options.dryRun;
|
|
4467
|
+
const traceFlag = options.trace;
|
|
4468
|
+
const streamFlag = options.stream;
|
|
4469
|
+
const strategyName = resolveStringOption(options.strategy, process.env.APCORE_CLI_STRATEGY);
|
|
4470
|
+
const approvalTimeout = resolveIntOption(
|
|
4471
|
+
options.approvalTimeout,
|
|
4472
|
+
process.env.APCORE_CLI_APPROVAL_TIMEOUT,
|
|
4473
|
+
60
|
|
4474
|
+
);
|
|
4475
|
+
const approvalToken = options.approvalToken;
|
|
4476
|
+
const schemaKwargs = {};
|
|
4477
|
+
for (const opt of schemaOptions) {
|
|
4478
|
+
const commanderKey = opt.name.replace(/_([a-z0-9])/g, (_, c) => c.toUpperCase());
|
|
4479
|
+
if (commanderKey in options) {
|
|
4480
|
+
schemaKwargs[opt.name] = options[commanderKey];
|
|
4481
|
+
} else if (opt.name in options) {
|
|
4482
|
+
schemaKwargs[opt.name] = options[opt.name];
|
|
4483
|
+
}
|
|
4652
4484
|
}
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4485
|
+
let merged = {};
|
|
4486
|
+
const startTime = performance.now();
|
|
4487
|
+
try {
|
|
4488
|
+
merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
|
|
4489
|
+
const reconverted = reconvertEnumValues(merged, schemaOptions);
|
|
4490
|
+
merged = reconverted;
|
|
4491
|
+
if (dryRun) {
|
|
4492
|
+
if (!executor.validate) {
|
|
4493
|
+
process.stderr.write("Error: Executor does not support validate.\n");
|
|
4494
|
+
process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
|
|
4495
|
+
}
|
|
4496
|
+
const preflight = await executor.validate(moduleId, merged);
|
|
4497
|
+
formatPreflightResult(preflight, outputFormat);
|
|
4498
|
+
if (traceFlag) {
|
|
4499
|
+
const pureSteps = /* @__PURE__ */ new Set([
|
|
4500
|
+
"context_creation",
|
|
4501
|
+
"call_chain_guard",
|
|
4502
|
+
"module_lookup",
|
|
4503
|
+
"acl_check",
|
|
4504
|
+
"input_validation"
|
|
4505
|
+
]);
|
|
4506
|
+
const allSteps = [
|
|
4507
|
+
"context_creation",
|
|
4508
|
+
"call_chain_guard",
|
|
4509
|
+
"module_lookup",
|
|
4510
|
+
"acl_check",
|
|
4511
|
+
"approval_gate",
|
|
4512
|
+
"middleware_before",
|
|
4513
|
+
"input_validation",
|
|
4514
|
+
"execute",
|
|
4515
|
+
"output_validation",
|
|
4516
|
+
"middleware_after",
|
|
4517
|
+
"return_result"
|
|
4518
|
+
];
|
|
4519
|
+
process.stderr.write("\nPipeline preview (dry-run):\n");
|
|
4520
|
+
for (const s of allSteps) {
|
|
4521
|
+
if (pureSteps.has(s)) {
|
|
4522
|
+
process.stderr.write(` \u2713 ${s.padEnd(24)} (pure \u2014 would execute)
|
|
4523
|
+
`);
|
|
4524
|
+
} else {
|
|
4525
|
+
process.stderr.write(` \u25CB ${s.padEnd(24)} (impure \u2014 skipped in dry-run)
|
|
4526
|
+
`);
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
}
|
|
4530
|
+
process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
|
|
4659
4531
|
}
|
|
4660
|
-
if (
|
|
4661
|
-
|
|
4532
|
+
if (resolvedSchema.properties) {
|
|
4533
|
+
const validationErr = validateInputSchema(resolvedSchema, merged);
|
|
4534
|
+
if (validationErr) {
|
|
4535
|
+
throw new SchemaValidationError(`Validation failed: ${validationErr}`);
|
|
4536
|
+
}
|
|
4662
4537
|
}
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4538
|
+
if (approvalToken) {
|
|
4539
|
+
merged._approval_token = approvalToken;
|
|
4540
|
+
}
|
|
4541
|
+
await checkApproval(moduleDef, autoApprove, approvalTimeout);
|
|
4542
|
+
if (streamFlag) {
|
|
4543
|
+
if (resolveFormat(outputFormat) === "table") {
|
|
4544
|
+
process.stderr.write("Warning: Streaming mode always outputs JSONL; --format table is ignored.\n");
|
|
4545
|
+
}
|
|
4546
|
+
const annotations = moduleDef.annotations;
|
|
4547
|
+
const isStreaming = annotations?.streaming === true;
|
|
4548
|
+
if (!isStreaming) {
|
|
4549
|
+
process.stderr.write(
|
|
4550
|
+
`Warning: Module '${moduleId}' does not declare streaming support. Falling back to standard execution.
|
|
4551
|
+
`
|
|
4552
|
+
);
|
|
4553
|
+
}
|
|
4554
|
+
if (isStreaming && executor.stream) {
|
|
4555
|
+
let chunks = 0;
|
|
4556
|
+
for await (const chunk of executor.stream(moduleId, merged)) {
|
|
4557
|
+
chunks++;
|
|
4558
|
+
process.stdout.write(JSON.stringify(chunk) + "\n");
|
|
4559
|
+
if (process.stderr.isTTY) {
|
|
4560
|
+
process.stderr.write(`\rStreaming ${moduleId}... (${chunks} chunks)`);
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
if (process.stderr.isTTY) {
|
|
4564
|
+
process.stderr.write("\n");
|
|
4565
|
+
}
|
|
4566
|
+
const durationMs2 = Math.round(performance.now() - startTime);
|
|
4567
|
+
const { getAuditLogger: getAuditLogger3 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4568
|
+
const auditLogger2 = getAuditLogger3();
|
|
4569
|
+
if (auditLogger2) {
|
|
4570
|
+
auditLogger2.logExecution(moduleId, merged, "success", 0, durationMs2);
|
|
4571
|
+
}
|
|
4572
|
+
return;
|
|
4573
|
+
}
|
|
4668
4574
|
}
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4575
|
+
if (traceFlag && executor.callWithTrace) {
|
|
4576
|
+
const [result2, trace] = await executor.callWithTrace(
|
|
4577
|
+
moduleId,
|
|
4578
|
+
merged,
|
|
4579
|
+
strategyName ? { strategy: strategyName } : void 0
|
|
4580
|
+
);
|
|
4581
|
+
const durationMs2 = Math.round(performance.now() - startTime);
|
|
4582
|
+
const resolved = resolveFormat(outputFormat);
|
|
4583
|
+
if (resolved === "json" || !process.stdout.isTTY) {
|
|
4584
|
+
const traceData = {
|
|
4585
|
+
strategy: trace.strategyName,
|
|
4586
|
+
total_duration_ms: trace.totalDurationMs,
|
|
4587
|
+
success: trace.success,
|
|
4588
|
+
steps: trace.steps.map((s) => ({
|
|
4589
|
+
name: s.name,
|
|
4590
|
+
duration_ms: s.durationMs,
|
|
4591
|
+
skipped: s.skipped,
|
|
4592
|
+
...s.skipped ? { skip_reason: s.skipReason ?? null } : {}
|
|
4593
|
+
}))
|
|
4594
|
+
};
|
|
4595
|
+
let output;
|
|
4596
|
+
if (typeof result2 === "object" && result2 !== null && !Array.isArray(result2)) {
|
|
4597
|
+
output = { ...result2, _trace: traceData };
|
|
4598
|
+
} else {
|
|
4599
|
+
output = { result: result2, _trace: traceData };
|
|
4600
|
+
}
|
|
4601
|
+
process.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
4602
|
+
} else {
|
|
4603
|
+
formatExecResult(result2, outputFormat, outputFields);
|
|
4604
|
+
const stepCount = trace.steps.length;
|
|
4605
|
+
process.stderr.write(
|
|
4606
|
+
`
|
|
4607
|
+
Pipeline Trace (strategy: ${trace.strategyName}, ${stepCount} steps, ${trace.totalDurationMs.toFixed(1)}ms)
|
|
4608
|
+
`
|
|
4609
|
+
);
|
|
4610
|
+
for (const s of trace.steps) {
|
|
4611
|
+
if (s.skipped) {
|
|
4612
|
+
const reason = s.skipReason ?? "n/a";
|
|
4613
|
+
process.stderr.write(` \u25CB ${s.name.padEnd(24)} ${"\u2014".padStart(8)} skipped (${reason})
|
|
4614
|
+
`);
|
|
4615
|
+
} else {
|
|
4616
|
+
process.stderr.write(` \u2713 ${s.name.padEnd(24)} ${(s.durationMs.toFixed(1) + "ms").padStart(8)}
|
|
4617
|
+
`);
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
const { getAuditLogger: getAL2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4622
|
+
const al2 = getAL2();
|
|
4623
|
+
if (al2) {
|
|
4624
|
+
al2.logExecution(moduleId, merged, "success", 0, durationMs2);
|
|
4625
|
+
}
|
|
4626
|
+
return;
|
|
4672
4627
|
}
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4628
|
+
let result;
|
|
4629
|
+
if (strategyName && executor.callWithTrace) {
|
|
4630
|
+
const [res] = await executor.callWithTrace(
|
|
4631
|
+
moduleId,
|
|
4632
|
+
merged,
|
|
4633
|
+
{ strategy: strategyName }
|
|
4679
4634
|
);
|
|
4680
|
-
|
|
4635
|
+
result = res;
|
|
4636
|
+
if (strategyName !== "standard" && process.stderr.isTTY) {
|
|
4637
|
+
process.stderr.write(`Warning: Using '${strategyName}' strategy.
|
|
4638
|
+
`);
|
|
4639
|
+
}
|
|
4681
4640
|
} else {
|
|
4682
|
-
|
|
4683
|
-
|
|
4641
|
+
const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
|
|
4642
|
+
const sandbox = new Sandbox2(sandboxEnabled);
|
|
4643
|
+
result = await sandbox.execute(moduleId, merged, executor);
|
|
4644
|
+
}
|
|
4645
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
4646
|
+
formatExecResult(result, outputFormat, outputFields);
|
|
4647
|
+
const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4648
|
+
const auditLogger = getAuditLogger2();
|
|
4649
|
+
if (auditLogger) {
|
|
4650
|
+
auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
|
|
4651
|
+
}
|
|
4652
|
+
} catch (err) {
|
|
4653
|
+
const exitCode = exitCodeForError(err);
|
|
4654
|
+
const durationMs = Math.round(performance.now() - startTime);
|
|
4655
|
+
try {
|
|
4656
|
+
const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
4657
|
+
const auditLogger = getAuditLogger2();
|
|
4658
|
+
if (auditLogger) {
|
|
4659
|
+
auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
|
|
4684
4660
|
}
|
|
4685
|
-
|
|
4661
|
+
} catch {
|
|
4662
|
+
}
|
|
4663
|
+
if (outputFormat === "json" || !process.stderr.isTTY) {
|
|
4664
|
+
emitErrorJson(err, exitCode);
|
|
4665
|
+
} else {
|
|
4666
|
+
emitErrorTty(err, exitCode);
|
|
4686
4667
|
}
|
|
4668
|
+
process.exit(exitCode);
|
|
4669
|
+
}
|
|
4670
|
+
});
|
|
4671
|
+
return cmd;
|
|
4672
|
+
}
|
|
4673
|
+
async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
|
|
4674
|
+
const cliKwargsNonNull = {};
|
|
4675
|
+
for (const [k, v] of Object.entries(cliKwargs)) {
|
|
4676
|
+
if (v !== null && v !== void 0) {
|
|
4677
|
+
cliKwargsNonNull[k] = v;
|
|
4687
4678
|
}
|
|
4688
|
-
this.groupMapBuilt = true;
|
|
4689
4679
|
}
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
*
|
|
4693
|
-
* FE-13: the built-in subcommand list is no longer folded in here — those
|
|
4694
|
-
* commands live under the `apcli` prefix and are registered directly by
|
|
4695
|
-
* `createCli`.
|
|
4696
|
-
*/
|
|
4697
|
-
listCommands() {
|
|
4698
|
-
this.buildGroupMap();
|
|
4699
|
-
const reserved = getReservedGroupNames();
|
|
4700
|
-
const groupNames = [...this.groupMap.keys()].filter(
|
|
4701
|
-
(g) => !reserved.has(g)
|
|
4702
|
-
);
|
|
4703
|
-
const topNames = [...this.topLevelModules.keys()];
|
|
4704
|
-
return [.../* @__PURE__ */ new Set([...groupNames, ...topNames])].sort();
|
|
4680
|
+
if (!stdinFlag) {
|
|
4681
|
+
return cliKwargsNonNull;
|
|
4705
4682
|
}
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
);
|
|
4721
|
-
this.groupCache.set(cmdName, lazyGrp);
|
|
4722
|
-
return lazyGrp.command;
|
|
4723
|
-
}
|
|
4724
|
-
if (this.topLevelModules.has(cmdName)) {
|
|
4725
|
-
if (this.commandCache.has(cmdName)) {
|
|
4726
|
-
return this.commandCache.get(cmdName);
|
|
4727
|
-
}
|
|
4728
|
-
const [, descriptor] = this.topLevelModules.get(cmdName);
|
|
4729
|
-
const cmd = buildModuleCommand(
|
|
4730
|
-
descriptor,
|
|
4731
|
-
this.executor,
|
|
4732
|
-
this.helpTextMaxLength,
|
|
4733
|
-
cmdName
|
|
4734
|
-
);
|
|
4735
|
-
this.commandCache.set(cmdName, cmd);
|
|
4736
|
-
return cmd;
|
|
4683
|
+
let raw;
|
|
4684
|
+
let source;
|
|
4685
|
+
if (stdinFlag === "-") {
|
|
4686
|
+
raw = await readStdin();
|
|
4687
|
+
source = "STDIN";
|
|
4688
|
+
} else {
|
|
4689
|
+
source = `file '${stdinFlag}'`;
|
|
4690
|
+
try {
|
|
4691
|
+
raw = readFileSync3(stdinFlag, "utf-8");
|
|
4692
|
+
} catch (err) {
|
|
4693
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4694
|
+
process.stderr.write(`Error: Could not read input ${source}: ${msg}
|
|
4695
|
+
`);
|
|
4696
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4737
4697
|
}
|
|
4738
|
-
return null;
|
|
4739
4698
|
}
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4699
|
+
const rawSize = Buffer.byteLength(raw, "utf-8");
|
|
4700
|
+
if (rawSize > 10485760 && !largeInput) {
|
|
4701
|
+
process.stderr.write(
|
|
4702
|
+
`Error: ${source} input exceeds 10MB limit. Use --large-input to override.
|
|
4703
|
+
`
|
|
4704
|
+
);
|
|
4705
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4743
4706
|
}
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
return this.topLevelModules;
|
|
4707
|
+
if (!raw) {
|
|
4708
|
+
return cliKwargsNonNull;
|
|
4747
4709
|
}
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4710
|
+
let parsed;
|
|
4711
|
+
try {
|
|
4712
|
+
parsed = JSON.parse(raw);
|
|
4713
|
+
} catch {
|
|
4714
|
+
process.stderr.write(`Error: ${source} does not contain valid JSON.
|
|
4715
|
+
`);
|
|
4716
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4751
4717
|
}
|
|
4752
|
-
|
|
4718
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
4719
|
+
process.stderr.write(
|
|
4720
|
+
`Error: ${source} JSON must be an object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.
|
|
4721
|
+
`
|
|
4722
|
+
);
|
|
4723
|
+
process.exit(EXIT_CODES.INVALID_CLI_INPUT);
|
|
4724
|
+
}
|
|
4725
|
+
return { ...parsed, ...cliKwargsNonNull };
|
|
4726
|
+
}
|
|
4727
|
+
function readStdin() {
|
|
4728
|
+
return new Promise((resolve2, reject) => {
|
|
4729
|
+
const chunks = [];
|
|
4730
|
+
const onData = (chunk) => chunks.push(chunk);
|
|
4731
|
+
const onEnd = () => {
|
|
4732
|
+
cleanup();
|
|
4733
|
+
resolve2(Buffer.concat(chunks).toString("utf-8"));
|
|
4734
|
+
};
|
|
4735
|
+
const onError = (err) => {
|
|
4736
|
+
cleanup();
|
|
4737
|
+
reject(err);
|
|
4738
|
+
};
|
|
4739
|
+
const cleanup = () => {
|
|
4740
|
+
process.stdin.removeListener("data", onData);
|
|
4741
|
+
process.stdin.removeListener("end", onEnd);
|
|
4742
|
+
process.stdin.removeListener("error", onError);
|
|
4743
|
+
};
|
|
4744
|
+
process.stdin.on("data", onData);
|
|
4745
|
+
process.stdin.on("end", onEnd);
|
|
4746
|
+
process.stdin.on("error", onError);
|
|
4747
|
+
process.stdin.resume();
|
|
4748
|
+
});
|
|
4749
|
+
}
|
|
4750
|
+
function reconvertEnumValues(kwargs, options) {
|
|
4751
|
+
const result = { ...kwargs };
|
|
4752
|
+
for (const opt of options) {
|
|
4753
|
+
if (!opt.enumOriginalTypes) continue;
|
|
4754
|
+
const paramName = opt.name;
|
|
4755
|
+
if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
|
|
4756
|
+
continue;
|
|
4757
|
+
}
|
|
4758
|
+
const strVal = String(result[paramName]);
|
|
4759
|
+
const origType = opt.enumOriginalTypes[strVal];
|
|
4760
|
+
if (origType === "int") {
|
|
4761
|
+
result[paramName] = parseInt(strVal, 10);
|
|
4762
|
+
} else if (origType === "float") {
|
|
4763
|
+
result[paramName] = parseFloat(strVal);
|
|
4764
|
+
} else if (origType === "bool") {
|
|
4765
|
+
result[paramName] = strVal.toLowerCase() === "true";
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
return result;
|
|
4769
|
+
}
|
|
4753
4770
|
|
|
4754
4771
|
// src/index.ts
|
|
4755
4772
|
init_errors();
|