apcore-cli 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -205,7 +205,7 @@ function buildSandboxEnv(tmpDir) {
205
205
  env.TMPDIR = tmpDir;
206
206
  return env;
207
207
  }
208
- var SANDBOX_ALLOW_KEYS, SANDBOX_ALLOW_PREFIX, SANDBOX_DENY_PREFIX, SANDBOX_DENY_KEYS, SANDBOX_OUTPUT_SIZE_LIMIT, Sandbox;
208
+ var SANDBOX_ALLOW_KEYS, SANDBOX_ALLOW_PREFIX, SANDBOX_DENY_PREFIX, SANDBOX_DENY_KEYS, SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT, Sandbox;
209
209
  var init_sandbox = __esm({
210
210
  "src/security/sandbox.ts"() {
211
211
  "use strict";
@@ -215,14 +215,41 @@ var init_sandbox = __esm({
215
215
  SANDBOX_ALLOW_PREFIX = "APCORE_";
216
216
  SANDBOX_DENY_PREFIX = "APCORE_AUTH_";
217
217
  SANDBOX_DENY_KEYS = ["APCORE_AUTH_API_KEY"];
218
- SANDBOX_OUTPUT_SIZE_LIMIT = 64 * 1024 * 1024;
218
+ SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT = 64 * 1024 * 1024;
219
219
  Sandbox = class {
220
+ /** Default post-capture stdout+stderr byte budget for sandboxed children. */
221
+ static DEFAULT_MAX_OUTPUT_BYTES = SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT;
220
222
  enabled;
221
223
  timeoutSeconds;
224
+ extensionsRoot = null;
225
+ maxOutputBytes = SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT;
222
226
  constructor(enabled = false, timeoutSeconds = 300) {
223
227
  this.enabled = enabled;
224
228
  this.timeoutSeconds = timeoutSeconds;
225
229
  }
230
+ /**
231
+ * Set the extensions root that is forwarded to the sandboxed runner via
232
+ * `APCORE_EXTENSIONS_ROOT`. The path is resolved to absolute when injected
233
+ * so the child (whose cwd is the fresh sandbox tempdir) can locate modules.
234
+ *
235
+ * Builder-style — returns `this` so call sites can chain. Mirrors Python's
236
+ * `Sandbox.with_extensions_root` (D1-004 cross-SDK parity).
237
+ */
238
+ withExtensionsRoot(extensionsRoot) {
239
+ this.extensionsRoot = extensionsRoot;
240
+ return this;
241
+ }
242
+ /**
243
+ * Cap the post-capture stdout+stderr byte budget for the sandboxed
244
+ * subprocess. Default: 64 MiB (`Sandbox.DEFAULT_MAX_OUTPUT_BYTES`).
245
+ *
246
+ * Builder-style — returns `this`. Mirrors Python's
247
+ * `Sandbox.with_max_output_bytes` (D1-004 cross-SDK parity).
248
+ */
249
+ withMaxOutputBytes(maxOutputBytes) {
250
+ this.maxOutputBytes = maxOutputBytes;
251
+ return this;
252
+ }
226
253
  /**
227
254
  * Execute a module, optionally inside a sandboxed subprocess.
228
255
  */
@@ -235,10 +262,16 @@ var init_sandbox = __esm({
235
262
  async _sandboxedExecute(moduleId, inputData) {
236
263
  const { spawn } = await import("child_process");
237
264
  const { tmpdir } = await import("os");
238
- const { join: join3 } = await import("path");
265
+ const { join: join4 } = await import("path");
239
266
  const { mkdtempSync, rmSync } = await import("fs");
240
- const tmpDir = mkdtempSync(join3(tmpdir(), "apcore_sandbox_"));
267
+ const tmpDir = mkdtempSync(join4(tmpdir(), "apcore_sandbox_"));
241
268
  const env = buildSandboxEnv(tmpDir);
269
+ const { resolve: resolvePath } = await import("path");
270
+ if (this.extensionsRoot !== null) {
271
+ env.APCORE_EXTENSIONS_ROOT = resolvePath(this.extensionsRoot);
272
+ } else if (env.APCORE_EXTENSIONS_ROOT) {
273
+ env.APCORE_EXTENSIONS_ROOT = resolvePath(env.APCORE_EXTENSIONS_ROOT);
274
+ }
242
275
  const binaryPath = process.argv[1];
243
276
  const child = spawn(process.execPath, [binaryPath, "--internal-sandbox-runner", moduleId], {
244
277
  env,
@@ -247,11 +280,14 @@ var init_sandbox = __esm({
247
280
  });
248
281
  let stdout = "";
249
282
  let stderr = "";
250
- let totalBytes = 0;
283
+ let stdoutBytes = 0;
284
+ let stderrBytes = 0;
251
285
  let sizeExceeded = false;
286
+ const outputCap = this.maxOutputBytes;
252
287
  child.stdout.on("data", (chunk) => {
253
- totalBytes += chunk.length;
254
- if (totalBytes > SANDBOX_OUTPUT_SIZE_LIMIT) {
288
+ if (sizeExceeded) return;
289
+ stdoutBytes += chunk.length;
290
+ if (stdoutBytes > outputCap) {
255
291
  sizeExceeded = true;
256
292
  child.kill("SIGKILL");
257
293
  return;
@@ -259,6 +295,13 @@ var init_sandbox = __esm({
259
295
  stdout += chunk.toString();
260
296
  });
261
297
  child.stderr.on("data", (chunk) => {
298
+ if (sizeExceeded) return;
299
+ stderrBytes += chunk.length;
300
+ if (stderrBytes > outputCap) {
301
+ sizeExceeded = true;
302
+ child.kill("SIGKILL");
303
+ return;
304
+ }
262
305
  stderr += chunk.toString();
263
306
  });
264
307
  child.stdin.write(JSON.stringify(inputData));
@@ -279,7 +322,10 @@ var init_sandbox = __esm({
279
322
  } catch {
280
323
  }
281
324
  if (sizeExceeded) {
282
- reject(new ModuleExecutionError(`Sandbox module '${moduleId}' output exceeded 64MiB limit.`));
325
+ const limitMiB = Math.floor(outputCap / (1024 * 1024));
326
+ reject(new ModuleExecutionError(
327
+ `Sandbox module '${moduleId}' output exceeded ${limitMiB}MiB limit.`
328
+ ));
283
329
  return;
284
330
  }
285
331
  if (code !== 0) {
@@ -400,6 +446,10 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
400
446
  properties: {},
401
447
  required: []
402
448
  };
449
+ if (typeof obj.properties === "object" && obj.properties !== null) {
450
+ Object.assign(merged.properties, obj.properties);
451
+ }
452
+ const siblingRequired = Array.isArray(obj.required) ? obj.required.slice() : [];
403
453
  const allRequiredSets = [];
404
454
  for (const subSchema of obj[keyword]) {
405
455
  const resolved = resolveNode(
@@ -420,6 +470,7 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
420
470
  allRequiredSets.push(new Set(resolved.required));
421
471
  }
422
472
  }
473
+ let branchRequired = [];
423
474
  if (allRequiredSets.length > 0) {
424
475
  let intersection = allRequiredSets[0];
425
476
  for (let i = 1; i < allRequiredSets.length; i++) {
@@ -427,10 +478,17 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
427
478
  [...intersection].filter((x) => allRequiredSets[i].has(x))
428
479
  );
429
480
  }
430
- merged.required = [...intersection];
431
- } else {
432
- merged.required = [];
481
+ branchRequired = [...intersection];
482
+ }
483
+ const seen = /* @__PURE__ */ new Set();
484
+ const combinedRequired = [];
485
+ for (const r of [...siblingRequired, ...branchRequired]) {
486
+ if (!seen.has(r)) {
487
+ seen.add(r);
488
+ combinedRequired.push(r);
489
+ }
433
490
  }
491
+ merged.required = combinedRequired;
434
492
  for (const [k, v] of Object.entries(obj)) {
435
493
  if (k !== keyword && !(k in merged)) {
436
494
  merged[k] = v;
@@ -446,7 +504,7 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
446
504
  propSchema,
447
505
  defs,
448
506
  visited,
449
- depth + 1,
507
+ depth,
450
508
  maxDepth,
451
509
  moduleId
452
510
  );
@@ -462,6 +520,30 @@ var init_ref_resolver = __esm({
462
520
  }
463
521
  });
464
522
 
523
+ // src/logger.ts
524
+ function setLogLevel(level) {
525
+ const upper = level.toUpperCase();
526
+ if (upper in LEVELS) {
527
+ currentLevel = upper;
528
+ }
529
+ }
530
+ function shouldLog(level) {
531
+ return LEVELS[level] >= LEVELS[currentLevel];
532
+ }
533
+ function warn(message) {
534
+ if (shouldLog("WARNING")) process.stderr.write(`WARNING: ${message}
535
+ `);
536
+ }
537
+ var LEVELS, currentLevel;
538
+ var init_logger = __esm({
539
+ "src/logger.ts"() {
540
+ "use strict";
541
+ init_esm_shims();
542
+ LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 };
543
+ currentLevel = "WARNING";
544
+ }
545
+ });
546
+
465
547
  // src/schema-parser.ts
466
548
  function mapType(propName, propSchema) {
467
549
  const schemaType = propSchema.type;
@@ -499,6 +581,13 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
499
581
  const requiredList = schema.required ?? [];
500
582
  const options = [];
501
583
  const flagNames = {};
584
+ for (const reqName of requiredList) {
585
+ if (!(reqName in properties)) {
586
+ warn(
587
+ `Required property '${reqName}' not found in properties, skipping.`
588
+ );
589
+ }
590
+ }
502
591
  for (const [propName, propSchema] of Object.entries(properties)) {
503
592
  const flagName = "--" + propName.replace(/_/g, "-");
504
593
  if (RESERVED_NAMES.has(propName)) {
@@ -506,14 +595,14 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
506
595
  `Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.
507
596
  `
508
597
  );
509
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
598
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
510
599
  }
511
600
  if (flagName in flagNames) {
512
601
  process.stderr.write(
513
602
  `Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.
514
603
  `
515
604
  );
516
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
605
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
517
606
  }
518
607
  flagNames[flagName] = propName;
519
608
  const typeResult = mapType(propName, propSchema);
@@ -529,7 +618,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
529
618
  `Error: Flag name collision: boolean property '${propName}' auto-generates '${noFlag}' which is already used by property '${flagNames[noFlag]}'.
530
619
  `
531
620
  );
532
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
621
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
533
622
  }
534
623
  flagNames[noFlag] = propName;
535
624
  const defaultVal = propSchema.default ?? false;
@@ -606,6 +695,7 @@ var init_schema_parser = __esm({
606
695
  "use strict";
607
696
  init_esm_shims();
608
697
  init_errors();
698
+ init_logger();
609
699
  BOOLEAN_FLAG = /* @__PURE__ */ Symbol("BOOLEAN_FLAG");
610
700
  RESERVED_NAMES = /* @__PURE__ */ new Set([
611
701
  "input",
@@ -763,6 +853,26 @@ var init_approval = __esm({
763
853
 
764
854
  // src/output.ts
765
855
  import yaml from "js-yaml";
856
+ function descriptorToScanned(m) {
857
+ const metadata = m.metadata ?? {};
858
+ const display = metadata["display"] ?? null;
859
+ return {
860
+ moduleId: m.id,
861
+ description: m.description ?? "",
862
+ inputSchema: m.inputSchema ?? {},
863
+ outputSchema: m.outputSchema ?? {},
864
+ tags: m.tags ?? [],
865
+ target: "",
866
+ version: "1.0.0",
867
+ annotations: m.annotations ?? null,
868
+ documentation: null,
869
+ suggestedAlias: null,
870
+ examples: [],
871
+ metadata,
872
+ display,
873
+ warnings: []
874
+ };
875
+ }
766
876
  function csvCellString(value) {
767
877
  if (value === null || value === void 0) return "";
768
878
  if (typeof value === "object") return JSON.stringify(value);
@@ -791,7 +901,7 @@ function formatTable(headers, rows) {
791
901
  );
792
902
  return [headerLine, sep2, ...dataLines].join("\n") + "\n";
793
903
  }
794
- function formatModuleList(modules, format, filterTags, showDeps = false, exposureFilter) {
904
+ async function formatModuleList(modules, format, filterTags, showDeps = false, exposureFilter) {
795
905
  if (format === "table") {
796
906
  if (modules.length === 0 && filterTags && filterTags.length > 0) {
797
907
  process.stdout.write(
@@ -836,6 +946,17 @@ function formatModuleList(modules, format, filterTags, showDeps = false, exposur
836
946
  return entry;
837
947
  });
838
948
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
949
+ } else if (format === "markdown" || format === "skill") {
950
+ let toolkit;
951
+ try {
952
+ toolkit = await import("apcore-toolkit");
953
+ } catch {
954
+ throw new Error(TOOLKIT_MISSING_HINT);
955
+ }
956
+ const scanned = modules.map(descriptorToScanned);
957
+ process.stdout.write(
958
+ toolkit.formatModules(scanned, { style: format, display: true }) + "\n"
959
+ );
839
960
  }
840
961
  }
841
962
  function annotationsToDict(annotations) {
@@ -849,7 +970,7 @@ function annotationsToDict(annotations) {
849
970
  }
850
971
  return Object.keys(result).length > 0 ? result : null;
851
972
  }
852
- function formatModuleDetail(moduleDef, format) {
973
+ async function formatModuleDetail(moduleDef, format) {
853
974
  if (format === "table") {
854
975
  process.stdout.write(`
855
976
  Module: ${moduleDef.id}
@@ -920,6 +1041,16 @@ Tags: ${tags.join(", ")}
920
1041
  }
921
1042
  }
922
1043
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1044
+ } else if (format === "markdown" || format === "skill") {
1045
+ let toolkit;
1046
+ try {
1047
+ toolkit = await import("apcore-toolkit");
1048
+ } catch {
1049
+ throw new Error(TOOLKIT_MISSING_HINT);
1050
+ }
1051
+ process.stdout.write(
1052
+ toolkit.formatModule(descriptorToScanned(moduleDef), { style: format, display: true }) + "\n"
1053
+ );
923
1054
  }
924
1055
  }
925
1056
  function selectFields(result, fields) {
@@ -1068,35 +1199,13 @@ function firstFailedExitCode(result) {
1068
1199
  }
1069
1200
  return EXIT_CODES.MODULE_EXECUTE_ERROR;
1070
1201
  }
1202
+ var TOOLKIT_MISSING_HINT;
1071
1203
  var init_output = __esm({
1072
1204
  "src/output.ts"() {
1073
1205
  "use strict";
1074
1206
  init_esm_shims();
1075
1207
  init_errors();
1076
- }
1077
- });
1078
-
1079
- // src/logger.ts
1080
- function setLogLevel(level) {
1081
- const upper = level.toUpperCase();
1082
- if (upper in LEVELS) {
1083
- currentLevel = upper;
1084
- }
1085
- }
1086
- function shouldLog(level) {
1087
- return LEVELS[level] >= LEVELS[currentLevel];
1088
- }
1089
- function warn(message) {
1090
- if (shouldLog("WARNING")) process.stderr.write(`WARNING: ${message}
1091
- `);
1092
- }
1093
- var LEVELS, currentLevel;
1094
- var init_logger = __esm({
1095
- "src/logger.ts"() {
1096
- "use strict";
1097
- init_esm_shims();
1098
- LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 };
1099
- currentLevel = "WARNING";
1208
+ TOOLKIT_MISSING_HINT = "The 'markdown' and 'skill' output formats require the apcore-toolkit peer dependency. Install with: npm install apcore-toolkit@^0.6";
1100
1209
  }
1101
1210
  });
1102
1211
 
@@ -1853,8 +1962,8 @@ var init_audit = __esm({
1853
1962
  );
1854
1963
  logPath;
1855
1964
  writeFailureWarned = false;
1856
- constructor(path5) {
1857
- this.logPath = path5 ?? _AuditLogger.DEFAULT_PATH;
1965
+ constructor(path6) {
1966
+ this.logPath = path6 ?? _AuditLogger.DEFAULT_PATH;
1858
1967
  this.ensureDirectory();
1859
1968
  }
1860
1969
  ensureDirectory() {
@@ -1900,16 +2009,112 @@ var init_audit = __esm({
1900
2009
  try {
1901
2010
  return os.userInfo().username;
1902
2011
  } catch {
1903
- return process.env.USER ?? process.env.USERNAME ?? "unknown";
2012
+ return process.env.USER ?? process.env.LOGNAME ?? process.env.USERNAME ?? "unknown";
1904
2013
  }
1905
2014
  }
1906
2015
  };
1907
2016
  }
1908
2017
  });
1909
2018
 
2019
+ // src/system-usage.ts
2020
+ import * as fs4 from "fs";
2021
+ import * as os2 from "os";
2022
+ import * as path4 from "path";
2023
+ function computeSummary(options = {}) {
2024
+ const auditPath = options.auditPath ?? DEFAULT_AUDIT_PATH;
2025
+ const period = options.period ?? "24h";
2026
+ const cutoff = (options.now ?? /* @__PURE__ */ new Date()).getTime() - PERIOD_TO_MS[period];
2027
+ if (!fs4.existsSync(auditPath)) {
2028
+ return /* @__PURE__ */ new Map();
2029
+ }
2030
+ let raw;
2031
+ try {
2032
+ raw = fs4.readFileSync(auditPath, "utf-8");
2033
+ } catch {
2034
+ return /* @__PURE__ */ new Map();
2035
+ }
2036
+ const counts = /* @__PURE__ */ new Map();
2037
+ const errors = /* @__PURE__ */ new Map();
2038
+ const latencySum = /* @__PURE__ */ new Map();
2039
+ for (const line of raw.split("\n")) {
2040
+ const trimmed = line.trim();
2041
+ if (!trimmed) continue;
2042
+ let entry;
2043
+ try {
2044
+ entry = JSON.parse(trimmed);
2045
+ } catch {
2046
+ continue;
2047
+ }
2048
+ const ts = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
2049
+ if (Number.isNaN(ts) || ts < cutoff) continue;
2050
+ const moduleId = typeof entry.module_id === "string" ? entry.module_id : null;
2051
+ if (!moduleId) continue;
2052
+ counts.set(moduleId, (counts.get(moduleId) ?? 0) + 1);
2053
+ if (entry.status === "error") {
2054
+ errors.set(moduleId, (errors.get(moduleId) ?? 0) + 1);
2055
+ }
2056
+ const duration = entry.duration_ms;
2057
+ if (typeof duration === "number") {
2058
+ latencySum.set(moduleId, (latencySum.get(moduleId) ?? 0) + duration);
2059
+ }
2060
+ }
2061
+ const out = /* @__PURE__ */ new Map();
2062
+ for (const [id, calls] of counts) {
2063
+ out.set(id, {
2064
+ module_id: id,
2065
+ calls,
2066
+ errors: errors.get(id) ?? 0,
2067
+ latency_ms: calls > 0 ? (latencySum.get(id) ?? 0) / calls : 0
2068
+ });
2069
+ }
2070
+ return out;
2071
+ }
2072
+ function sortModulesByUsage(modules, field, options = {}) {
2073
+ const reverse = options.reverse ?? true;
2074
+ const summary = computeSummary({
2075
+ auditPath: options.auditPath,
2076
+ period: options.period
2077
+ });
2078
+ if (summary.size === 0) {
2079
+ modules.sort((a, b) => (a.id ?? a.module_id ?? "").localeCompare(b.id ?? b.module_id ?? ""));
2080
+ if (reverse) modules.reverse();
2081
+ return { used: false };
2082
+ }
2083
+ const key = (m) => {
2084
+ const id = m.id ?? m.module_id ?? "";
2085
+ const s = summary.get(id);
2086
+ if (!s) return 0;
2087
+ return field === "latency" ? s.latency_ms : field === "calls" ? s.calls : s.errors;
2088
+ };
2089
+ modules.sort((a, b) => {
2090
+ const diff = key(a) - key(b);
2091
+ if (diff !== 0) return reverse ? -diff : diff;
2092
+ return (a.id ?? a.module_id ?? "").localeCompare(b.id ?? b.module_id ?? "");
2093
+ });
2094
+ return { used: true };
2095
+ }
2096
+ var PERIOD_TO_MS, DEFAULT_AUDIT_PATH;
2097
+ var init_system_usage = __esm({
2098
+ "src/system-usage.ts"() {
2099
+ "use strict";
2100
+ init_esm_shims();
2101
+ PERIOD_TO_MS = {
2102
+ "1h": 60 * 60 * 1e3,
2103
+ "24h": 24 * 60 * 60 * 1e3,
2104
+ "7d": 7 * 24 * 60 * 60 * 1e3,
2105
+ "30d": 30 * 24 * 60 * 60 * 1e3
2106
+ };
2107
+ DEFAULT_AUDIT_PATH = path4.join(
2108
+ os2.homedir(),
2109
+ ".apcore-cli",
2110
+ "audit.jsonl"
2111
+ );
2112
+ }
2113
+ });
2114
+
1910
2115
  // src/security/config-encryptor.ts
1911
2116
  import * as crypto2 from "crypto";
1912
- import * as os2 from "os";
2117
+ import * as os3 from "os";
1913
2118
  async function getKeytar() {
1914
2119
  if (keytarModule) return keytarModule;
1915
2120
  try {
@@ -2038,7 +2243,7 @@ var init_config_encryptor = __esm({
2038
2243
  );
2039
2244
  _ConfigEncryptor.weakFallbackWarned = true;
2040
2245
  }
2041
- const hostname2 = os2.hostname();
2246
+ const hostname2 = os3.hostname();
2042
2247
  const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
2043
2248
  const material = `${hostname2}:${username}`;
2044
2249
  return crypto2.pbkdf2Sync(material, salt, PBKDF2_ITERATIONS, 32, "sha256");
@@ -2070,7 +2275,7 @@ var init_config_encryptor = __esm({
2070
2275
  const nonce = data.subarray(0, 12);
2071
2276
  const tag = data.subarray(12, 28);
2072
2277
  const ct = data.subarray(28);
2073
- const hostname2 = os2.hostname();
2278
+ const hostname2 = os3.hostname();
2074
2279
  const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
2075
2280
  const passphrase = process.env.APCORE_CLI_CONFIG_PASSPHRASE;
2076
2281
  const materials = passphrase ? [passphrase, `${hostname2}:${username}`] : [`${hostname2}:${username}`];
@@ -2230,7 +2435,9 @@ function getAnnotationFlag(moduleDef, flag) {
2230
2435
  return ann[attr] === true;
2231
2436
  }
2232
2437
  function registerListCommand(apcliGroup, registry, exposureFilter) {
2233
- const listCmd = new Command2("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--flat", "Show flat list (no grouping).", false).option("--format <format>", "Output format.", void 0).option("-s, --search <query>", "Filter by substring match on ID and description.").addOption(
2438
+ const listCmd = new Command2("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--flat", "Show flat list (no grouping).", false).addOption(
2439
+ new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
2440
+ ).option("-s, --search <query>", "Filter by substring match on ID and description.").addOption(
2234
2441
  new Option2("--status <status>", "Filter by module status.").choices(["enabled", "disabled", "all"]).default("enabled")
2235
2442
  ).option("-a, --annotation <flag>", "Filter by annotation flag (AND logic). Repeatable.", collectAnnotation, []).addOption(
2236
2443
  new Option2("--sort <field>", "Sort order.").choices(["id", "calls", "errors", "latency"]).default("id")
@@ -2280,14 +2487,18 @@ function registerListCommand(apcliGroup, registry, exposureFilter) {
2280
2487
  }
2281
2488
  }
2282
2489
  if (opts.sort === "calls" || opts.sort === "errors" || opts.sort === "latency") {
2283
- process.stderr.write(
2284
- `Warning: Usage data not available; sorting by id. Sort by ${opts.sort} requires system.usage modules.
2490
+ const { used } = sortModulesByUsage(modules, opts.sort, { reverse: !opts.reverse });
2491
+ if (!used) {
2492
+ process.stderr.write(
2493
+ `note: no usage data available for --sort ${opts.sort}; sorted by id. Run some modules first to populate ~/.apcore-cli/audit.jsonl.
2285
2494
  `
2286
- );
2287
- }
2288
- modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
2289
- if (opts.reverse) {
2290
- modules.reverse();
2495
+ );
2496
+ }
2497
+ } else {
2498
+ modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
2499
+ if (opts.reverse) {
2500
+ modules.reverse();
2501
+ }
2291
2502
  }
2292
2503
  let showExposureCol = false;
2293
2504
  if (exposureFilter && opts.exposure !== "all") {
@@ -2302,12 +2513,14 @@ function registerListCommand(apcliGroup, registry, exposureFilter) {
2302
2513
  }
2303
2514
  const fmt = resolveFormat(opts.format);
2304
2515
  const filterTagsArg = opts.tag.length > 0 ? opts.tag : void 0;
2305
- formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
2516
+ void formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
2306
2517
  });
2307
2518
  apcliGroup.addCommand(listCmd);
2308
2519
  }
2309
2520
  function registerDescribeCommand(apcliGroup, registry) {
2310
- const describeCmd = new Command2("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").option("--format <format>", "Output format.", void 0).action((moduleId, opts) => {
2521
+ const describeCmd = new Command2("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").addOption(
2522
+ new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
2523
+ ).action((moduleId, opts) => {
2311
2524
  validateModuleId(moduleId);
2312
2525
  const moduleDef = registry.getModule(moduleId);
2313
2526
  if (!moduleDef) {
@@ -2318,7 +2531,7 @@ function registerDescribeCommand(apcliGroup, registry) {
2318
2531
  process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2319
2532
  }
2320
2533
  const fmt = resolveFormat(opts.format);
2321
- formatModuleDetail(moduleDef, fmt);
2534
+ void formatModuleDetail(moduleDef, fmt);
2322
2535
  });
2323
2536
  apcliGroup.addCommand(describeCmd);
2324
2537
  }
@@ -2445,6 +2658,7 @@ var init_discovery = __esm({
2445
2658
  init_main();
2446
2659
  init_output();
2447
2660
  init_audit();
2661
+ init_system_usage();
2448
2662
  TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
2449
2663
  }
2450
2664
  });
@@ -2469,6 +2683,15 @@ function emitErrorAndExit(e) {
2469
2683
  `);
2470
2684
  process.exit(exitCodeForError(e));
2471
2685
  }
2686
+ async function requireApprovalForSystemCommand(moduleId, autoApprove) {
2687
+ const syntheticModuleDef = {
2688
+ id: moduleId,
2689
+ name: moduleId,
2690
+ description: `system command: ${moduleId}`,
2691
+ annotations: { requires_approval: true }
2692
+ };
2693
+ await checkApproval(syntheticModuleDef, autoApprove, void 0);
2694
+ }
2472
2695
  function formatHealthSummaryTty(result) {
2473
2696
  const summary = result.summary ?? {};
2474
2697
  const modules = result.modules ?? [];
@@ -2609,9 +2832,10 @@ function registerUsageCommand(apcliGroup, executor) {
2609
2832
  apcliGroup.addCommand(usageCmd);
2610
2833
  }
2611
2834
  function registerEnableCommand(apcliGroup, executor) {
2612
- const enableCmd = new Command3("enable").description("Enable a disabled module at runtime.").argument("<module-id>", "Module ID to enable").requiredOption("--reason <reason>", "Reason for enabling (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2835
+ const enableCmd = new Command3("enable").description("Enable a disabled module at runtime.").argument("<module-id>", "Module ID to enable").requiredOption("--reason <reason>", "Reason for enabling (required for audit).").option("-y, --yes", "Skip approval prompt (audit D11-B-001 cross-SDK parity).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2613
2836
  const fmt = resolveFormat(opts.format);
2614
2837
  try {
2838
+ await requireApprovalForSystemCommand("system.control.toggle_feature", opts.yes);
2615
2839
  const result = await callSystemModule(executor, "system.control.toggle_feature", {
2616
2840
  module_id: moduleId,
2617
2841
  enabled: true,
@@ -2629,9 +2853,10 @@ function registerEnableCommand(apcliGroup, executor) {
2629
2853
  apcliGroup.addCommand(enableCmd);
2630
2854
  }
2631
2855
  function registerDisableCommand(apcliGroup, executor) {
2632
- const disableCmd = new Command3("disable").description("Disable a module at runtime (calls are rejected until re-enabled).").argument("<module-id>", "Module ID to disable").requiredOption("--reason <reason>", "Reason for disabling (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2856
+ const disableCmd = new Command3("disable").description("Disable a module at runtime (calls are rejected until re-enabled).").argument("<module-id>", "Module ID to disable").requiredOption("--reason <reason>", "Reason for disabling (required for audit).").option("-y, --yes", "Skip approval prompt (audit D11-B-001 cross-SDK parity).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2633
2857
  const fmt = resolveFormat(opts.format);
2634
2858
  try {
2859
+ await requireApprovalForSystemCommand("system.control.toggle_feature", opts.yes);
2635
2860
  const result = await callSystemModule(executor, "system.control.toggle_feature", {
2636
2861
  module_id: moduleId,
2637
2862
  enabled: false,
@@ -2649,9 +2874,10 @@ function registerDisableCommand(apcliGroup, executor) {
2649
2874
  apcliGroup.addCommand(disableCmd);
2650
2875
  }
2651
2876
  function registerReloadCommand(apcliGroup, executor) {
2652
- const reloadCmd = new Command3("reload").description("Hot-reload a module from disk.").argument("<module-id>", "Module ID to reload").requiredOption("--reason <reason>", "Reason for reload (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2877
+ const reloadCmd = new Command3("reload").description("Hot-reload a module from disk.").argument("<module-id>", "Module ID to reload").requiredOption("--reason <reason>", "Reason for reload (required for audit).").option("-y, --yes", "Skip approval prompt (audit D11-B-001 cross-SDK parity).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2653
2878
  const fmt = resolveFormat(opts.format);
2654
2879
  try {
2880
+ await requireApprovalForSystemCommand("system.control.reload_module", opts.yes);
2655
2881
  const result = await callSystemModule(executor, "system.control.reload_module", {
2656
2882
  module_id: moduleId,
2657
2883
  reason: opts.reason
@@ -2689,7 +2915,7 @@ function registerConfigCommand(apcliGroup, executor) {
2689
2915
  }
2690
2916
  });
2691
2917
  configGroup.addCommand(configGetCmd);
2692
- const configSetCmd = new Command3("set").description("Update a runtime configuration value (audit-logged; server-side approval gate applies).").argument("<key>", "Configuration key (dot-path)").argument("<value>", "New value").requiredOption("--reason <reason>", "Reason for config change (required for audit).").option("--format <format>", "Output format.").action(async (key, value, opts) => {
2918
+ const configSetCmd = new Command3("set").description("Update a runtime configuration value (audit-logged; client-side approval gate applies).").argument("<key>", "Configuration key (dot-path)").argument("<value>", "New value").requiredOption("--reason <reason>", "Reason for config change (required for audit).").option("-y, --yes", "Skip approval prompt (audit D11-B-001 cross-SDK parity).", false).option("--format <format>", "Output format.").action(async (key, value, opts) => {
2693
2919
  const fmt = resolveFormat(opts.format);
2694
2920
  let parsedValue;
2695
2921
  try {
@@ -2698,6 +2924,7 @@ function registerConfigCommand(apcliGroup, executor) {
2698
2924
  parsedValue = value;
2699
2925
  }
2700
2926
  try {
2927
+ await requireApprovalForSystemCommand("system.control.update_config", opts.yes);
2701
2928
  const result = await callSystemModule(executor, "system.control.update_config", {
2702
2929
  key,
2703
2930
  value: parsedValue,
@@ -2724,6 +2951,7 @@ var init_system_cmd = __esm({
2724
2951
  "src/system-cmd.ts"() {
2725
2952
  "use strict";
2726
2953
  init_esm_shims();
2954
+ init_approval();
2727
2955
  init_errors();
2728
2956
  init_output();
2729
2957
  }
@@ -2905,14 +3133,33 @@ var init_strategy = __esm({
2905
3133
  });
2906
3134
 
2907
3135
  // src/builtin-group.ts
2908
- var RESERVED_GROUP_NAMES, VALID_USER_MODES, APCLI_SUBCOMMAND_NAMES, ApcliGroup;
3136
+ function setReservedGroupNames(names) {
3137
+ _effectiveReservedNames = names;
3138
+ }
3139
+ function _validateBuiltinGroupName(name) {
3140
+ if (!name || !_NAME_REGEX.test(name)) {
3141
+ throw new ApcliGroupError(
3142
+ `builtinGroupName ${JSON.stringify(name)} must match /^[a-z][a-z0-9_-]*$/ (non-empty, lowercase, alphanumeric + '_' / '-', leading letter).`
3143
+ );
3144
+ }
3145
+ }
3146
+ var ApcliGroupError, DEFAULT_BUILTIN_GROUP_NAME, RESERVED_GROUP_NAMES, _effectiveReservedNames, _NAME_REGEX, VALID_USER_MODES, APCLI_SUBCOMMAND_NAMES, ApcliGroup;
2909
3147
  var init_builtin_group = __esm({
2910
3148
  "src/builtin-group.ts"() {
2911
3149
  "use strict";
2912
3150
  init_esm_shims();
2913
3151
  init_errors();
2914
3152
  init_logger();
2915
- RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set(["apcli"]);
3153
+ ApcliGroupError = class extends Error {
3154
+ constructor(message) {
3155
+ super(message);
3156
+ this.name = "ApcliGroupError";
3157
+ }
3158
+ };
3159
+ DEFAULT_BUILTIN_GROUP_NAME = "apcli";
3160
+ RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set([DEFAULT_BUILTIN_GROUP_NAME]);
3161
+ _effectiveReservedNames = RESERVED_GROUP_NAMES;
3162
+ _NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
2916
3163
  VALID_USER_MODES = /* @__PURE__ */ new Set([
2917
3164
  "all",
2918
3165
  "none",
@@ -2941,6 +3188,7 @@ var init_builtin_group = __esm({
2941
3188
  _disableEnv;
2942
3189
  _registryInjected;
2943
3190
  _fromCliConfig;
3191
+ _name;
2944
3192
  constructor(init) {
2945
3193
  this._mode = init.mode;
2946
3194
  this._include = init.include;
@@ -2948,6 +3196,16 @@ var init_builtin_group = __esm({
2948
3196
  this._disableEnv = init.disableEnv;
2949
3197
  this._registryInjected = init.registryInjected;
2950
3198
  this._fromCliConfig = init.fromCliConfig;
3199
+ this._name = init.name;
3200
+ }
3201
+ /**
3202
+ * Resolved name for the built-in command group (default `"apcli"`).
3203
+ * Overridable via createCli's `builtinGroupName` option for downstream
3204
+ * branded CLIs that want a custom namespace. Cross-SDK parity with
3205
+ * Python `ApcliGroup.name` (2026-05-08).
3206
+ */
3207
+ get name() {
3208
+ return this._name;
2951
3209
  }
2952
3210
  /**
2953
3211
  * Tier 1 constructor — config came from `createCli({ apcli })`.
@@ -2968,6 +3226,18 @@ var init_builtin_group = __esm({
2968
3226
  * Env var (Tier 2) may override the yaml-supplied mode.
2969
3227
  */
2970
3228
  static fromYaml(config, opts) {
3229
+ if (config !== null && config !== void 0 && typeof config !== "boolean" && (typeof config !== "object" || Array.isArray(config))) {
3230
+ const got = Array.isArray(config) ? "array" : typeof config;
3231
+ warn(
3232
+ `apcore.yaml apcli has unexpected type ${got}; using auto-detect.`
3233
+ );
3234
+ return _ApcliGroup._build(
3235
+ void 0,
3236
+ opts,
3237
+ /*fromCliConfig*/
3238
+ false
3239
+ );
3240
+ }
2971
3241
  return _ApcliGroup._build(
2972
3242
  config,
2973
3243
  opts,
@@ -2981,8 +3251,12 @@ var init_builtin_group = __esm({
2981
3251
  * Use this in programmatic contexts where throwing/exiting is unwanted.
2982
3252
  */
2983
3253
  static tryFromYaml(config, opts) {
2984
- if (config !== null && config !== void 0 && typeof config !== "boolean" && typeof config !== "object") {
2985
- return [null, `apcore.yaml 'apcli:' must be a bool, object, or null; got ${typeof config}`];
3254
+ if (config !== null && config !== void 0 && typeof config !== "boolean" && (typeof config !== "object" || Array.isArray(config))) {
3255
+ const got = Array.isArray(config) ? "array" : typeof config;
3256
+ return [
3257
+ null,
3258
+ `apcore.yaml 'apcli:' must be a bool, object, or null; got ${got}`
3259
+ ];
2986
3260
  }
2987
3261
  if (config !== null && config !== void 0 && typeof config === "object" && !Array.isArray(config)) {
2988
3262
  const mode = config["mode"];
@@ -2999,6 +3273,8 @@ var init_builtin_group = __esm({
2999
3273
  // Internal builder — shared by both factories
3000
3274
  // -------------------------------------------------------------------------
3001
3275
  static _build(config, opts, fromCliConfig) {
3276
+ const name = opts.name ?? DEFAULT_BUILTIN_GROUP_NAME;
3277
+ _validateBuiltinGroupName(name);
3002
3278
  if (config === true) {
3003
3279
  return new _ApcliGroup({
3004
3280
  mode: "all",
@@ -3006,7 +3282,8 @@ var init_builtin_group = __esm({
3006
3282
  exclude: [],
3007
3283
  disableEnv: false,
3008
3284
  registryInjected: opts.registryInjected,
3009
- fromCliConfig
3285
+ fromCliConfig,
3286
+ name
3010
3287
  });
3011
3288
  }
3012
3289
  if (config === false) {
@@ -3016,7 +3293,8 @@ var init_builtin_group = __esm({
3016
3293
  exclude: [],
3017
3294
  disableEnv: false,
3018
3295
  registryInjected: opts.registryInjected,
3019
- fromCliConfig
3296
+ fromCliConfig,
3297
+ name
3020
3298
  });
3021
3299
  }
3022
3300
  if (config === void 0 || config === null) {
@@ -3026,7 +3304,8 @@ var init_builtin_group = __esm({
3026
3304
  exclude: [],
3027
3305
  disableEnv: false,
3028
3306
  registryInjected: opts.registryInjected,
3029
- fromCliConfig
3307
+ fromCliConfig,
3308
+ name
3030
3309
  });
3031
3310
  }
3032
3311
  if (typeof config !== "object" || Array.isArray(config)) {
@@ -3074,7 +3353,8 @@ var init_builtin_group = __esm({
3074
3353
  exclude,
3075
3354
  disableEnv,
3076
3355
  registryInjected: opts.registryInjected,
3077
- fromCliConfig
3356
+ fromCliConfig,
3357
+ name
3078
3358
  });
3079
3359
  }
3080
3360
  /**
@@ -3167,7 +3447,8 @@ var init_builtin_group = __esm({
3167
3447
  */
3168
3448
  _parseEnv(raw) {
3169
3449
  if (raw === void 0 || raw === "") return null;
3170
- const normalized = raw.toLowerCase();
3450
+ const normalized = raw.trim().toLowerCase();
3451
+ if (normalized === "") return null;
3171
3452
  if (normalized === "show" || normalized === "1" || normalized === "true") {
3172
3453
  return "all";
3173
3454
  }
@@ -3376,6 +3657,34 @@ var init_canonical_help = __esm({
3376
3657
  }
3377
3658
  });
3378
3659
 
3660
+ // src/validate.ts
3661
+ function validateModuleId(moduleId) {
3662
+ if (moduleId.length > MAX_MODULE_ID_LENGTH) {
3663
+ process.stderr.write(
3664
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is ${MAX_MODULE_ID_LENGTH} characters.
3665
+ `
3666
+ );
3667
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3668
+ }
3669
+ if (!MODULE_ID_PATTERN.test(moduleId)) {
3670
+ process.stderr.write(
3671
+ `Error: Invalid module ID format: '${moduleId}'.
3672
+ `
3673
+ );
3674
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3675
+ }
3676
+ }
3677
+ var MODULE_ID_PATTERN, MAX_MODULE_ID_LENGTH;
3678
+ var init_validate = __esm({
3679
+ "src/validate.ts"() {
3680
+ "use strict";
3681
+ init_esm_shims();
3682
+ init_errors();
3683
+ MODULE_ID_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/;
3684
+ MAX_MODULE_ID_LENGTH = 192;
3685
+ }
3686
+ });
3687
+
3379
3688
  // src/main.ts
3380
3689
  var main_exports = {};
3381
3690
  __export(main_exports, {
@@ -3384,9 +3693,6 @@ __export(main_exports, {
3384
3693
  clearBindingDisplayMap: () => clearBindingDisplayMap,
3385
3694
  collectInput: () => collectInput,
3386
3695
  createCli: () => createCli,
3387
- docsUrl: () => docsUrl,
3388
- emitErrorJson: () => emitErrorJson,
3389
- emitErrorTty: () => emitErrorTty,
3390
3696
  lookupBindingDisplay: () => lookupBindingDisplay,
3391
3697
  main: () => main,
3392
3698
  reconvertEnumValues: () => reconvertEnumValues,
@@ -3394,12 +3700,11 @@ __export(main_exports, {
3394
3700
  resolveStringOption: () => resolveStringOption,
3395
3701
  setDocsUrl: () => setDocsUrl,
3396
3702
  setVerboseHelp: () => setVerboseHelp,
3397
- validateModuleId: () => validateModuleId,
3398
- verboseHelp: () => verboseHelp
3703
+ validateModuleId: () => validateModuleId
3399
3704
  });
3400
- import { readFileSync as readFileSync2 } from "fs";
3705
+ import { readFileSync as readFileSync3 } from "fs";
3401
3706
  import { fileURLToPath as fileURLToPath2 } from "url";
3402
- import * as path4 from "path";
3707
+ import * as path5 from "path";
3403
3708
  import { Command as Command5, CommanderError, Option as Option4 } from "commander";
3404
3709
  function setVerboseHelp(verbose) {
3405
3710
  verboseHelp = verbose;
@@ -3491,6 +3796,10 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3491
3796
  let app;
3492
3797
  let expose;
3493
3798
  let apcliOption;
3799
+ let appVersion;
3800
+ let appDescription;
3801
+ let allowedPrefixes;
3802
+ let builtinGroupName;
3494
3803
  if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
3495
3804
  extensionsDir = extensionsDirOrOpts.extensionsDir;
3496
3805
  progName = extensionsDirOrOpts.progName ?? progName;
@@ -3501,6 +3810,10 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3501
3810
  extraCommands = extensionsDirOrOpts.extraCommands;
3502
3811
  expose = extensionsDirOrOpts.expose;
3503
3812
  apcliOption = extensionsDirOrOpts.apcli;
3813
+ appVersion = extensionsDirOrOpts.version;
3814
+ appDescription = extensionsDirOrOpts.description;
3815
+ builtinGroupName = extensionsDirOrOpts.builtinGroupName;
3816
+ allowedPrefixes = extensionsDirOrOpts.allowedPrefixes;
3504
3817
  } else {
3505
3818
  extensionsDir = extensionsDirOrOpts;
3506
3819
  }
@@ -3511,7 +3824,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3511
3824
  setAuditLogger(auditLogger);
3512
3825
  } catch {
3513
3826
  }
3514
- const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
3827
+ const resolvedProgName = progName ?? path5.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
3515
3828
  const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
3516
3829
  setLogLevel(cliLogLevel);
3517
3830
  if (app && (registry || executor)) {
@@ -3537,7 +3850,10 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3537
3850
  }
3538
3851
  }
3539
3852
  const registryInjected = registry !== void 0;
3540
- const program = new Command5(resolvedProgName).exitOverride().version(VERSION, "-V, --version", "Print version").helpOption("-h, --help", "Print help").addHelpCommand("help [command]", "Print this message or the help of the given subcommand(s)").description("apcore CLI \u2014 execute apcore modules from the command line").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in apcore options)");
3853
+ const program = new Command5(resolvedProgName).exitOverride().helpOption("-h, --help", "Print help").addHelpCommand("help [command]", "Print this message or the help of the given subcommand(s)").description(appDescription ?? `${resolvedProgName} CLI`).option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in options)");
3854
+ if (appVersion) {
3855
+ program.version(appVersion, "-V, --version", "Print version");
3856
+ }
3541
3857
  program.configureHelp({ formatHelp: canonicalFormatHelp });
3542
3858
  if (!registryInjected) {
3543
3859
  program.option("--extensions-dir <path>", "Path to extensions directory");
@@ -3545,21 +3861,39 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3545
3861
  program.option("--binding <path>", "Path to binding.yaml for display overlay");
3546
3862
  }
3547
3863
  let apcliCfg;
3548
- if (apcliOption instanceof ApcliGroup) {
3549
- apcliCfg = apcliOption;
3550
- } else if (apcliOption !== void 0) {
3551
- apcliCfg = ApcliGroup.fromCliConfig(apcliOption, { registryInjected });
3552
- } else {
3553
- let yamlVal = null;
3554
- try {
3555
- const resolver = new ConfigResolver();
3556
- yamlVal = resolver.resolveObject("apcli");
3557
- } catch {
3558
- yamlVal = null;
3864
+ try {
3865
+ if (apcliOption instanceof ApcliGroup) {
3866
+ if (builtinGroupName !== void 0 && builtinGroupName !== "apcli" && apcliOption.name !== builtinGroupName) {
3867
+ throw new Error(
3868
+ `builtinGroupName=${JSON.stringify(builtinGroupName)} conflicts with the name on the supplied ApcliGroup (${JSON.stringify(apcliOption.name)}). Pass only one.`
3869
+ );
3870
+ }
3871
+ apcliCfg = apcliOption;
3872
+ } else if (apcliOption !== void 0) {
3873
+ apcliCfg = ApcliGroup.fromCliConfig(apcliOption, {
3874
+ registryInjected,
3875
+ name: builtinGroupName
3876
+ });
3877
+ } else {
3878
+ let yamlVal = null;
3879
+ try {
3880
+ const resolver = new ConfigResolver();
3881
+ yamlVal = resolver.resolveObject("apcli");
3882
+ } catch {
3883
+ yamlVal = null;
3884
+ }
3885
+ apcliCfg = ApcliGroup.fromYaml(yamlVal, {
3886
+ registryInjected,
3887
+ name: builtinGroupName
3888
+ });
3559
3889
  }
3560
- apcliCfg = ApcliGroup.fromYaml(yamlVal, { registryInjected });
3890
+ } catch (e) {
3891
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : String(e)}
3892
+ `);
3893
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3561
3894
  }
3562
- const apcliGroup = program.command("apcli", { hidden: !apcliCfg.isGroupVisible() }).description("apcore-cli built-in commands");
3895
+ setReservedGroupNames(/* @__PURE__ */ new Set([apcliCfg.name]));
3896
+ const apcliGroup = program.command(apcliCfg.name, { hidden: !apcliCfg.isGroupVisible() }).description("Built-in commands");
3563
3897
  if (registry) {
3564
3898
  program._registry = registry;
3565
3899
  if (executor) {
@@ -3585,17 +3919,17 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3585
3919
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3586
3920
  }
3587
3921
  _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter);
3588
- _registerDeprecationShims(program, apcliGroup, registryInjected, resolvedProgName);
3589
3922
  program.addHelpText("after", [
3590
3923
  "",
3591
- "Use --help --verbose to show all options (including built-in apcore options).",
3924
+ "Use --help --verbose to show all options (including built-in options).",
3592
3925
  "Use --help --man to display a formatted man page."
3593
3926
  ].join("\n"));
3594
- configureManHelp(program, resolvedProgName, VERSION);
3927
+ configureManHelp(program, resolvedProgName, appVersion ?? VERSION);
3595
3928
  if (extraCommands && extraCommands.length > 0) {
3929
+ const _reservedForExtra = /* @__PURE__ */ new Set([apcliCfg.name]);
3596
3930
  for (const cmd of extraCommands) {
3597
3931
  const cmdName = cmd.name();
3598
- if (RESERVED_GROUP_NAMES.has(cmdName)) {
3932
+ if (_reservedForExtra.has(cmdName)) {
3599
3933
  process.stderr.write(
3600
3934
  `Error: extraCommands name '${cmdName}' is reserved
3601
3935
  `
@@ -3604,21 +3938,11 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3604
3938
  }
3605
3939
  const existing = program.commands.find((c) => c.name() === cmdName);
3606
3940
  if (existing) {
3607
- const isShim = existing.__isDeprecationShim === true;
3608
- if (isShim) {
3609
- warn(
3610
- `extraCommands '${cmdName}' overrides the deprecation shim for the same name. The shim will be removed.`
3611
- );
3612
- const cmds = program.commands;
3613
- const idx = cmds.indexOf(existing);
3614
- if (idx >= 0) cmds.splice(idx, 1);
3615
- } else {
3616
- process.stderr.write(
3617
- `Error: extraCommands name '${cmdName}' collides with an existing command
3941
+ process.stderr.write(
3942
+ `Error: extraCommands name '${cmdName}' collides with an existing command
3618
3943
  `
3619
- );
3620
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3621
- }
3944
+ );
3945
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3622
3946
  }
3623
3947
  program.addCommand(cmd);
3624
3948
  }
@@ -3627,7 +3951,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3627
3951
  const opts = thisCommand.opts();
3628
3952
  const commandsDir = opts.commandsDir;
3629
3953
  const bindingPath = opts.binding;
3630
- await applyToolkitIntegration(commandsDir, bindingPath);
3954
+ await applyToolkitIntegration(commandsDir, bindingPath, { allowedPrefixes });
3631
3955
  });
3632
3956
  return program;
3633
3957
  }
@@ -3677,40 +4001,13 @@ function _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exp
3677
4001
  entry.register(apcliGroup, registry, executor);
3678
4002
  }
3679
4003
  }
3680
- function _registerDeprecationShims(root, apcliGroup, registryInjected, cliName) {
3681
- if (registryInjected) return;
3682
- for (const name of _DEPRECATED_ROOT_COMMANDS) {
3683
- const apcliSub = apcliGroup.commands.find((c) => c.name() === name);
3684
- if (!apcliSub) continue;
3685
- if (root.commands.some((c) => c.name() === name)) continue;
3686
- const shim = root.command(name).description(`[DEPRECATED] Use '${cliName} apcli ${name}' instead.`).allowUnknownOption(true).allowExcessArguments(true).helpOption(false);
3687
- shim.__isDeprecationShim = true;
3688
- shim.action(async function() {
3689
- process.stderr.write(
3690
- `WARNING: '${name}' as a root-level command is deprecated. Use '${cliName} apcli ${name}' instead.
3691
- Will be removed in v0.8. See: https://aiperceivable.github.io/apcore-cli/features/builtin-group/#11-migration
3692
- `
3693
- );
3694
- const tail = _collectShimForwardArgs(this);
3695
- await apcliSub.parseAsync(tail, { from: "user" });
3696
- });
3697
- }
3698
- }
3699
- function _collectShimForwardArgs(shim) {
3700
- const shimArgs = (shim.args ?? []).slice();
3701
- if (shimArgs.length > 0) return shimArgs;
3702
- const shimName = shim.name();
3703
- const idx = process.argv.indexOf(shimName);
3704
- if (idx < 0) return [];
3705
- return process.argv.slice(idx + 1);
3706
- }
3707
4004
  function lookupBindingDisplay(moduleId) {
3708
4005
  return bindingDisplayMap.get(moduleId);
3709
4006
  }
3710
4007
  function clearBindingDisplayMap() {
3711
4008
  bindingDisplayMap.clear();
3712
4009
  }
3713
- async function applyToolkitIntegration(commandsDir, bindingPath) {
4010
+ async function applyToolkitIntegration(commandsDir, bindingPath, options = {}) {
3714
4011
  if (!commandsDir && !bindingPath) {
3715
4012
  return;
3716
4013
  }
@@ -3730,14 +4027,14 @@ async function applyToolkitIntegration(commandsDir, bindingPath) {
3730
4027
  }
3731
4028
  if (bindingPath) {
3732
4029
  try {
3733
- await loadBindingDisplayOverlay(toolkit, bindingPath);
4030
+ await loadBindingDisplayOverlay(toolkit, bindingPath, options.allowedPrefixes);
3734
4031
  } catch (err) {
3735
4032
  const msg = err instanceof Error ? err.message : String(err);
3736
4033
  warn(`apcore-toolkit: failed to load binding '${bindingPath}': ${msg}`);
3737
4034
  }
3738
4035
  }
3739
4036
  }
3740
- async function loadBindingDisplayOverlay(toolkit, bindingPath) {
4037
+ async function loadBindingDisplayOverlay(toolkit, bindingPath, allowedPrefixes) {
3741
4038
  const BindingLoaderCtor = toolkit.BindingLoader;
3742
4039
  const DisplayResolverCtor = toolkit.DisplayResolver;
3743
4040
  if (!BindingLoaderCtor || !DisplayResolverCtor) {
@@ -3747,11 +4044,23 @@ async function loadBindingDisplayOverlay(toolkit, bindingPath) {
3747
4044
  const scanned = loader.load(bindingPath);
3748
4045
  const resolver = new DisplayResolverCtor();
3749
4046
  const resolved = resolver.resolve(scanned, { bindingPath });
4047
+ const prefixes = allowedPrefixes && allowedPrefixes.length > 0 ? allowedPrefixes : null;
4048
+ const isTargetAllowed = (target) => {
4049
+ if (!prefixes) return true;
4050
+ if (typeof target !== "string" || target.length === 0) return true;
4051
+ return prefixes.some((p) => target.startsWith(p));
4052
+ };
3750
4053
  for (const mod of resolved) {
3751
4054
  if (!mod || typeof mod !== "object") continue;
3752
4055
  const entry = mod;
3753
4056
  const id = typeof entry.moduleId === "string" ? entry.moduleId : null;
3754
4057
  if (!id) continue;
4058
+ if (!isTargetAllowed(entry.target)) {
4059
+ warn(
4060
+ `apcore-toolkit: dropped binding entry '${id}' \u2014 target '${String(entry.target)}' is outside allowedPrefixes`
4061
+ );
4062
+ continue;
4063
+ }
3755
4064
  const meta = entry.metadata ?? {};
3756
4065
  const display = meta.display;
3757
4066
  if (display && typeof display === "object" && !Array.isArray(display)) {
@@ -3761,7 +4070,12 @@ async function loadBindingDisplayOverlay(toolkit, bindingPath) {
3761
4070
  }
3762
4071
  function main(progName) {
3763
4072
  verboseHelp = hasVerboseFlag();
3764
- const program = createCli(void 0, progName, verboseHelp);
4073
+ const program = createCli({
4074
+ progName,
4075
+ verbose: verboseHelp,
4076
+ version: VERSION,
4077
+ description: `${progName ?? "apcore-cli"} \u2014 execute apcore modules from the command line`
4078
+ });
3765
4079
  try {
3766
4080
  program.parse(process.argv);
3767
4081
  } catch (error) {
@@ -4068,22 +4382,6 @@ Pipeline Trace (strategy: ${trace.strategyName}, ${stepCount} steps, ${trace.tot
4068
4382
  });
4069
4383
  return cmd;
4070
4384
  }
4071
- function validateModuleId(moduleId) {
4072
- if (moduleId.length > 192) {
4073
- process.stderr.write(
4074
- `Error: Invalid module ID format: '${moduleId}'. Maximum length is 192 characters.
4075
- `
4076
- );
4077
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
4078
- }
4079
- if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
4080
- process.stderr.write(
4081
- `Error: Invalid module ID format: '${moduleId}'.
4082
- `
4083
- );
4084
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
4085
- }
4086
- }
4087
4385
  async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
4088
4386
  const cliKwargsNonNull = {};
4089
4387
  for (const [k, v] of Object.entries(cliKwargs)) {
@@ -4102,7 +4400,7 @@ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
4102
4400
  } else {
4103
4401
  source = `file '${stdinFlag}'`;
4104
4402
  try {
4105
- raw = readFileSync2(stdinFlag, "utf-8");
4403
+ raw = readFileSync3(stdinFlag, "utf-8");
4106
4404
  } catch (err) {
4107
4405
  const msg = err instanceof Error ? err.message : String(err);
4108
4406
  process.stderr.write(`Error: Could not read input ${source}: ${msg}
@@ -4181,7 +4479,7 @@ function reconvertEnumValues(kwargs, options) {
4181
4479
  }
4182
4480
  return result;
4183
4481
  }
4184
- var __dirname2, verboseHelp, docsUrl, VERSION, _ALWAYS_REGISTERED, _DEPRECATED_ROOT_COMMANDS, bindingDisplayMap;
4482
+ var __dirname2, verboseHelp, docsUrl, VERSION, _ALWAYS_REGISTERED, bindingDisplayMap;
4185
4483
  var init_main = __esm({
4186
4484
  "src/main.ts"() {
4187
4485
  "use strict";
@@ -4204,31 +4502,17 @@ var init_main = __esm({
4204
4502
  init_exposure();
4205
4503
  init_audit();
4206
4504
  init_canonical_help();
4207
- __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
4505
+ init_validate();
4506
+ __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
4208
4507
  verboseHelp = false;
4209
4508
  docsUrl = null;
4210
4509
  VERSION = "0.0.0";
4211
4510
  try {
4212
- const pkg = JSON.parse(readFileSync2(path4.resolve(__dirname2, "../package.json"), "utf-8"));
4511
+ const pkg = JSON.parse(readFileSync3(path5.resolve(__dirname2, "../package.json"), "utf-8"));
4213
4512
  VERSION = pkg.version;
4214
4513
  } catch {
4215
4514
  }
4216
4515
  _ALWAYS_REGISTERED = /* @__PURE__ */ new Set(["exec"]);
4217
- _DEPRECATED_ROOT_COMMANDS = [
4218
- "list",
4219
- "describe",
4220
- "exec",
4221
- "init",
4222
- "validate",
4223
- "health",
4224
- "usage",
4225
- "enable",
4226
- "disable",
4227
- "reload",
4228
- "config",
4229
- "completion",
4230
- "describe-pipeline"
4231
- ];
4232
4516
  bindingDisplayMap = /* @__PURE__ */ new Map();
4233
4517
  }
4234
4518
  });