apcore-cli 0.4.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.
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -46,11 +52,19 @@ function exitCodeForError(error) {
46
52
  SCHEMA_CIRCULAR_REF: EXIT_CODES.SCHEMA_CIRCULAR_REF,
47
53
  APPROVAL_DENIED: EXIT_CODES.APPROVAL_DENIED,
48
54
  APPROVAL_TIMEOUT: EXIT_CODES.APPROVAL_TIMEOUT,
55
+ APPROVAL_PENDING: EXIT_CODES.APPROVAL_DENIED,
49
56
  CONFIG_NOT_FOUND: EXIT_CODES.CONFIG_NOT_FOUND,
50
57
  CONFIG_INVALID: EXIT_CODES.CONFIG_INVALID,
51
58
  MODULE_EXECUTE_ERROR: EXIT_CODES.MODULE_EXECUTE_ERROR,
52
59
  MODULE_TIMEOUT: EXIT_CODES.MODULE_TIMEOUT,
53
- ACL_DENIED: EXIT_CODES.ACL_DENIED
60
+ ACL_DENIED: EXIT_CODES.ACL_DENIED,
61
+ // Config Bus errors (apcore >= 0.15.0)
62
+ CONFIG_NAMESPACE_RESERVED: EXIT_CODES.CONFIG_NAMESPACE_RESERVED,
63
+ CONFIG_NAMESPACE_DUPLICATE: EXIT_CODES.CONFIG_NAMESPACE_DUPLICATE,
64
+ CONFIG_ENV_PREFIX_CONFLICT: EXIT_CODES.CONFIG_ENV_PREFIX_CONFLICT,
65
+ CONFIG_MOUNT_ERROR: EXIT_CODES.CONFIG_MOUNT_ERROR,
66
+ CONFIG_BIND_ERROR: EXIT_CODES.CONFIG_BIND_ERROR,
67
+ ERROR_FORMATTER_DUPLICATE: EXIT_CODES.ERROR_FORMATTER_DUPLICATE
54
68
  };
55
69
  if (code && code in codeMap) {
56
70
  return codeMap[code];
@@ -120,6 +134,14 @@ var init_errors = __esm({
120
134
  CONFIG_INVALID: 47,
121
135
  SCHEMA_CIRCULAR_REF: 48,
122
136
  ACL_DENIED: 77,
137
+ // Config Bus errors (apcore >= 0.15.0)
138
+ CONFIG_NAMESPACE_RESERVED: 78,
139
+ CONFIG_NAMESPACE_DUPLICATE: 78,
140
+ CONFIG_ENV_PREFIX_CONFLICT: 78,
141
+ CONFIG_ENV_MAP_CONFLICT: 78,
142
+ CONFIG_MOUNT_ERROR: 66,
143
+ CONFIG_BIND_ERROR: 65,
144
+ ERROR_FORMATTER_DUPLICATE: 70,
123
145
  KEYBOARD_INTERRUPT: 130
124
146
  };
125
147
  }
@@ -131,10 +153,10 @@ init_esm_shims();
131
153
  // src/main.ts
132
154
  init_esm_shims();
133
155
  init_errors();
134
- import { readFileSync } from "fs";
135
- import { fileURLToPath as fileURLToPath2 } from "url";
136
- import * as path3 from "path";
137
- import { Command, CommanderError, Option } from "commander";
156
+ import { readFileSync as readFileSync3 } from "fs";
157
+ import { fileURLToPath as fileURLToPath3 } from "url";
158
+ import * as path4 from "path";
159
+ import { Command as Command6, CommanderError, Option as Option4 } from "commander";
138
160
 
139
161
  // src/ref-resolver.ts
140
162
  init_esm_shims();
@@ -151,6 +173,188 @@ import * as readline from "readline";
151
173
 
152
174
  // src/output.ts
153
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
+ }
154
358
 
155
359
  // src/logger.ts
156
360
  init_esm_shims();
@@ -162,6 +366,13 @@ function setLogLevel(level) {
162
366
  currentLevel = upper;
163
367
  }
164
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
+ }
165
376
 
166
377
  // src/init-cmd.ts
167
378
  init_esm_shims();
@@ -309,27 +520,766 @@ function createBindingModule(moduleId, prefix, funcName, description, outputDir)
309
520
  // src/display-helpers.ts
310
521
  init_esm_shims();
311
522
 
