apcore-cli 0.5.0 → 0.6.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
@@ -142,6 +142,7 @@ var init_errors = __esm({
142
142
  CONFIG_NAMESPACE_RESERVED: 78,
143
143
  CONFIG_NAMESPACE_DUPLICATE: 78,
144
144
  CONFIG_ENV_PREFIX_CONFLICT: 78,
145
+ CONFIG_ENV_MAP_CONFLICT: 78,
145
146
  CONFIG_MOUNT_ERROR: 66,
146
147
  CONFIG_BIND_ERROR: 65,
147
148
  ERROR_FORMATTER_DUPLICATE: 70,
@@ -515,7 +516,7 @@ init_errors();
515
516
  import { readFileSync as readFileSync3 } from "fs";
516
517
  import { fileURLToPath as fileURLToPath3 } from "url";
517
518
  import * as path6 from "path";
518
- import { Command as Command2, CommanderError, Option as Option2 } from "commander";
519
+ import { Command as Command6, CommanderError, Option as Option4 } from "commander";
519
520
 
520
521
  // src/ref-resolver.ts
521
522
  init_esm_shims();
@@ -733,7 +734,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
733
734
  flags: `--${flagBase}, --no-${flagBase}`,
734
735
  description: helpText,
735
736
  defaultValue: defaultVal,
736
- required: false,
737
+ required: isRequired,
737
738
  isBooleanFlag: true
738
739
  });
739
740
  } else if ("enum" in propSchema && Array.isArray(propSchema.enum)) {
@@ -744,7 +745,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
744
745
  flags: `${flagName} <value>`,
745
746
  description: helpText,
746
747
  defaultValue,
747
- required: false
748
+ required: isRequired
748
749
  });
749
750
  } else {
750
751
  const stringValues = enumValues.map(String);
@@ -763,7 +764,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
763
764
  flags: `${flagName} <value>`,
764
765
  description: helpText,
765
766
  defaultValue: defaultValue !== void 0 ? String(defaultValue) : void 0,
766
- required: false,
767
+ required: isRequired,
767
768
  choices: stringValues,
768
769
  enumOriginalTypes: Object.keys(enumOriginalTypes).length > 0 ? enumOriginalTypes : void 0
769
770
  });
@@ -788,7 +789,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
788
789
  flags: `${flagName} <value>`,
789
790
  description: helpText,
790
791
  defaultValue,
791
- required: false,
792
+ required: isRequired,
792
793
  parseArg
793
794
  });
794
795
  }
@@ -805,7 +806,41 @@ function getAnnotation(annotations, key, defaultValue = void 0) {
805
806
  const ann = annotations;
806
807
  return key in ann ? ann[key] : defaultValue;
807
808
  }
