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.
package/dist/index.js CHANGED
@@ -220,8 +220,8 @@ var init_audit = __esm({
220
220
  );
221
221
  logPath;
222
222
  writeFailureWarned = false;
223
- constructor(path5) {
224
- this.logPath = path5 ?? _AuditLogger.DEFAULT_PATH;
223
+ constructor(path6) {
224
+ this.logPath = path6 ?? _AuditLogger.DEFAULT_PATH;
225
225
  this.ensureDirectory();
226
226
  }
227
227
  ensureDirectory() {
@@ -267,7 +267,7 @@ var init_audit = __esm({
267
267
  try {
268
268
  return os.userInfo().username;
269
269
  } catch {
270
- return process.env.USER ?? process.env.USERNAME ?? "unknown";
270
+ return process.env.USER ?? process.env.LOGNAME ?? process.env.USERNAME ?? "unknown";
271
271
  }
272
272
  }
273
273
  };
@@ -276,7 +276,7 @@ var init_audit = __esm({
276
276
 
277
277
  // src/security/config-encryptor.ts
278
278
  import * as crypto2 from "crypto";
279
- import * as os2 from "os";
279
+ import * as os3 from "os";
280
280
  async function getKeytar() {
281
281
  if (keytarModule) return keytarModule;
282
282
  try {
@@ -405,7 +405,7 @@ var init_config_encryptor = __esm({
405
405
  );
406
406
  _ConfigEncryptor.weakFallbackWarned = true;
407
407
  }
408
- const hostname2 = os2.hostname();
408
+ const hostname2 = os3.hostname();
409
409
  const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
410
410
  const material = `${hostname2}:${username}`;
411
411
  return crypto2.pbkdf2Sync(material, salt, PBKDF2_ITERATIONS, 32, "sha256");
@@ -437,7 +437,7 @@ var init_config_encryptor = __esm({
437
437
  const nonce = data.subarray(0, 12);
438
438
  const tag = data.subarray(12, 28);
439
439
  const ct = data.subarray(28);
440
- const hostname2 = os2.hostname();
440
+ const hostname2 = os3.hostname();
441
441
  const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
442
442
  const passphrase = process.env.APCORE_CLI_CONFIG_PASSPHRASE;
443
443
  const materials = passphrase ? [passphrase, `${hostname2}:${username}`] : [`${hostname2}:${username}`];
@@ -557,7 +557,7 @@ function buildSandboxEnv(tmpDir) {
557
557
  env.TMPDIR = tmpDir;
558
558
  return env;
559
559
  }
560
- var SANDBOX_ALLOW_KEYS, SANDBOX_ALLOW_PREFIX, SANDBOX_DENY_PREFIX, SANDBOX_DENY_KEYS, SANDBOX_OUTPUT_SIZE_LIMIT, Sandbox;
560
+ var SANDBOX_ALLOW_KEYS, SANDBOX_ALLOW_PREFIX, SANDBOX_DENY_PREFIX, SANDBOX_DENY_KEYS, SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT, Sandbox;
561
561
  var init_sandbox = __esm({
562
562
  "src/security/sandbox.ts"() {
563
563
  "use strict";
@@ -567,14 +567,41 @@ var init_sandbox = __esm({
567
567
  SANDBOX_ALLOW_PREFIX = "APCORE_";
568
568
  SANDBOX_DENY_PREFIX = "APCORE_AUTH_";
569
569
  SANDBOX_DENY_KEYS = ["APCORE_AUTH_API_KEY"];
570
- SANDBOX_OUTPUT_SIZE_LIMIT = 64 * 1024 * 1024;
570
+ SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT = 64 * 1024 * 1024;
571
571
  Sandbox = class {
572
+ /** Default post-capture stdout+stderr byte budget for sandboxed children. */
573
+ static DEFAULT_MAX_OUTPUT_BYTES = SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT;
572
574
  enabled;
573
575
  timeoutSeconds;
576
+ extensionsRoot = null;
577
+ maxOutputBytes = SANDBOX_DEFAULT_OUTPUT_SIZE_LIMIT;
574
578
  constructor(enabled = false, timeoutSeconds = 300) {
575
579
  this.enabled = enabled;
576
580
  this.timeoutSeconds = timeoutSeconds;
577
581
  }
582
+ /**
583
+ * Set the extensions root that is forwarded to the sandboxed runner via
584
+ * `APCORE_EXTENSIONS_ROOT`. The path is resolved to absolute when injected
585
+ * so the child (whose cwd is the fresh sandbox tempdir) can locate modules.
586
+ *
587
+ * Builder-style — returns `this` so call sites can chain. Mirrors Python's
588
+ * `Sandbox.with_extensions_root` (D1-004 cross-SDK parity).
589
+ */
590
+ withExtensionsRoot(extensionsRoot) {
591
+ this.extensionsRoot = extensionsRoot;
592
+ return this;
593
+ }
594
+ /**
595
+ * Cap the post-capture stdout+stderr byte budget for the sandboxed
596
+ * subprocess. Default: 64 MiB (`Sandbox.DEFAULT_MAX_OUTPUT_BYTES`).
597
+ *
598
+ * Builder-style — returns `this`. Mirrors Python's
599
+ * `Sandbox.with_max_output_bytes` (D1-004 cross-SDK parity).
600
+ */
601
+ withMaxOutputBytes(maxOutputBytes) {
602
+ this.maxOutputBytes = maxOutputBytes;
603
+ return this;
604
+ }
578
605
  /**
579
606
  * Execute a module, optionally inside a sandboxed subprocess.
580
607
  */
@@ -587,10 +614,16 @@ var init_sandbox = __esm({
587
614
  async _sandboxedExecute(moduleId, inputData) {
588
615
  const { spawn } = await import("child_process");
589
616
  const { tmpdir } = await import("os");
590
- const { join: join3 } = await import("path");
617
+ const { join: join4 } = await import("path");
591
618
  const { mkdtempSync, rmSync } = await import("fs");
592
- const tmpDir = mkdtempSync(join3(tmpdir(), "apcore_sandbox_"));
619
+ const tmpDir = mkdtempSync(join4(tmpdir(), "apcore_sandbox_"));
593
620
  const env = buildSandboxEnv(tmpDir);
621
+ const { resolve: resolvePath } = await import("path");
622
+ if (this.extensionsRoot !== null) {
623
+ env.APCORE_EXTENSIONS_ROOT = resolvePath(this.extensionsRoot);
624
+ } else if (env.APCORE_EXTENSIONS_ROOT) {
625
+ env.APCORE_EXTENSIONS_ROOT = resolvePath(env.APCORE_EXTENSIONS_ROOT);
626
+ }
594
627
  const binaryPath = process.argv[1];
595
628
  const child = spawn(process.execPath, [binaryPath, "--internal-sandbox-runner", moduleId], {
596
629
  env,
@@ -599,11 +632,14 @@ var init_sandbox = __esm({
599
632
  });
600
633
  let stdout = "";
601
634
  let stderr = "";
602
- let totalBytes = 0;
635
+ let stdoutBytes = 0;
636
+ let stderrBytes = 0;
603
637
  let sizeExceeded = false;
638
+ const outputCap = this.maxOutputBytes;
604
639
  child.stdout.on("data", (chunk) => {
605
- totalBytes += chunk.length;
606
- if (totalBytes > SANDBOX_OUTPUT_SIZE_LIMIT) {
640
+ if (sizeExceeded) return;
641
+ stdoutBytes += chunk.length;
642
+ if (stdoutBytes > outputCap) {
607
643
  sizeExceeded = true;
608
644
  child.kill("SIGKILL");
609
645
  return;
@@ -611,6 +647,13 @@ var init_sandbox = __esm({
611
647
  stdout += chunk.toString();
612
648
  });
613
649
  child.stderr.on("data", (chunk) => {
650
+ if (sizeExceeded) return;
651
+ stderrBytes += chunk.length;
652
+ if (stderrBytes > outputCap) {
653
+ sizeExceeded = true;
654
+ child.kill("SIGKILL");
655
+ return;
656
+ }
614
657
  stderr += chunk.toString();
615
658
  });
616
659
  child.stdin.write(JSON.stringify(inputData));
@@ -631,7 +674,10 @@ var init_sandbox = __esm({
631
674
  } catch {
632
675
  }
633
676
  if (sizeExceeded) {
634
- reject(new ModuleExecutionError(`Sandbox module '${moduleId}' output exceeded 64MiB limit.`));
677
+ const limitMiB = Math.floor(outputCap / (1024 * 1024));
678
+ reject(new ModuleExecutionError(
679
+ `Sandbox module '${moduleId}' output exceeded ${limitMiB}MiB limit.`
680
+ ));
635
681
  return;
636
682
  }
637
683
  if (code !== 0) {
@@ -685,9 +731,9 @@ init_esm_shims();
685
731
  // src/main.ts
686
732
  init_esm_shims();
687
733
  init_errors();
688
- import { readFileSync as readFileSync2 } from "fs";
734
+ import { readFileSync as readFileSync3 } from "fs";
689
735
  import { fileURLToPath as fileURLToPath2 } from "url";
690
- import * as path4 from "path";
736
+ import * as path5 from "path";
691
737
  import { Command as Command5, CommanderError, Option as Option4 } from "commander";
692
738
 
693
739
  // src/ref-resolver.ts
@@ -786,6 +832,10 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
786
832
  properties: {},
787
833
  required: []
788
834
  };
835
+ if (typeof obj.properties === "object" && obj.properties !== null) {
836
+ Object.assign(merged.properties, obj.properties);
837
+ }
838
+ const siblingRequired = Array.isArray(obj.required) ? obj.required.slice() : [];
789
839
  const allRequiredSets = [];
790
840
  for (const subSchema of obj[keyword]) {
791
841
  const resolved = resolveNode(
@@ -806,6 +856,7 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
806
856
  allRequiredSets.push(new Set(resolved.required));
807
857
  }
808
858
  }
859
+ let branchRequired = [];
809
860
  if (allRequiredSets.length > 0) {
810
861
  let intersection = allRequiredSets[0];
811
862
  for (let i = 1; i < allRequiredSets.length; i++) {
@@ -813,10 +864,17 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
813
864
  [...intersection].filter((x) => allRequiredSets[i].has(x))
814
865
  );
815
866
  }
816
- merged.required = [...intersection];
817
- } else {
818
- merged.required = [];
867
+ branchRequired = [...intersection];
819
868
  }
869
+ const seen = /* @__PURE__ */ new Set();
870
+ const combinedRequired = [];
871
+ for (const r of [...siblingRequired, ...branchRequired]) {
872
+ if (!seen.has(r)) {
873
+ seen.add(r);
874
+ combinedRequired.push(r);
875
+ }
876
+ }
877
+ merged.required = combinedRequired;
820
878
  for (const [k, v] of Object.entries(obj)) {
821
879
  if (k !== keyword && !(k in merged)) {
822
880
  merged[k] = v;
@@ -832,7 +890,7 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
832
890
  propSchema,
833
891
  defs,
834
892
  visited,
835
- depth + 1,
893
+ depth,
836
894
  maxDepth,
837
895
  moduleId
838
896
  );
@@ -844,6 +902,7 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
844
902
  // src/schema-parser.ts
845
903
  init_esm_shims();
846
904
  init_errors();
905
+ init_logger();
847
906
  var BOOLEAN_FLAG = /* @__PURE__ */ Symbol("BOOLEAN_FLAG");
848
907
  function mapType(propName, propSchema) {
849
908
  const schemaType = propSchema.type;
@@ -896,6 +955,13 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
896
955
  const requiredList = schema.required ?? [];
897
956
  const options = [];
898
957
  const flagNames = {};
958
+ for (const reqName of requiredList) {
959
+ if (!(reqName in properties)) {
960
+ warn(
961
+ `Required property '${reqName}' not found in properties, skipping.`
962
+ );
963
+ }
964
+ }
899
965
  for (const [propName, propSchema] of Object.entries(properties)) {
900
966
  const flagName = "--" + propName.replace(/_/g, "-");
901
967
  if (RESERVED_NAMES.has(propName)) {
@@ -903,14 +969,14 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
903
969
  `Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.
904
970
  `
905
971
  );
906
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
972
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
907
973
  }
908
974
  if (flagName in flagNames) {
909
975
  process.stderr.write(
910
976
  `Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.
911
977
  `
912
978
  );
913
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
979
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
914
980
  }
915
981
  flagNames[flagName] = propName;
916
982
  const typeResult = mapType(propName, propSchema);
@@ -926,7 +992,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
926
992
  `Error: Flag name collision: boolean property '${propName}' auto-generates '${noFlag}' which is already used by property '${flagNames[noFlag]}'.
927
993
  `
928
994
  );
929
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
995
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
930
996
  }
931
997
  flagNames[noFlag] = propName;
932
998
  const defaultVal = propSchema.default ?? false;
@@ -1132,6 +1198,27 @@ async function promptWithTimeout(moduleDef, timeout) {
1132
1198
  init_esm_shims();
1133
1199
  init_errors();
1134
1200
  import yaml from "js-yaml";
1201
+ var TOOLKIT_MISSING_HINT = "The 'markdown' and 'skill' output formats require the apcore-toolkit peer dependency. Install with: npm install apcore-toolkit@^0.6";
1202
+ function descriptorToScanned(m) {
1203
+ const metadata = m.metadata ?? {};
1204
+ const display = metadata["display"] ?? null;
1205
+ return {
1206
+ moduleId: m.id,
1207
+ description: m.description ?? "",
1208
+ inputSchema: m.inputSchema ?? {},
1209
+ outputSchema: m.outputSchema ?? {},
1210
+ tags: m.tags ?? [],
1211
+ target: "",
1212
+ version: "1.0.0",
1213
+ annotations: m.annotations ?? null,
1214
+ documentation: null,
1215
+ suggestedAlias: null,
1216
+ examples: [],
1217
+ metadata,
1218
+ display,
1219
+ warnings: []
1220
+ };
1221
+ }
1135
1222
  function csvCellString(value) {
1136
1223
  if (value === null || value === void 0) return "";
1137
1224
  if (typeof value === "object") return JSON.stringify(value);
@@ -1160,7 +1247,7 @@ function formatTable(headers, rows) {
1160
1247
  );
1161
1248
  return [headerLine, sep2, ...dataLines].join("\n") + "\n";
1162
1249
  }
1163
- function formatModuleList(modules, format, filterTags, showDeps = false, exposureFilter) {
1250
+ async function formatModuleList(modules, format, filterTags, showDeps = false, exposureFilter) {
1164
1251
  if (format === "table") {
1165
1252
  if (modules.length === 0 && filterTags && filterTags.length > 0) {
1166
1253
  process.stdout.write(
@@ -1205,6 +1292,17 @@ function formatModuleList(modules, format, filterTags, showDeps = false, exposur
1205
1292
  return entry;
1206
1293
  });
1207
1294
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1295
+ } else if (format === "markdown" || format === "skill") {
1296
+ let toolkit;
1297
+ try {
1298
+ toolkit = await import("apcore-toolkit");
1299
+ } catch {
1300
+ throw new Error(TOOLKIT_MISSING_HINT);
1301
+ }
1302
+ const scanned = modules.map(descriptorToScanned);
1303
+ process.stdout.write(
1304
+ toolkit.formatModules(scanned, { style: format, display: true }) + "\n"
1305
+ );
1208
1306
  }
1209
1307
  }
1210
1308
  function annotationsToDict(annotations) {
@@ -1218,7 +1316,7 @@ function annotationsToDict(annotations) {
1218
1316
  }
1219
1317
  return Object.keys(result).length > 0 ? result : null;
1220
1318
  }
1221
- function formatModuleDetail(moduleDef, format) {
1319
+ async function formatModuleDetail(moduleDef, format) {
1222
1320
  if (format === "table") {
1223
1321
  process.stdout.write(`
1224
1322
  Module: ${moduleDef.id}
@@ -1289,6 +1387,16 @@ Tags: ${tags.join(", ")}
1289
1387
  }
1290
1388
  }
1291
1389
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1390
+ } else if (format === "markdown" || format === "skill") {
1391
+ let toolkit;
1392
+ try {
1393
+ toolkit = await import("apcore-toolkit");
1394
+ } catch {
1395
+ throw new Error(TOOLKIT_MISSING_HINT);
1396
+ }
1397
+ process.stdout.write(
1398
+ toolkit.formatModule(descriptorToScanned(moduleDef), { style: format, display: true }) + "\n"
1399
+ );
1292
1400
  }
1293
1401
  }
1294
1402
  function selectFields(result, fields) {
@@ -2133,6 +2241,98 @@ init_esm_shims();
2133
2241
  import { Command as Command2, Option as Option2 } from "commander";
2134
2242
  init_errors();
2135
2243
  init_audit();
2244
+
2245
+ // src/system-usage.ts
2246
+ init_esm_shims();
2247
+ import * as fs4 from "fs";
2248
+ import * as os2 from "os";
2249
+ import * as path4 from "path";
2250
+ var PERIOD_TO_MS = {
2251
+ "1h": 60 * 60 * 1e3,
2252
+ "24h": 24 * 60 * 60 * 1e3,
2253
+ "7d": 7 * 24 * 60 * 60 * 1e3,
2254
+ "30d": 30 * 24 * 60 * 60 * 1e3
2255
+ };
2256
+ var DEFAULT_AUDIT_PATH = path4.join(
2257
+ os2.homedir(),
2258
+ ".apcore-cli",
2259
+ "audit.jsonl"
2260
+ );
2261
+ function computeSummary(options = {}) {
2262
+ const auditPath = options.auditPath ?? DEFAULT_AUDIT_PATH;
2263
+ const period = options.period ?? "24h";
2264
+ const cutoff = (options.now ?? /* @__PURE__ */ new Date()).getTime() - PERIOD_TO_MS[period];
2265
+ if (!fs4.existsSync(auditPath)) {
2266
+ return /* @__PURE__ */ new Map();
2267
+ }
2268
+ let raw;
2269
+ try {
2270
+ raw = fs4.readFileSync(auditPath, "utf-8");
2271
+ } catch {
2272
+ return /* @__PURE__ */ new Map();
2273
+ }
2274
+ const counts = /* @__PURE__ */ new Map();
2275
+ const errors = /* @__PURE__ */ new Map();
2276
+ const latencySum = /* @__PURE__ */ new Map();
2277
+ for (const line of raw.split("\n")) {
2278
+ const trimmed = line.trim();
2279
+ if (!trimmed) continue;
2280
+ let entry;
2281
+ try {
2282
+ entry = JSON.parse(trimmed);
2283
+ } catch {
2284
+ continue;
2285
+ }
2286
+ const ts = typeof entry.timestamp === "string" ? Date.parse(entry.timestamp) : NaN;
2287
+ if (Number.isNaN(ts) || ts < cutoff) continue;
2288
+ const moduleId = typeof entry.module_id === "string" ? entry.module_id : null;
2289
+ if (!moduleId) continue;
2290
+ counts.set(moduleId, (counts.get(moduleId) ?? 0) + 1);
2291
+ if (entry.status === "error") {
2292
+ errors.set(moduleId, (errors.get(moduleId) ?? 0) + 1);
2293
+ }
2294
+ const duration = entry.duration_ms;
2295
+ if (typeof duration === "number") {
2296
+ latencySum.set(moduleId, (latencySum.get(moduleId) ?? 0) + duration);
2297
+ }
2298
+ }
2299
+ const out = /* @__PURE__ */ new Map();
2300
+ for (const [id, calls] of counts) {
2301
+ out.set(id, {
2302
+ module_id: id,
2303
+ calls,
2304
+ errors: errors.get(id) ?? 0,
2305
+ latency_ms: calls > 0 ? (latencySum.get(id) ?? 0) / calls : 0
2306
+ });
2307
+ }
2308
+ return out;
2309
+ }
2310
+ function sortModulesByUsage(modules, field, options = {}) {
2311
+ const reverse = options.reverse ?? true;
2312
+ const summary = computeSummary({
2313
+ auditPath: options.auditPath,
2314
+ period: options.period
2315
+ });
2316
+ if (summary.size === 0) {
2317
+ modules.sort((a, b) => (a.id ?? a.module_id ?? "").localeCompare(b.id ?? b.module_id ?? ""));
2318
+ if (reverse) modules.reverse();
2319
+ return { used: false };
2320
+ }
2321
+ const key = (m) => {
2322
+ const id = m.id ?? m.module_id ?? "";
2323
+ const s = summary.get(id);
2324
+ if (!s) return 0;
2325
+ return field === "latency" ? s.latency_ms : field === "calls" ? s.calls : s.errors;
2326
+ };
2327
+ modules.sort((a, b) => {
2328
+ const diff = key(a) - key(b);
2329
+ if (diff !== 0) return reverse ? -diff : diff;
2330
+ return (a.id ?? a.module_id ?? "").localeCompare(b.id ?? b.module_id ?? "");
2331
+ });
2332
+ return { used: true };
2333
+ }
2334
+
2335
+ // src/discovery.ts
2136
2336
  var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
2137
2337
  function validateTag(tag) {
2138
2338
  if (!TAG_PATTERN.test(tag)) {
@@ -2166,7 +2366,9 @@ function getAnnotationFlag(moduleDef, flag) {
2166
2366
  return ann[attr] === true;
2167
2367
  }
2168
2368
  function registerListCommand(apcliGroup, registry, exposureFilter) {
2169
- 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(
2369
+ 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(
2370
+ new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
2371
+ ).option("-s, --search <query>", "Filter by substring match on ID and description.").addOption(
2170
2372
  new Option2("--status <status>", "Filter by module status.").choices(["enabled", "disabled", "all"]).default("enabled")
2171
2373
  ).option("-a, --annotation <flag>", "Filter by annotation flag (AND logic). Repeatable.", collectAnnotation, []).addOption(
2172
2374
  new Option2("--sort <field>", "Sort order.").choices(["id", "calls", "errors", "latency"]).default("id")
@@ -2216,14 +2418,18 @@ function registerListCommand(apcliGroup, registry, exposureFilter) {
2216
2418
  }
2217
2419
  }
2218
2420
  if (opts.sort === "calls" || opts.sort === "errors" || opts.sort === "latency") {
2219
- process.stderr.write(
2220
- `Warning: Usage data not available; sorting by id. Sort by ${opts.sort} requires system.usage modules.
2421
+ const { used } = sortModulesByUsage(modules, opts.sort, { reverse: !opts.reverse });
2422
+ if (!used) {
2423
+ process.stderr.write(
2424
+ `note: no usage data available for --sort ${opts.sort}; sorted by id. Run some modules first to populate ~/.apcore-cli/audit.jsonl.
2221
2425
  `
2222
- );
2223
- }
2224
- modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
2225
- if (opts.reverse) {
2226
- modules.reverse();
2426
+ );
2427
+ }
2428
+ } else {
2429
+ modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
2430
+ if (opts.reverse) {
2431
+ modules.reverse();
2432
+ }
2227
2433
  }
2228
2434
  let showExposureCol = false;
2229
2435
  if (exposureFilter && opts.exposure !== "all") {
@@ -2238,12 +2444,14 @@ function registerListCommand(apcliGroup, registry, exposureFilter) {
2238
2444
  }
2239
2445
  const fmt = resolveFormat(opts.format);
2240
2446
  const filterTagsArg = opts.tag.length > 0 ? opts.tag : void 0;
2241
- formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
2447
+ void formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
2242
2448
  });
2243
2449
  apcliGroup.addCommand(listCmd);
2244
2450
  }
2245
2451
  function registerDescribeCommand(apcliGroup, registry) {
2246
- 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) => {
2452
+ const describeCmd = new Command2("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").addOption(
2453
+ new Option2("--format <format>", "Output format.").choices(["table", "json", "csv", "yaml", "jsonl", "markdown", "skill"])
2454
+ ).action((moduleId, opts) => {
2247
2455
  validateModuleId(moduleId);
2248
2456
  const moduleDef = registry.getModule(moduleId);
2249
2457
  if (!moduleDef) {
@@ -2254,7 +2462,7 @@ function registerDescribeCommand(apcliGroup, registry) {
2254
2462
  process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2255
2463
  }
2256
2464
  const fmt = resolveFormat(opts.format);
2257
- formatModuleDetail(moduleDef, fmt);
2465
+ void formatModuleDetail(moduleDef, fmt);
2258
2466
  });
2259
2467
  apcliGroup.addCommand(describeCmd);
2260
2468
  }
@@ -2374,8 +2582,8 @@ function registerValidateCommand(cli, registry, executor) {
2374
2582
 
2375
2583
  // src/system-cmd.ts
2376
2584
  init_esm_shims();
2377
- init_errors();
2378
2585
  import { Command as Command3 } from "commander";
2586
+ init_errors();
2379
2587
  async function callSystemModule(executor, moduleId, inputs) {
2380
2588
  if (executor.call) {
2381
2589
  return executor.call(moduleId, inputs);
@@ -2394,6 +2602,15 @@ function emitErrorAndExit(e) {
2394
2602
  `);
2395
2603
  process.exit(exitCodeForError(e));
2396
2604
  }
2605
+ async function requireApprovalForSystemCommand(moduleId, autoApprove) {
2606
+ const syntheticModuleDef = {
2607
+ id: moduleId,
2608
+ name: moduleId,
2609
+ description: `system command: ${moduleId}`,
2610
+ annotations: { requires_approval: true }
2611
+ };
2612
+ await checkApproval(syntheticModuleDef, autoApprove, void 0);
2613
+ }
2397
2614
  function formatHealthSummaryTty(result) {
2398
2615
  const summary = result.summary ?? {};
2399
2616
  const modules = result.modules ?? [];
@@ -2534,9 +2751,10 @@ function registerUsageCommand(apcliGroup, executor) {
2534
2751
  apcliGroup.addCommand(usageCmd);
2535
2752
  }
2536
2753
  function registerEnableCommand(apcliGroup, executor) {
2537
- 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) => {
2754
+ 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) => {
2538
2755
  const fmt = resolveFormat(opts.format);
2539
2756
  try {
2757
+ await requireApprovalForSystemCommand("system.control.toggle_feature", opts.yes);
2540
2758
  const result = await callSystemModule(executor, "system.control.toggle_feature", {
2541
2759
  module_id: moduleId,
2542
2760
  enabled: true,
@@ -2554,9 +2772,10 @@ function registerEnableCommand(apcliGroup, executor) {
2554
2772
  apcliGroup.addCommand(enableCmd);
2555
2773
  }
2556
2774
  function registerDisableCommand(apcliGroup, executor) {
2557
- 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) => {
2775
+ 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) => {
2558
2776
  const fmt = resolveFormat(opts.format);
2559
2777
  try {
2778
+ await requireApprovalForSystemCommand("system.control.toggle_feature", opts.yes);
2560
2779
  const result = await callSystemModule(executor, "system.control.toggle_feature", {
2561
2780
  module_id: moduleId,
2562
2781
  enabled: false,
@@ -2574,9 +2793,10 @@ function registerDisableCommand(apcliGroup, executor) {
2574
2793
  apcliGroup.addCommand(disableCmd);
2575
2794
  }
2576
2795
  function registerReloadCommand(apcliGroup, executor) {
2577
- 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) => {
2796
+ 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) => {
2578
2797
  const fmt = resolveFormat(opts.format);
2579
2798
  try {
2799
+ await requireApprovalForSystemCommand("system.control.reload_module", opts.yes);
2580
2800
  const result = await callSystemModule(executor, "system.control.reload_module", {
2581
2801
  module_id: moduleId,
2582
2802
  reason: opts.reason
@@ -2614,7 +2834,7 @@ function registerConfigCommand(apcliGroup, executor) {
2614
2834
  }
2615
2835
  });
2616
2836
  configGroup.addCommand(configGetCmd);
2617
- 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) => {
2837
+ 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) => {
2618
2838
  const fmt = resolveFormat(opts.format);
2619
2839
  let parsedValue;
2620
2840
  try {
@@ -2623,6 +2843,7 @@ function registerConfigCommand(apcliGroup, executor) {
2623
2843
  parsedValue = value;
2624
2844
  }
2625
2845
  try {
2846
+ await requireApprovalForSystemCommand("system.control.update_config", opts.yes);
2626
2847
  const result = await callSystemModule(executor, "system.control.update_config", {
2627
2848
  key,
2628
2849
  value: parsedValue,
@@ -2818,7 +3039,29 @@ function registerPipelineCommand(cli, executor) {
2818
3039
  init_esm_shims();
2819
3040
  init_errors();
2820
3041
  init_logger();
2821
- var RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set(["apcli"]);
3042
+ var ApcliGroupError = class extends Error {
3043
+ constructor(message) {
3044
+ super(message);
3045
+ this.name = "ApcliGroupError";
3046
+ }
3047
+ };
3048
+ var DEFAULT_BUILTIN_GROUP_NAME = "apcli";
3049
+ var RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set([DEFAULT_BUILTIN_GROUP_NAME]);
3050
+ var _effectiveReservedNames = RESERVED_GROUP_NAMES;
3051
+ function getReservedGroupNames() {
3052
+ return _effectiveReservedNames;
3053
+ }
3054
+ function setReservedGroupNames(names) {
3055
+ _effectiveReservedNames = names;
3056
+ }
3057
+ var _NAME_REGEX = /^[a-z][a-z0-9_-]*$/;
3058
+ function _validateBuiltinGroupName(name) {
3059
+ if (!name || !_NAME_REGEX.test(name)) {
3060
+ throw new ApcliGroupError(
3061
+ `builtinGroupName ${JSON.stringify(name)} must match /^[a-z][a-z0-9_-]*$/ (non-empty, lowercase, alphanumeric + '_' / '-', leading letter).`
3062
+ );
3063
+ }
3064
+ }
2822
3065
  var VALID_USER_MODES = /* @__PURE__ */ new Set([
2823
3066
  "all",
2824
3067
  "none",
@@ -2847,6 +3090,7 @@ var ApcliGroup = class _ApcliGroup {
2847
3090
  _disableEnv;
2848
3091
  _registryInjected;
2849
3092
  _fromCliConfig;
3093
+ _name;
2850
3094
  constructor(init) {
2851
3095
  this._mode = init.mode;
2852
3096
  this._include = init.include;
@@ -2854,6 +3098,16 @@ var ApcliGroup = class _ApcliGroup {
2854
3098
  this._disableEnv = init.disableEnv;
2855
3099
  this._registryInjected = init.registryInjected;
2856
3100
  this._fromCliConfig = init.fromCliConfig;
3101
+ this._name = init.name;
3102
+ }
3103
+ /**
3104
+ * Resolved name for the built-in command group (default `"apcli"`).
3105
+ * Overridable via createCli's `builtinGroupName` option for downstream
3106
+ * branded CLIs that want a custom namespace. Cross-SDK parity with
3107
+ * Python `ApcliGroup.name` (2026-05-08).
3108
+ */
3109
+ get name() {
3110
+ return this._name;
2857
3111
  }
2858
3112
  /**
2859
3113
  * Tier 1 constructor — config came from `createCli({ apcli })`.
@@ -2874,6 +3128,18 @@ var ApcliGroup = class _ApcliGroup {
2874
3128
  * Env var (Tier 2) may override the yaml-supplied mode.
2875
3129
  */
2876
3130
  static fromYaml(config, opts) {
3131
+ if (config !== null && config !== void 0 && typeof config !== "boolean" && (typeof config !== "object" || Array.isArray(config))) {
3132
+ const got = Array.isArray(config) ? "array" : typeof config;
3133
+ warn(
3134
+ `apcore.yaml apcli has unexpected type ${got}; using auto-detect.`
3135
+ );
3136
+ return _ApcliGroup._build(
3137
+ void 0,
3138
+ opts,
3139
+ /*fromCliConfig*/
3140
+ false
3141
+ );
3142
+ }
2877
3143
  return _ApcliGroup._build(
2878
3144
  config,
2879
3145
  opts,
@@ -2887,8 +3153,12 @@ var ApcliGroup = class _ApcliGroup {
2887
3153
  * Use this in programmatic contexts where throwing/exiting is unwanted.
2888
3154
  */
2889
3155
  static tryFromYaml(config, opts) {
2890
- if (config !== null && config !== void 0 && typeof config !== "boolean" && typeof config !== "object") {
2891
- return [null, `apcore.yaml 'apcli:' must be a bool, object, or null; got ${typeof config}`];
3156
+ if (config !== null && config !== void 0 && typeof config !== "boolean" && (typeof config !== "object" || Array.isArray(config))) {
3157
+ const got = Array.isArray(config) ? "array" : typeof config;
3158
+ return [
3159
+ null,
3160
+ `apcore.yaml 'apcli:' must be a bool, object, or null; got ${got}`
3161
+ ];
2892
3162
  }
2893
3163
  if (config !== null && config !== void 0 && typeof config === "object" && !Array.isArray(config)) {
2894
3164
  const mode = config["mode"];
@@ -2905,6 +3175,8 @@ var ApcliGroup = class _ApcliGroup {
2905
3175
  // Internal builder — shared by both factories
2906
3176
  // -------------------------------------------------------------------------
2907
3177
  static _build(config, opts, fromCliConfig) {
3178
+ const name = opts.name ?? DEFAULT_BUILTIN_GROUP_NAME;
3179
+ _validateBuiltinGroupName(name);
2908
3180
  if (config === true) {
2909
3181
  return new _ApcliGroup({
2910
3182
  mode: "all",
@@ -2912,7 +3184,8 @@ var ApcliGroup = class _ApcliGroup {
2912
3184
  exclude: [],
2913
3185
  disableEnv: false,
2914
3186
  registryInjected: opts.registryInjected,
2915
- fromCliConfig
3187
+ fromCliConfig,
3188
+ name
2916
3189
  });
2917
3190
  }
2918
3191
  if (config === false) {
@@ -2922,7 +3195,8 @@ var ApcliGroup = class _ApcliGroup {
2922
3195
  exclude: [],
2923
3196
  disableEnv: false,
2924
3197
  registryInjected: opts.registryInjected,
2925
- fromCliConfig
3198
+ fromCliConfig,
3199
+ name
2926
3200
  });
2927
3201
  }
2928
3202
  if (config === void 0 || config === null) {
@@ -2932,7 +3206,8 @@ var ApcliGroup = class _ApcliGroup {
2932
3206
  exclude: [],
2933
3207
  disableEnv: false,
2934
3208
  registryInjected: opts.registryInjected,
2935
- fromCliConfig
3209
+ fromCliConfig,
3210
+ name
2936
3211
  });
2937
3212
  }
2938
3213
  if (typeof config !== "object" || Array.isArray(config)) {
@@ -2980,7 +3255,8 @@ var ApcliGroup = class _ApcliGroup {
2980
3255
  exclude,
2981
3256
  disableEnv,
2982
3257
  registryInjected: opts.registryInjected,
2983
- fromCliConfig
3258
+ fromCliConfig,
3259
+ name
2984
3260
  });
2985
3261
  }
2986
3262
  /**
@@ -3073,7 +3349,8 @@ var ApcliGroup = class _ApcliGroup {
3073
3349
  */
3074
3350
  _parseEnv(raw) {
3075
3351
  if (raw === void 0 || raw === "") return null;
3076
- const normalized = raw.toLowerCase();
3352
+ const normalized = raw.trim().toLowerCase();
3353
+ if (normalized === "") return null;
3077
3354
  if (normalized === "show" || normalized === "1" || normalized === "true") {
3078
3355
  return "all";
3079
3356
  }
@@ -3272,8 +3549,30 @@ function canonicalFormatHelp(cmd, helper) {
3272
3549
  return sections.join("\n\n") + "\n";
3273
3550
  }
3274
3551
 
3552
+ // src/validate.ts
3553
+ init_esm_shims();
3554
+ init_errors();
3555
+ var MODULE_ID_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/;
3556
+ var MAX_MODULE_ID_LENGTH = 192;
3557
+ function validateModuleId(moduleId) {
3558
+ if (moduleId.length > MAX_MODULE_ID_LENGTH) {
3559
+ process.stderr.write(
3560
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is ${MAX_MODULE_ID_LENGTH} characters.
3561
+ `
3562
+ );
3563
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3564
+ }
3565
+ if (!MODULE_ID_PATTERN.test(moduleId)) {
3566
+ process.stderr.write(
3567
+ `Error: Invalid module ID format: '${moduleId}'.
3568
+ `
3569
+ );
3570
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3571
+ }
3572
+ }
3573
+
3275
3574
  // src/main.ts
3276
- var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
3575
+ var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
3277
3576
  var verboseHelp = false;
3278
3577
  function setVerboseHelp(verbose) {
3279
3578
  verboseHelp = verbose;
@@ -3312,7 +3611,7 @@ function resolveStringOption(cliValue, envValue) {
3312
3611
  }
3313
3612
  var VERSION = "0.0.0";
3314
3613
  try {
3315
- const pkg = JSON.parse(readFileSync2(path4.resolve(__dirname2, "../package.json"), "utf-8"));
3614
+ const pkg = JSON.parse(readFileSync3(path5.resolve(__dirname2, "../package.json"), "utf-8"));
3316
3615
  VERSION = pkg.version;
3317
3616
  } catch {
3318
3617
  }
@@ -3372,6 +3671,10 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3372
3671
  let app;
3373
3672
  let expose;
3374
3673
  let apcliOption;
3674
+ let appVersion;
3675
+ let appDescription;
3676
+ let allowedPrefixes;
3677
+ let builtinGroupName;
3375
3678
  if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
3376
3679
  extensionsDir = extensionsDirOrOpts.extensionsDir;
3377
3680
  progName = extensionsDirOrOpts.progName ?? progName;
@@ -3382,6 +3685,10 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3382
3685
  extraCommands = extensionsDirOrOpts.extraCommands;
3383
3686
  expose = extensionsDirOrOpts.expose;
3384
3687
  apcliOption = extensionsDirOrOpts.apcli;
3688
+ appVersion = extensionsDirOrOpts.version;
3689
+ appDescription = extensionsDirOrOpts.description;
3690
+ builtinGroupName = extensionsDirOrOpts.builtinGroupName;
3691
+ allowedPrefixes = extensionsDirOrOpts.allowedPrefixes;
3385
3692
  } else {
3386
3693
  extensionsDir = extensionsDirOrOpts;
3387
3694
  }
@@ -3392,7 +3699,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3392
3699
  setAuditLogger(auditLogger);
3393
3700
  } catch {
3394
3701
  }
3395
- const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
3702
+ const resolvedProgName = progName ?? path5.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
3396
3703
  const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
3397
3704
  setLogLevel(cliLogLevel);
3398
3705
  if (app && (registry || executor)) {
@@ -3418,7 +3725,10 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3418
3725
  }
3419
3726
  }
3420
3727
  const registryInjected = registry !== void 0;
3421
- 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)");
3728
+ 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)");
3729
+ if (appVersion) {
3730
+ program.version(appVersion, "-V, --version", "Print version");
3731
+ }
3422
3732
  program.configureHelp({ formatHelp: canonicalFormatHelp });
3423
3733
  if (!registryInjected) {
3424
3734
  program.option("--extensions-dir <path>", "Path to extensions directory");
@@ -3426,21 +3736,39 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3426
3736
  program.option("--binding <path>", "Path to binding.yaml for display overlay");
3427
3737
  }
3428
3738
  let apcliCfg;
3429
- if (apcliOption instanceof ApcliGroup) {
3430
- apcliCfg = apcliOption;
3431
- } else if (apcliOption !== void 0) {
3432
- apcliCfg = ApcliGroup.fromCliConfig(apcliOption, { registryInjected });
3433
- } else {
3434
- let yamlVal = null;
3435
- try {
3436
- const resolver = new ConfigResolver();
3437
- yamlVal = resolver.resolveObject("apcli");
3438
- } catch {
3439
- yamlVal = null;
3739
+ try {
3740
+ if (apcliOption instanceof ApcliGroup) {
3741
+ if (builtinGroupName !== void 0 && builtinGroupName !== "apcli" && apcliOption.name !== builtinGroupName) {
3742
+ throw new Error(
3743
+ `builtinGroupName=${JSON.stringify(builtinGroupName)} conflicts with the name on the supplied ApcliGroup (${JSON.stringify(apcliOption.name)}). Pass only one.`
3744
+ );
3745
+ }
3746
+ apcliCfg = apcliOption;
3747
+ } else if (apcliOption !== void 0) {
3748
+ apcliCfg = ApcliGroup.fromCliConfig(apcliOption, {
3749
+ registryInjected,
3750
+ name: builtinGroupName
3751
+ });
3752
+ } else {
3753
+ let yamlVal = null;
3754
+ try {
3755
+ const resolver = new ConfigResolver();
3756
+ yamlVal = resolver.resolveObject("apcli");
3757
+ } catch {
3758
+ yamlVal = null;
3759
+ }
3760
+ apcliCfg = ApcliGroup.fromYaml(yamlVal, {
3761
+ registryInjected,
3762
+ name: builtinGroupName
3763
+ });
3440
3764
  }
3441
- apcliCfg = ApcliGroup.fromYaml(yamlVal, { registryInjected });
3765
+ } catch (e) {
3766
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : String(e)}
3767
+ `);
3768
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3442
3769
  }
3443
- const apcliGroup = program.command("apcli", { hidden: !apcliCfg.isGroupVisible() }).description("apcore-cli built-in commands");
3770
+ setReservedGroupNames(/* @__PURE__ */ new Set([apcliCfg.name]));
3771
+ const apcliGroup = program.command(apcliCfg.name, { hidden: !apcliCfg.isGroupVisible() }).description("Built-in commands");
3444
3772
  if (registry) {
3445
3773
  program._registry = registry;
3446
3774
  if (executor) {
@@ -3466,17 +3794,17 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3466
3794
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3467
3795
  }
3468
3796
  _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter);
3469
- _registerDeprecationShims(program, apcliGroup, registryInjected, resolvedProgName);
3470
3797
  program.addHelpText("after", [
3471
3798
  "",
3472
- "Use --help --verbose to show all options (including built-in apcore options).",
3799
+ "Use --help --verbose to show all options (including built-in options).",
3473
3800
  "Use --help --man to display a formatted man page."
3474
3801
  ].join("\n"));
3475
- configureManHelp(program, resolvedProgName, VERSION);
3802
+ configureManHelp(program, resolvedProgName, appVersion ?? VERSION);
3476
3803
  if (extraCommands && extraCommands.length > 0) {
3804
+ const _reservedForExtra = /* @__PURE__ */ new Set([apcliCfg.name]);
3477
3805
  for (const cmd of extraCommands) {
3478
3806
  const cmdName = cmd.name();
3479
- if (RESERVED_GROUP_NAMES.has(cmdName)) {
3807
+ if (_reservedForExtra.has(cmdName)) {
3480
3808
  process.stderr.write(
3481
3809
  `Error: extraCommands name '${cmdName}' is reserved
3482
3810
  `
@@ -3485,21 +3813,11 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3485
3813
  }
3486
3814
  const existing = program.commands.find((c) => c.name() === cmdName);
3487
3815
  if (existing) {
3488
- const isShim = existing.__isDeprecationShim === true;
3489
- if (isShim) {
3490
- warn(
3491
- `extraCommands '${cmdName}' overrides the deprecation shim for the same name. The shim will be removed.`
3492
- );
3493
- const cmds = program.commands;
3494
- const idx = cmds.indexOf(existing);
3495
- if (idx >= 0) cmds.splice(idx, 1);
3496
- } else {
3497
- process.stderr.write(
3498
- `Error: extraCommands name '${cmdName}' collides with an existing command
3816
+ process.stderr.write(
3817
+ `Error: extraCommands name '${cmdName}' collides with an existing command
3499
3818
  `
3500
- );
3501
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3502
- }
3819
+ );
3820
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3503
3821
  }
3504
3822
  program.addCommand(cmd);
3505
3823
  }
@@ -3508,7 +3826,7 @@ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3508
3826
  const opts = thisCommand.opts();
3509
3827
  const commandsDir = opts.commandsDir;
3510
3828
  const bindingPath = opts.binding;
3511
- await applyToolkitIntegration(commandsDir, bindingPath);
3829
+ await applyToolkitIntegration(commandsDir, bindingPath, { allowedPrefixes });
3512
3830
  });
3513
3831
  return program;
3514
3832
  }
@@ -3559,53 +3877,11 @@ function _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exp
3559
3877
  entry.register(apcliGroup, registry, executor);
3560
3878
  }
3561
3879
  }
3562
- var _DEPRECATED_ROOT_COMMANDS = [
3563
- "list",
3564
- "describe",
3565
- "exec",
3566
- "init",
3567
- "validate",
3568
- "health",
3569
- "usage",
3570
- "enable",
3571
- "disable",
3572
- "reload",
3573
- "config",
3574
- "completion",
3575
- "describe-pipeline"
3576
- ];
3577
- function _registerDeprecationShims(root, apcliGroup, registryInjected, cliName) {
3578
- if (registryInjected) return;
3579
- for (const name of _DEPRECATED_ROOT_COMMANDS) {
3580
- const apcliSub = apcliGroup.commands.find((c) => c.name() === name);
3581
- if (!apcliSub) continue;
3582
- if (root.commands.some((c) => c.name() === name)) continue;
3583
- const shim = root.command(name).description(`[DEPRECATED] Use '${cliName} apcli ${name}' instead.`).allowUnknownOption(true).allowExcessArguments(true).helpOption(false);
3584
- shim.__isDeprecationShim = true;
3585
- shim.action(async function() {
3586
- process.stderr.write(
3587
- `WARNING: '${name}' as a root-level command is deprecated. Use '${cliName} apcli ${name}' instead.
3588
- Will be removed in v0.8. See: https://aiperceivable.github.io/apcore-cli/features/builtin-group/#11-migration
3589
- `
3590
- );
3591
- const tail = _collectShimForwardArgs(this);
3592
- await apcliSub.parseAsync(tail, { from: "user" });
3593
- });
3594
- }
3595
- }
3596
- function _collectShimForwardArgs(shim) {
3597
- const shimArgs = (shim.args ?? []).slice();
3598
- if (shimArgs.length > 0) return shimArgs;
3599
- const shimName = shim.name();
3600
- const idx = process.argv.indexOf(shimName);
3601
- if (idx < 0) return [];
3602
- return process.argv.slice(idx + 1);
3603
- }
3604
3880
  var bindingDisplayMap = /* @__PURE__ */ new Map();
3605
3881
  function lookupBindingDisplay(moduleId) {
3606
3882
  return bindingDisplayMap.get(moduleId);
3607
3883
  }
3608
- async function applyToolkitIntegration(commandsDir, bindingPath) {
3884
+ async function applyToolkitIntegration(commandsDir, bindingPath, options = {}) {
3609
3885
  if (!commandsDir && !bindingPath) {
3610
3886
  return;
3611
3887
  }
@@ -3625,14 +3901,14 @@ async function applyToolkitIntegration(commandsDir, bindingPath) {
3625
3901
  }
3626
3902
  if (bindingPath) {
3627
3903
  try {
3628
- await loadBindingDisplayOverlay(toolkit, bindingPath);
3904
+ await loadBindingDisplayOverlay(toolkit, bindingPath, options.allowedPrefixes);
3629
3905
  } catch (err) {
3630
3906
  const msg = err instanceof Error ? err.message : String(err);
3631
3907
  warn(`apcore-toolkit: failed to load binding '${bindingPath}': ${msg}`);
3632
3908
  }
3633
3909
  }
3634
3910
  }
3635
- async function loadBindingDisplayOverlay(toolkit, bindingPath) {
3911
+ async function loadBindingDisplayOverlay(toolkit, bindingPath, allowedPrefixes) {
3636
3912
  const BindingLoaderCtor = toolkit.BindingLoader;
3637
3913
  const DisplayResolverCtor = toolkit.DisplayResolver;
3638
3914
  if (!BindingLoaderCtor || !DisplayResolverCtor) {
@@ -3642,11 +3918,23 @@ async function loadBindingDisplayOverlay(toolkit, bindingPath) {
3642
3918
  const scanned = loader.load(bindingPath);
3643
3919
  const resolver = new DisplayResolverCtor();
3644
3920
  const resolved = resolver.resolve(scanned, { bindingPath });
3921
+ const prefixes = allowedPrefixes && allowedPrefixes.length > 0 ? allowedPrefixes : null;
3922
+ const isTargetAllowed = (target) => {
3923
+ if (!prefixes) return true;
3924
+ if (typeof target !== "string" || target.length === 0) return true;
3925
+ return prefixes.some((p) => target.startsWith(p));
3926
+ };
3645
3927
  for (const mod of resolved) {
3646
3928
  if (!mod || typeof mod !== "object") continue;
3647
3929
  const entry = mod;
3648
3930
  const id = typeof entry.moduleId === "string" ? entry.moduleId : null;
3649
3931
  if (!id) continue;
3932
+ if (!isTargetAllowed(entry.target)) {
3933
+ warn(
3934
+ `apcore-toolkit: dropped binding entry '${id}' \u2014 target '${String(entry.target)}' is outside allowedPrefixes`
3935
+ );
3936
+ continue;
3937
+ }
3650
3938
  const meta = entry.metadata ?? {};
3651
3939
  const display = meta.display;
3652
3940
  if (display && typeof display === "object" && !Array.isArray(display)) {
@@ -3656,7 +3944,12 @@ async function loadBindingDisplayOverlay(toolkit, bindingPath) {
3656
3944
  }
3657
3945
  function main(progName) {
3658
3946
  verboseHelp = hasVerboseFlag();
3659
- const program = createCli(void 0, progName, verboseHelp);
3947
+ const program = createCli({
3948
+ progName,
3949
+ verbose: verboseHelp,
3950
+ version: VERSION,
3951
+ description: `${progName ?? "apcore-cli"} \u2014 execute apcore modules from the command line`
3952
+ });
3660
3953
  try {
3661
3954
  program.parse(process.argv);
3662
3955
  } catch (error) {
@@ -3963,22 +4256,6 @@ Pipeline Trace (strategy: ${trace.strategyName}, ${stepCount} steps, ${trace.tot
3963
4256
  });
3964
4257
  return cmd;
3965
4258
  }
3966
- function validateModuleId(moduleId) {
3967
- if (moduleId.length > 192) {
3968
- process.stderr.write(
3969
- `Error: Invalid module ID format: '${moduleId}'. Maximum length is 192 characters.
3970
- `
3971
- );
3972
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3973
- }
3974
- if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
3975
- process.stderr.write(
3976
- `Error: Invalid module ID format: '${moduleId}'.
3977
- `
3978
- );
3979
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3980
- }
3981
- }
3982
4259
  async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
3983
4260
  const cliKwargsNonNull = {};
3984
4261
  for (const [k, v] of Object.entries(cliKwargs)) {
@@ -3997,7 +4274,7 @@ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
3997
4274
  } else {
3998
4275
  source = `file '${stdinFlag}'`;
3999
4276
  try {
4000
- raw = readFileSync2(stdinFlag, "utf-8");
4277
+ raw = readFileSync3(stdinFlag, "utf-8");
4001
4278
  } catch (err) {
4002
4279
  const msg = err instanceof Error ? err.message : String(err);
4003
4280
  process.stderr.write(`Error: Could not read input ${source}: ${msg}
@@ -4083,7 +4360,7 @@ import { Command as Command6 } from "commander";
4083
4360
  init_logger();
4084
4361
  init_errors();
4085
4362
  function assertNotReserved(kind, name, moduleId) {
4086
- if (!RESERVED_GROUP_NAMES.has(name)) return;
4363
+ if (!getReservedGroupNames().has(name)) return;
4087
4364
  let msg;
4088
4365
  if (kind === "group") {
4089
4366
  msg = `Error: Module '${moduleId}': display.cli.group '${name}' is reserved. Use a different CLI alias or set display.cli.group to another value.
@@ -4337,8 +4614,9 @@ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
4337
4614
  */
4338
4615
  listCommands() {
4339
4616
  this.buildGroupMap();
4617
+ const reserved = getReservedGroupNames();
4340
4618
  const groupNames = [...this.groupMap.keys()].filter(
4341
- (g) => !RESERVED_GROUP_NAMES.has(g)
4619
+ (g) => !reserved.has(g)
4342
4620
  );
4343
4621
  const topNames = [...this.topLevelModules.keys()];
4344
4622
  return [.../* @__PURE__ */ new Set([...groupNames, ...topNames])].sort();
@@ -4397,6 +4675,7 @@ init_logger();
4397
4675
  init_security();
4398
4676
  export {
4399
4677
  ApcliGroup,
4678
+ ApcliGroupError,
4400
4679
  ApprovalDeniedError,
4401
4680
  ApprovalTimeoutError,
4402
4681
  AuditLogger,
@@ -4423,10 +4702,10 @@ export {
4423
4702
  collectInput,
4424
4703
  configureManHelp,
4425
4704
  createCli,
4426
- emitErrorJson,
4427
- emitErrorTty,
4428
4705
  exitCodeForError,
4429
4706
  formatExecResult,
4707
+ formatModuleDetail,
4708
+ formatModuleList,
4430
4709
  getAuditLogger,
4431
4710
  getLogLevel,
4432
4711
  main,
@@ -4445,6 +4724,7 @@ export {
4445
4724
  registerReloadCommand,
4446
4725
  registerUsageCommand,
4447
4726
  registerValidateCommand,
4727
+ resolveFormat,
4448
4728
  resolveRefs,
4449
4729
  schemaToCliOptions,
4450
4730
  setAuditLogger,