apcore-cli 0.7.0 → 0.8.1

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