808
- async function checkApproval(moduleDef, autoApprove) {
809
+ var CliApprovalHandler = class {
810
+ autoApprove;
811
+ timeout;
812
+ constructor(autoApprove = false, timeout = 60) {
813
+ this.autoApprove = autoApprove;
814
+ this.timeout = Math.max(1, Math.min(timeout, 3600));
815
+ }
816
+ async requestApproval(request) {
817
+ const moduleId = request.module_id ?? "unknown";
818
+ if (this.autoApprove) {
819
+ return { status: "approved", approved_by: "auto_approve" };
820
+ }
821
+ const envVal = process.env.APCORE_CLI_AUTO_APPROVE ?? "";
822
+ if (envVal === "1") {
823
+ return { status: "approved", approved_by: "env_auto_approve" };
824
+ }
825
+ if (!process.stdin.isTTY) {
826
+ return { status: "rejected", reason: "Non-interactive session without --yes" };
827
+ }
828
+ const annotations = request.annotations;
829
+ const extra = annotations?.extra ?? {};
830
+ const message = extra.approval_message ?? `Module '${moduleId}' requires approval to execute.`;
831
+ process.stderr.write(message + "\n");
832
+ try {
833
+ await promptWithTimeout({ id: moduleId }, this.timeout);
834
+ return { status: "approved", approved_by: "tty_user" };
835
+ } catch {
836
+ return { status: "rejected", reason: "User rejected or timed out" };
837
+ }
838
+ }
839
+ async checkApproval(_approvalId) {
840
+ return { status: "rejected", reason: "CLI does not support async approval polling" };
841
+ }
842
+ };
843
+ async function checkApproval(moduleDef, autoApprove, timeout = 60) {
809
844
  const annotations = moduleDef.annotations;
810
845
  let requiresApproval;
811
846
  if (moduleDef.requiresApproval !== void 0) {
@@ -839,7 +874,7 @@ async function checkApproval(moduleDef, autoApprove) {
839
874
  );
840
875
  process.exit(EXIT_CODES.APPROVAL_DENIED);
841
876
  }
842
- await promptWithTimeout(moduleDef, 60);
877
+ await promptWithTimeout(moduleDef, timeout);
843
878
  }
844
879
  async function promptWithTimeout(moduleDef, timeout) {
845
880
  timeout = Math.max(1, Math.min(timeout, 3600));
@@ -912,7 +947,7 @@ function formatTable(headers, rows) {
912
947
  );
913
948
  return [headerLine, sep2, ...dataLines].join("\n") + "\n";
914
949
  }
915
- function formatModuleList(modules, format, filterTags) {
950
+ function formatModuleList(modules, format, filterTags, showDeps = false) {
916
951
  if (format === "table") {
917
952
  if (modules.length === 0 && filterTags && filterTags.length > 0) {
918
953
  process.stdout.write(
@@ -925,19 +960,29 @@ function formatModuleList(modules, format, filterTags) {
925
960
  process.stdout.write("No modules found.\n");
926
961
  return;
927
962
  }
928
- const headers = ["ID", "Description", "Tags"];
929
- const rows = modules.map((m) => [
930
- m.id,
931
- truncate(m.description, 80),
932
- (m.tags ?? []).join(", ")
933
- ]);
963
+ const headers = showDeps ? ["ID", "Description", "Tags", "Deps"] : ["ID", "Description", "Tags"];
964
+ const rows = modules.map((m) => {
965
+ const base = [m.id, truncate(m.description, 80), (m.tags ?? []).join(", ")];
966
+ if (showDeps) {
967
+ const deps = m.dependencies;
968
+ base.push(String(Array.isArray(deps) ? deps.length : 0));
969
+ }
970
+ return base;
971
+ });
934
972
  process.stdout.write(formatTable(headers, rows));
935
973
  } else if (format === "json") {
936
- const result = modules.map((m) => ({
937
- id: m.id,
938
- description: m.description,
939
- tags: m.tags ?? []
940
- }));
974
+ const result = modules.map((m) => {
975
+ const entry = {
976
+ id: m.id,
977
+ description: m.description,
978
+ tags: m.tags ?? []
979
+ };
980
+ if (showDeps) {
981
+ const deps = m.dependencies;
982
+ entry.dependency_count = Array.isArray(deps) ? deps.length : 0;
983
+ }
984
+ return entry;
985
+ });
941
986
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
942
987
  }
943
988
  }
@@ -1025,24 +1070,171 @@ Tags: ${tags.join(", ")}
1025
1070
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1026
1071
  }
1027
1072
  }
1028
- function formatExecResult(result, format) {
1073
+ function selectFields(result, fields) {
1074
+ const selected = {};
1075
+ for (const f of fields.split(",")) {
1076
+ const key = f.trim();
1077
+ let val = result;
1078
+ for (const part of key.split(".")) {
1079
+ if (val && typeof val === "object" && !Array.isArray(val)) {
1080
+ val = val[part];
1081
+ } else {
1082
+ val = void 0;
1083
+ break;
1084
+ }
1085
+ }
1086
+ selected[key] = val;
1087
+ }
1088
+ return selected;
1089
+ }
1090
+ function formatExecResult(result, format, fields) {
1029
1091
  if (result === null || result === void 0) {
1030
1092
  return;
1031
1093
  }
1094
+ let effective_result = result;
1095
+ if (fields && typeof result === "object" && !Array.isArray(result) && result !== null) {
1096
+ effective_result = selectFields(result, fields);
1097
+ }
1032
1098
  const effective = resolveFormat(format);
1033
- if (effective === "table" && typeof result === "object" && !Array.isArray(result)) {
1034
- const entries = Object.entries(result);
1099
+ if (effective === "csv") {
1100
+ if (typeof effective_result === "object" && !Array.isArray(effective_result) && effective_result !== null) {
1101
+ const obj = effective_result;
1102
+ const keys = Object.keys(obj);
1103
+ const header = keys.map(escapeCsvField).join(",");
1104
+ const row = keys.map((k) => escapeCsvField(String(obj[k]))).join(",");
1105
+ process.stdout.write(header + "\n" + row + "\n");
1106
+ } else if (Array.isArray(effective_result) && effective_result.length > 0 && typeof effective_result[0] === "object") {
1107
+ const keys = Object.keys(effective_result[0]);
1108
+ const header = keys.map(escapeCsvField).join(",");
1109
+ const rows = effective_result.map((item) => {
1110
+ const obj = item;
1111
+ return keys.map((k) => escapeCsvField(String(obj[k]))).join(",");
1112
+ });
1113
+ process.stdout.write(header + "\n" + rows.join("\n") + "\n");
1114
+ } else {
1115
+ process.stdout.write(JSON.stringify(effective_result) + "\n");
1116
+ }
1117
+ } else if (effective === "yaml") {
1118
+ if (typeof effective_result === "object" && !Array.isArray(effective_result) && effective_result !== null) {
1119
+ const obj = effective_result;
1120
+ const lines = Object.entries(obj).map(([k, v]) => {
1121
+ if (v === null || v === void 0) return `${k}: null`;
1122
+ if (typeof v === "object") return `${k}: ${JSON.stringify(v)}`;
1123
+ return `${k}: ${v}`;
1124
+ });
1125
+ process.stdout.write(lines.join("\n") + "\n");
1126
+ } else if (Array.isArray(effective_result)) {
1127
+ for (const item of effective_result) {
1128
+ if (typeof item === "object" && item !== null) {
1129
+ const obj = item;
1130
+ const lines = Object.entries(obj).map(([k, v]) => ` ${k}: ${v}`);
1131
+ process.stdout.write("- " + lines.join("\n ") + "\n");
1132
+ } else {
1133
+ process.stdout.write(`- ${item}
1134
+ `);
1135
+ }
1136
+ }
1137
+ } else {
1138
+ process.stdout.write(String(effective_result) + "\n");
1139
+ }
1140
+ } else if (effective === "jsonl") {
1141
+ if (Array.isArray(effective_result)) {
1142
+ for (const item of effective_result) {
1143
+ process.stdout.write(JSON.stringify(item) + "\n");
1144
+ }
1145
+ } else {
1146
+ process.stdout.write(JSON.stringify(effective_result) + "\n");
1147
+ }
1148
+ } else if (effective === "table" && typeof effective_result === "object" && !Array.isArray(effective_result)) {
1149
+ const entries = Object.entries(effective_result);
1035
1150
  const headers = ["Key", "Value"];
1036
1151
  const rows = entries.map(([k, v]) => [String(k), String(v)]);
1037
1152
  process.stdout.write(formatTable(headers, rows));
1038
- } else if (typeof result === "object") {
1039
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1040
- } else if (typeof result === "string") {
1041
- process.stdout.write(result + "\n");
1153
+ } else if (typeof effective_result === "object") {
1154
+ process.stdout.write(JSON.stringify(effective_result, null, 2) + "\n");
1155
+ } else if (typeof effective_result === "string") {
1156
+ process.stdout.write(effective_result + "\n");
1157
+ } else {
1158
+ process.stdout.write(String(effective_result) + "\n");
1159
+ }
1160
+ }
1161
+ function escapeCsvField(value) {
1162
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
1163
+ return '"' + value.replace(/"/g, '""') + '"';
1164
+ }
1165
+ return value;
1166
+ }
1167
+ function formatPreflightResult(result, format) {
1168
+ const resolved = resolveFormat(format);
1169
+ if (resolved === "json" || !process.stdout.isTTY) {
1170
+ const payload = {
1171
+ valid: result.valid,
1172
+ requires_approval: result.requires_approval,
1173
+ checks: result.checks.map((c) => {
1174
+ const entry = { check: c.check, passed: c.passed };
1175
+ if (c.error !== void 0 && c.error !== null) {
1176
+ entry.error = c.error;
1177
+ }
1178
+ if (c.warnings && c.warnings.length > 0) {
1179
+ entry.warnings = c.warnings;
1180
+ }
1181
+ return entry;
1182
+ })
1183
+ };
1184
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
1042
1185
  } else {
1043
- process.stdout.write(String(result) + "\n");
1186
+ for (const c of result.checks) {
1187
+ const hasWarnings = (c.warnings?.length ?? 0) > 0;
1188
+ let sym;
1189
+ if (c.passed && hasWarnings) {
1190
+ sym = "\u26A0";
1191
+ } else if (c.passed) {
1192
+ sym = "\u2713";
1193
+ } else if (c.passed === false) {
1194
+ sym = "\u2717";
1195
+ } else {
1196
+ sym = "\u25CB";
1197
+ }
1198
+ let status = ` ${sym} ${c.check.padEnd(20)}`;
1199
+ if (c.error) {
1200
+ const detail = typeof c.error === "object" ? JSON.stringify(c.error) : String(c.error);
1201
+ status += ` ${detail}`;
1202
+ } else if (c.passed && !hasWarnings) {
1203
+ status += " OK";
1204
+ } else if (!c.passed) {
1205
+ status += " Skipped";
1206
+ }
1207
+ process.stdout.write(status + "\n");
1208
+ for (const w of c.warnings ?? []) {
1209
+ process.stdout.write(` Warning: ${w}
1210
+ `);
1211
+ }
1212
+ }
1213
+ const errors = result.checks.filter((c) => !c.passed).length;
1214
+ const warnings = result.checks.reduce((sum, c) => sum + (c.warnings?.length ?? 0), 0);
1215
+ const tag = result.valid ? "PASS" : "FAIL";
1216
+ process.stdout.write(`
1217
+ Result: ${tag} (${errors} error(s), ${warnings} warning(s))
1218
+ `);
1044
1219
  }
1045
1220
  }
1221
+ function firstFailedExitCode(result) {
1222
+ const checkToExit = {
1223
+ module_id: 2,
1224
+ module_lookup: 44,
1225
+ call_chain: 1,
1226
+ acl: 77,
1227
+ schema: 45,
1228
+ approval: 46,
1229
+ module_preflight: 1
1230
+ };
1231
+ for (const check of result.checks) {
1232
+ if (!check.passed) {
1233
+ return checkToExit[check.check] ?? 1;
1234
+ }
1235
+ }
1236
+ return 1;
1237
+ }
1046
1238
 
1047
1239
  // src/logger.ts
1048
1240
  init_esm_shims();
@@ -1254,7 +1446,14 @@ var DEFAULTS = {
1254
1446
  "apcore-cli.stdin_buffer_limit": 10485760,
1255
1447
  "apcore-cli.auto_approve": false,
1256
1448
  "apcore-cli.help_text_max_length": 1e3,
1257
- "apcore-cli.logging_level": "WARNING"
1449
+ "apcore-cli.logging_level": "WARNING",
1450
+ // FE-11 config keys
1451
+ "cli.approval_timeout": 60,
1452
+ "cli.strategy": "standard",
1453
+ "cli.group_depth": 1,
1454
+ "apcore-cli.approval_timeout": 60,
1455
+ "apcore-cli.strategy": "standard",
1456
+ "apcore-cli.group_depth": 1
1258
1457
  };
1259
1458
  var NAMESPACE_TO_LEGACY = {
1260
1459
  "apcore-cli.stdin_buffer_limit": "cli.stdin_buffer_limit",
@@ -1276,7 +1475,10 @@ function registerConfigNamespace() {
1276
1475
  stdin_buffer_limit: 10485760,
1277
1476
  auto_approve: false,
1278
1477
  help_text_max_length: 1e3,
1279
- logging_level: "WARNING"
1478
+ logging_level: "WARNING",
1479
+ approval_timeout: 60,
1480
+ strategy: "standard",
1481
+ group_depth: 1
1280
1482
  }
1281
1483
  });
1282
1484
  }
@@ -1844,288 +2046,610 @@ function registerShellCommands(cli, progName = "apcore-cli") {
1844
2046
  cli.addCommand(manCmd);
1845
2047
  }
1846
2048
 
1847
- // src/main.ts
1848
- var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
1849
- var verboseHelp = false;
1850
- function setVerboseHelp(verbose) {
1851
- verboseHelp = verbose;
2049
+ // src/discovery.ts
2050
+ init_esm_shims();
2051
+ init_errors();
2052
+ import { Command as Command2, Option as Option2 } from "commander";
2053
+ var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
2054
+ function validateTag(tag) {
2055
+ if (!TAG_PATTERN.test(tag)) {
2056
+ process.stderr.write(
2057
+ `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
2058
+ `
2059
+ );
2060
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2061
+ }
1852
2062
  }
1853
- var docsUrl = null;
1854
- function setDocsUrl(url) {
1855
- docsUrl = url;
2063
+ function collectTag(value, previous) {
2064
+ return previous.concat([value]);
1856
2065
  }
1857
- function hasVerboseFlag() {
1858
- return process.argv.includes("--verbose");
2066
+ function collectAnnotation(value, previous) {
2067
+ return previous.concat([value]);
1859
2068
  }
1860
- var VERSION = "0.0.0";
1861
- try {
1862
- const pkg = JSON.parse(readFileSync3(path6.resolve(__dirname3, "../package.json"), "utf-8"));
1863
- VERSION = pkg.version;
1864
- } catch {
2069
+ function getAnnotationFlag(moduleDef, flag) {
2070
+ const annotations = moduleDef.annotations;
2071
+ if (!annotations || typeof annotations !== "object") return false;
2072
+ const ann = annotations;
2073
+ const map = {
2074
+ "destructive": "destructive",
2075
+ "requires-approval": "requires_approval",
2076
+ "readonly": "readonly",
2077
+ "streaming": "streaming",
2078
+ "cacheable": "cacheable",
2079
+ "idempotent": "idempotent"
2080
+ };
2081
+ const attr = map[flag] ?? flag;
2082
+ return ann[attr] === true;
1865
2083
  }
1866
- function createCli(extensionsDir, progName, verbose = false) {
1867
- verboseHelp = verbose;
1868
- registerConfigNamespace();
1869
- const resolvedProgName = progName ?? path6.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1870
- const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
1871
- setLogLevel(cliLogLevel);
1872
- const program = new Command2(resolvedProgName).exitOverride().version(VERSION, "--version", `Show ${resolvedProgName} version`).description("apcore CLI \u2014 execute apcore modules from the command line").option("--extensions-dir <path>", "Path to extensions directory").option("--commands-dir <path>", "Path to convention-based commands directory").option("--binding <path>", "Path to binding.yaml for display overlay").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in apcore options)");
1873
- const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
1874
- void resolvedExtDir;
1875
- program.addHelpText("after", [
1876
- "",
1877
- "Use --help --verbose to show all options (including built-in apcore options).",
1878
- "Use --help --man to display a formatted man page."
1879
- ].join("\n"));
1880
- registerInitCommand(program);
1881
- configureManHelp(program, resolvedProgName, VERSION);
1882
- program.hook("preAction", async (thisCommand) => {
1883
- const opts = thisCommand.opts();
1884
- const commandsDir = opts.commandsDir;
1885
- const bindingPath = opts.binding;
1886
- await applyToolkitIntegration(commandsDir, bindingPath);
2084
+ function registerDiscoveryCommands(cli, registry) {
2085
+ 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(
2086
+ new Option2("--status <status>", "Filter by module status.").choices(["enabled", "disabled", "all"]).default("enabled")
2087
+ ).option("-a, --annotation <flag>", "Filter by annotation flag (AND logic). Repeatable.", collectAnnotation, []).addOption(
2088
+ new Option2("--sort <field>", "Sort order.").choices(["id", "calls", "errors", "latency"]).default("id")
2089
+ ).option("--reverse", "Reverse sort order.", false).option("--deprecated", "Include deprecated modules.", false).option("--deps", "Show dependency count column.", false).action((opts) => {
2090
+ for (const t of opts.tag) {
2091
+ validateTag(t);
2092
+ }
2093
+ let modules = [];
2094
+ for (const m of registry.listModules()) {
2095
+ modules.push(m);
2096
+ }
2097
+ if (opts.tag.length > 0) {
2098
+ const filterTags = new Set(opts.tag);
2099
+ modules = modules.filter((m) => {
2100
+ const mTags = m.tags ?? [];
2101
+ return [...filterTags].every((t) => mTags.includes(t));
2102
+ });
2103
+ }
2104
+ if (opts.search) {
2105
+ const query = opts.search.toLowerCase();
2106
+ modules = modules.filter(
2107
+ (m) => (m.id ?? "").toLowerCase().includes(query) || (m.description ?? "").toLowerCase().includes(query)
2108
+ );
2109
+ }
2110
+ if (opts.status === "enabled") {
2111
+ modules = modules.filter((m) => {
2112
+ const enabled = m.enabled;
2113
+ return enabled !== false;
2114
+ });
2115
+ } else if (opts.status === "disabled") {
2116
+ modules = modules.filter((m) => {
2117
+ const enabled = m.enabled;
2118
+ return enabled === false;
2119
+ });
2120
+ }
2121
+ if (!opts.deprecated) {
2122
+ modules = modules.filter((m) => {
2123
+ const deprecated = m.deprecated;
2124
+ return deprecated !== true;
2125
+ });
2126
+ }
2127
+ if (opts.annotation.length > 0) {
2128
+ for (const annFlag of opts.annotation) {
2129
+ modules = modules.filter((m) => getAnnotationFlag(m, annFlag));
2130
+ }
2131
+ }
2132
+ if (opts.sort === "calls" || opts.sort === "errors" || opts.sort === "latency") {
2133
+ process.stderr.write(
2134
+ `Warning: Usage data not available; sorting by id. Sort by ${opts.sort} requires system.usage modules.
2135
+ `
2136
+ );
2137
+ }
2138
+ modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
2139
+ if (opts.reverse) {
2140
+ modules.reverse();
2141
+ }
2142
+ const fmt = resolveFormat(opts.format);
2143
+ const filterTagsArg = opts.tag.length > 0 ? opts.tag : void 0;
2144
+ formatModuleList(modules, fmt, filterTagsArg, opts.deps);
1887
2145
  });
1888
- return program;
2146
+ cli.addCommand(listCmd);
2147
+ 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) => {
2148
+ validateModuleId(moduleId);
2149
+ const moduleDef = registry.getModule(moduleId);
2150
+ if (!moduleDef) {
2151
+ process.stderr.write(
2152
+ `Error: Module '${moduleId}' not found.
2153
+ `
2154
+ );
2155
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2156
+ }
2157
+ const fmt = resolveFormat(opts.format);
2158
+ formatModuleDetail(moduleDef, fmt);
2159
+ });
2160
+ cli.addCommand(describeCmd);
1889
2161
  }
1890
- async function applyToolkitIntegration(commandsDir, bindingPath) {
1891
- if (!commandsDir && !bindingPath) {
1892
- return;
1893
- }
1894
- try {
1895
- const toolkitModule = "apcore-toolkit";
1896
- const toolkit = await import(
1897
- /* @vite-ignore */
1898
- toolkitModule
1899
- );
1900
- if (commandsDir) {
1901
- console.warn("Convention scanning not yet available in TypeScript toolkit");
2162
+ function registerValidateCommand(cli, registry, executor) {
2163
+ const validateCmd = new Command2("validate").description("Run preflight checks without executing a module.").argument("<module-id>", "Module ID to validate").option("--input <source>", "JSON input file or '-' for stdin.").option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2164
+ validateModuleId(moduleId);
2165
+ const moduleDef = registry.getModule(moduleId);
2166
+ if (!moduleDef) {
2167
+ process.stderr.write(`Error: Module '${moduleId}' not found.
2168
+ `);
2169
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
1902
2170
  }
1903
- if (bindingPath) {
1904
- const resolver = new toolkit.DisplayResolver();
1905
- void resolver;
2171
+ const merged = opts.input ? await collectInput(opts.input, {}, false) : {};
2172
+ if (!executor.validate) {
2173
+ process.stderr.write("Error: Executor does not support validate.\n");
2174
+ process.exit(1);
1906
2175
  }
1907
- } catch {
1908
- console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
2176
+ const preflight = await executor.validate(moduleId, merged);
2177
+ formatPreflightResult(preflight, opts.format);
2178
+ process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
2179
+ });
2180
+ cli.addCommand(validateCmd);
2181
+ }
2182
+
2183
+ // src/system-cmd.ts
2184
+ init_esm_shims();
2185
+ import { Command as Command3 } from "commander";
2186
+ async function callSystemModule(executor, moduleId, inputs) {
2187
+ if (executor.call) {
2188
+ return executor.call(moduleId, inputs);
1909
2189
  }
2190
+ return executor.execute(moduleId, inputs);
1910
2191
  }
1911
- function main(progName) {
1912
- verboseHelp = hasVerboseFlag();
1913
- const program = createCli(void 0, progName, verboseHelp);
1914
- try {
1915
- program.parse(process.argv);
1916
- } catch (error2) {
1917
- if (error2 instanceof CommanderError) {
1918
- process.exit(error2.exitCode);
1919
- }
1920
- const code = exitCodeForError(error2);
1921
- if (error2 instanceof Error) {
1922
- process.stderr.write(`Error: ${error2.message}
2192
+ function formatHealthSummaryTty(result) {
2193
+ const summary = result.summary ?? {};
2194
+ const modules = result.modules ?? [];
2195
+ if (modules.length === 0) {
2196
+ process.stdout.write("No modules found.\n");
2197
+ return;
2198
+ }
2199
+ const total = summary.total_modules ?? modules.length;
2200
+ process.stdout.write(`Health Overview (${total} modules)
2201
+
1923
2202
  `);
1924
- }
1925
- process.exit(code);
2203
+ process.stdout.write(` ${"Module".padEnd(28)} ${"Status".padEnd(12)} ${"Error Rate".padEnd(12)} Top Error
2204
+ `);
2205
+ process.stdout.write(` ${"-".repeat(28)} ${"-".repeat(12)} ${"-".repeat(12)} ${"-".repeat(20)}
2206
+ `);
2207
+ for (const m of modules) {
2208
+ const top = m.top_error;
2209
+ const topStr = top ? `${top.code} (${top.count ?? "?"})` : "\u2014";
2210
+ const rate = `${((m.error_rate ?? 0) * 100).toFixed(1)}%`;
2211
+ process.stdout.write(
2212
+ ` ${String(m.module_id).padEnd(28)} ${String(m.status).padEnd(12)} ${rate.padEnd(12)} ${topStr}
2213
+ `
2214
+ );
2215
+ }
2216
+ const parts = [];
2217
+ for (const key of ["healthy", "degraded", "error"]) {
2218
+ const count = summary[key];
2219
+ if (count) parts.push(`${count} ${key}`);
1926
2220
  }
2221
+ process.stdout.write(`
2222
+ Summary: ${parts.join(", ") || "no data"}
2223
+ `);
1927
2224
  }
1928
- function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName, verbose = verboseHelp) {
1929
- const moduleId = moduleDef.id;
1930
- let resolvedSchema = {};
1931
- let schemaOptions = [];
1932
- const display = getDisplay(moduleDef);
1933
- const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1934
- const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
1935
- const cmdHelp = cliDisplay.description ?? moduleDef.description;
1936
- const inputSchema = moduleDef.inputSchema;
1937
- if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
1938
- try {
1939
- resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
1940
- } catch {
1941
- resolvedSchema = inputSchema;
2225
+ function formatHealthModuleTty(result) {
2226
+ process.stdout.write(`Module: ${result.module_id ?? "?"}
2227
+ `);
2228
+ process.stdout.write(`Status: ${result.status ?? "unknown"}
2229
+ `);
2230
+ const total = result.total_calls ?? 0;
2231
+ const errors = result.error_count ?? 0;
2232
+ const rate = result.error_rate ?? 0;
2233
+ const avg = result.avg_latency_ms ?? 0;
2234
+ const p99 = result.p99_latency_ms ?? 0;
2235
+ process.stdout.write(`Calls: ${total.toLocaleString()} total | ${errors.toLocaleString()} errors | ${(rate * 100).toFixed(1)}% error rate
2236
+ `);
2237
+ process.stdout.write(`Latency: ${avg.toFixed(0)}ms avg | ${p99.toFixed(0)}ms p99
2238
+ `);
2239
+ const recent = result.recent_errors ?? [];
2240
+ if (recent.length > 0) {
2241
+ process.stdout.write(`
2242
+ Recent Errors (top ${recent.length}):
2243
+ `);
2244
+ for (const e of recent) {
2245
+ const count = e.count ?? "?";
2246
+ const last = e.last_occurred ?? "?";
2247
+ process.stdout.write(` ${String(e.code ?? "?").padEnd(24)} x${count} (last: ${last})
2248
+ `);
1942
2249
  }
1943
- schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
1944
- }
1945
- const cmd = new Command2(effectiveCmdName).description(cmdHelp);
1946
- const inputOpt = new Option2("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
1947
- const yesOpt = new Option2("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
1948
- const largeInputOpt = new Option2("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
1949
- const formatOpt = new Option2("--format <format>", "Set output format: 'json' for machine-readable, 'table' for human-readable");
1950
- const sandboxOpt = new Option2("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
1951
- if (!verbose) {
1952
- inputOpt.hideHelp();
1953
- yesOpt.hideHelp();
1954
- largeInputOpt.hideHelp();
1955
- formatOpt.hideHelp();
1956
- }
1957
- cmd.addOption(inputOpt);
1958
- cmd.addOption(yesOpt);
1959
- cmd.addOption(largeInputOpt);
1960
- cmd.addOption(formatOpt);
1961
- cmd.addOption(sandboxOpt);
1962
- const footerParts = [];
1963
- if (!verbose) {
1964
- footerParts.push("Use --verbose to show all options (including built-in apcore options).");
1965
2250
  }
1966
- if (docsUrl) {
1967
- footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
2251
+ }
2252
+ function formatUsageSummaryTty(result) {
2253
+ const modules = result.modules ?? [];
2254
+ const period = result.period ?? "?";
2255
+ if (modules.length === 0) {
2256
+ process.stdout.write(`No usage data for period ${period}.
2257
+ `);
2258
+ return;
1968
2259
  }
1969
- if (footerParts.length > 0) {
1970
- cmd.addHelpText("after", "\n" + footerParts.join("\n") + "\n");
2260
+ process.stdout.write(`Usage Summary (last ${period})
2261
+
2262
+ `);
2263
+ process.stdout.write(` ${"Module".padEnd(24)} ${"Calls".padStart(8)} ${"Errors".padStart(8)} ${"Avg Latency".padStart(12)} ${"Trend".padStart(10)}
2264
+ `);
2265
+ process.stdout.write(` ${"-".repeat(24)} ${"-".repeat(8)} ${"-".repeat(8)} ${"-".repeat(12)} ${"-".repeat(10)}
2266
+ `);
2267
+ for (const m of modules) {
2268
+ const avg = `${(m.avg_latency_ms ?? 0).toFixed(0)}ms`;
2269
+ process.stdout.write(
2270
+ ` ${String(m.module_id).padEnd(24)} ${String(m.call_count ?? 0).padStart(8)} ${String(m.error_count ?? 0).padStart(8)} ${avg.padStart(12)} ${String(m.trend ?? "").padStart(10)}
2271
+ `
2272
+ );
1971
2273
  }
1972
- for (const opt of schemaOptions) {
1973
- if (opt.parseArg) {
1974
- cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
2274
+ const totalCalls = result.total_calls ?? modules.reduce((s, m) => s + (m.call_count ?? 0), 0);
2275
+ const totalErrors = result.total_errors ?? modules.reduce((s, m) => s + (m.error_count ?? 0), 0);
2276
+ process.stdout.write(`
2277
+ Total: ${totalCalls.toLocaleString()} calls | ${totalErrors.toLocaleString()} errors
2278
+ `);
2279
+ }
2280
+ async function registerSystemCommands(cli, executor) {
2281
+ try {
2282
+ if (executor.validate) {
2283
+ await executor.validate("system.health.summary", {});
1975
2284
  } else {
1976
- cmd.option(opt.flags, opt.description, opt.defaultValue);
2285
+ await callSystemModule(executor, "system.health.summary", { include_healthy: true });
1977
2286
  }
2287
+ } catch {
2288
+ debug("System modules not available; skipping system command registration.");
2289
+ return;
1978
2290
  }
1979
- cmd.action(async (options) => {
1980
- const stdinFlag = options.input;
1981
- const autoApprove = options.yes;
1982
- const largeInput = options.largeInput;
1983
- const outputFormat = options.format;
1984
- const sandboxEnabled = options.sandbox;
1985
- const schemaKwargs = {};
1986
- const builtinKeys = /* @__PURE__ */ new Set(["input", "yes", "largeInput", "format", "sandbox", "verbose"]);
1987
- for (const [k, v] of Object.entries(options)) {
1988
- if (!builtinKeys.has(k)) {
1989
- schemaKwargs[k] = v;
2291
+ const healthCmd = new Command3("health").description("Show module health status. Optionally specify a module ID for details.").argument("[module-id]", "Module ID for detailed health").option("--threshold <number>", "Error rate threshold (default: 0.01).", parseFloat, 0.01).option("--all", "Include healthy modules.", false).option("--errors <count>", "Max recent errors (module detail only).", parseInt, 10).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2292
+ const fmt = resolveFormat(opts.format);
2293
+ try {
2294
+ if (moduleId) {
2295
+ const result = await callSystemModule(executor, "system.health.module", {
2296
+ module_id: moduleId,
2297
+ error_limit: opts.errors
2298
+ });
2299
+ if (fmt === "json" || !process.stdout.isTTY) {
2300
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2301
+ } else {
2302
+ formatHealthModuleTty(result);
2303
+ }
2304
+ } else {
2305
+ const result = await callSystemModule(executor, "system.health.summary", {
2306
+ error_rate_threshold: opts.threshold,
2307
+ include_healthy: opts.all
2308
+ });
2309
+ if (fmt === "json" || !process.stdout.isTTY) {
2310
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2311
+ } else {
2312
+ formatHealthSummaryTty(result);
2313
+ }
1990
2314
  }
2315
+ } catch (e) {
2316
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2317
+ `);
2318
+ process.exit(1);
1991
2319
  }
2320
+ });
2321
+ cli.addCommand(healthCmd);
2322
+ const usageCmd = new Command3("usage").description("Show module usage statistics. Optionally specify a module ID for details.").argument("[module-id]", "Module ID for detailed usage").option("--period <period>", "Time window: 1h, 24h, 7d, 30d.", "24h").option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2323
+ const fmt = resolveFormat(opts.format);
1992
2324
  try {
1993
- const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
1994
- const reconverted = reconvertEnumValues(merged, schemaOptions);
1995
- await checkApproval(moduleDef, autoApprove);
1996
- const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
1997
- const sandbox = new Sandbox2(sandboxEnabled);
1998
- const startTime = performance.now();
1999
- const result = await sandbox.execute(moduleId, reconverted, executor);
2000
- const durationMs = Math.round(performance.now() - startTime);
2001
- const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2002
- const auditLogger = getAuditLogger2();
2003
- if (auditLogger) {
2004
- auditLogger.logExecution(moduleId, reconverted, "success", 0, durationMs);
2325
+ let result;
2326
+ if (moduleId) {
2327
+ result = await callSystemModule(executor, "system.usage.module", {
2328
+ module_id: moduleId,
2329
+ period: opts.period
2330
+ });
2331
+ } else {
2332
+ result = await callSystemModule(executor, "system.usage.summary", {
2333
+ period: opts.period
2334
+ });
2005
2335
  }
2006
- formatExecResult(result, outputFormat);
2007
- } catch (err) {
2008
- const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2009
- const auditLogger = getAuditLogger2();
2010
- const code = exitCodeForError(err);
2011
- if (auditLogger) {
2012
- auditLogger.logExecution(moduleId, {}, "error", code, 0);
2336
+ if (fmt === "json" || !process.stdout.isTTY) {
2337
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2338
+ } else if (moduleId) {
2339
+ formatExecResult(result, fmt);
2340
+ } else {
2341
+ formatUsageSummaryTty(result);
2013
2342
  }
2014
- if (err instanceof Error) {
2015
- process.stderr.write(`Error: ${err.message}
2343
+ } catch (e) {
2344
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2345
+ `);
2346
+ process.exit(1);
2347
+ }
2348
+ });
2349
+ cli.addCommand(usageCmd);
2350
+ 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.", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2351
+ if (!opts.yes) {
2352
+ process.stderr.write("Note: This command requires approval. Use --yes to bypass.\n");
2353
+ }
2354
+ const fmt = resolveFormat(opts.format);
2355
+ try {
2356
+ const result = await callSystemModule(executor, "system.control.toggle_feature", {
2357
+ module_id: moduleId,
2358
+ enabled: true,
2359
+ reason: opts.reason
2360
+ });
2361
+ if (fmt === "json" || !process.stdout.isTTY) {
2362
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2363
+ } else {
2364
+ process.stdout.write(`Module '${moduleId}' enabled.
2365
+ Reason: ${opts.reason}
2016
2366
  `);
2017
2367
  }
2018
- process.exit(code);
2368
+ } catch (e) {
2369
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2370
+ `);
2371
+ process.exit(1);
2019
2372
  }
2020
2373
  });
2021
- return cmd;
2022
- }
2023
- function validateModuleId(moduleId) {
2024
- if (moduleId.length > 128) {
2025
- process.stderr.write(
2026
- `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
2027
- `
2028
- );
2029
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2030
- }
2031
- if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
2032
- process.stderr.write(
2033
- `Error: Invalid module ID format: '${moduleId}'.
2034
- `
2035
- );
2036
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2037
- }
2038
- }
2039
- async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
2040
- const cliKwargsNonNull = {};
2041
- for (const [k, v] of Object.entries(cliKwargs)) {
2042
- if (v !== null && v !== void 0) {
2043
- cliKwargsNonNull[k] = v;
2374
+ cli.addCommand(enableCmd);
2375
+ 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.", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2376
+ if (!opts.yes) {
2377
+ process.stderr.write("Note: This command requires approval. Use --yes to bypass.\n");
2044
2378
  }
2045
- }
2046
- if (!stdinFlag) {
2047
- return cliKwargsNonNull;
2048
- }
2049
- if (stdinFlag === "-") {
2050
- const raw = await readStdin();
2051
- const rawSize = Buffer.byteLength(raw, "utf-8");
2052
- if (rawSize > 10485760 && !largeInput) {
2053
- process.stderr.write(
2054
- "Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
2055
- );
2056
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2379
+ const fmt = resolveFormat(opts.format);
2380
+ try {
2381
+ const result = await callSystemModule(executor, "system.control.toggle_feature", {
2382
+ module_id: moduleId,
2383
+ enabled: false,
2384
+ reason: opts.reason
2385
+ });
2386
+ if (fmt === "json" || !process.stdout.isTTY) {
2387
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2388
+ } else {
2389
+ process.stdout.write(`Module '${moduleId}' disabled.
2390
+ Reason: ${opts.reason}
2391
+ `);
2392
+ }
2393
+ } catch (e) {
2394
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2395
+ `);
2396
+ process.exit(1);
2057
2397
  }
2058
- if (!raw) {
2059
- return cliKwargsNonNull;
2398
+ });
2399
+ cli.addCommand(disableCmd);
2400
+ 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.", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2401
+ if (!opts.yes) {
2402
+ process.stderr.write("Note: This command requires approval. Use --yes to bypass.\n");
2060
2403
  }
2061
- let stdinData;
2404
+ const fmt = resolveFormat(opts.format);
2062
2405
  try {
2063
- stdinData = JSON.parse(raw);
2406
+ const result = await callSystemModule(executor, "system.control.reload_module", {
2407
+ module_id: moduleId,
2408
+ reason: opts.reason
2409
+ });
2410
+ if (fmt === "json" || !process.stdout.isTTY) {
2411
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2412
+ } else {
2413
+ const prev = result.previous_version ?? "?";
2414
+ const newVer = result.new_version ?? "?";
2415
+ const dur = result.reload_duration_ms ?? "?";
2416
+ process.stdout.write(`Module '${moduleId}' reloaded.
2417
+ `);
2418
+ process.stdout.write(` Version: ${prev} -> ${newVer}
2419
+ `);
2420
+ process.stdout.write(` Duration: ${dur}ms
2421
+ `);
2422
+ }
2423
+ } catch (e) {
2424
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2425
+ `);
2426
+ process.exit(1);
2427
+ }
2428
+ });
2429
+ cli.addCommand(reloadCmd);
2430
+ const configGroup = new Command3("config").description("Read or update runtime configuration.");
2431
+ const configGetCmd = new Command3("get").description("Read a configuration value by dot-path key.").argument("<key>", "Configuration key (dot-path)").option("--format <format>", "Output format.", "table").action(async (key, opts) => {
2432
+ const fmt = resolveFormat(opts.format);
2433
+ try {
2434
+ const result = await callSystemModule(executor, "system.config.get", { key });
2435
+ const value = result?.value ?? result;
2436
+ if (fmt === "json" || !process.stdout.isTTY) {
2437
+ process.stdout.write(JSON.stringify({ key, value }, null, 2) + "\n");
2438
+ } else {
2439
+ process.stdout.write(`${key} = ${JSON.stringify(value)}
2440
+ `);
2441
+ }
2442
+ } catch (e) {
2443
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2444
+ `);
2445
+ process.exit(1);
2446
+ }
2447
+ });
2448
+ configGroup.addCommand(configGetCmd);
2449
+ const configSetCmd = new Command3("set").description("Update a runtime configuration value (requires approval).").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) => {
2450
+ const fmt = resolveFormat(opts.format);
2451
+ let parsedValue;
2452
+ try {
2453
+ parsedValue = JSON.parse(value);
2064
2454
  } catch {
2065
- process.stderr.write(
2066
- "Error: STDIN does not contain valid JSON.\n"
2067
- );
2068
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2455
+ parsedValue = value;
2069
2456
  }
2070
- if (typeof stdinData !== "object" || stdinData === null || Array.isArray(stdinData)) {
2071
- process.stderr.write(
2072
- `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? "array" : typeof stdinData}.
2073
- `
2074
- );
2075
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2457
+ try {
2458
+ const result = await callSystemModule(executor, "system.control.update_config", {
2459
+ key,
2460
+ value: parsedValue,
2461
+ reason: opts.reason
2462
+ });
2463
+ if (fmt === "json" || !process.stdout.isTTY) {
2464
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
2465
+ } else {
2466
+ const old = result.old_value ?? "?";
2467
+ const newVal = result.new_value ?? "?";
2468
+ process.stdout.write(`Config updated: ${key}
2469
+ `);
2470
+ process.stdout.write(` ${JSON.stringify(old)} -> ${JSON.stringify(newVal)}
2471
+ `);
2472
+ process.stdout.write(` Reason: ${opts.reason}
2473
+ `);
2474
+ }
2475
+ } catch (e) {
2476
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2477
+ `);
2478
+ process.exit(1);
2076
2479
  }
2077
- return { ...stdinData, ...cliKwargsNonNull };
2078
- }
2079
- return cliKwargsNonNull;
2080
- }
2081
- function readStdin() {
2082
- return new Promise((resolve3, reject) => {
2083
- const chunks = [];
2084
- const onData = (chunk) => chunks.push(chunk);
2085
- const onEnd = () => {
2086
- cleanup();
2087
- resolve3(Buffer.concat(chunks).toString("utf-8"));
2088
- };
2089
- const onError = (err) => {
2090
- cleanup();
2091
- reject(err);
2092
- };
2093
- const cleanup = () => {
2094
- process.stdin.removeListener("data", onData);
2095
- process.stdin.removeListener("end", onEnd);
2096
- process.stdin.removeListener("error", onError);
2097
- };
2098
- process.stdin.on("data", onData);
2099
- process.stdin.on("end", onEnd);
2100
- process.stdin.on("error", onError);
2101
- process.stdin.resume();
2102
2480
  });
2481
+ configGroup.addCommand(configSetCmd);
2482
+ cli.addCommand(configGroup);
2103
2483
  }
2104
- function reconvertEnumValues(kwargs, options) {
2105
- const result = { ...kwargs };
2106
- for (const opt of options) {
2107
- if (!opt.enumOriginalTypes) continue;
2108
- const paramName = opt.name;
2109
- if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
2110
- continue;
2484
+
2485
+ // src/strategy.ts
2486
+ init_esm_shims();
2487
+ import { Command as Command4, Option as Option3 } from "commander";
2488
+ var PRESET_STEPS = {
2489
+ standard: [
2490
+ "context_creation",
2491
+ "call_chain_guard",
2492
+ "module_lookup",
2493
+ "acl_check",
2494
+ "approval_gate",
2495
+ "middleware_before",
2496
+ "input_validation",
2497
+ "execute",
2498
+ "output_validation",
2499
+ "middleware_after",
2500
+ "return_result"
2501
+ ],
2502
+ internal: [
2503
+ "context_creation",
2504
+ "call_chain_guard",
2505
+ "module_lookup",
2506
+ "middleware_before",
2507
+ "input_validation",
2508
+ "execute",
2509
+ "output_validation",
2510
+ "middleware_after",
2511
+ "return_result"
2512
+ ],
2513
+ testing: [
2514
+ "context_creation",
2515
+ "module_lookup",
2516
+ "middleware_before",
2517
+ "input_validation",
2518
+ "execute",
2519
+ "output_validation",
2520
+ "middleware_after",
2521
+ "return_result"
2522
+ ],
2523
+ performance: [
2524
+ "context_creation",
2525
+ "call_chain_guard",
2526
+ "module_lookup",
2527
+ "acl_check",
2528
+ "approval_gate",
2529
+ "input_validation",
2530
+ "execute",
2531
+ "output_validation",
2532
+ "return_result"
2533
+ ],
2534
+ minimal: [
2535
+ "context_creation",
2536
+ "module_lookup",
2537
+ "execute",
2538
+ "return_result"
2539
+ ]
2540
+ };
2541
+ function registerPipelineCommand(cli, executor) {
2542
+ const pipelineCmd = new Command4("describe-pipeline").description("Show the execution pipeline steps for a strategy.").addOption(
2543
+ new Option3("--strategy <name>", "Strategy to describe (default: standard).").choices(["standard", "internal", "testing", "performance", "minimal"]).default("standard")
2544
+ ).option("--format <format>", "Output format.").action((opts) => {
2545
+ const fmt = resolveFormat(opts.format);
2546
+ let strategyObj = null;
2547
+ const ex = executor;
2548
+ if (typeof ex._resolve_strategy_name === "function" || typeof ex._resolveStrategyName === "function") {
2549
+ try {
2550
+ const fn = ex._resolve_strategy_name ?? ex._resolveStrategyName;
2551
+ strategyObj = fn(opts.strategy);
2552
+ } catch {
2553
+ strategyObj = null;
2554
+ }
2111
2555
  }
2112
- const strVal = String(result[paramName]);
2113
- const origType = opt.enumOriginalTypes[strVal];
2114
- if (origType === "int") {
2115
- result[paramName] = parseInt(strVal, 10);
2116
- } else if (origType === "float") {
2117
- result[paramName] = parseFloat(strVal);
2118
- } else if (origType === "bool") {
2119
- result[paramName] = strVal.toLowerCase() === "true";
2556
+ if (!strategyObj) {
2557
+ const steps = PRESET_STEPS[opts.strategy] ?? [];
2558
+ const pureSteps = /* @__PURE__ */ new Set([
2559
+ "context_creation",
2560
+ "call_chain_guard",
2561
+ "module_lookup",
2562
+ "acl_check",
2563
+ "input_validation"
2564
+ ]);
2565
+ const nonRemovable = /* @__PURE__ */ new Set([
2566
+ "context_creation",
2567
+ "module_lookup",
2568
+ "execute",
2569
+ "return_result"
2570
+ ]);
2571
+ if (fmt === "json" || !process.stdout.isTTY) {
2572
+ const payload = {
2573
+ strategy: opts.strategy,
2574
+ step_count: steps.length,
2575
+ steps: steps.map((s, i) => ({
2576
+ index: i + 1,
2577
+ name: s,
2578
+ pure: pureSteps.has(s),
2579
+ removable: !nonRemovable.has(s)
2580
+ }))
2581
+ };
2582
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
2583
+ } else {
2584
+ process.stdout.write(`Pipeline: ${opts.strategy} (${steps.length} steps)
2585
+
2586
+ `);
2587
+ process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
2588
+ `);
2589
+ process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
2590
+ `);
2591
+ for (let i = 0; i < steps.length; i++) {
2592
+ const pure = pureSteps.has(steps[i]) ? "yes" : "no";
2593
+ const removable = nonRemovable.has(steps[i]) ? "no" : "yes";
2594
+ process.stdout.write(` ${String(i + 1).padEnd(4)} ${steps[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} \u2014
2595
+ `);
2596
+ }
2597
+ }
2598
+ return;
2120
2599
  }
2121
- }
2122
- return result;
2600
+ const stepsInfo = strategyObj.steps.map((step) => ({
2601
+ name: step.name,
2602
+ pure: step.pure ?? false,
2603
+ removable: step.removable ?? true,
2604
+ timeout_ms: step.timeout_ms ?? null
2605
+ }));
2606
+ if (fmt === "json" || !process.stdout.isTTY) {
2607
+ const payload = {
2608
+ strategy: opts.strategy,
2609
+ step_count: stepsInfo.length,
2610
+ steps: stepsInfo.map((s, i) => ({ index: i + 1, ...s }))
2611
+ };
2612
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
2613
+ } else {
2614
+ process.stdout.write(`Pipeline: ${opts.strategy} (${stepsInfo.length} steps)
2615
+
2616
+ `);
2617
+ process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
2618
+ `);
2619
+ process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
2620
+ `);
2621
+ for (let i = 0; i < stepsInfo.length; i++) {
2622
+ const s = stepsInfo[i];
2623
+ const pure = s.pure ? "yes" : "no";
2624
+ const removable = s.removable ? "yes" : "no";
2625
+ const timeout = s.timeout_ms !== null ? `${s.timeout_ms}ms` : "\u2014";
2626
+ process.stdout.write(` ${String(i + 1).padEnd(4)} ${s.name.padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} ${timeout}
2627
+ `);
2628
+ }
2629
+ }
2630
+ });
2631
+ cli.addCommand(pipelineCmd);
2123
2632
  }
2124
2633
 
2125
2634
  // src/cli.ts
2126
2635
  init_esm_shims();
2127
- import { Command as Command3 } from "commander";
2128
- var BUILTIN_COMMANDS = ["completion", "describe", "exec", "init", "list", "man"];
2636
+ import { Command as Command5 } from "commander";
2637
+ var BUILTIN_COMMANDS = [
2638
+ "completion",
2639
+ "config",
2640
+ "describe",
2641
+ "describe-pipeline",
2642
+ "disable",
2643
+ "enable",
2644
+ "exec",
2645
+ "health",
2646
+ "init",
2647
+ "list",
2648
+ "man",
2649
+ "reload",
2650
+ "usage",
2651
+ "validate"
2652
+ ];
2129
2653
  var LazyModuleGroup = class {
2130
2654
  registry;
2131
2655
  executor;
@@ -2208,7 +2732,7 @@ var LazyGroup = class {
2208
2732
  this.members = members;
2209
2733
  this._executor = executor;
2210
2734
  this._helpTextMaxLength = helpTextMaxLength;
2211
- this.command = new Command3(name).description(`${name} commands`);
2735
+ this.command = new Command5(name).description(`${name} commands`);
2212
2736
  for (const [cmdName, [, descriptor]] of this.members) {
2213
2737
  const cmd = buildModuleCommand(
2214
2738
  descriptor,
@@ -2252,8 +2776,13 @@ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
2252
2776
  groupMapBuilt = false;
2253
2777
  /**
2254
2778
  * Determine (groupName | null, commandName) for a module from its display overlay.
2779
+ *
2780
+ * @param groupDepth Number of dotted segments to consume as the group prefix.
2781
+ * Defaults to 1 (e.g., "math.add" → group="math", cmd="add").
2782
+ * Set to 2 for multi-level grouping (e.g., "math.trig.sin" →
2783
+ * group="math.trig", cmd="sin").
2255
2784
  */
2256
- static resolveGroup(moduleId, descriptor) {
2785
+ static resolveGroup(moduleId, descriptor, groupDepth = 1) {
2257
2786
  if (!moduleId) {
2258
2787
  warn("Empty module_id encountered in resolveGroup");
2259
2788
  return [null, ""];
@@ -2269,9 +2798,10 @@ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
2269
2798
  }
2270
2799
  const cliName = cliDisplay.alias ?? moduleId;
2271
2800
  if (cliName.includes(".")) {
2272
- const dotIdx = cliName.indexOf(".");
2273
- const group = cliName.substring(0, dotIdx);
2274
- const cmd = cliName.substring(dotIdx + 1);
2801
+ const parts = cliName.split(".");
2802
+ const depth = Math.max(1, Math.min(groupDepth, parts.length - 1));
2803
+ const group = parts.slice(0, depth).join(".");
2804
+ const cmd = parts.slice(depth).join(".");
2275
2805
  return [group, cmd];
2276
2806
  }
2277
2807
  return [null, cliName];
@@ -2377,58 +2907,615 @@ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
2377
2907
  }
2378
2908
  };
2379
2909
 
2380
- // src/discovery.ts
2381
- init_esm_shims();
2382
- init_errors();
2383
- import { Command as Command4 } from "commander";
2384
- var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
2385
- function validateTag(tag) {
2386
- if (!TAG_PATTERN.test(tag)) {
2387
- process.stderr.write(
2388
- `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
2389
- `
2390
- );
2391
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2392
- }
2910
+ // src/main.ts
2911
+ var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
2912
+ var verboseHelp = false;
2913
+ function setVerboseHelp(verbose) {
2914
+ verboseHelp = verbose;
2393
2915
  }
2394
- function collectTag(value, previous) {
2395
- return previous.concat([value]);
2916
+ var docsUrl = null;
2917
+ function setDocsUrl(url) {
2918
+ docsUrl = url;
2396
2919
  }
2397
- function registerDiscoveryCommands(cli, registry) {
2398
- const listCmd = new Command4("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--format <format>", "Output format.", void 0).action((opts) => {
2399
- for (const t of opts.tag) {
2400
- validateTag(t);
2920
+ function hasVerboseFlag() {
2921
+ return process.argv.includes("--verbose");
2922
+ }
2923
+ var VERSION = "0.0.0";
2924
+ try {
2925
+ const pkg = JSON.parse(readFileSync3(path6.resolve(__dirname3, "../package.json"), "utf-8"));
2926
+ VERSION = pkg.version;
2927
+ } catch {
2928
+ }
2929
+ var ERROR_CODE_MAP = {
2930
+ MODULE_NOT_FOUND: 44,
2931
+ MODULE_LOAD_ERROR: 44,
2932
+ MODULE_DISABLED: 44,
2933
+ SCHEMA_VALIDATION_ERROR: 45,
2934
+ SCHEMA_CIRCULAR_REF: 48,
2935
+ APPROVAL_DENIED: 46,
2936
+ APPROVAL_TIMEOUT: 46,
2937
+ APPROVAL_PENDING: 46,
2938
+ CONFIG_NOT_FOUND: 47,
2939
+ CONFIG_INVALID: 47,
2940
+ MODULE_EXECUTE_ERROR: 1,
2941
+ MODULE_TIMEOUT: 1,
2942
+ ACL_DENIED: 77,
2943
+ CONFIG_NAMESPACE_RESERVED: 78,
2944
+ CONFIG_NAMESPACE_DUPLICATE: 78,
2945
+ CONFIG_ENV_PREFIX_CONFLICT: 78,
2946
+ CONFIG_ENV_MAP_CONFLICT: 78,
2947
+ CONFIG_MOUNT_ERROR: 66,
2948
+ CONFIG_BIND_ERROR: 65,
2949
+ ERROR_FORMATTER_DUPLICATE: 70
2950
+ };
2951
+ function emitErrorJson(e, exitCode) {
2952
+ const err = e instanceof Error ? e : new Error(String(e));
2953
+ const errRecord = err;
2954
+ const code = errRecord.code ?? "UNKNOWN";
2955
+ const payload = {
2956
+ error: true,
2957
+ code,
2958
+ message: err.message,
2959
+ exit_code: exitCode
2960
+ };
2961
+ for (const field of ["details", "suggestion", "ai_guidance", "retryable", "user_fixable"]) {
2962
+ const val = errRecord[field];
2963
+ if (val !== void 0 && val !== null) {
2964
+ payload[field] = val;
2965
+ }
2966
+ }
2967
+ process.stderr.write(JSON.stringify(payload) + "\n");
2968
+ }
2969
+ function emitErrorTty(e, exitCode) {
2970
+ const err = e instanceof Error ? e : new Error(String(e));
2971
+ const errRecord = err;
2972
+ const code = errRecord.code;
2973
+ const header = code ? `Error [${code}]: ${err.message}` : `Error: ${err.message}`;
2974
+ process.stderr.write(header + "\n");
2975
+ const details = errRecord.details;
2976
+ if (details && typeof details === "object" && !Array.isArray(details)) {
2977
+ process.stderr.write("\n Details:\n");
2978
+ for (const [k, v] of Object.entries(details)) {
2979
+ process.stderr.write(` ${k}: ${v}
2980
+ `);
2401
2981
  }
2402
- const modules = [];
2403
- for (const m of registry.listModules()) {
2404
- modules.push(m);
2982
+ }
2983
+ const suggestion = errRecord.suggestion;
2984
+ if (suggestion) {
2985
+ process.stderr.write(`
2986
+ Suggestion: ${suggestion}
2987
+ `);
2988
+ }
2989
+ const retryable = errRecord.retryable;
2990
+ if (retryable !== void 0 && retryable !== null) {
2991
+ const label = retryable ? "Yes" : "No (same input will fail again)";
2992
+ process.stderr.write(` Retryable: ${label}
2993
+ `);
2994
+ }
2995
+ process.stderr.write(`
2996
+ Exit code: ${exitCode}
2997
+ `);
2998
+ }
2999
+ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3000
+ let extensionsDir;
3001
+ let registry;
3002
+ let executor;
3003
+ let extraCommands;
3004
+ if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
3005
+ extensionsDir = extensionsDirOrOpts.extensionsDir;
3006
+ progName = extensionsDirOrOpts.progName ?? progName;
3007
+ verbose = extensionsDirOrOpts.verbose ?? verbose;
3008
+ registry = extensionsDirOrOpts.registry;
3009
+ executor = extensionsDirOrOpts.executor;
3010
+ extraCommands = extensionsDirOrOpts.extraCommands;
3011
+ } else {
3012
+ extensionsDir = extensionsDirOrOpts;
3013
+ }
3014
+ verboseHelp = verbose;
3015
+ registerConfigNamespace();
3016
+ const resolvedProgName = progName ?? path6.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
3017
+ const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
3018
+ setLogLevel(cliLogLevel);
3019
+ const program = new Command6(resolvedProgName).exitOverride().version(VERSION, "--version", `Show ${resolvedProgName} version`).description("apcore CLI \u2014 execute apcore modules from the command line").option("--extensions-dir <path>", "Path to extensions directory").option("--commands-dir <path>", "Path to convention-based commands directory").option("--binding <path>", "Path to binding.yaml for display overlay").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in apcore options)");
3020
+ if (executor && !registry) {
3021
+ throw new Error("executor requires registry \u2014 pass both or neither");
3022
+ }
3023
+ if (registry) {
3024
+ program._registry = registry;
3025
+ if (executor) {
3026
+ program._executor = executor;
3027
+ registerValidateCommand(program, registry, executor);
3028
+ void registerSystemCommands(program, executor);
3029
+ registerPipelineCommand(program, executor);
2405
3030
  }
2406
- let filtered = modules;
2407
- if (opts.tag.length > 0) {
2408
- const filterTags = new Set(opts.tag);
2409
- filtered = modules.filter((m) => {
2410
- const mTags = m.tags ?? [];
2411
- return [...filterTags].every((t) => mTags.includes(t));
2412
- });
3031
+ } else {
3032
+ const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
3033
+ void resolvedExtDir;
3034
+ }
3035
+ program.addHelpText("after", [
3036
+ "",
3037
+ "Use --help --verbose to show all options (including built-in apcore options).",
3038
+ "Use --help --man to display a formatted man page."
3039
+ ].join("\n"));
3040
+ registerInitCommand(program);
3041
+ configureManHelp(program, resolvedProgName, VERSION);
3042
+ if (extraCommands && extraCommands.length > 0) {
3043
+ const existingNames = /* @__PURE__ */ new Set([
3044
+ ...BUILTIN_COMMANDS,
3045
+ ...program.commands.map((c) => c.name())
3046
+ ]);
3047
+ for (const cmd of extraCommands) {
3048
+ const cmdName = cmd.name();
3049
+ if (existingNames.has(cmdName)) {
3050
+ process.stderr.write(
3051
+ `Warning: Extra command '${cmdName}' collides with a built-in command and will be skipped.
3052
+ `
3053
+ );
3054
+ continue;
3055
+ }
3056
+ program.addCommand(cmd);
3057
+ existingNames.add(cmdName);
2413
3058
  }
2414
- const fmt = resolveFormat(opts.format);
2415
- formatModuleList(filtered, fmt, opts.tag.length > 0 ? opts.tag : void 0);
3059
+ }
3060
+ program.hook("preAction", async (thisCommand) => {
3061
+ const opts = thisCommand.opts();
3062
+ const commandsDir = opts.commandsDir;
3063
+ const bindingPath = opts.binding;
3064
+ await applyToolkitIntegration(commandsDir, bindingPath);
2416
3065
  });
2417
- cli.addCommand(listCmd);
2418
- const describeCmd = new Command4("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) => {
2419
- validateModuleId(moduleId);
2420
- const moduleDef = registry.getModule(moduleId);
2421
- if (!moduleDef) {
3066
+ return program;
3067
+ }
3068
+ async function applyToolkitIntegration(commandsDir, bindingPath) {
3069
+ if (!commandsDir && !bindingPath) {
3070
+ return;
3071
+ }
3072
+ try {
3073
+ const toolkitModule = "apcore-toolkit";
3074
+ const toolkit = await import(
3075
+ /* @vite-ignore */
3076
+ toolkitModule
3077
+ );
3078
+ if (commandsDir) {
3079
+ console.warn("Convention scanning not yet available in TypeScript toolkit");
3080
+ }
3081
+ if (bindingPath) {
3082
+ const resolver = new toolkit.DisplayResolver();
3083
+ void resolver;
3084
+ }
3085
+ } catch {
3086
+ console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
3087
+ }
3088
+ }
3089
+ function main(progName) {
3090
+ verboseHelp = hasVerboseFlag();
3091
+ const program = createCli(void 0, progName, verboseHelp);
3092
+ try {
3093
+ program.parse(process.argv);
3094
+ } catch (error2) {
3095
+ if (error2 instanceof CommanderError) {
3096
+ process.exit(error2.exitCode);
3097
+ }
3098
+ const code = exitCodeForError(error2);
3099
+ if (error2 instanceof Error) {
3100
+ process.stderr.write(`Error: ${error2.message}
3101
+ `);
3102
+ }
3103
+ process.exit(code);
3104
+ }
3105
+ }
3106
+ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName, verbose = verboseHelp) {
3107
+ const moduleId = moduleDef.id;
3108
+ let resolvedSchema = {};
3109
+ let schemaOptions = [];
3110
+ const display = getDisplay(moduleDef);
3111
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
3112
+ const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
3113
+ const cmdHelp = cliDisplay.description ?? moduleDef.description;
3114
+ const inputSchema = moduleDef.inputSchema;
3115
+ if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
3116
+ try {
3117
+ resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
3118
+ } catch {
3119
+ resolvedSchema = inputSchema;
3120
+ }
3121
+ schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
3122
+ }
3123
+ const cmd = new Command6(effectiveCmdName).description(cmdHelp);
3124
+ const inputOpt = new Option4("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
3125
+ const yesOpt = new Option4("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
3126
+ const largeInputOpt = new Option4("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
3127
+ const formatOpt = new Option4("--format <format>", "Output format: json, table, csv, yaml, jsonl.").choices(["json", "table", "csv", "yaml", "jsonl"]);
3128
+ const fieldsOpt = new Option4("--fields <fields>", "Comma-separated dot-paths to select from the result (e.g., 'status,data.count').");
3129
+ const sandboxOpt = new Option4("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
3130
+ const dryRunOpt = new Option4("--dry-run", "Run preflight checks without executing the module. Shows validation results.").default(false);
3131
+ const traceOpt = new Option4("--trace", "Show execution pipeline trace with per-step timing after the result.").default(false);
3132
+ const streamOpt = new Option4("--stream", "Stream module output as JSONL (one JSON object per line, flushed immediately).").default(false);
3133
+ const strategyOpt = new Option4("--strategy <name>", "Execution pipeline strategy: standard (default), internal, testing, performance.").choices(["standard", "internal", "testing", "performance", "minimal"]);
3134
+ const approvalTimeoutOpt = new Option4("--approval-timeout <seconds>", "Override approval prompt timeout in seconds (default: 60).").argParser(parseInt);
3135
+ const approvalTokenOpt = new Option4("--approval-token <token>", "Resume a pending approval with the given token (for async approval flows).");
3136
+ if (!verbose) {
3137
+ inputOpt.hideHelp();
3138
+ yesOpt.hideHelp();
3139
+ largeInputOpt.hideHelp();
3140
+ formatOpt.hideHelp();
3141
+ fieldsOpt.hideHelp();
3142
+ dryRunOpt.hideHelp();
3143
+ traceOpt.hideHelp();
3144
+ streamOpt.hideHelp();
3145
+ strategyOpt.hideHelp();
3146
+ approvalTimeoutOpt.hideHelp();
3147
+ approvalTokenOpt.hideHelp();
3148
+ }
3149
+ cmd.addOption(inputOpt);
3150
+ cmd.addOption(yesOpt);
3151
+ cmd.addOption(largeInputOpt);
3152
+ cmd.addOption(formatOpt);
3153
+ cmd.addOption(fieldsOpt);
3154
+ cmd.addOption(sandboxOpt);
3155
+ cmd.addOption(dryRunOpt);
3156
+ cmd.addOption(traceOpt);
3157
+ cmd.addOption(streamOpt);
3158
+ cmd.addOption(strategyOpt);
3159
+ cmd.addOption(approvalTimeoutOpt);
3160
+ cmd.addOption(approvalTokenOpt);
3161
+ const footerParts = [];
3162
+ if (!verbose) {
3163
+ footerParts.push("Use --verbose to show all options (including built-in apcore options).");
3164
+ }
3165
+ if (docsUrl) {
3166
+ footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
3167
+ }
3168
+ if (footerParts.length > 0) {
3169
+ cmd.addHelpText("after", "\n" + footerParts.join("\n") + "\n");
3170
+ }
3171
+ const reservedNames = /* @__PURE__ */ new Set([
3172
+ "input",
3173
+ "yes",
3174
+ "largeInput",
3175
+ "format",
3176
+ "fields",
3177
+ "sandbox",
3178
+ "verbose",
3179
+ "dryRun",
3180
+ "trace",
3181
+ "stream",
3182
+ "strategy",
3183
+ "approvalTimeout",
3184
+ "approvalToken"
3185
+ ]);
3186
+ for (const opt of schemaOptions) {
3187
+ if (reservedNames.has(opt.name)) {
2422
3188
  process.stderr.write(
2423
- `Error: Module '${moduleId}' not found.
3189
+ `Error: Module '${moduleId}' schema property '${opt.name}' conflicts with a reserved CLI option name. Rename the property.
2424
3190
  `
2425
3191
  );
2426
- process.exit(EXIT_CODES.MODULE_NOT_FOUND);
3192
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3193
+ }
3194
+ }
3195
+ for (const opt of schemaOptions) {
3196
+ if (opt.parseArg) {
3197
+ cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
3198
+ } else {
3199
+ cmd.option(opt.flags, opt.description, opt.defaultValue);
3200
+ }
3201
+ }
3202
+ cmd.action(async (options) => {
3203
+ const stdinFlag = options.input;
3204
+ const autoApprove = options.yes;
3205
+ const largeInput = options.largeInput;
3206
+ const outputFormat = options.format;
3207
+ const outputFields = options.fields;
3208
+ const sandboxEnabled = options.sandbox;
3209
+ const dryRun = options.dryRun;
3210
+ const traceFlag = options.trace;
3211
+ const streamFlag = options.stream;
3212
+ const strategyName = options.strategy;
3213
+ const approvalTimeout = options.approvalTimeout ?? 60;
3214
+ const approvalToken = options.approvalToken;
3215
+ const schemaKwargs = {};
3216
+ const builtinKeys = /* @__PURE__ */ new Set([
3217
+ "input",
3218
+ "yes",
3219
+ "largeInput",
3220
+ "format",
3221
+ "fields",
3222
+ "sandbox",
3223
+ "verbose",
3224
+ "dryRun",
3225
+ "trace",
3226
+ "stream",
3227
+ "strategy",
3228
+ "approvalTimeout",
3229
+ "approvalToken"
3230
+ ]);
3231
+ for (const [k, v] of Object.entries(options)) {
3232
+ if (!builtinKeys.has(k)) {
3233
+ schemaKwargs[k] = v;
3234
+ }
3235
+ }
3236
+ let merged = {};
3237
+ try {
3238
+ merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
3239
+ const reconverted = reconvertEnumValues(merged, schemaOptions);
3240
+ merged = reconverted;
3241
+ if (dryRun) {
3242
+ if (!executor.validate) {
3243
+ process.stderr.write("Error: Executor does not support validate.\n");
3244
+ process.exit(1);
3245
+ }
3246
+ const preflight = await executor.validate(moduleId, merged);
3247
+ formatPreflightResult(preflight, outputFormat);
3248
+ if (traceFlag) {
3249
+ const pureSteps = /* @__PURE__ */ new Set([
3250
+ "context_creation",
3251
+ "call_chain_guard",
3252
+ "module_lookup",
3253
+ "acl_check",
3254
+ "input_validation"
3255
+ ]);
3256
+ const allSteps = [
3257
+ "context_creation",
3258
+ "call_chain_guard",
3259
+ "module_lookup",
3260
+ "acl_check",
3261
+ "approval_gate",
3262
+ "middleware_before",
3263
+ "input_validation",
3264
+ "execute",
3265
+ "output_validation",
3266
+ "middleware_after",
3267
+ "return_result"
3268
+ ];
3269
+ process.stderr.write("\nPipeline preview (dry-run):\n");
3270
+ for (const s of allSteps) {
3271
+ if (pureSteps.has(s)) {
3272
+ process.stderr.write(` \u2713 ${s.padEnd(24)} (pure \u2014 would execute)
3273
+ `);
3274
+ } else {
3275
+ process.stderr.write(` \u25CB ${s.padEnd(24)} (impure \u2014 skipped in dry-run)
3276
+ `);
3277
+ }
3278
+ }
3279
+ }
3280
+ process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
3281
+ }
3282
+ if (approvalToken) {
3283
+ merged._approval_token = approvalToken;
3284
+ }
3285
+ await checkApproval(moduleDef, autoApprove, approvalTimeout);
3286
+ const startTime = performance.now();
3287
+ if (streamFlag) {
3288
+ if (resolveFormat(outputFormat) === "table") {
3289
+ process.stderr.write("Warning: Streaming mode always outputs JSONL; --format table is ignored.\n");
3290
+ }
3291
+ const annotations = moduleDef.annotations;
3292
+ const isStreaming = annotations?.streaming === true;
3293
+ if (!isStreaming) {
3294
+ process.stderr.write(
3295
+ `Warning: Module '${moduleId}' does not declare streaming support. Falling back to standard execution.
3296
+ `
3297
+ );
3298
+ }
3299
+ if (isStreaming && executor.stream) {
3300
+ let chunks = 0;
3301
+ for await (const chunk of executor.stream(moduleId, merged)) {
3302
+ chunks++;
3303
+ process.stdout.write(JSON.stringify(chunk) + "\n");
3304
+ if (process.stderr.isTTY) {
3305
+ process.stderr.write(`\rStreaming ${moduleId}... (${chunks} chunks)`);
3306
+ }
3307
+ }
3308
+ if (process.stderr.isTTY) {
3309
+ process.stderr.write("\n");
3310
+ }
3311
+ const durationMs2 = Math.round(performance.now() - startTime);
3312
+ const { getAuditLogger: getAuditLogger3 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
3313
+ const auditLogger2 = getAuditLogger3();
3314
+ if (auditLogger2) {
3315
+ auditLogger2.logExecution(moduleId, merged, "success", 0, durationMs2);
3316
+ }
3317
+ return;
3318
+ }
3319
+ }
3320
+ if (traceFlag && executor.callWithTrace) {
3321
+ const [result2, trace] = await executor.callWithTrace(
3322
+ moduleId,
3323
+ merged,
3324
+ strategyName ? { strategy: strategyName } : void 0
3325
+ );
3326
+ const durationMs2 = Math.round(performance.now() - startTime);
3327
+ const { getAuditLogger: getAuditLogger3 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
3328
+ const auditLogger2 = getAuditLogger3();
3329
+ if (auditLogger2) {
3330
+ auditLogger2.logExecution(moduleId, merged, "success", 0, durationMs2);
3331
+ }
3332
+ const resolved = resolveFormat(outputFormat);
3333
+ if (resolved === "json" || !process.stdout.isTTY) {
3334
+ const traceData = {
3335
+ strategy: trace.strategy_name,
3336
+ total_duration_ms: trace.total_duration_ms,
3337
+ success: trace.success,
3338
+ steps: trace.steps.map((s) => ({
3339
+ name: s.name,
3340
+ duration_ms: s.duration_ms,
3341
+ skipped: s.skipped,
3342
+ ...s.skipped ? { skip_reason: s.skip_reason } : {}
3343
+ }))
3344
+ };
3345
+ let output;
3346
+ if (typeof result2 === "object" && result2 !== null && !Array.isArray(result2)) {
3347
+ output = { ...result2, _trace: traceData };
3348
+ } else {
3349
+ output = { result: result2, _trace: traceData };
3350
+ }
3351
+ process.stdout.write(JSON.stringify(output, null, 2) + "\n");
3352
+ } else {
3353
+ formatExecResult(result2, outputFormat, outputFields);
3354
+ const stepCount = trace.steps.length;
3355
+ process.stderr.write(
3356
+ `
3357
+ Pipeline Trace (strategy: ${trace.strategy_name}, ${stepCount} steps, ${trace.total_duration_ms.toFixed(1)}ms)
3358
+ `
3359
+ );
3360
+ for (const s of trace.steps) {
3361
+ if (s.skipped) {
3362
+ const reason = s.skip_reason ?? "n/a";
3363
+ process.stderr.write(` \u25CB ${s.name.padEnd(24)} ${"\u2014".padStart(8)} skipped (${reason})
3364
+ `);
3365
+ } else {
3366
+ process.stderr.write(` \u2713 ${s.name.padEnd(24)} ${(s.duration_ms.toFixed(1) + "ms").padStart(8)}
3367
+ `);
3368
+ }
3369
+ }
3370
+ }
3371
+ return;
3372
+ }
3373
+ let result;
3374
+ if (strategyName && executor.callWithTrace) {
3375
+ const [res] = await executor.callWithTrace(
3376
+ moduleId,
3377
+ merged,
3378
+ { strategy: strategyName }
3379
+ );
3380
+ result = res;
3381
+ if (strategyName !== "standard" && process.stderr.isTTY) {
3382
+ process.stderr.write(`Warning: Using '${strategyName}' strategy.
3383
+ `);
3384
+ }
3385
+ } else {
3386
+ const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
3387
+ const sandbox = new Sandbox2(sandboxEnabled);
3388
+ result = await sandbox.execute(moduleId, merged, executor);
3389
+ }
3390
+ const durationMs = Math.round(performance.now() - startTime);
3391
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
3392
+ const auditLogger = getAuditLogger2();
3393
+ if (auditLogger) {
3394
+ auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
3395
+ }
3396
+ formatExecResult(result, outputFormat, outputFields);
3397
+ } catch (err) {
3398
+ const errRecord = err;
3399
+ const errorCode = typeof errRecord?.code === "string" ? errRecord.code : void 0;
3400
+ const exitCode = errorCode && errorCode in ERROR_CODE_MAP ? ERROR_CODE_MAP[errorCode] : exitCodeForError(err);
3401
+ try {
3402
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
3403
+ const auditLogger = getAuditLogger2();
3404
+ if (auditLogger) {
3405
+ auditLogger.logExecution(moduleId, merged, "error", exitCode, 0);
3406
+ }
3407
+ } catch {
3408
+ }
3409
+ if (outputFormat === "json" || !process.stderr.isTTY) {
3410
+ emitErrorJson(err, exitCode);
3411
+ } else {
3412
+ emitErrorTty(err, exitCode);
3413
+ }
3414
+ process.exit(exitCode);
2427
3415
  }
2428
- const fmt = resolveFormat(opts.format);
2429
- formatModuleDetail(moduleDef, fmt);
2430
3416
  });
2431
- cli.addCommand(describeCmd);
3417
+ return cmd;
3418
+ }
3419
+ function validateModuleId(moduleId) {
3420
+ if (moduleId.length > 128) {
3421
+ process.stderr.write(
3422
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
3423
+ `
3424
+ );
3425
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3426
+ }
3427
+ if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
3428
+ process.stderr.write(
3429
+ `Error: Invalid module ID format: '${moduleId}'.
3430
+ `
3431
+ );
3432
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3433
+ }
3434
+ }
3435
+ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
3436
+ const cliKwargsNonNull = {};
3437
+ for (const [k, v] of Object.entries(cliKwargs)) {
3438
+ if (v !== null && v !== void 0) {
3439
+ cliKwargsNonNull[k] = v;
3440
+ }
3441
+ }
3442
+ if (!stdinFlag) {
3443
+ return cliKwargsNonNull;
3444
+ }
3445
+ if (stdinFlag === "-") {
3446
+ const raw = await readStdin();
3447
+ const rawSize = Buffer.byteLength(raw, "utf-8");
3448
+ if (rawSize > 10485760 && !largeInput) {
3449
+ process.stderr.write(
3450
+ "Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
3451
+ );
3452
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3453
+ }
3454
+ if (!raw) {
3455
+ return cliKwargsNonNull;
3456
+ }
3457
+ let stdinData;
3458
+ try {
3459
+ stdinData = JSON.parse(raw);
3460
+ } catch {
3461
+ process.stderr.write(
3462
+ "Error: STDIN does not contain valid JSON.\n"
3463
+ );
3464
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3465
+ }
3466
+ if (typeof stdinData !== "object" || stdinData === null || Array.isArray(stdinData)) {
3467
+ process.stderr.write(
3468
+ `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? "array" : typeof stdinData}.
3469
+ `
3470
+ );
3471
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3472
+ }
3473
+ return { ...stdinData, ...cliKwargsNonNull };
3474
+ }
3475
+ return cliKwargsNonNull;
3476
+ }
3477
+ function readStdin() {
3478
+ return new Promise((resolve3, reject) => {
3479
+ const chunks = [];
3480
+ const onData = (chunk) => chunks.push(chunk);
3481
+ const onEnd = () => {
3482
+ cleanup();
3483
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
3484
+ };
3485
+ const onError = (err) => {
3486
+ cleanup();
3487
+ reject(err);
3488
+ };
3489
+ const cleanup = () => {
3490
+ process.stdin.removeListener("data", onData);
3491
+ process.stdin.removeListener("end", onEnd);
3492
+ process.stdin.removeListener("error", onError);
3493
+ };
3494
+ process.stdin.on("data", onData);
3495
+ process.stdin.on("end", onEnd);
3496
+ process.stdin.on("error", onError);
3497
+ process.stdin.resume();
3498
+ });
3499
+ }
3500
+ function reconvertEnumValues(kwargs, options) {
3501
+ const result = { ...kwargs };
3502
+ for (const opt of options) {
3503
+ if (!opt.enumOriginalTypes) continue;
3504
+ const paramName = opt.name;
3505
+ if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
3506
+ continue;
3507
+ }
3508
+ const strVal = String(result[paramName]);
3509
+ const origType = opt.enumOriginalTypes[strVal];
3510
+ if (origType === "int") {
3511
+ result[paramName] = parseInt(strVal, 10);
3512
+ } else if (origType === "float") {
3513
+ result[paramName] = parseFloat(strVal);
3514
+ } else if (origType === "bool") {
3515
+ result[paramName] = strVal.toLowerCase() === "true";
3516
+ }
3517
+ }
3518
+ return result;
2432
3519
  }
2433
3520
 
2434
3521
  // src/index.ts
@@ -2441,6 +3528,7 @@ export {
2441
3528
  AuthProvider,
2442
3529
  AuthenticationError,
2443
3530
  BUILTIN_COMMANDS,
3531
+ CliApprovalHandler,
2444
3532
  ConfigDecryptionError,
2445
3533
  ConfigEncryptor,
2446
3534
  ConfigResolver,
@@ -2462,12 +3550,16 @@ export {
2462
3550
  createCli,
2463
3551
  debug,
2464
3552
  docsUrl,
3553
+ emitErrorJson,
3554
+ emitErrorTty,
2465
3555
  error,
2466
3556
  exitCodeForError,
2467
3557
  extractHelp,
3558
+ firstFailedExitCode,
2468
3559
  formatExecResult,
2469
3560
  formatModuleDetail,
2470
3561
  formatModuleList,
3562
+ formatPreflightResult,
2471
3563
  getAuditLogger,
2472
3564
  getCliDisplayFields,
2473
3565
  getDisplay,
@@ -2479,7 +3571,10 @@ export {
2479
3571
  registerConfigNamespace,
2480
3572
  registerDiscoveryCommands,
2481
3573
  registerInitCommand,
3574
+ registerPipelineCommand,
2482
3575
  registerShellCommands,
3576
+ registerSystemCommands,
3577
+ registerValidateCommand,
2483
3578
  resolveFormat,
2484
3579
  resolveRefs,
2485
3580
  schemaToCliOptions,