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.
@@ -138,6 +138,7 @@ var init_errors = __esm({
138
138
  CONFIG_NAMESPACE_RESERVED: 78,
139
139
  CONFIG_NAMESPACE_DUPLICATE: 78,
140
140
  CONFIG_ENV_PREFIX_CONFLICT: 78,
141
+ CONFIG_ENV_MAP_CONFLICT: 78,
141
142
  CONFIG_MOUNT_ERROR: 66,
142
143
  CONFIG_BIND_ERROR: 65,
143
144
  ERROR_FORMATTER_DUPLICATE: 70,
@@ -155,7 +156,7 @@ init_errors();
155
156
  import { readFileSync as readFileSync3 } from "fs";
156
157
  import { fileURLToPath as fileURLToPath3 } from "url";
157
158
  import * as path4 from "path";
158
- import { Command as Command2, CommanderError, Option as Option2 } from "commander";
159
+ import { Command as Command6, CommanderError, Option as Option4 } from "commander";
159
160
 
160
161
  // src/ref-resolver.ts
161
162
  init_esm_shims();
@@ -172,6 +173,188 @@ import * as readline from "readline";
172
173
 
173
174
  // src/output.ts
174
175
  init_esm_shims();
176
+ function resolveFormat(explicitFormat) {
177
+ if (explicitFormat !== void 0) {
178
+ return explicitFormat;
179
+ }
180
+ return process.stdout.isTTY ? "table" : "json";
181
+ }
182
+ function formatTable(headers, rows) {
183
+ const colWidths = headers.map(
184
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
185
+ );
186
+ const sep2 = colWidths.map((w) => "-".repeat(w)).join(" ");
187
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(" ");
188
+ const dataLines = rows.map(
189
+ (row) => row.map((cell, i) => (cell ?? "").padEnd(colWidths[i])).join(" ")
190
+ );
191
+ return [headerLine, sep2, ...dataLines].join("\n") + "\n";
192
+ }
193
+ function selectFields(result, fields) {
194
+ const selected = {};
195
+ for (const f of fields.split(",")) {
196
+ const key = f.trim();
197
+ let val = result;
198
+ for (const part of key.split(".")) {
199
+ if (val && typeof val === "object" && !Array.isArray(val)) {
200
+ val = val[part];
201
+ } else {
202
+ val = void 0;
203
+ break;
204
+ }
205
+ }
206
+ selected[key] = val;
207
+ }
208
+ return selected;
209
+ }
210
+ function formatExecResult(result, format, fields) {
211
+ if (result === null || result === void 0) {
212
+ return;
213
+ }
214
+ let effective_result = result;
215
+ if (fields && typeof result === "object" && !Array.isArray(result) && result !== null) {
216
+ effective_result = selectFields(result, fields);
217
+ }
218
+ const effective = resolveFormat(format);
219
+ if (effective === "csv") {
220
+ if (typeof effective_result === "object" && !Array.isArray(effective_result) && effective_result !== null) {
221
+ const obj = effective_result;
222
+ const keys = Object.keys(obj);
223
+ const header = keys.map(escapeCsvField).join(",");
224
+ const row = keys.map((k) => escapeCsvField(String(obj[k]))).join(",");
225
+ process.stdout.write(header + "\n" + row + "\n");
226
+ } else if (Array.isArray(effective_result) && effective_result.length > 0 && typeof effective_result[0] === "object") {
227
+ const keys = Object.keys(effective_result[0]);
228
+ const header = keys.map(escapeCsvField).join(",");
229
+ const rows = effective_result.map((item) => {
230
+ const obj = item;
231
+ return keys.map((k) => escapeCsvField(String(obj[k]))).join(",");
232
+ });
233
+ process.stdout.write(header + "\n" + rows.join("\n") + "\n");
234
+ } else {
235
+ process.stdout.write(JSON.stringify(effective_result) + "\n");
236
+ }
237
+ } else if (effective === "yaml") {
238
+ if (typeof effective_result === "object" && !Array.isArray(effective_result) && effective_result !== null) {
239
+ const obj = effective_result;
240
+ const lines = Object.entries(obj).map(([k, v]) => {
241
+ if (v === null || v === void 0) return `${k}: null`;
242
+ if (typeof v === "object") return `${k}: ${JSON.stringify(v)}`;
243
+ return `${k}: ${v}`;
244
+ });
245
+ process.stdout.write(lines.join("\n") + "\n");
246
+ } else if (Array.isArray(effective_result)) {
247
+ for (const item of effective_result) {
248
+ if (typeof item === "object" && item !== null) {
249
+ const obj = item;
250
+ const lines = Object.entries(obj).map(([k, v]) => ` ${k}: ${v}`);
251
+ process.stdout.write("- " + lines.join("\n ") + "\n");
252
+ } else {
253
+ process.stdout.write(`- ${item}
254
+ `);
255
+ }
256
+ }
257
+ } else {
258
+ process.stdout.write(String(effective_result) + "\n");
259
+ }
260
+ } else if (effective === "jsonl") {
261
+ if (Array.isArray(effective_result)) {
262
+ for (const item of effective_result) {
263
+ process.stdout.write(JSON.stringify(item) + "\n");
264
+ }
265
+ } else {
266
+ process.stdout.write(JSON.stringify(effective_result) + "\n");
267
+ }
268
+ } else if (effective === "table" && typeof effective_result === "object" && !Array.isArray(effective_result)) {
269
+ const entries = Object.entries(effective_result);
270
+ const headers = ["Key", "Value"];
271
+ const rows = entries.map(([k, v]) => [String(k), String(v)]);
272
+ process.stdout.write(formatTable(headers, rows));
273
+ } else if (typeof effective_result === "object") {
274
+ process.stdout.write(JSON.stringify(effective_result, null, 2) + "\n");
275
+ } else if (typeof effective_result === "string") {
276
+ process.stdout.write(effective_result + "\n");
277
+ } else {
278
+ process.stdout.write(String(effective_result) + "\n");
279
+ }
280
+ }
281
+ function escapeCsvField(value) {
282
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
283
+ return '"' + value.replace(/"/g, '""') + '"';
284
+ }
285
+ return value;
286
+ }
287
+ function formatPreflightResult(result, format) {
288
+ const resolved = resolveFormat(format);
289
+ if (resolved === "json" || !process.stdout.isTTY) {
290
+ const payload = {
291
+ valid: result.valid,
292
+ requires_approval: result.requires_approval,
293
+ checks: result.checks.map((c) => {
294
+ const entry = { check: c.check, passed: c.passed };
295
+ if (c.error !== void 0 && c.error !== null) {
296
+ entry.error = c.error;
297
+ }
298
+ if (c.warnings && c.warnings.length > 0) {
299
+ entry.warnings = c.warnings;
300
+ }
301
+ return entry;
302
+ })
303
+ };
304
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
305
+ } else {
306
+ for (const c of result.checks) {
307
+ const hasWarnings = (c.warnings?.length ?? 0) > 0;
308
+ let sym;
309
+ if (c.passed && hasWarnings) {
310
+ sym = "\u26A0";
311
+ } else if (c.passed) {
312
+ sym = "\u2713";
313
+ } else if (c.passed === false) {
314
+ sym = "\u2717";
315
+ } else {
316
+ sym = "\u25CB";
317
+ }
318
+ let status = ` ${sym} ${c.check.padEnd(20)}`;
319
+ if (c.error) {
320
+ const detail = typeof c.error === "object" ? JSON.stringify(c.error) : String(c.error);
321
+ status += ` ${detail}`;
322
+ } else if (c.passed && !hasWarnings) {
323
+ status += " OK";
324
+ } else if (!c.passed) {
325
+ status += " Skipped";
326
+ }
327
+ process.stdout.write(status + "\n");
328
+ for (const w of c.warnings ?? []) {
329
+ process.stdout.write(` Warning: ${w}
330
+ `);
331
+ }
332
+ }
333
+ const errors = result.checks.filter((c) => !c.passed).length;
334
+ const warnings = result.checks.reduce((sum, c) => sum + (c.warnings?.length ?? 0), 0);
335
+ const tag = result.valid ? "PASS" : "FAIL";
336
+ process.stdout.write(`
337
+ Result: ${tag} (${errors} error(s), ${warnings} warning(s))
338
+ `);
339
+ }
340
+ }
341
+ function firstFailedExitCode(result) {
342
+ const checkToExit = {
343
+ module_id: 2,
344
+ module_lookup: 44,
345
+ call_chain: 1,
346
+ acl: 77,
347
+ schema: 45,
348
+ approval: 46,
349
+ module_preflight: 1
350
+ };
351
+ for (const check of result.checks) {
352
+ if (!check.passed) {
353
+ return checkToExit[check.check] ?? 1;
354
+ }
355
+ }
356
+ return 1;
357
+ }
175
358
 
176
359
  // src/logger.ts
177
360
  init_esm_shims();
@@ -183,6 +366,13 @@ function setLogLevel(level) {
183
366
  currentLevel = upper;
184
367
  }
185
368
  }
369
+ function shouldLog(level) {
370
+ return LEVELS[level] >= LEVELS[currentLevel];
371
+ }
372
+ function debug(message) {
373
+ if (shouldLog("DEBUG")) process.stderr.write(`DEBUG: ${message}
374
+ `);
375
+ }
186
376
 
187
377
  // src/init-cmd.ts
188
378
  init_esm_shims();
@@ -354,7 +544,10 @@ function registerConfigNamespace() {
354
544
  stdin_buffer_limit: 10485760,
355
545
  auto_approve: false,
356
546
  help_text_max_length: 1e3,
357
- logging_level: "WARNING"
547
+ logging_level: "WARNING",
548
+ approval_timeout: 60,
549
+ strategy: "standard",
550
+ group_depth: 1
358
551
  }
359
552
  });
