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