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