360
553
  }
@@ -518,6 +711,502 @@ function configureManHelp(program, progName, version, description, docsUrl) {
518
711
  });
519
712
  }
520
713
 
714
+ // src/discovery.ts
715
+ init_esm_shims();
716
+ init_errors();
717
+ import { Command as Command2, Option as Option2 } from "commander";
718
+ function registerValidateCommand(cli, registry, executor) {
719
+ 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) => {
720
+ validateModuleId(moduleId);
721
+ const moduleDef = registry.getModule(moduleId);
722
+ if (!moduleDef) {
723
+ process.stderr.write(`Error: Module '${moduleId}' not found.
724
+ `);
725
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
726
+ }
727
+ const merged = opts.input ? await collectInput(opts.input, {}, false) : {};
728
+ if (!executor.validate) {
729
+ process.stderr.write("Error: Executor does not support validate.\n");
730
+ process.exit(1);
731
+ }
732
+ const preflight = await executor.validate(moduleId, merged);
733
+ formatPreflightResult(preflight, opts.format);
734
+ process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
735
+ });
736
+ cli.addCommand(validateCmd);
737
+ }
738
+
739
+ // src/system-cmd.ts
740
+ init_esm_shims();
741
+ import { Command as Command3 } from "commander";
742
+ async function callSystemModule(executor, moduleId, inputs) {
743
+ if (executor.call) {
744
+ return executor.call(moduleId, inputs);
745
+ }
746
+ return executor.execute(moduleId, inputs);
747
+ }
748
+ function formatHealthSummaryTty(result) {
749
+ const summary = result.summary ?? {};
750
+ const modules = result.modules ?? [];
751
+ if (modules.length === 0) {
752
+ process.stdout.write("No modules found.\n");
753
+ return;
754
+ }
755
+ const total = summary.total_modules ?? modules.length;
756
+ process.stdout.write(`Health Overview (${total} modules)
757
+
758
+ `);
759
+ process.stdout.write(` ${"Module".padEnd(28)} ${"Status".padEnd(12)} ${"Error Rate".padEnd(12)} Top Error
760
+ `);
761
+ process.stdout.write(` ${"-".repeat(28)} ${"-".repeat(12)} ${"-".repeat(12)} ${"-".repeat(20)}
762
+ `);
763
+ for (const m of modules) {
764
+ const top = m.top_error;
765
+ const topStr = top ? `${top.code} (${top.count ?? "?"})` : "\u2014";
766
+ const rate = `${((m.error_rate ?? 0) * 100).toFixed(1)}%`;
767
+ process.stdout.write(
768
+ ` ${String(m.module_id).padEnd(28)} ${String(m.status).padEnd(12)} ${rate.padEnd(12)} ${topStr}
769
+ `
770
+ );
771
+ }
772
+ const parts = [];
773
+ for (const key of ["healthy", "degraded", "error"]) {
774
+ const count = summary[key];
775
+ if (count) parts.push(`${count} ${key}`);
776
+ }
777
+ process.stdout.write(`
778
+ Summary: ${parts.join(", ") || "no data"}
779
+ `);
780
+ }
781
+ function formatHealthModuleTty(result) {
782
+ process.stdout.write(`Module: ${result.module_id ?? "?"}
783
+ `);
784
+ process.stdout.write(`Status: ${result.status ?? "unknown"}
785
+ `);
786
+ const total = result.total_calls ?? 0;
787
+ const errors = result.error_count ?? 0;
788
+ const rate = result.error_rate ?? 0;
789
+ const avg = result.avg_latency_ms ?? 0;
790
+ const p99 = result.p99_latency_ms ?? 0;
791
+ process.stdout.write(`Calls: ${total.toLocaleString()} total | ${errors.toLocaleString()} errors | ${(rate * 100).toFixed(1)}% error rate
792
+ `);
793
+ process.stdout.write(`Latency: ${avg.toFixed(0)}ms avg | ${p99.toFixed(0)}ms p99
794
+ `);
795
+ const recent = result.recent_errors ?? [];
796
+ if (recent.length > 0) {
797
+ process.stdout.write(`
798
+ Recent Errors (top ${recent.length}):
799
+ `);
800
+ for (const e of recent) {
801
+ const count = e.count ?? "?";
802
+ const last = e.last_occurred ?? "?";
803
+ process.stdout.write(` ${String(e.code ?? "?").padEnd(24)} x${count} (last: ${last})
804
+ `);
805
+ }
806
+ }
807
+ }
808
+ function formatUsageSummaryTty(result) {
809
+ const modules = result.modules ?? [];
810
+ const period = result.period ?? "?";
811
+ if (modules.length === 0) {
812
+ process.stdout.write(`No usage data for period ${period}.
813
+ `);
814
+ return;
815
+ }
816
+ process.stdout.write(`Usage Summary (last ${period})
817
+
818
+ `);
819
+ process.stdout.write(` ${"Module".padEnd(24)} ${"Calls".padStart(8)} ${"Errors".padStart(8)} ${"Avg Latency".padStart(12)} ${"Trend".padStart(10)}
820
+ `);
821
+ process.stdout.write(` ${"-".repeat(24)} ${"-".repeat(8)} ${"-".repeat(8)} ${"-".repeat(12)} ${"-".repeat(10)}
822
+ `);
823
+ for (const m of modules) {
824
+ const avg = `${(m.avg_latency_ms ?? 0).toFixed(0)}ms`;
825
+ process.stdout.write(
826
+ ` ${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)}
827
+ `
828
+ );
829
+ }
830
+ const totalCalls = result.total_calls ?? modules.reduce((s, m) => s + (m.call_count ?? 0), 0);
831
+ const totalErrors = result.total_errors ?? modules.reduce((s, m) => s + (m.error_count ?? 0), 0);
832
+ process.stdout.write(`
833
+ Total: ${totalCalls.toLocaleString()} calls | ${totalErrors.toLocaleString()} errors
834
+ `);
835
+ }
836
+ async function registerSystemCommands(cli, executor) {
837
+ try {
838
+ if (executor.validate) {
839
+ await executor.validate("system.health.summary", {});
840
+ } else {
841
+ await callSystemModule(executor, "system.health.summary", { include_healthy: true });
842
+ }
843
+ } catch {
844
+ debug("System modules not available; skipping system command registration.");
845
+ return;
846
+ }
847
+ 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) => {
848
+ const fmt = resolveFormat(opts.format);
849
+ try {
850
+ if (moduleId) {
851
+ const result = await callSystemModule(executor, "system.health.module", {
852
+ module_id: moduleId,
853
+ error_limit: opts.errors
854
+ });
855
+ if (fmt === "json" || !process.stdout.isTTY) {
856
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
857
+ } else {
858
+ formatHealthModuleTty(result);
859
+ }
860
+ } else {
861
+ const result = await callSystemModule(executor, "system.health.summary", {
862
+ error_rate_threshold: opts.threshold,
863
+ include_healthy: opts.all
864
+ });
865
+ if (fmt === "json" || !process.stdout.isTTY) {
866
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
867
+ } else {
868
+ formatHealthSummaryTty(result);
869
+ }
870
+ }
871
+ } catch (e) {
872
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
873
+ `);
874
+ process.exit(1);
875
+ }
876
+ });
877
+ cli.addCommand(healthCmd);
878
+ 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) => {
879
+ const fmt = resolveFormat(opts.format);
880
+ try {
881
+ let result;
882
+ if (moduleId) {
883
+ result = await callSystemModule(executor, "system.usage.module", {
884
+ module_id: moduleId,
885
+ period: opts.period
886
+ });
887
+ } else {
888
+ result = await callSystemModule(executor, "system.usage.summary", {
889
+ period: opts.period
890
+ });
891
+ }
892
+ if (fmt === "json" || !process.stdout.isTTY) {
893
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
894
+ } else if (moduleId) {
895
+ formatExecResult(result, fmt);
896
+ } else {
897
+ formatUsageSummaryTty(result);
898
+ }
899
+ } catch (e) {
900
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
901
+ `);
902
+ process.exit(1);
903
+ }
904
+ });
905
+ cli.addCommand(usageCmd);
906
+ 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) => {
907
+ if (!opts.yes) {
908
+ process.stderr.write("Note: This command requires approval. Use --yes to bypass.\n");
909
+ }
910
+ const fmt = resolveFormat(opts.format);
911
+ try {
912
+ const result = await callSystemModule(executor, "system.control.toggle_feature", {
913
+ module_id: moduleId,
914
+ enabled: true,
915
+ reason: opts.reason
916
+ });
917
+ if (fmt === "json" || !process.stdout.isTTY) {
918
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
919
+ } else {
920
+ process.stdout.write(`Module '${moduleId}' enabled.
921
+ Reason: ${opts.reason}
922
+ `);
923
+ }
924
+ } catch (e) {
925
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
926
+ `);
927
+ process.exit(1);
928
+ }
929
+ });
930
+ cli.addCommand(enableCmd);
931
+ 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) => {
932
+ if (!opts.yes) {
933
+ process.stderr.write("Note: This command requires approval. Use --yes to bypass.\n");
934
+ }
935
+ const fmt = resolveFormat(opts.format);
936
+ try {
937
+ const result = await callSystemModule(executor, "system.control.toggle_feature", {
938
+ module_id: moduleId,
939
+ enabled: false,
940
+ reason: opts.reason
941
+ });
942
+ if (fmt === "json" || !process.stdout.isTTY) {
943
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
944
+ } else {
945
+ process.stdout.write(`Module '${moduleId}' disabled.
946
+ Reason: ${opts.reason}
947
+ `);
948
+ }
949
+ } catch (e) {
950
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
951
+ `);
952
+ process.exit(1);
953
+ }
954
+ });
955
+ cli.addCommand(disableCmd);
956
+ 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) => {
957
+ if (!opts.yes) {
958
+ process.stderr.write("Note: This command requires approval. Use --yes to bypass.\n");
959
+ }
960
+ const fmt = resolveFormat(opts.format);
961
+ try {
962
+ const result = await callSystemModule(executor, "system.control.reload_module", {
963
+ module_id: moduleId,
964
+ reason: opts.reason
965
+ });
966
+ if (fmt === "json" || !process.stdout.isTTY) {
967
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
968
+ } else {
969
+ const prev = result.previous_version ?? "?";
970
+ const newVer = result.new_version ?? "?";
971
+ const dur = result.reload_duration_ms ?? "?";
972
+ process.stdout.write(`Module '${moduleId}' reloaded.
973
+ `);
974
+ process.stdout.write(` Version: ${prev} -> ${newVer}
975
+ `);
976
+ process.stdout.write(` Duration: ${dur}ms
977
+ `);
978
+ }
979
+ } catch (e) {
980
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
981
+ `);
982
+ process.exit(1);
983
+ }
984
+ });
985
+ cli.addCommand(reloadCmd);
986
+ const configGroup = new Command3("config").description("Read or update runtime configuration.");
987
+ 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) => {
988
+ const fmt = resolveFormat(opts.format);
989
+ try {
990
+ const result = await callSystemModule(executor, "system.config.get", { key });
991
+ const value = result?.value ?? result;
992
+ if (fmt === "json" || !process.stdout.isTTY) {
993
+ process.stdout.write(JSON.stringify({ key, value }, null, 2) + "\n");
994
+ } else {
995
+ process.stdout.write(`${key} = ${JSON.stringify(value)}
996
+ `);
997
+ }
998
+ } catch (e) {
999
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
1000
+ `);
1001
+ process.exit(1);
1002
+ }
1003
+ });
1004
+ configGroup.addCommand(configGetCmd);
1005
+ 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) => {
1006
+ const fmt = resolveFormat(opts.format);
1007
+ let parsedValue;
1008
+ try {
1009
+ parsedValue = JSON.parse(value);
1010
+ } catch {
1011
+ parsedValue = value;
1012
+ }
1013
+ try {
1014
+ const result = await callSystemModule(executor, "system.control.update_config", {
1015
+ key,
1016
+ value: parsedValue,
1017
+ reason: opts.reason
1018
+ });
1019
+ if (fmt === "json" || !process.stdout.isTTY) {
1020
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1021
+ } else {
1022
+ const old = result.old_value ?? "?";
1023
+ const newVal = result.new_value ?? "?";
1024
+ process.stdout.write(`Config updated: ${key}
1025
+ `);
1026
+ process.stdout.write(` ${JSON.stringify(old)} -> ${JSON.stringify(newVal)}
1027
+ `);
1028
+ process.stdout.write(` Reason: ${opts.reason}
1029
+ `);
1030
+ }
1031
+ } catch (e) {
1032
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
1033
+ `);
1034
+ process.exit(1);
1035
+ }
1036
+ });
1037
+ configGroup.addCommand(configSetCmd);
1038
+ cli.addCommand(configGroup);
1039
+ }
1040
+
1041
+ // src/strategy.ts
1042
+ init_esm_shims();
1043
+ import { Command as Command4, Option as Option3 } from "commander";
1044
+ var PRESET_STEPS = {
1045
+ standard: [
1046
+ "context_creation",
1047
+ "call_chain_guard",
1048
+ "module_lookup",
1049
+ "acl_check",
1050
+ "approval_gate",
1051
+ "middleware_before",
1052
+ "input_validation",
1053
+ "execute",
1054
+ "output_validation",
1055
+ "middleware_after",
1056
+ "return_result"
1057
+ ],
1058
+ internal: [
1059
+ "context_creation",
1060
+ "call_chain_guard",
1061
+ "module_lookup",
1062
+ "middleware_before",
1063
+ "input_validation",
1064
+ "execute",
1065
+ "output_validation",
1066
+ "middleware_after",
1067
+ "return_result"
1068
+ ],
1069
+ testing: [
1070
+ "context_creation",
1071
+ "module_lookup",
1072
+ "middleware_before",
1073
+ "input_validation",
1074
+ "execute",
1075
+ "output_validation",
1076
+ "middleware_after",
1077
+ "return_result"
1078
+ ],
1079
+ performance: [
1080
+ "context_creation",
1081
+ "call_chain_guard",
1082
+ "module_lookup",
1083
+ "acl_check",
1084
+ "approval_gate",
1085
+ "input_validation",
1086
+ "execute",
1087
+ "output_validation",
1088
+ "return_result"
1089
+ ],
1090
+ minimal: [
1091
+ "context_creation",
1092
+ "module_lookup",
1093
+ "execute",
1094
+ "return_result"
1095
+ ]
1096
+ };
1097
+ function registerPipelineCommand(cli, executor) {
1098
+ const pipelineCmd = new Command4("describe-pipeline").description("Show the execution pipeline steps for a strategy.").addOption(
1099
+ new Option3("--strategy <name>", "Strategy to describe (default: standard).").choices(["standard", "internal", "testing", "performance", "minimal"]).default("standard")
1100
+ ).option("--format <format>", "Output format.").action((opts) => {
1101
+ const fmt = resolveFormat(opts.format);
1102
+ let strategyObj = null;
1103
+ const ex = executor;
1104
+ if (typeof ex._resolve_strategy_name === "function" || typeof ex._resolveStrategyName === "function") {
1105
+ try {
1106
+ const fn = ex._resolve_strategy_name ?? ex._resolveStrategyName;
1107
+ strategyObj = fn(opts.strategy);
1108
+ } catch {
1109
+ strategyObj = null;
1110
+ }
1111
+ }
1112
+ if (!strategyObj) {
1113
+ const steps = PRESET_STEPS[opts.strategy] ?? [];
1114
+ const pureSteps = /* @__PURE__ */ new Set([
1115
+ "context_creation",
1116
+ "call_chain_guard",
1117
+ "module_lookup",
1118
+ "acl_check",
1119
+ "input_validation"
1120
+ ]);
1121
+ const nonRemovable = /* @__PURE__ */ new Set([
1122
+ "context_creation",
1123
+ "module_lookup",
1124
+ "execute",
1125
+ "return_result"
1126
+ ]);
1127
+ if (fmt === "json" || !process.stdout.isTTY) {
1128
+ const payload = {
1129
+ strategy: opts.strategy,
1130
+ step_count: steps.length,
1131
+ steps: steps.map((s, i) => ({
1132
+ index: i + 1,
1133
+ name: s,
1134
+ pure: pureSteps.has(s),
1135
+ removable: !nonRemovable.has(s)
1136
+ }))
1137
+ };
1138
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
1139
+ } else {
1140
+ process.stdout.write(`Pipeline: ${opts.strategy} (${steps.length} steps)
1141
+
1142
+ `);
1143
+ process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
1144
+ `);
1145
+ process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
1146
+ `);
1147
+ for (let i = 0; i < steps.length; i++) {
1148
+ const pure = pureSteps.has(steps[i]) ? "yes" : "no";
1149
+ const removable = nonRemovable.has(steps[i]) ? "no" : "yes";
1150
+ process.stdout.write(` ${String(i + 1).padEnd(4)} ${steps[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} \u2014
1151
+ `);
1152
+ }
1153
+ }
1154
+ return;
1155
+ }
1156
+ const stepsInfo = strategyObj.steps.map((step) => ({
1157
+ name: step.name,
1158
+ pure: step.pure ?? false,
1159
+ removable: step.removable ?? true,
1160
+ timeout_ms: step.timeout_ms ?? null
1161
+ }));
1162
+ if (fmt === "json" || !process.stdout.isTTY) {
1163
+ const payload = {
1164
+ strategy: opts.strategy,
1165
+ step_count: stepsInfo.length,
1166
+ steps: stepsInfo.map((s, i) => ({ index: i + 1, ...s }))
1167
+ };
1168
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
1169
+ } else {
1170
+ process.stdout.write(`Pipeline: ${opts.strategy} (${stepsInfo.length} steps)
1171
+
1172
+ `);
1173
+ process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
1174
+ `);
1175
+ process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
1176
+ `);
1177
+ for (let i = 0; i < stepsInfo.length; i++) {
1178
+ const s = stepsInfo[i];
1179
+ const pure = s.pure ? "yes" : "no";
1180
+ const removable = s.removable ? "yes" : "no";
1181
+ const timeout = s.timeout_ms !== null ? `${s.timeout_ms}ms` : "\u2014";
1182
+ process.stdout.write(` ${String(i + 1).padEnd(4)} ${s.name.padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} ${timeout}
1183
+ `);
1184
+ }
1185
+ }
1186
+ });
1187
+ cli.addCommand(pipelineCmd);
1188
+ }
1189
+
1190
+ // src/cli.ts
1191
+ init_esm_shims();
1192
+ import { Command as Command5 } from "commander";
1193
+ var BUILTIN_COMMANDS = [
1194
+ "completion",
1195
+ "config",
1196
+ "describe",
1197
+ "describe-pipeline",
1198
+ "disable",
1199
+ "enable",
1200
+ "exec",
1201
+ "health",
1202
+ "init",
1203
+ "list",
1204
+ "man",
1205
+ "reload",
1206
+ "usage",
1207
+ "validate"
1208
+ ];
1209
+
521
1210
  // src/main.ts
522
1211
  var __dirname3 = path4.dirname(fileURLToPath3(import.meta.url));
523
1212
  var verboseHelp = false;
@@ -530,15 +1219,42 @@ try {
530
1219
  VERSION = pkg.version;
531
1220
  } catch {
532
1221
  }
533
- function createCli(extensionsDir, progName, verbose = false) {
1222
+ function createCli(extensionsDirOrOpts, progName, verbose = false) {
1223
+ let extensionsDir;
1224
+ let registry;
1225
+ let executor;
1226
+ let extraCommands;
1227
+ if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
1228
+ extensionsDir = extensionsDirOrOpts.extensionsDir;
1229
+ progName = extensionsDirOrOpts.progName ?? progName;
1230
+ verbose = extensionsDirOrOpts.verbose ?? verbose;
1231
+ registry = extensionsDirOrOpts.registry;
1232
+ executor = extensionsDirOrOpts.executor;
1233
+ extraCommands = extensionsDirOrOpts.extraCommands;
1234
+ } else {
1235
+ extensionsDir = extensionsDirOrOpts;
1236
+ }
534
1237
  verboseHelp = verbose;
535
1238
  registerConfigNamespace();
536
1239
  const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
537
1240
  const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
538
1241
  setLogLevel(cliLogLevel);
539
- 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)");
540
- const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
541
- void resolvedExtDir;
1242
+ 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)");
1243
+ if (executor && !registry) {
1244
+ throw new Error("executor requires registry \u2014 pass both or neither");
1245
+ }
1246
+ if (registry) {
1247
+ program._registry = registry;
1248
+ if (executor) {
1249
+ program._executor = executor;
1250
+ registerValidateCommand(program, registry, executor);
1251
+ void registerSystemCommands(program, executor);
1252
+ registerPipelineCommand(program, executor);
1253
+ }
1254
+ } else {
1255
+ const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
1256
+ void resolvedExtDir;
1257
+ }
542
1258
  program.addHelpText("after", [
543
1259
  "",
544
1260
  "Use --help --verbose to show all options (including built-in apcore options).",
@@ -546,6 +1262,24 @@ function createCli(extensionsDir, progName, verbose = false) {
546
1262
  ].join("\n"));
547
1263
  registerInitCommand(program);
548
1264
  configureManHelp(program, resolvedProgName, VERSION);
1265
+ if (extraCommands && extraCommands.length > 0) {
1266
+ const existingNames = /* @__PURE__ */ new Set([
1267
+ ...BUILTIN_COMMANDS,
1268
+ ...program.commands.map((c) => c.name())
1269
+ ]);
1270
+ for (const cmd of extraCommands) {
1271
+ const cmdName = cmd.name();
1272
+ if (existingNames.has(cmdName)) {
1273
+ process.stderr.write(
1274
+ `Warning: Extra command '${cmdName}' collides with a built-in command and will be skipped.
1275
+ `
1276
+ );
1277
+ continue;
1278
+ }
1279
+ program.addCommand(cmd);
1280
+ existingNames.add(cmdName);
1281
+ }
1282
+ }
549
1283
  program.hook("preAction", async (thisCommand) => {
550
1284
  const opts = thisCommand.opts();
551
1285
  const commandsDir = opts.commandsDir;
@@ -592,6 +1326,87 @@ function main(progName) {
592
1326
  process.exit(code);
593
1327
  }
594
1328
  }
1329
+ function validateModuleId(moduleId) {
1330
+ if (moduleId.length > 128) {
1331
+ process.stderr.write(
1332
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
1333
+ `
1334
+ );
1335
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1336
+ }
1337
+ if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
1338
+ process.stderr.write(
1339
+ `Error: Invalid module ID format: '${moduleId}'.
1340
+ `
1341
+ );
1342
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1343
+ }
1344
+ }
1345
+ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
1346
+ const cliKwargsNonNull = {};
1347
+ for (const [k, v] of Object.entries(cliKwargs)) {
1348
+ if (v !== null && v !== void 0) {
1349
+ cliKwargsNonNull[k] = v;
1350
+ }
1351
+ }
1352
+ if (!stdinFlag) {
1353
+ return cliKwargsNonNull;
1354
+ }
1355
+ if (stdinFlag === "-") {
1356
+ const raw = await readStdin();
1357
+ const rawSize = Buffer.byteLength(raw, "utf-8");
1358
+ if (rawSize > 10485760 && !largeInput) {
1359
+ process.stderr.write(
1360
+ "Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
1361
+ );
1362
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1363
+ }
1364
+ if (!raw) {
1365
+ return cliKwargsNonNull;
1366
+ }
1367
+ let stdinData;
1368
+ try {
1369
+ stdinData = JSON.parse(raw);
1370
+ } catch {
1371
+ process.stderr.write(
1372
+ "Error: STDIN does not contain valid JSON.\n"
1373
+ );
1374
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1375
+ }
1376
+ if (typeof stdinData !== "object" || stdinData === null || Array.isArray(stdinData)) {
1377
+ process.stderr.write(
1378
+ `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? "array" : typeof stdinData}.
1379
+ `
1380
+ );
1381
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1382
+ }
1383
+ return { ...stdinData, ...cliKwargsNonNull };
1384
+ }
1385
+ return cliKwargsNonNull;
1386
+ }
1387
+ function readStdin() {
1388
+ return new Promise((resolve3, reject) => {
1389
+ const chunks = [];
1390
+ const onData = (chunk) => chunks.push(chunk);
1391
+ const onEnd = () => {
1392
+ cleanup();
1393
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
1394
+ };
1395
+ const onError = (err) => {
1396
+ cleanup();
1397
+ reject(err);
1398
+ };
1399
+ const cleanup = () => {
1400
+ process.stdin.removeListener("data", onData);
1401
+ process.stdin.removeListener("end", onEnd);
1402
+ process.stdin.removeListener("error", onError);
1403
+ };
1404
+ process.stdin.on("data", onData);
1405
+ process.stdin.on("end", onEnd);
1406
+ process.stdin.on("error", onError);
1407
+ process.stdin.resume();
1408
+ });
1409
+ }
595
1410
 
596
1411
  // bin/apcore-cli.ts
597
1412
  main("apcore-cli");