312
- // src/main.ts
523
+ // src/config.ts
524
+ init_esm_shims();
525
+ import * as fs2 from "fs";
526
+ import yaml from "js-yaml";
527
+ var NAMESPACE_TO_LEGACY = {
528
+ "apcore-cli.stdin_buffer_limit": "cli.stdin_buffer_limit",
529
+ "apcore-cli.auto_approve": "cli.auto_approve",
530
+ "apcore-cli.help_text_max_length": "cli.help_text_max_length",
531
+ "apcore-cli.logging_level": "logging.level"
532
+ };
533
+ var LEGACY_TO_NAMESPACE = Object.fromEntries(
534
+ Object.entries(NAMESPACE_TO_LEGACY).map(([k, v]) => [v, k])
535
+ );
536
+ function registerConfigNamespace() {
537
+ try {
538
+ const { Config } = __require("apcore-js");
539
+ if (typeof Config?.registerNamespace === "function") {
540
+ Config.registerNamespace({
541
+ name: "apcore-cli",
542
+ envPrefix: "APCORE_CLI",
543
+ defaults: {
544
+ stdin_buffer_limit: 10485760,
545
+ auto_approve: false,
546
+ help_text_max_length: 1e3,
547
+ logging_level: "WARNING",
548
+ approval_timeout: 60,
549
+ strategy: "standard",
550
+ group_depth: 1
551
+ }
552
+ });
553
+ }
554
+ } catch {
555
+ }
556
+ }
557
+
558
+ // src/shell.ts
559
+ init_esm_shims();
560
+ init_errors();
561
+ import { readFileSync as readFileSync2 } from "fs";
562
+ import { fileURLToPath as fileURLToPath2 } from "url";
563
+ import * as path3 from "path";
564
+ import { spawnSync } from "child_process";
565
+ import { Command, Help, Option } from "commander";
313
566
  var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
