apcore-cli 0.9.0 → 0.10.0

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