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.
- package/CHANGELOG.md +39 -0
- package/README.md +20 -1
- package/dist/bin/apcore-cli.js +1043 -12
- package/dist/bin/apcore-cli.js.map +1 -1
- package/dist/index.d.ts +155 -22
- package/dist/index.js +2234 -1041
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/bin/apcore-cli.js
CHANGED
|
@@ -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
|
|
136
|
-
import * as
|
|
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/
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
330
|
-
|
|
331
|
-
|
|
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");
|