567
+ var SHELL_VERSION = "0.0.0";
568
+ try {
569
+ const pkg = JSON.parse(readFileSync2(path3.resolve(__dirname2, "../package.json"), "utf-8"));
570
+ SHELL_VERSION = pkg.version;
571
+ } catch {
572
+ }
573
+ function roffEscape(s) {
574
+ return s.replace(/\\/g, "\\\\").replace(/-/g, "\\-").replace(/'/g, "\\(aq");
575
+ }
576
+ function buildProgramManPage(program, progName, version, description, docsUrl) {
577
+ const help = new Help();
578
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
579
+ const s = [];
580
+ const resolvedDesc = description ?? program.description() ?? `${progName} CLI`;
581
+ s.push(`.TH "${progName.toUpperCase()}" "1" "${today}" "${progName} ${version}" "${progName} Manual"`);
582
+ s.push(".SH NAME");
583
+ s.push(`${progName} \\- ${roffEscape(resolvedDesc)}`);
584
+ s.push(".SH SYNOPSIS");
585
+ s.push(`\\fB${progName}\\fR [\\fIglobal\\-options\\fR] \\fIcommand\\fR [\\fIcommand\\-options\\fR]`);
586
+ if (resolvedDesc) {
587
+ s.push(".SH DESCRIPTION");
588
+ s.push(roffEscape(resolvedDesc));
589
+ }
590
+ const globalOpts = help.visibleOptions(program).filter((o) => !["help", "version", "all", "man"].includes(o.long?.replace("--", "") ?? ""));
591
+ if (globalOpts.length > 0) {
592
+ s.push(".SH GLOBAL OPTIONS");
593
+ for (const opt of globalOpts) {
594
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
595
+ s.push(".TP");
596
+ s.push(`\\fB${roffEscape(flag)}\\fR`);
597
+ if (opt.description) s.push(roffEscape(opt.description));
598
+ }
599
+ }
600
+ const allCommands = help.visibleCommands(program);
601
+ if (allCommands.length > 0) {
602
+ s.push(".SH COMMANDS");
603
+ for (const cmd of allCommands) {
604
+ if (cmd.name() === "help") continue;
605
+ const desc = help.subcommandDescription(cmd);
606
+ s.push(".TP");
607
+ s.push(`\\fB${progName} ${roffEscape(cmd.name())}\\fR`);
608
+ if (desc) s.push(roffEscape(desc));
609
+ const cmdHelp = new Help();
610
+ const opts = cmdHelp.visibleOptions(cmd).filter((o) => !["help", "version"].includes(o.long?.replace("--", "") ?? ""));
611
+ for (const opt of opts) {
612
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
613
+ s.push(".RS");
614
+ s.push(".TP");
615
+ s.push(`\\fB${roffEscape(flag)}\\fR`);
616
+ if (opt.description) s.push(roffEscape(opt.description));
617
+ s.push(".RE");
618
+ }
619
+ const subCmds = cmdHelp.visibleCommands(cmd).filter((c) => c.name() !== "help");
620
+ for (const sub of subCmds) {
621
+ const subDesc = help.subcommandDescription(sub);
622
+ s.push(".TP");
623
+ s.push(`\\fB${progName} ${roffEscape(cmd.name())} ${roffEscape(sub.name())}\\fR`);
624
+ if (subDesc) s.push(roffEscape(subDesc));
625
+ const subOpts = cmdHelp.visibleOptions(sub).filter((o) => !["help", "version"].includes(o.long?.replace("--", "") ?? ""));
626
+ for (const opt of subOpts) {
627
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
628
+ s.push(".RS");
629
+ s.push(".TP");
630
+ s.push(`\\fB${roffEscape(flag)}\\fR`);
631
+ if (opt.description) s.push(roffEscape(opt.description));
632
+ s.push(".RE");
633
+ }
634
+ }
635
+ }
636
+ }
637
+ s.push(".SH ENVIRONMENT");
638
+ s.push(".TP");
639
+ s.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
640
+ s.push("Path to the apcore extensions directory.");
641
+ s.push(".TP");
642
+ s.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
643
+ s.push("Set to \\fB1\\fR to bypass approval prompts.");
644
+ s.push(".TP");
645
+ s.push("\\fBAPCORE_CLI_LOGGING_LEVEL\\fR");
646
+ s.push("CLI\\-specific logging verbosity (DEBUG|INFO|WARNING|ERROR).");
647
+ s.push(".SH EXIT CODES");
648
+ const exitCodes = [
649
+ ["0", "Success."],
650
+ ["1", "Module execution error."],
651
+ ["2", "Invalid CLI input or missing argument."],
652
+ ["44", "Module not found, disabled, or failed to load."],
653
+ ["45", "Input failed JSON Schema validation."],
654
+ ["46", "Approval denied or timed out."],
655
+ ["47", "Configuration error."],
656
+ ["77", "ACL denied."],
657
+ ["130", "Cancelled by user (SIGINT)."]
658
+ ];
659
+ for (const [code, meaning] of exitCodes) {
660
+ s.push(`.TP
661
+ \\fB${code}\\fR
662
+ ${meaning}`);
663
+ }
664
+ s.push(".SH SEE ALSO");
665
+ s.push(`\\fB${progName} \\-\\-help \\-\\-verbose\\fR for full option list.`);
666
+ if (docsUrl) {
667
+ s.push(`.PP
668
+ Full documentation at \\fI${roffEscape(docsUrl)}\\fR`);
669
+ }
670
+ return s.join("\n");
671
+ }
672
+ function configureManHelp(program, progName, version, description, docsUrl) {
673
+ const manOpt = new Option("--man", "Output man page in roff format (use with --help)").hideHelp();
674
+ program.addOption(manOpt);
675
+ program.addHelpText("beforeAll", () => {
676
+ if (program.opts().man) {
677
+ const roff = buildProgramManPage(program, progName, version, description, docsUrl) + "\n";
678
+ if (process.stdout.isTTY) {
679
+ const pagers = [
680
+ { cmd: "mandoc", args: ["-a"] },
681
+ { cmd: "groff", args: ["-man", "-Tutf8"] }
682
+ ];
683
+ let rendered = false;
684
+ for (const { cmd, args } of pagers) {
685
+ const result = spawnSync(cmd, args, {
686
+ input: roff,
687
+ stdio: ["pipe", "pipe", "pipe"],
688
+ encoding: "utf-8"
689
+ });
690
+ if (result.status === 0 && result.stdout) {
691
+ const pager = process.env.PAGER || "less";
692
+ const pagerResult = spawnSync(pager, ["-R"], {
693
+ input: result.stdout,
694
+ stdio: ["pipe", "inherit", "inherit"]
695
+ });
696
+ if (pagerResult.status !== null) {
697
+ rendered = true;
698
+ break;
699
+ }
700
+ }
701
+ }
702
+ if (!rendered) {
703
+ process.stdout.write(roff);
704
+ }
705
+ } else {
706
+ process.stdout.write(roff);
707
+ }
708
+ process.exit(0);
709
+ }
710
+ return "";
711
+ });
712
+ }
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
+
1210
+ // src/main.ts
1211
+ var __dirname3 = path4.dirname(fileURLToPath3(import.meta.url));
314
1212
  var verboseHelp = false;
315
1213
  function hasVerboseFlag() {
316
1214
  return process.argv.includes("--verbose");
317
1215
  }
318
1216
  var VERSION = "0.0.0";
319
1217
  try {
320
- const pkg = JSON.parse(readFileSync(path3.resolve(__dirname2, "../package.json"), "utf-8"));
1218
+ const pkg = JSON.parse(readFileSync3(path4.resolve(__dirname3, "../package.json"), "utf-8"));
321
1219
  VERSION = pkg.version;
322
1220
  } catch {
323
1221
  }
324
- 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
+ }
325
1237
  verboseHelp = verbose;
326
- const resolvedProgName = progName ?? path3.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1238
+ registerConfigNamespace();
1239
+ const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
327
1240
  const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
328
1241
  setLogLevel(cliLogLevel);
329
- const program = new Command(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)");
330
- const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
331
- 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
+ }
1258
+ program.addHelpText("after", [
1259
+ "",
1260
+ "Use --help --verbose to show all options (including built-in apcore options).",
1261
+ "Use --help --man to display a formatted man page."
1262
+ ].join("\n"));
332
1263
  registerInitCommand(program);
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
+ }
333
1283
  program.hook("preAction", async (thisCommand) => {
334
1284
  const opts = thisCommand.opts();
335
1285
  const commandsDir = opts.commandsDir;
@@ -376,6 +1326,87 @@ function main(progName) {
376
1326
  process.exit(code);
377
1327
  }
378
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
+ }
379
1410
 
380
1411
  // bin/apcore-cli.ts
381
1412
  main("apcore-cli");