apcore-cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
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
  };
@@ -50,11 +56,19 @@ function exitCodeForError(error2) {
50
56
  SCHEMA_CIRCULAR_REF: EXIT_CODES.SCHEMA_CIRCULAR_REF,
51
57
  APPROVAL_DENIED: EXIT_CODES.APPROVAL_DENIED,
52
58
  APPROVAL_TIMEOUT: EXIT_CODES.APPROVAL_TIMEOUT,
59
+ APPROVAL_PENDING: EXIT_CODES.APPROVAL_DENIED,
53
60
  CONFIG_NOT_FOUND: EXIT_CODES.CONFIG_NOT_FOUND,
54
61
  CONFIG_INVALID: EXIT_CODES.CONFIG_INVALID,
55
62
  MODULE_EXECUTE_ERROR: EXIT_CODES.MODULE_EXECUTE_ERROR,
56
63
  MODULE_TIMEOUT: EXIT_CODES.MODULE_TIMEOUT,
57
- ACL_DENIED: EXIT_CODES.ACL_DENIED
64
+ ACL_DENIED: EXIT_CODES.ACL_DENIED,
65
+ // Config Bus errors (apcore >= 0.15.0)
66
+ CONFIG_NAMESPACE_RESERVED: EXIT_CODES.CONFIG_NAMESPACE_RESERVED,
67
+ CONFIG_NAMESPACE_DUPLICATE: EXIT_CODES.CONFIG_NAMESPACE_DUPLICATE,
68
+ CONFIG_ENV_PREFIX_CONFLICT: EXIT_CODES.CONFIG_ENV_PREFIX_CONFLICT,
69
+ CONFIG_MOUNT_ERROR: EXIT_CODES.CONFIG_MOUNT_ERROR,
70
+ CONFIG_BIND_ERROR: EXIT_CODES.CONFIG_BIND_ERROR,
71
+ ERROR_FORMATTER_DUPLICATE: EXIT_CODES.ERROR_FORMATTER_DUPLICATE
58
72
  };
59
73
  if (code && code in codeMap) {
60
74
  return codeMap[code];
@@ -124,6 +138,13 @@ var init_errors = __esm({
124
138
  CONFIG_INVALID: 47,
125
139
  SCHEMA_CIRCULAR_REF: 48,
126
140
  ACL_DENIED: 77,
141
+ // Config Bus errors (apcore >= 0.15.0)
142
+ CONFIG_NAMESPACE_RESERVED: 78,
143
+ CONFIG_NAMESPACE_DUPLICATE: 78,
144
+ CONFIG_ENV_PREFIX_CONFLICT: 78,
145
+ CONFIG_MOUNT_ERROR: 66,
146
+ CONFIG_BIND_ERROR: 65,
147
+ ERROR_FORMATTER_DUPLICATE: 70,
127
148
  KEYBOARD_INTERRUPT: 130
128
149
  };
129
150
  }
@@ -137,9 +158,9 @@ __export(audit_exports, {
137
158
  setAuditLogger: () => setAuditLogger
138
159
  });
139
160
  import * as crypto from "crypto";
140
- import * as fs2 from "fs";
161
+ import * as fs3 from "fs";
141
162
  import * as os from "os";
142
- import * as path3 from "path";
163
+ import * as path4 from "path";
143
164
  function setAuditLogger(auditLogger) {
144
165
  _auditLogger = auditLogger;
145
166
  }
@@ -153,7 +174,7 @@ var init_audit = __esm({
153
174
  init_esm_shims();
154
175
  _auditLogger = null;
155
176
  AuditLogger = class _AuditLogger {
156
- static DEFAULT_PATH = path3.join(
177
+ static DEFAULT_PATH = path4.join(
157
178
  os.homedir(),
158
179
  ".apcore-cli",
159
180
  "audit.jsonl"
@@ -165,7 +186,7 @@ var init_audit = __esm({
165
186
  }
166
187
  ensureDirectory() {
167
188
  try {
168
- fs2.mkdirSync(path3.dirname(this.logPath), { recursive: true });
189
+ fs3.mkdirSync(path4.dirname(this.logPath), { recursive: true });
169
190
  } catch {
170
191
  }
171
192
  }
@@ -180,7 +201,7 @@ var init_audit = __esm({
180
201
  duration_ms: durationMs
181
202
  };
182
203
  try {
183
- fs2.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
204
+ fs3.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
184
205
  } catch (err) {
185
206
  console.warn(`Could not write audit log: ${err}`);
186
207
  }
@@ -381,9 +402,9 @@ var init_auth = __esm({
381
402
 
382
403
  // src/security/sandbox.ts
383
404
  import * as child_process from "child_process";
384
- import * as fs3 from "fs";
405
+ import * as fs4 from "fs";
385
406
  import * as os3 from "os";
386
- import * as path4 from "path";
407
+ import * as path5 from "path";
387
408
  var Sandbox;
388
409
  var init_sandbox = __esm({
389
410
  "src/security/sandbox.ts"() {
@@ -416,8 +437,8 @@ var init_sandbox = __esm({
416
437
  env[key] = value;
417
438
  }
418
439
  }
419
- const tmpDir = fs3.mkdtempSync(
420
- path4.join(os3.tmpdir(), "apcore_sandbox_")
440
+ const tmpDir = fs4.mkdtempSync(
441
+ path5.join(os3.tmpdir(), "apcore_sandbox_")
421
442
  );
422
443
  try {
423
444
  env.HOME = tmpDir;
@@ -455,7 +476,7 @@ var init_sandbox = __esm({
455
476
  );
456
477
  } finally {
457
478
  try {
458
- fs3.rmSync(tmpDir, { recursive: true, force: true });
479
+ fs4.rmSync(tmpDir, { recursive: true, force: true });
459
480
  } catch {
460
481
  }
461
482
  }
@@ -491,10 +512,10 @@ init_esm_shims();
491
512
  // src/main.ts
492
513
  init_esm_shims();
493
514
  init_errors();
494
- import { readFileSync } from "fs";
495
- import { fileURLToPath as fileURLToPath2 } from "url";
496
- import * as path5 from "path";
497
- import { Command, CommanderError, Option } from "commander";
515
+ import { readFileSync as readFileSync3 } from "fs";
516
+ import { fileURLToPath as fileURLToPath3 } from "url";
517
+ import * as path6 from "path";
518
+ import { Command as Command2, CommanderError, Option as Option2 } from "commander";
498
519
 
499
520
  // src/ref-resolver.ts
500
521
  init_esm_shims();
@@ -1218,1120 +1239,1196 @@ function getCliDisplayFields(descriptor) {
1218
1239
  return [name, desc, tags];
1219
1240
  }
1220
1241
 
1221
- // src/main.ts
1222
- var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
1223
- var verboseHelp = false;
1224
- function setVerboseHelp(verbose) {
1225
- verboseHelp = verbose;
1226
- }
1227
- var docsUrl = null;
1228
- function setDocsUrl(url) {
1229
- docsUrl = url;
1230
- }
1231
- function hasVerboseFlag() {
1232
- return process.argv.includes("--verbose");
1233
- }
1234
- var VERSION = "0.0.0";
1235
- try {
1236
- const pkg = JSON.parse(readFileSync(path5.resolve(__dirname2, "../package.json"), "utf-8"));
1237
- VERSION = pkg.version;
1238
- } catch {
1239
- }
1240
- function createCli(extensionsDir, progName, verbose = false) {
1241
- verboseHelp = verbose;
1242
- const resolvedProgName = progName ?? path5.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1243
- const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
1244
- setLogLevel(cliLogLevel);
1245
- 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)");
1246
- const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
1247
- void resolvedExtDir;
1248
- registerInitCommand(program);
1249
- program.hook("preAction", async (thisCommand) => {
1250
- const opts = thisCommand.opts();
1251
- const commandsDir = opts.commandsDir;
1252
- const bindingPath = opts.binding;
1253
- await applyToolkitIntegration(commandsDir, bindingPath);
1254
- });
1255
- return program;
1256
- }
1257
- async function applyToolkitIntegration(commandsDir, bindingPath) {
1258
- if (!commandsDir && !bindingPath) {
1259
- return;
1260
- }
1242
+ // src/config.ts
1243
+ init_esm_shims();
1244
+ import * as fs2 from "fs";
1245
+ import yaml from "js-yaml";
1246
+ var DEFAULTS = {
1247
+ "extensions.root": "./extensions",
1248
+ "logging.level": "WARNING",
1249
+ "sandbox.enabled": false,
1250
+ "cli.stdin_buffer_limit": 10485760,
1251
+ "cli.auto_approve": false,
1252
+ "cli.help_text_max_length": 1e3,
1253
+ // Namespace-mode aliases (apcore >= 0.15.0 Config Bus)
1254
+ "apcore-cli.stdin_buffer_limit": 10485760,
1255
+ "apcore-cli.auto_approve": false,
1256
+ "apcore-cli.help_text_max_length": 1e3,
1257
+ "apcore-cli.logging_level": "WARNING"
1258
+ };
1259
+ var NAMESPACE_TO_LEGACY = {
1260
+ "apcore-cli.stdin_buffer_limit": "cli.stdin_buffer_limit",
1261
+ "apcore-cli.auto_approve": "cli.auto_approve",
1262
+ "apcore-cli.help_text_max_length": "cli.help_text_max_length",
1263
+ "apcore-cli.logging_level": "logging.level"
1264
+ };
1265
+ var LEGACY_TO_NAMESPACE = Object.fromEntries(
1266
+ Object.entries(NAMESPACE_TO_LEGACY).map(([k, v]) => [v, k])
1267
+ );
1268
+ function registerConfigNamespace() {
1261
1269
  try {
1262
- const toolkitModule = "apcore-toolkit";
1263
- const toolkit = await import(
1264
- /* @vite-ignore */
1265
- toolkitModule
1266
- );
1267
- if (commandsDir) {
1268
- console.warn("Convention scanning not yet available in TypeScript toolkit");
1269
- }
1270
- if (bindingPath) {
1271
- const resolver = new toolkit.DisplayResolver();
1272
- void resolver;
1270
+ const { Config } = __require("apcore-js");
1271
+ if (typeof Config?.registerNamespace === "function") {
1272
+ Config.registerNamespace({
1273
+ name: "apcore-cli",
1274
+ envPrefix: "APCORE_CLI",
1275
+ defaults: {
1276
+ stdin_buffer_limit: 10485760,
1277
+ auto_approve: false,
1278
+ help_text_max_length: 1e3,
1279
+ logging_level: "WARNING"
1280
+ }
1281
+ });
1273
1282
  }
1274
1283
  } catch {
1275
- console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
1276
1284
  }
1277
1285
  }
1278
- function main(progName) {
1279
- verboseHelp = hasVerboseFlag();
1280
- const program = createCli(void 0, progName, verboseHelp);
1281
- try {
1282
- program.parse(process.argv);
1283
- } catch (error2) {
1284
- if (error2 instanceof CommanderError) {
1285
- process.exit(error2.exitCode);
1286
+ var ConfigResolver = class {
1287
+ cliFlags;
1288
+ configPath;
1289
+ fileCache = null;
1290
+ fileCacheLoaded = false;
1291
+ constructor(cliFlags, configPath) {
1292
+ this.cliFlags = cliFlags ?? {};
1293
+ this.configPath = configPath ?? "apcore.yaml";
1294
+ }
1295
+ /**
1296
+ * Resolve a single configuration key across all four tiers.
1297
+ */
1298
+ resolve(key, cliFlag, envVar) {
1299
+ const flagKey = cliFlag ?? key;
1300
+ if (flagKey in this.cliFlags) {
1301
+ const value = this.cliFlags[flagKey];
1302
+ if (value !== null && value !== void 0) {
1303
+ return value;
1304
+ }
1286
1305
  }
1287
- const code = exitCodeForError(error2);
1288
- if (error2 instanceof Error) {
1289
- process.stderr.write(`Error: ${error2.message}
1290
- `);
1306
+ if (envVar) {
1307
+ const envValue = process.env[envVar];
1308
+ if (envValue !== void 0 && envValue !== "") {
1309
+ return envValue;
1310
+ }
1291
1311
  }
1292
- process.exit(code);
1293
- }
1294
- }
1295
- function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName, verbose = verboseHelp) {
1296
- const moduleId = moduleDef.id;
1297
- let resolvedSchema = {};
1298
- let schemaOptions = [];
1299
- const display = getDisplay(moduleDef);
1300
- const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1301
- const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
1302
- const cmdHelp = cliDisplay.description ?? moduleDef.description;
1303
- const inputSchema = moduleDef.inputSchema;
1304
- if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
1305
- try {
1306
- resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
1307
- } catch {
1308
- resolvedSchema = inputSchema;
1312
+ const fileValue = this.resolveFromFile(key);
1313
+ if (fileValue !== void 0) {
1314
+ return fileValue;
1309
1315
  }
1310
- schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
1311
- }
1312
- const cmd = new Command(effectiveCmdName).description(cmdHelp);
1313
- const inputOpt = new Option("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
1314
- const yesOpt = new Option("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
1315
- const largeInputOpt = new Option("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
1316
- const formatOpt = new Option("--format <format>", "Set output format: 'json' for machine-readable, 'table' for human-readable");
1317
- const sandboxOpt = new Option("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
1318
- if (!verbose) {
1319
- inputOpt.hideHelp();
1320
- yesOpt.hideHelp();
1321
- largeInputOpt.hideHelp();
1322
- formatOpt.hideHelp();
1323
- }
1324
- cmd.addOption(inputOpt);
1325
- cmd.addOption(yesOpt);
1326
- cmd.addOption(largeInputOpt);
1327
- cmd.addOption(formatOpt);
1328
- cmd.addOption(sandboxOpt);
1329
- const footerParts = [];
1330
- if (!verbose) {
1331
- footerParts.push("Use --verbose to show all options (including built-in apcore options).");
1332
- }
1333
- if (docsUrl) {
1334
- footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
1335
- }
1336
- if (footerParts.length > 0) {
1337
- cmd.addHelpText("after", "\n" + footerParts.join("\n") + "\n");
1338
- }
1339
- for (const opt of schemaOptions) {
1340
- if (opt.parseArg) {
1341
- cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
1342
- } else {
1343
- cmd.option(opt.flags, opt.description, opt.defaultValue);
1316
+ const altKey = NAMESPACE_TO_LEGACY[key] ?? LEGACY_TO_NAMESPACE[key];
1317
+ if (altKey) {
1318
+ const altFileValue = this.resolveFromFile(altKey);
1319
+ if (altFileValue !== void 0) {
1320
+ return altFileValue;
1321
+ }
1344
1322
  }
1323
+ return DEFAULTS[key];
1345
1324
  }
1346
- cmd.action(async (options) => {
1347
- const stdinFlag = options.input;
1348
- const autoApprove = options.yes;
1349
- const largeInput = options.largeInput;
1350
- const outputFormat = options.format;
1351
- const sandboxEnabled = options.sandbox;
1352
- const schemaKwargs = {};
1353
- const builtinKeys = /* @__PURE__ */ new Set(["input", "yes", "largeInput", "format", "sandbox", "verbose"]);
1354
- for (const [k, v] of Object.entries(options)) {
1355
- if (!builtinKeys.has(k)) {
1356
- schemaKwargs[k] = v;
1357
- }
1325
+ /**
1326
+ * Load a value from the config file using a dot-separated key path.
1327
+ */
1328
+ resolveFromFile(key) {
1329
+ if (!this.fileCacheLoaded) {
1330
+ this.fileCache = this.loadConfigFile();
1331
+ this.fileCacheLoaded = true;
1332
+ }
1333
+ if (this.fileCache === null) {
1334
+ return void 0;
1358
1335
  }
1336
+ return this.fileCache[key];
1337
+ }
1338
+ /**
1339
+ * Load and flatten a YAML config file.
1340
+ */
1341
+ loadConfigFile() {
1342
+ let content;
1359
1343
  try {
1360
- const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
1361
- const reconverted = reconvertEnumValues(merged, schemaOptions);
1362
- await checkApproval(moduleDef, autoApprove);
1363
- const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
1364
- const sandbox = new Sandbox2(sandboxEnabled);
1365
- const startTime = performance.now();
1366
- const result = await sandbox.execute(moduleId, reconverted, executor);
1367
- const durationMs = Math.round(performance.now() - startTime);
1368
- const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
1369
- const auditLogger = getAuditLogger2();
1370
- if (auditLogger) {
1371
- auditLogger.logExecution(moduleId, reconverted, "success", 0, durationMs);
1372
- }
1373
- formatExecResult(result, outputFormat);
1344
+ content = fs2.readFileSync(this.configPath, "utf-8");
1374
1345
  } catch (err) {
1375
- const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
1376
- const auditLogger = getAuditLogger2();
1377
- const code = exitCodeForError(err);
1378
- if (auditLogger) {
1379
- auditLogger.logExecution(moduleId, {}, "error", code, 0);
1380
- }
1381
- if (err instanceof Error) {
1382
- process.stderr.write(`Error: ${err.message}
1383
- `);
1346
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1347
+ return null;
1384
1348
  }
1385
- process.exit(code);
1349
+ console.warn(
1350
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1351
+ );
1352
+ return null;
1386
1353
  }
1387
- });
1388
- return cmd;
1389
- }
1390
- function validateModuleId(moduleId) {
1391
- if (moduleId.length > 128) {
1392
- process.stderr.write(
1393
- `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
1394
- `
1395
- );
1396
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1397
- }
1398
- if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
1399
- process.stderr.write(
1400
- `Error: Invalid module ID format: '${moduleId}'.
1401
- `
1402
- );
1403
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1404
- }
1405
- }
1406
- async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
1407
- const cliKwargsNonNull = {};
1408
- for (const [k, v] of Object.entries(cliKwargs)) {
1409
- if (v !== null && v !== void 0) {
1410
- cliKwargsNonNull[k] = v;
1354
+ let parsed;
1355
+ try {
1356
+ parsed = yaml.load(content);
1357
+ } catch {
1358
+ console.warn(
1359
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1360
+ );
1361
+ return null;
1411
1362
  }
1412
- }
1413
- if (!stdinFlag) {
1414
- return cliKwargsNonNull;
1415
- }
1416
- if (stdinFlag === "-") {
1417
- const raw = await readStdin();
1418
- const rawSize = Buffer.byteLength(raw, "utf-8");
1419
- if (rawSize > 10485760 && !largeInput) {
1420
- process.stderr.write(
1421
- "Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
1422
- );
1423
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1424
- }
1425
- if (!raw) {
1426
- return cliKwargsNonNull;
1427
- }
1428
- let stdinData;
1429
- try {
1430
- stdinData = JSON.parse(raw);
1431
- } catch {
1432
- process.stderr.write(
1433
- "Error: STDIN does not contain valid JSON.\n"
1363
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1364
+ console.warn(
1365
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1434
1366
  );
1435
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1367
+ return null;
1436
1368
  }
1437
- if (typeof stdinData !== "object" || stdinData === null || Array.isArray(stdinData)) {
1438
- process.stderr.write(
1439
- `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? "array" : typeof stdinData}.
1440
- `
1441
- );
1442
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1369
+ return this.flattenDict(parsed);
1370
+ }
1371
+ /**
1372
+ * Flatten nested dict to dot-notation keys.
1373
+ */
1374
+ flattenDict(d, prefix = "") {
1375
+ const result = {};
1376
+ for (const [key, value] of Object.entries(d)) {
1377
+ const fullKey = prefix ? `${prefix}.${key}` : key;
1378
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1379
+ Object.assign(
1380
+ result,
1381
+ this.flattenDict(value, fullKey)
1382
+ );
1383
+ } else {
1384
+ result[fullKey] = value;
1385
+ }
1443
1386
  }
1444
- return { ...stdinData, ...cliKwargsNonNull };
1387
+ return result;
1445
1388
  }
1446
- return cliKwargsNonNull;
1389
+ };
1390
+
1391
+ // src/shell.ts
1392
+ init_esm_shims();
1393
+ init_errors();
1394
+ import { readFileSync as readFileSync2 } from "fs";
1395
+ import { fileURLToPath as fileURLToPath2 } from "url";
1396
+ import * as path3 from "path";
1397
+ import { spawnSync } from "child_process";
1398
+ import { Command, Help, Option } from "commander";
1399
+ var __dirname2 = path3.dirname(fileURLToPath2(import.meta.url));
1400
+ var SHELL_VERSION = "0.0.0";
1401
+ try {
1402
+ const pkg = JSON.parse(readFileSync2(path3.resolve(__dirname2, "../package.json"), "utf-8"));
1403
+ SHELL_VERSION = pkg.version;
1404
+ } catch {
1447
1405
  }
1448
- function readStdin() {
1449
- return new Promise((resolve3, reject) => {
1450
- const chunks = [];
1451
- const onData = (chunk) => chunks.push(chunk);
1452
- const onEnd = () => {
1453
- cleanup();
1454
- resolve3(Buffer.concat(chunks).toString("utf-8"));
1455
- };
1456
- const onError = (err) => {
1457
- cleanup();
1458
- reject(err);
1459
- };
1460
- const cleanup = () => {
1461
- process.stdin.removeListener("data", onData);
1462
- process.stdin.removeListener("end", onEnd);
1463
- process.stdin.removeListener("error", onError);
1464
- };
1465
- process.stdin.on("data", onData);
1466
- process.stdin.on("end", onEnd);
1467
- process.stdin.on("error", onError);
1468
- process.stdin.resume();
1469
- });
1406
+ function makeFunctionName(progName) {
1407
+ return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
1470
1408
  }
1471
- function reconvertEnumValues(kwargs, options) {
1472
- const result = { ...kwargs };
1473
- for (const opt of options) {
1474
- if (!opt.enumOriginalTypes) continue;
1475
- const paramName = opt.name;
1476
- if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
1477
- continue;
1478
- }
1479
- const strVal = String(result[paramName]);
1480
- const origType = opt.enumOriginalTypes[strVal];
1481
- if (origType === "int") {
1482
- result[paramName] = parseInt(strVal, 10);
1483
- } else if (origType === "float") {
1484
- result[paramName] = parseFloat(strVal);
1485
- } else if (origType === "bool") {
1486
- result[paramName] = strVal.toLowerCase() === "true";
1487
- }
1488
- }
1489
- return result;
1409
+ function shellQuote(s) {
1410
+ return "'" + s.replace(/'/g, "'\\''") + "'";
1411
+ }
1412
+ function generateBashCompletion(progName) {
1413
+ const fn = makeFunctionName(progName);
1414
+ const quoted = shellQuote(progName);
1415
+ const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e "process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})" 2>/dev/null`;
1416
+ const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1417
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1418
+ const ids=JSON.parse(m).map(x=>x.id);
1419
+ const g=new Set(),t=[];
1420
+ ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1421
+ console.log([...g].sort().concat(t.sort()).join(' '))
1422
+ " 2>/dev/null`;
1423
+ const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1424
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1425
+ const g=process.env._APCORE_GRP;
1426
+ JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1427
+ " 2>/dev/null`;
1428
+ return `${fn}() {
1429
+ local cur prev opts
1430
+ COMPREPLY=()
1431
+ cur="\${COMP_WORDS[COMP_CWORD]}"
1432
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
1433
+
1434
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
1435
+ opts="completion describe exec init list man"
1436
+ local groups_and_top=$(${groupsAndTopCmd})
1437
+ COMPREPLY=( $(compgen -W "\${opts} \${groups_and_top}" -- \${cur}) )
1438
+ return 0
1439
+ fi
1440
+
1441
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
1442
+ if [[ "\${COMP_WORDS[1]}" == "exec" ]]; then
1443
+ local modules=$(${moduleListCmd})
1444
+ COMPREPLY=( $(compgen -W "\${modules}" -- \${cur}) )
1445
+ return 0
1446
+ fi
1447
+ export _APCORE_GRP="\${COMP_WORDS[1]}"
1448
+ local group_cmds=$(${groupCmdsCmd})
1449
+ COMPREPLY=( $(compgen -W "\${group_cmds}" -- \${cur}) )
1450
+ return 0
1451
+ fi
1490
1452
  }
1453
+ complete -F ${fn} ${quoted}
1454
+ `;
1455
+ }
1456
+ function generateZshCompletion(progName) {
1457
+ const fn = makeFunctionName(progName);
1458
+ const quoted = shellQuote(progName);
1459
+ const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e "process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})" 2>/dev/null`;
1460
+ const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1461
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1462
+ const ids=JSON.parse(m).map(x=>x.id);
1463
+ const g=new Set(),t=[];
1464
+ ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1465
+ console.log([...g].sort().concat(t.sort()).join(' '))
1466
+ " 2>/dev/null`;
1467
+ const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1468
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1469
+ const g=process.env._APCORE_GRP;
1470
+ JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1471
+ " 2>/dev/null`;
1472
+ return `#compdef ${progName}
1491
1473
 
1492
- // src/cli.ts
1493
- init_esm_shims();
1494
- import { Command as Command2 } from "commander";
1495
- var BUILTIN_COMMANDS = ["completion", "describe", "exec", "init", "list", "man"];
1496
- var LazyModuleGroup = class {
1497
- registry;
1498
- executor;
1499
- helpTextMaxLength;
1500
- commandCache = /* @__PURE__ */ new Map();
1501
- /** alias -> canonical module_id (populated lazily) */
1502
- aliasMap = /* @__PURE__ */ new Map();
1503
- /** module_id -> descriptor cache (populated during alias map build) */
1504
- descriptorCache = /* @__PURE__ */ new Map();
1505
- aliasMapBuilt = false;
1506
- constructor(registry, executor, helpTextMaxLength = 1e3) {
1507
- this.registry = registry;
1508
- this.executor = executor;
1509
- this.helpTextMaxLength = helpTextMaxLength;
1474
+ ${fn}() {
1475
+ local -a commands
1476
+ commands=(
1477
+ 'list:List available modules'
1478
+ 'describe:Show module metadata and schema'
1479
+ 'completion:Generate shell completion script'
1480
+ 'init:Scaffolding commands'
1481
+ 'man:Generate man page'
1482
+ )
1483
+
1484
+ _arguments -C \\
1485
+ '1:command:->command' \\
1486
+ '*::arg:->args'
1487
+
1488
+ case "$state" in
1489
+ command)
1490
+ _describe -t commands '${progName} commands' commands
1491
+ local -a groups_and_top
1492
+ groups_and_top=($(${groupsAndTopCmd}))
1493
+ compadd -a groups_and_top
1494
+ ;;
1495
+ args)
1496
+ case "\${words[1]}" in
1497
+ exec)
1498
+ local modules
1499
+ modules=($(${moduleListCmd}))
1500
+ compadd -a modules
1501
+ ;;
1502
+ *)
1503
+ export _APCORE_GRP="\${words[1]}"
1504
+ local -a group_cmds
1505
+ group_cmds=($(${groupCmdsCmd}))
1506
+ compadd -a group_cmds
1507
+ ;;
1508
+ esac
1509
+ ;;
1510
+ esac
1511
+ }
1512
+
1513
+ compdef ${fn} ${quoted}
1514
+ `;
1515
+ }
1516
+ function generateFishCompletion(progName) {
1517
+ const quoted = shellQuote(progName);
1518
+ const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e \\"process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})\\" 2>/dev/null`;
1519
+ const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e \\"const m=require('fs').readFileSync('/dev/stdin','utf8');const ids=JSON.parse(m).map(x=>x.id);const g=new Set(),t=[];ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});console.log([...g].sort().concat(t.sort()).join('\\\\n'))\\" 2>/dev/null`;
1520
+ return `# Fish completions for ${progName}
1521
+ complete -c ${quoted} -n "__fish_use_subcommand" -a list -d "List available modules"
1522
+ complete -c ${quoted} -n "__fish_use_subcommand" -a describe -d "Show module metadata and schema"
1523
+ complete -c ${quoted} -n "__fish_use_subcommand" -a completion -d "Generate shell completion script"
1524
+ complete -c ${quoted} -n "__fish_use_subcommand" -a init -d "Scaffolding commands"
1525
+ complete -c ${quoted} -n "__fish_use_subcommand" -a man -d "Generate man page"
1526
+ complete -c ${quoted} -n "__fish_use_subcommand" -a "(${groupsAndTopCmd})" -d "Module group"
1527
+
1528
+ complete -c ${quoted} -n "__fish_seen_subcommand_from exec" -a "(${moduleListCmd})"
1529
+
1530
+ function __apcore_group_cmds
1531
+ set -l grp (commandline -opc)[2]
1532
+ set -x _APCORE_GRP $grp
1533
+ ${quoted} list --format json 2>/dev/null | node -e "
1534
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1535
+ const g=process.env._APCORE_GRP;
1536
+ JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1537
+ " 2>/dev/null
1538
+ end
1539
+
1540
+ # Group subcommand completion \u2014 matches when position 1 is not a builtin
1541
+ complete -c ${quoted} -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from list describe completion init man exec" -a "(__apcore_group_cmds)"
1542
+ `;
1543
+ }
1544
+ function buildSynopsis(command, progName, commandName) {
1545
+ if (!command) {
1546
+ return `\\fB${progName} ${commandName}\\fR [OPTIONS]`;
1510
1547
  }
1511
- /**
1512
- * Build alias->module_id map from display overlay metadata.
1513
- */
1514
- buildAliasMap() {
1515
- if (this.aliasMapBuilt) {
1516
- return;
1517
- }
1518
- try {
1519
- for (const descriptor of this.registry.listModules()) {
1520
- const moduleId = descriptor.id;
1521
- this.descriptorCache.set(moduleId, descriptor);
1522
- const display = getDisplay(descriptor);
1523
- const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1524
- const cliAlias = cliDisplay.alias;
1525
- if (cliAlias && cliAlias !== moduleId) {
1526
- this.aliasMap.set(cliAlias, moduleId);
1527
- }
1528
- }
1529
- this.aliasMapBuilt = true;
1530
- } catch {
1531
- warn("Failed to build alias map from registry");
1548
+ const parts = [`\\fB${progName} ${commandName}\\fR`];
1549
+ for (const opt of command.options) {
1550
+ const flag = opt.long ?? opt.short ?? "";
1551
+ if (opt.isBoolean?.()) {
1552
+ parts.push(`[${flag}]`);
1553
+ } else if (opt.required) {
1554
+ const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
1555
+ parts.push(`${flag} \\fI${typeName}\\fR`);
1556
+ } else {
1557
+ const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
1558
+ parts.push(`[${flag} \\fI${typeName}\\fR]`);
1532
1559
  }
1533
1560
  }
1534
- /**
1535
- * List all available command names from the Registry.
1536
- */
1537
- listCommands() {
1538
- this.buildAliasMap();
1539
- const reverse = /* @__PURE__ */ new Map();
1540
- for (const [alias, moduleId] of this.aliasMap) {
1541
- reverse.set(moduleId, alias);
1561
+ for (const arg of command.registeredArguments ?? []) {
1562
+ const meta = arg.name().toUpperCase();
1563
+ if (arg.required) {
1564
+ parts.push(`\\fI${meta}\\fR`);
1565
+ } else {
1566
+ parts.push(`[\\fI${meta}\\fR]`);
1542
1567
  }
1543
- const moduleIds = this.registry.listModules().map((m) => m.id);
1544
- const names = moduleIds.map((mid) => reverse.get(mid) ?? mid);
1545
- return [...new Set(names)].sort();
1546
1568
  }
1547
- /**
1548
- * Get or lazily build a Commander Command for the given module.
1549
- */
1550
- getCommand(cmdName) {
1551
- if (this.commandCache.has(cmdName)) {
1552
- return this.commandCache.get(cmdName);
1553
- }
1554
- this.buildAliasMap();
1555
- const moduleId = this.aliasMap.get(cmdName) ?? cmdName;
1556
- let moduleDef = this.descriptorCache.get(moduleId);
1557
- if (!moduleDef) {
1558
- moduleDef = this.registry.getModule(moduleId) ?? void 0;
1559
- }
1560
- if (!moduleDef) {
1561
- return null;
1562
- }
1563
- const cmd = buildModuleCommand(moduleDef, this.executor, this.helpTextMaxLength, cmdName);
1564
- this.commandCache.set(cmdName, cmd);
1565
- return cmd;
1569
+ return parts.join(" ");
1570
+ }
1571
+ function generateManPage(commandName, command, progName, version = SHELL_VERSION) {
1572
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1573
+ const title = `${progName}-${commandName}`.toUpperCase();
1574
+ const pkgLabel = `${progName} ${version}`;
1575
+ const manualLabel = `${progName} Manual`;
1576
+ const sections = [];
1577
+ sections.push(`.TH "${title}" "1" "${today}" "${pkgLabel}" "${manualLabel}"`);
1578
+ sections.push(".SH NAME");
1579
+ const desc = command?.description() ?? commandName;
1580
+ const nameDesc = desc.split("\n")[0].replace(/\.$/, "");
1581
+ sections.push(`${progName}-${commandName} \\- ${nameDesc}`);
1582
+ sections.push(".SH SYNOPSIS");
1583
+ sections.push(buildSynopsis(command, progName, commandName));
1584
+ if (command?.description()) {
1585
+ sections.push(".SH DESCRIPTION");
1586
+ sections.push(
1587
+ command.description().replace(/\\/g, "\\\\").replace(/-/g, "\\-")
1588
+ );
1566
1589
  }
1567
- };
1568
- var LazyGroup = class {
1569
- members;
1570
- _executor;
1571
- _helpTextMaxLength;
1572
- _cmdCache = /* @__PURE__ */ new Map();
1573
- command;
1574
- constructor(members, executor, name, helpTextMaxLength = 1e3) {
1575
- this.members = members;
1576
- this._executor = executor;
1577
- this._helpTextMaxLength = helpTextMaxLength;
1578
- this.command = new Command2(name).description(`${name} commands`);
1579
- for (const [cmdName, [, descriptor]] of this.members) {
1580
- const cmd = buildModuleCommand(
1581
- descriptor,
1582
- this._executor,
1583
- this._helpTextMaxLength,
1584
- cmdName
1585
- );
1586
- this._cmdCache.set(cmdName, cmd);
1587
- this.command.addCommand(cmd);
1590
+ if (command && command.options.length > 0) {
1591
+ sections.push(".SH OPTIONS");
1592
+ for (const opt of command.options) {
1593
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
1594
+ sections.push(".TP");
1595
+ if (opt.isBoolean?.()) {
1596
+ sections.push(`\\fB${flag}\\fR`);
1597
+ } else {
1598
+ sections.push(`\\fB${flag}\\fR \\fIVALUE\\fR`);
1599
+ }
1600
+ if (opt.description) {
1601
+ sections.push(opt.description);
1602
+ }
1603
+ if (opt.defaultValue !== void 0 && !opt.isBoolean?.()) {
1604
+ sections.push(`Default: ${opt.defaultValue}.`);
1605
+ }
1588
1606
  }
1589
1607
  }
1590
- listCommands() {
1591
- return [...this.members.keys()].sort();
1608
+ sections.push(".SH ENVIRONMENT");
1609
+ sections.push(".TP");
1610
+ sections.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
1611
+ sections.push(
1612
+ "Path to the apcore extensions directory. Overrides the default \\fI./extensions\\fR."
1613
+ );
1614
+ sections.push(".TP");
1615
+ sections.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
1616
+ sections.push(
1617
+ "Set to \\fB1\\fR to bypass approval prompts for modules that require human-in-the-loop confirmation."
1618
+ );
1619
+ sections.push(".TP");
1620
+ sections.push("\\fBAPCORE_CLI_LOGGING_LEVEL\\fR");
1621
+ sections.push(
1622
+ "CLI-specific logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Takes priority over \\fBAPCORE_LOGGING_LEVEL\\fR. Default: WARNING."
1623
+ );
1624
+ sections.push(".TP");
1625
+ sections.push("\\fBAPCORE_LOGGING_LEVEL\\fR");
1626
+ sections.push(
1627
+ "Global apcore logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Used as fallback when \\fBAPCORE_CLI_LOGGING_LEVEL\\fR is not set. Default: WARNING."
1628
+ );
1629
+ sections.push(".TP");
1630
+ sections.push("\\fBAPCORE_AUTH_API_KEY\\fR");
1631
+ sections.push(
1632
+ "API key for authenticating with the apcore registry."
1633
+ );
1634
+ sections.push(".SH EXIT CODES");
1635
+ const exitCodes = [
1636
+ ["0", "Success."],
1637
+ ["1", "Module execution error."],
1638
+ ["2", "Invalid CLI input or missing argument."],
1639
+ ["44", "Module not found, disabled, or failed to load."],
1640
+ ["45", "Input failed JSON Schema validation."],
1641
+ [
1642
+ "46",
1643
+ "Approval denied, timed out, or no interactive terminal available."
1644
+ ],
1645
+ [
1646
+ "47",
1647
+ "Configuration error (extensions directory not found or unreadable)."
1648
+ ],
1649
+ ["48", "Schema contains a circular \\fB$ref\\fR."],
1650
+ ["77", "ACL denied \u2014 insufficient permissions for this module."],
1651
+ ["130", "Execution cancelled by user (SIGINT / Ctrl\\-C)."]
1652
+ ];
1653
+ for (const [code, meaning] of exitCodes) {
1654
+ sections.push(`.TP
1655
+ \\fB${code}\\fR
1656
+ ${meaning}`);
1592
1657
  }
1593
- getCommand(cmdName) {
1594
- if (this._cmdCache.has(cmdName)) {
1595
- return this._cmdCache.get(cmdName);
1596
- }
1597
- const entry = this.members.get(cmdName);
1598
- if (!entry) {
1599
- return null;
1600
- }
1601
- const [, descriptor] = entry;
1602
- const cmd = buildModuleCommand(
1603
- descriptor,
1604
- this._executor,
1605
- this._helpTextMaxLength,
1606
- cmdName
1607
- );
1608
- this._cmdCache.set(cmdName, cmd);
1609
- return cmd;
1658
+ sections.push(".SH SEE ALSO");
1659
+ sections.push(
1660
+ [
1661
+ `\\fB${progName}\\fR(1)`,
1662
+ `\\fB${progName}\\-list\\fR(1)`,
1663
+ `\\fB${progName}\\-describe\\fR(1)`,
1664
+ `\\fB${progName}\\-completion\\fR(1)`
1665
+ ].join(", ")
1666
+ );
1667
+ return sections.join("\n");
1668
+ }
1669
+ function roffEscape(s) {
1670
+ return s.replace(/\\/g, "\\\\").replace(/-/g, "\\-").replace(/'/g, "\\(aq");
1671
+ }
1672
+ function buildProgramManPage(program, progName, version, description, docsUrl2) {
1673
+ const help = new Help();
1674
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1675
+ const s = [];
1676
+ const resolvedDesc = description ?? program.description() ?? `${progName} CLI`;
1677
+ s.push(`.TH "${progName.toUpperCase()}" "1" "${today}" "${progName} ${version}" "${progName} Manual"`);
1678
+ s.push(".SH NAME");
1679
+ s.push(`${progName} \\- ${roffEscape(resolvedDesc)}`);
1680
+ s.push(".SH SYNOPSIS");
1681
+ s.push(`\\fB${progName}\\fR [\\fIglobal\\-options\\fR] \\fIcommand\\fR [\\fIcommand\\-options\\fR]`);
1682
+ if (resolvedDesc) {
1683
+ s.push(".SH DESCRIPTION");
1684
+ s.push(roffEscape(resolvedDesc));
1610
1685
  }
1611
- };
1612
- var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
1613
- /** groupName -> { cmdName -> [moduleId, descriptor] } */
1614
- groupMap = /* @__PURE__ */ new Map();
1615
- /** cmdName -> [moduleId, descriptor] for top-level (ungrouped) modules */
1616
- topLevelModules = /* @__PURE__ */ new Map();
1617
- /** Cached LazyGroup instances */
1618
- groupCache = /* @__PURE__ */ new Map();
1619
- groupMapBuilt = false;
1620
- /**
1621
- * Determine (groupName | null, commandName) for a module from its display overlay.
1622
- */
1623
- static resolveGroup(moduleId, descriptor) {
1624
- if (!moduleId) {
1625
- warn("Empty module_id encountered in resolveGroup");
1626
- return [null, ""];
1627
- }
1628
- const display = getDisplay(descriptor);
1629
- const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1630
- const explicitGroup = cliDisplay.group;
1631
- if (typeof explicitGroup === "string" && explicitGroup !== "") {
1632
- return [explicitGroup, cliDisplay.alias ?? moduleId];
1633
- }
1634
- if (explicitGroup === "") {
1635
- return [null, cliDisplay.alias ?? moduleId];
1636
- }
1637
- const cliName = cliDisplay.alias ?? moduleId;
1638
- if (cliName.includes(".")) {
1639
- const dotIdx = cliName.indexOf(".");
1640
- const group = cliName.substring(0, dotIdx);
1641
- const cmd = cliName.substring(dotIdx + 1);
1642
- return [group, cmd];
1686
+ const globalOpts = help.visibleOptions(program).filter((o) => !["help", "version", "all", "man"].includes(o.long?.replace("--", "") ?? ""));
1687
+ if (globalOpts.length > 0) {
1688
+ s.push(".SH GLOBAL OPTIONS");
1689
+ for (const opt of globalOpts) {
1690
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
1691
+ s.push(".TP");
1692
+ s.push(`\\fB${roffEscape(flag)}\\fR`);
1693
+ if (opt.description) s.push(roffEscape(opt.description));
1643
1694
  }
1644
- return [null, cliName];
1645
1695
  }
1646
- /**
1647
- * Build the group map from registry modules.
1648
- */
1649
- buildGroupMap() {
1650
- if (this.groupMapBuilt) {
1651
- return;
1652
- }
1653
- try {
1654
- this.buildAliasMap();
1655
- for (const descriptor of this.registry.listModules()) {
1656
- const moduleId = descriptor.id;
1657
- const cached = this.descriptorCache.get(moduleId);
1658
- if (!cached) {
1659
- continue;
1696
+ const allCommands = help.visibleCommands(program);
1697
+ if (allCommands.length > 0) {
1698
+ s.push(".SH COMMANDS");
1699
+ for (const cmd of allCommands) {
1700
+ if (cmd.name() === "help") continue;
1701
+ const desc = help.subcommandDescription(cmd);
1702
+ s.push(".TP");
1703
+ s.push(`\\fB${progName} ${roffEscape(cmd.name())}\\fR`);
1704
+ if (desc) s.push(roffEscape(desc));
1705
+ const cmdHelp = new Help();
1706
+ const opts = cmdHelp.visibleOptions(cmd).filter((o) => !["help", "version"].includes(o.long?.replace("--", "") ?? ""));
1707
+ for (const opt of opts) {
1708
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
1709
+ s.push(".RS");
1710
+ s.push(".TP");
1711
+ s.push(`\\fB${roffEscape(flag)}\\fR`);
1712
+ if (opt.description) s.push(roffEscape(opt.description));
1713
+ s.push(".RE");
1714
+ }
1715
+ const subCmds = cmdHelp.visibleCommands(cmd).filter((c) => c.name() !== "help");
1716
+ for (const sub of subCmds) {
1717
+ const subDesc = help.subcommandDescription(sub);
1718
+ s.push(".TP");
1719
+ s.push(`\\fB${progName} ${roffEscape(cmd.name())} ${roffEscape(sub.name())}\\fR`);
1720
+ if (subDesc) s.push(roffEscape(subDesc));
1721
+ const subOpts = cmdHelp.visibleOptions(sub).filter((o) => !["help", "version"].includes(o.long?.replace("--", "") ?? ""));
1722
+ for (const opt of subOpts) {
1723
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
1724
+ s.push(".RS");
1725
+ s.push(".TP");
1726
+ s.push(`\\fB${roffEscape(flag)}\\fR`);
1727
+ if (opt.description) s.push(roffEscape(opt.description));
1728
+ s.push(".RE");
1660
1729
  }
1661
- const [group, cmd] = _GroupedModuleGroup.resolveGroup(moduleId, cached);
1662
- if (group === null) {
1663
- this.topLevelModules.set(cmd, [moduleId, cached]);
1664
- } else if (!/^[a-z][a-z0-9_-]*$/.test(group)) {
1665
- warn(
1666
- `Module '${moduleId}': group name '${group}' is not shell-safe \u2014 treating as top-level.`
1667
- );
1668
- this.topLevelModules.set(cmd, [moduleId, cached]);
1669
- } else {
1670
- if (!this.groupMap.has(group)) {
1671
- this.groupMap.set(group, /* @__PURE__ */ new Map());
1730
+ }
1731
+ }
1732
+ }
1733
+ s.push(".SH ENVIRONMENT");
1734
+ s.push(".TP");
1735
+ s.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
1736
+ s.push("Path to the apcore extensions directory.");
1737
+ s.push(".TP");
1738
+ s.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
1739
+ s.push("Set to \\fB1\\fR to bypass approval prompts.");
1740
+ s.push(".TP");
1741
+ s.push("\\fBAPCORE_CLI_LOGGING_LEVEL\\fR");
1742
+ s.push("CLI\\-specific logging verbosity (DEBUG|INFO|WARNING|ERROR).");
1743
+ s.push(".SH EXIT CODES");
1744
+ const exitCodes = [
1745
+ ["0", "Success."],
1746
+ ["1", "Module execution error."],
1747
+ ["2", "Invalid CLI input or missing argument."],
1748
+ ["44", "Module not found, disabled, or failed to load."],
1749
+ ["45", "Input failed JSON Schema validation."],
1750
+ ["46", "Approval denied or timed out."],
1751
+ ["47", "Configuration error."],
1752
+ ["77", "ACL denied."],
1753
+ ["130", "Cancelled by user (SIGINT)."]
1754
+ ];
1755
+ for (const [code, meaning] of exitCodes) {
1756
+ s.push(`.TP
1757
+ \\fB${code}\\fR
1758
+ ${meaning}`);
1759
+ }
1760
+ s.push(".SH SEE ALSO");
1761
+ s.push(`\\fB${progName} \\-\\-help \\-\\-verbose\\fR for full option list.`);
1762
+ if (docsUrl2) {
1763
+ s.push(`.PP
1764
+ Full documentation at \\fI${roffEscape(docsUrl2)}\\fR`);
1765
+ }
1766
+ return s.join("\n");
1767
+ }
1768
+ function configureManHelp(program, progName, version, description, docsUrl2) {
1769
+ const manOpt = new Option("--man", "Output man page in roff format (use with --help)").hideHelp();
1770
+ program.addOption(manOpt);
1771
+ program.addHelpText("beforeAll", () => {
1772
+ if (program.opts().man) {
1773
+ const roff = buildProgramManPage(program, progName, version, description, docsUrl2) + "\n";
1774
+ if (process.stdout.isTTY) {
1775
+ const pagers = [
1776
+ { cmd: "mandoc", args: ["-a"] },
1777
+ { cmd: "groff", args: ["-man", "-Tutf8"] }
1778
+ ];
1779
+ let rendered = false;
1780
+ for (const { cmd, args } of pagers) {
1781
+ const result = spawnSync(cmd, args, {
1782
+ input: roff,
1783
+ stdio: ["pipe", "pipe", "pipe"],
1784
+ encoding: "utf-8"
1785
+ });
1786
+ if (result.status === 0 && result.stdout) {
1787
+ const pager = process.env.PAGER || "less";
1788
+ const pagerResult = spawnSync(pager, ["-R"], {
1789
+ input: result.stdout,
1790
+ stdio: ["pipe", "inherit", "inherit"]
1791
+ });
1792
+ if (pagerResult.status !== null) {
1793
+ rendered = true;
1794
+ break;
1795
+ }
1672
1796
  }
1673
- this.groupMap.get(group).set(cmd, [moduleId, cached]);
1674
1797
  }
1675
- }
1676
- for (const groupName of this.groupMap.keys()) {
1677
- if (BUILTIN_COMMANDS.includes(groupName)) {
1678
- warn(
1679
- `Group name '${groupName}' collides with a built-in command and will be ignored`
1680
- );
1798
+ if (!rendered) {
1799
+ process.stdout.write(roff);
1681
1800
  }
1801
+ } else {
1802
+ process.stdout.write(roff);
1682
1803
  }
1683
- this.groupMapBuilt = true;
1684
- } catch {
1685
- warn("Failed to build group map");
1804
+ process.exit(0);
1805
+ }
1806
+ return "";
1807
+ });
1808
+ }
1809
+ function registerShellCommands(cli, progName = "apcore-cli") {
1810
+ const completionCmd = new Command("completion").description(
1811
+ "Generate a shell completion script and print it to stdout."
1812
+ ).argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
1813
+ const validShells = ["bash", "zsh", "fish"];
1814
+ if (!validShells.includes(shell)) {
1815
+ process.stderr.write(
1816
+ `Error: Unknown shell '${shell}'. Expected: bash, zsh, or fish.
1817
+ `
1818
+ );
1819
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1820
+ }
1821
+ const resolved = cli.name() || progName;
1822
+ const generators = {
1823
+ bash: () => generateBashCompletion(resolved),
1824
+ zsh: () => generateZshCompletion(resolved),
1825
+ fish: () => generateFishCompletion(resolved)
1826
+ };
1827
+ process.stdout.write(generators[shell]());
1828
+ });
1829
+ cli.addCommand(completionCmd);
1830
+ const manCmd = new Command("man").description("Generate a roff man page for COMMAND and print it to stdout.").argument("<command>", "Command to generate man page for").action((commandName) => {
1831
+ const knownBuiltins = /* @__PURE__ */ new Set(["completion", "describe", "exec", "init", "list", "man"]);
1832
+ const cmd = cli.commands.find((c) => c.name() === commandName) ?? null;
1833
+ if (!cmd && !knownBuiltins.has(commandName)) {
1834
+ process.stderr.write(
1835
+ `Error: Unknown command '${commandName}'.
1836
+ `
1837
+ );
1838
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1686
1839
  }
1840
+ const resolved = cli.name() || progName;
1841
+ const roff = generateManPage(commandName, cmd, resolved);
1842
+ process.stdout.write(roff);
1843
+ });
1844
+ cli.addCommand(manCmd);
1845
+ }
1846
+
1847
+ // src/main.ts
1848
+ var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
1849
+ var verboseHelp = false;
1850
+ function setVerboseHelp(verbose) {
1851
+ verboseHelp = verbose;
1852
+ }
1853
+ var docsUrl = null;
1854
+ function setDocsUrl(url) {
1855
+ docsUrl = url;
1856
+ }
1857
+ function hasVerboseFlag() {
1858
+ return process.argv.includes("--verbose");
1859
+ }
1860
+ var VERSION = "0.0.0";
1861
+ try {
1862
+ const pkg = JSON.parse(readFileSync3(path6.resolve(__dirname3, "../package.json"), "utf-8"));
1863
+ VERSION = pkg.version;
1864
+ } catch {
1865
+ }
1866
+ function createCli(extensionsDir, progName, verbose = false) {
1867
+ verboseHelp = verbose;
1868
+ registerConfigNamespace();
1869
+ const resolvedProgName = progName ?? path6.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1870
+ const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
1871
+ setLogLevel(cliLogLevel);
1872
+ const program = new Command2(resolvedProgName).exitOverride().version(VERSION, "--version", `Show ${resolvedProgName} version`).description("apcore CLI \u2014 execute apcore modules from the command line").option("--extensions-dir <path>", "Path to extensions directory").option("--commands-dir <path>", "Path to convention-based commands directory").option("--binding <path>", "Path to binding.yaml for display overlay").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in apcore options)");
1873
+ const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
1874
+ void resolvedExtDir;
1875
+ program.addHelpText("after", [
1876
+ "",
1877
+ "Use --help --verbose to show all options (including built-in apcore options).",
1878
+ "Use --help --man to display a formatted man page."
1879
+ ].join("\n"));
1880
+ registerInitCommand(program);
1881
+ configureManHelp(program, resolvedProgName, VERSION);
1882
+ program.hook("preAction", async (thisCommand) => {
1883
+ const opts = thisCommand.opts();
1884
+ const commandsDir = opts.commandsDir;
1885
+ const bindingPath = opts.binding;
1886
+ await applyToolkitIntegration(commandsDir, bindingPath);
1887
+ });
1888
+ return program;
1889
+ }
1890
+ async function applyToolkitIntegration(commandsDir, bindingPath) {
1891
+ if (!commandsDir && !bindingPath) {
1892
+ return;
1687
1893
  }
1688
- /**
1689
- * List all available command names: builtins + group names + top-level module names.
1690
- */
1691
- listCommands() {
1692
- this.buildGroupMap();
1693
- const groupNames = [...this.groupMap.keys()].filter(
1694
- (g) => !BUILTIN_COMMANDS.includes(g)
1894
+ try {
1895
+ const toolkitModule = "apcore-toolkit";
1896
+ const toolkit = await import(
1897
+ /* @vite-ignore */
1898
+ toolkitModule
1695
1899
  );
1696
- const topNames = [...this.topLevelModules.keys()];
1697
- return [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...groupNames, ...topNames])].sort();
1900
+ if (commandsDir) {
1901
+ console.warn("Convention scanning not yet available in TypeScript toolkit");
1902
+ }
1903
+ if (bindingPath) {
1904
+ const resolver = new toolkit.DisplayResolver();
1905
+ void resolver;
1906
+ }
1907
+ } catch {
1908
+ console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
1698
1909
  }
1699
- /**
1700
- * Get a command by name: check builtins -> group cache -> group map -> top-level modules.
1701
- */
1702
- getCommand(cmdName) {
1703
- this.buildGroupMap();
1704
- if (this.groupCache.has(cmdName)) {
1705
- return this.groupCache.get(cmdName).command;
1910
+ }
1911
+ function main(progName) {
1912
+ verboseHelp = hasVerboseFlag();
1913
+ const program = createCli(void 0, progName, verboseHelp);
1914
+ try {
1915
+ program.parse(process.argv);
1916
+ } catch (error2) {
1917
+ if (error2 instanceof CommanderError) {
1918
+ process.exit(error2.exitCode);
1706
1919
  }
1707
- if (this.groupMap.has(cmdName)) {
1708
- const lazyGrp = new LazyGroup(
1709
- this.groupMap.get(cmdName),
1710
- this.executor,
1711
- cmdName,
1712
- this.helpTextMaxLength
1713
- );
1714
- this.groupCache.set(cmdName, lazyGrp);
1715
- return lazyGrp.command;
1920
+ const code = exitCodeForError(error2);
1921
+ if (error2 instanceof Error) {
1922
+ process.stderr.write(`Error: ${error2.message}
1923
+ `);
1716
1924
  }
1717
- if (this.topLevelModules.has(cmdName)) {
1718
- if (this.commandCache.has(cmdName)) {
1719
- return this.commandCache.get(cmdName);
1720
- }
1721
- const [, descriptor] = this.topLevelModules.get(cmdName);
1722
- const cmd = buildModuleCommand(
1723
- descriptor,
1724
- this.executor,
1725
- this.helpTextMaxLength,
1726
- cmdName
1727
- );
1728
- this.commandCache.set(cmdName, cmd);
1729
- return cmd;
1925
+ process.exit(code);
1926
+ }
1927
+ }
1928
+ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName, verbose = verboseHelp) {
1929
+ const moduleId = moduleDef.id;
1930
+ let resolvedSchema = {};
1931
+ let schemaOptions = [];
1932
+ const display = getDisplay(moduleDef);
1933
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1934
+ const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
1935
+ const cmdHelp = cliDisplay.description ?? moduleDef.description;
1936
+ const inputSchema = moduleDef.inputSchema;
1937
+ if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
1938
+ try {
1939
+ resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
1940
+ } catch {
1941
+ resolvedSchema = inputSchema;
1730
1942
  }
1731
- return null;
1943
+ schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
1732
1944
  }
1733
- /** Expose groupMap for testing. */
1734
- getGroupMap() {
1735
- return this.groupMap;
1945
+ const cmd = new Command2(effectiveCmdName).description(cmdHelp);
1946
+ const inputOpt = new Option2("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
1947
+ const yesOpt = new Option2("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
1948
+ const largeInputOpt = new Option2("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
1949
+ const formatOpt = new Option2("--format <format>", "Set output format: 'json' for machine-readable, 'table' for human-readable");
1950
+ const sandboxOpt = new Option2("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
1951
+ if (!verbose) {
1952
+ inputOpt.hideHelp();
1953
+ yesOpt.hideHelp();
1954
+ largeInputOpt.hideHelp();
1955
+ formatOpt.hideHelp();
1736
1956
  }
1737
- /** Expose topLevelModules for testing. */
1738
- getTopLevelModules() {
1739
- return this.topLevelModules;
1957
+ cmd.addOption(inputOpt);
1958
+ cmd.addOption(yesOpt);
1959
+ cmd.addOption(largeInputOpt);
1960
+ cmd.addOption(formatOpt);
1961
+ cmd.addOption(sandboxOpt);
1962
+ const footerParts = [];
1963
+ if (!verbose) {
1964
+ footerParts.push("Use --verbose to show all options (including built-in apcore options).");
1740
1965
  }
1741
- /** Expose groupMapBuilt for testing. */
1742
- isGroupMapBuilt() {
1743
- return this.groupMapBuilt;
1966
+ if (docsUrl) {
1967
+ footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
1744
1968
  }
1745
- };
1746
-
1747
- // src/config.ts
1748
- init_esm_shims();
1749
- import * as fs4 from "fs";
1750
- import yaml from "js-yaml";
1751
- var DEFAULTS = {
1752
- "extensions.root": "./extensions",
1753
- "logging.level": "WARNING",
1754
- "sandbox.enabled": false,
1755
- "cli.stdin_buffer_limit": 10485760,
1756
- "cli.auto_approve": false,
1757
- "cli.help_text_max_length": 1e3
1758
- };
1759
- var ConfigResolver = class {
1760
- cliFlags;
1761
- configPath;
1762
- fileCache = null;
1763
- fileCacheLoaded = false;
1764
- constructor(cliFlags, configPath) {
1765
- this.cliFlags = cliFlags ?? {};
1766
- this.configPath = configPath ?? "apcore.yaml";
1969
+ if (footerParts.length > 0) {
1970
+ cmd.addHelpText("after", "\n" + footerParts.join("\n") + "\n");
1767
1971
  }
1768
- /**
1769
- * Resolve a single configuration key across all four tiers.
1770
- */
1771
- resolve(key, cliFlag, envVar) {
1772
- const flagKey = cliFlag ?? key;
1773
- if (flagKey in this.cliFlags) {
1774
- const value = this.cliFlags[flagKey];
1775
- if (value !== null && value !== void 0) {
1776
- return value;
1777
- }
1778
- }
1779
- if (envVar) {
1780
- const envValue = process.env[envVar];
1781
- if (envValue !== void 0 && envValue !== "") {
1782
- return envValue;
1783
- }
1784
- }
1785
- const fileValue = this.resolveFromFile(key);
1786
- if (fileValue !== void 0) {
1787
- return fileValue;
1972
+ for (const opt of schemaOptions) {
1973
+ if (opt.parseArg) {
1974
+ cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
1975
+ } else {
1976
+ cmd.option(opt.flags, opt.description, opt.defaultValue);
1788
1977
  }
1789
- return DEFAULTS[key];
1790
1978
  }
1791
- /**
1792
- * Load a value from the config file using a dot-separated key path.
1793
- */
1794
- resolveFromFile(key) {
1795
- if (!this.fileCacheLoaded) {
1796
- this.fileCache = this.loadConfigFile();
1797
- this.fileCacheLoaded = true;
1798
- }
1799
- if (this.fileCache === null) {
1800
- return void 0;
1979
+ cmd.action(async (options) => {
1980
+ const stdinFlag = options.input;
1981
+ const autoApprove = options.yes;
1982
+ const largeInput = options.largeInput;
1983
+ const outputFormat = options.format;
1984
+ const sandboxEnabled = options.sandbox;
1985
+ const schemaKwargs = {};
1986
+ const builtinKeys = /* @__PURE__ */ new Set(["input", "yes", "largeInput", "format", "sandbox", "verbose"]);
1987
+ for (const [k, v] of Object.entries(options)) {
1988
+ if (!builtinKeys.has(k)) {
1989
+ schemaKwargs[k] = v;
1990
+ }
1801
1991
  }
1802
- return this.fileCache[key];
1803
- }
1804
- /**
1805
- * Load and flatten a YAML config file.
1806
- */
1807
- loadConfigFile() {
1808
- let content;
1809
1992
  try {
1810
- content = fs4.readFileSync(this.configPath, "utf-8");
1993
+ const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
1994
+ const reconverted = reconvertEnumValues(merged, schemaOptions);
1995
+ await checkApproval(moduleDef, autoApprove);
1996
+ const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
1997
+ const sandbox = new Sandbox2(sandboxEnabled);
1998
+ const startTime = performance.now();
1999
+ const result = await sandbox.execute(moduleId, reconverted, executor);
2000
+ const durationMs = Math.round(performance.now() - startTime);
2001
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2002
+ const auditLogger = getAuditLogger2();
2003
+ if (auditLogger) {
2004
+ auditLogger.logExecution(moduleId, reconverted, "success", 0, durationMs);
2005
+ }
2006
+ formatExecResult(result, outputFormat);
1811
2007
  } catch (err) {
1812
- if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1813
- return null;
2008
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2009
+ const auditLogger = getAuditLogger2();
2010
+ const code = exitCodeForError(err);
2011
+ if (auditLogger) {
2012
+ auditLogger.logExecution(moduleId, {}, "error", code, 0);
1814
2013
  }
1815
- console.warn(
1816
- `Configuration file '${this.configPath}' is malformed, using defaults.`
1817
- );
1818
- return null;
1819
- }
1820
- let parsed;
1821
- try {
1822
- parsed = yaml.load(content);
1823
- } catch {
1824
- console.warn(
1825
- `Configuration file '${this.configPath}' is malformed, using defaults.`
1826
- );
1827
- return null;
1828
- }
1829
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1830
- console.warn(
1831
- `Configuration file '${this.configPath}' is malformed, using defaults.`
1832
- );
1833
- return null;
1834
- }
1835
- return this.flattenDict(parsed);
1836
- }
1837
- /**
1838
- * Flatten nested dict to dot-notation keys.
1839
- */
1840
- flattenDict(d, prefix = "") {
1841
- const result = {};
1842
- for (const [key, value] of Object.entries(d)) {
1843
- const fullKey = prefix ? `${prefix}.${key}` : key;
1844
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1845
- Object.assign(
1846
- result,
1847
- this.flattenDict(value, fullKey)
1848
- );
1849
- } else {
1850
- result[fullKey] = value;
2014
+ if (err instanceof Error) {
2015
+ process.stderr.write(`Error: ${err.message}
2016
+ `);
1851
2017
  }
2018
+ process.exit(code);
1852
2019
  }
1853
- return result;
1854
- }
1855
- };
1856
-
1857
- // src/discovery.ts
1858
- init_esm_shims();
1859
- init_errors();
1860
- import { Command as Command3 } from "commander";
1861
- var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
1862
- function validateTag(tag) {
1863
- if (!TAG_PATTERN.test(tag)) {
2020
+ });
2021
+ return cmd;
2022
+ }
2023
+ function validateModuleId(moduleId) {
2024
+ if (moduleId.length > 128) {
1864
2025
  process.stderr.write(
1865
- `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
2026
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
2027
+ `
2028
+ );
2029
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2030
+ }
2031
+ if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
2032
+ process.stderr.write(
2033
+ `Error: Invalid module ID format: '${moduleId}'.
1866
2034
  `
1867
2035
  );
1868
2036
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1869
2037
  }
1870
2038
  }
1871
- function collectTag(value, previous) {
1872
- return previous.concat([value]);
1873
- }
1874
- function registerDiscoveryCommands(cli, registry) {
1875
- const listCmd = new Command3("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--format <format>", "Output format.", void 0).action((opts) => {
1876
- for (const t of opts.tag) {
1877
- validateTag(t);
2039
+ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
2040
+ const cliKwargsNonNull = {};
2041
+ for (const [k, v] of Object.entries(cliKwargs)) {
2042
+ if (v !== null && v !== void 0) {
2043
+ cliKwargsNonNull[k] = v;
1878
2044
  }
1879
- const modules = [];
1880
- for (const m of registry.listModules()) {
1881
- modules.push(m);
2045
+ }
2046
+ if (!stdinFlag) {
2047
+ return cliKwargsNonNull;
2048
+ }
2049
+ if (stdinFlag === "-") {
2050
+ const raw = await readStdin();
2051
+ const rawSize = Buffer.byteLength(raw, "utf-8");
2052
+ if (rawSize > 10485760 && !largeInput) {
2053
+ process.stderr.write(
2054
+ "Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
2055
+ );
2056
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1882
2057
  }
1883
- let filtered = modules;
1884
- if (opts.tag.length > 0) {
1885
- const filterTags = new Set(opts.tag);
1886
- filtered = modules.filter((m) => {
1887
- const mTags = m.tags ?? [];
1888
- return [...filterTags].every((t) => mTags.includes(t));
1889
- });
2058
+ if (!raw) {
2059
+ return cliKwargsNonNull;
1890
2060
  }
1891
- const fmt = resolveFormat(opts.format);
1892
- formatModuleList(filtered, fmt, opts.tag.length > 0 ? opts.tag : void 0);
1893
- });
1894
- cli.addCommand(listCmd);
1895
- const describeCmd = new Command3("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").option("--format <format>", "Output format.", void 0).action((moduleId, opts) => {
1896
- validateModuleId(moduleId);
1897
- const moduleDef = registry.getModule(moduleId);
1898
- if (!moduleDef) {
2061
+ let stdinData;
2062
+ try {
2063
+ stdinData = JSON.parse(raw);
2064
+ } catch {
1899
2065
  process.stderr.write(
1900
- `Error: Module '${moduleId}' not found.
2066
+ "Error: STDIN does not contain valid JSON.\n"
2067
+ );
2068
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2069
+ }
2070
+ if (typeof stdinData !== "object" || stdinData === null || Array.isArray(stdinData)) {
2071
+ process.stderr.write(
2072
+ `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? "array" : typeof stdinData}.
1901
2073
  `
1902
2074
  );
1903
- process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2075
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1904
2076
  }
1905
- const fmt = resolveFormat(opts.format);
1906
- formatModuleDetail(moduleDef, fmt);
1907
- });
1908
- cli.addCommand(describeCmd);
1909
- }
1910
-
1911
- // src/shell.ts
1912
- init_esm_shims();
1913
- init_errors();
1914
- import { readFileSync as readFileSync3 } from "fs";
1915
- import { fileURLToPath as fileURLToPath3 } from "url";
1916
- import * as path6 from "path";
1917
- import { Command as Command4, Help, Option as Option2 } from "commander";
1918
- var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
1919
- var SHELL_VERSION = "0.0.0";
1920
- try {
1921
- const pkg = JSON.parse(readFileSync3(path6.resolve(__dirname3, "../package.json"), "utf-8"));
1922
- SHELL_VERSION = pkg.version;
1923
- } catch {
1924
- }
1925
- function makeFunctionName(progName) {
1926
- return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
1927
- }
1928
- function shellQuote(s) {
1929
- return "'" + s.replace(/'/g, "'\\''") + "'";
1930
- }
1931
- function generateBashCompletion(progName) {
1932
- const fn = makeFunctionName(progName);
1933
- const quoted = shellQuote(progName);
1934
- const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e "process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})" 2>/dev/null`;
1935
- const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1936
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1937
- const ids=JSON.parse(m).map(x=>x.id);
1938
- const g=new Set(),t=[];
1939
- ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1940
- console.log([...g].sort().concat(t.sort()).join(' '))
1941
- " 2>/dev/null`;
1942
- const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1943
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1944
- const g=process.env._APCORE_GRP;
1945
- JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1946
- " 2>/dev/null`;
1947
- return `${fn}() {
1948
- local cur prev opts
1949
- COMPREPLY=()
1950
- cur="\${COMP_WORDS[COMP_CWORD]}"
1951
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
1952
-
1953
- if [[ \${COMP_CWORD} -eq 1 ]]; then
1954
- opts="completion describe exec init list man"
1955
- local groups_and_top=$(${groupsAndTopCmd})
1956
- COMPREPLY=( $(compgen -W "\${opts} \${groups_and_top}" -- \${cur}) )
1957
- return 0
1958
- fi
1959
-
1960
- if [[ \${COMP_CWORD} -eq 2 ]]; then
1961
- if [[ "\${COMP_WORDS[1]}" == "exec" ]]; then
1962
- local modules=$(${moduleListCmd})
1963
- COMPREPLY=( $(compgen -W "\${modules}" -- \${cur}) )
1964
- return 0
1965
- fi
1966
- export _APCORE_GRP="\${COMP_WORDS[1]}"
1967
- local group_cmds=$(${groupCmdsCmd})
1968
- COMPREPLY=( $(compgen -W "\${group_cmds}" -- \${cur}) )
1969
- return 0
1970
- fi
1971
- }
1972
- complete -F ${fn} ${quoted}
1973
- `;
1974
- }
1975
- function generateZshCompletion(progName) {
1976
- const fn = makeFunctionName(progName);
1977
- const quoted = shellQuote(progName);
1978
- const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e "process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})" 2>/dev/null`;
1979
- const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1980
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1981
- const ids=JSON.parse(m).map(x=>x.id);
1982
- const g=new Set(),t=[];
1983
- ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1984
- console.log([...g].sort().concat(t.sort()).join(' '))
1985
- " 2>/dev/null`;
1986
- const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1987
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1988
- const g=process.env._APCORE_GRP;
1989
- JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1990
- " 2>/dev/null`;
1991
- return `#compdef ${progName}
1992
-
1993
- ${fn}() {
1994
- local -a commands
1995
- commands=(
1996
- 'list:List available modules'
1997
- 'describe:Show module metadata and schema'
1998
- 'completion:Generate shell completion script'
1999
- 'init:Scaffolding commands'
2000
- 'man:Generate man page'
2001
- )
2002
-
2003
- _arguments -C \\
2004
- '1:command:->command' \\
2005
- '*::arg:->args'
2006
-
2007
- case "$state" in
2008
- command)
2009
- _describe -t commands '${progName} commands' commands
2010
- local -a groups_and_top
2011
- groups_and_top=($(${groupsAndTopCmd}))
2012
- compadd -a groups_and_top
2013
- ;;
2014
- args)
2015
- case "\${words[1]}" in
2016
- exec)
2017
- local modules
2018
- modules=($(${moduleListCmd}))
2019
- compadd -a modules
2020
- ;;
2021
- *)
2022
- export _APCORE_GRP="\${words[1]}"
2023
- local -a group_cmds
2024
- group_cmds=($(${groupCmdsCmd}))
2025
- compadd -a group_cmds
2026
- ;;
2027
- esac
2028
- ;;
2029
- esac
2077
+ return { ...stdinData, ...cliKwargsNonNull };
2078
+ }
2079
+ return cliKwargsNonNull;
2030
2080
  }
2031
-
2032
- compdef ${fn} ${quoted}
2033
- `;
2081
+ function readStdin() {
2082
+ return new Promise((resolve3, reject) => {
2083
+ const chunks = [];
2084
+ const onData = (chunk) => chunks.push(chunk);
2085
+ const onEnd = () => {
2086
+ cleanup();
2087
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
2088
+ };
2089
+ const onError = (err) => {
2090
+ cleanup();
2091
+ reject(err);
2092
+ };
2093
+ const cleanup = () => {
2094
+ process.stdin.removeListener("data", onData);
2095
+ process.stdin.removeListener("end", onEnd);
2096
+ process.stdin.removeListener("error", onError);
2097
+ };
2098
+ process.stdin.on("data", onData);
2099
+ process.stdin.on("end", onEnd);
2100
+ process.stdin.on("error", onError);
2101
+ process.stdin.resume();
2102
+ });
2034
2103
  }
2035
- function generateFishCompletion(progName) {
2036
- const quoted = shellQuote(progName);
2037
- const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e \\"process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})\\" 2>/dev/null`;
2038
- const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e \\"const m=require('fs').readFileSync('/dev/stdin','utf8');const ids=JSON.parse(m).map(x=>x.id);const g=new Set(),t=[];ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});console.log([...g].sort().concat(t.sort()).join('\\\\n'))\\" 2>/dev/null`;
2039
- return `# Fish completions for ${progName}
2040
- complete -c ${quoted} -n "__fish_use_subcommand" -a list -d "List available modules"
2041
- complete -c ${quoted} -n "__fish_use_subcommand" -a describe -d "Show module metadata and schema"
2042
- complete -c ${quoted} -n "__fish_use_subcommand" -a completion -d "Generate shell completion script"
2043
- complete -c ${quoted} -n "__fish_use_subcommand" -a init -d "Scaffolding commands"
2044
- complete -c ${quoted} -n "__fish_use_subcommand" -a man -d "Generate man page"
2045
- complete -c ${quoted} -n "__fish_use_subcommand" -a "(${groupsAndTopCmd})" -d "Module group"
2046
-
2047
- complete -c ${quoted} -n "__fish_seen_subcommand_from exec" -a "(${moduleListCmd})"
2048
-
2049
- function __apcore_group_cmds
2050
- set -l grp (commandline -opc)[2]
2051
- set -x _APCORE_GRP $grp
2052
- ${quoted} list --format json 2>/dev/null | node -e "
2053
- const m=require('fs').readFileSync('/dev/stdin','utf8');
2054
- const g=process.env._APCORE_GRP;
2055
- JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
2056
- " 2>/dev/null
2057
- end
2058
-
2059
- # Group subcommand completion \u2014 matches when position 1 is not a builtin
2060
- complete -c ${quoted} -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from list describe completion init man exec" -a "(__apcore_group_cmds)"
2061
- `;
2104
+ function reconvertEnumValues(kwargs, options) {
2105
+ const result = { ...kwargs };
2106
+ for (const opt of options) {
2107
+ if (!opt.enumOriginalTypes) continue;
2108
+ const paramName = opt.name;
2109
+ if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
2110
+ continue;
2111
+ }
2112
+ const strVal = String(result[paramName]);
2113
+ const origType = opt.enumOriginalTypes[strVal];
2114
+ if (origType === "int") {
2115
+ result[paramName] = parseInt(strVal, 10);
2116
+ } else if (origType === "float") {
2117
+ result[paramName] = parseFloat(strVal);
2118
+ } else if (origType === "bool") {
2119
+ result[paramName] = strVal.toLowerCase() === "true";
2120
+ }
2121
+ }
2122
+ return result;
2062
2123
  }
2063
- function buildSynopsis(command, progName, commandName) {
2064
- if (!command) {
2065
- return `\\fB${progName} ${commandName}\\fR [OPTIONS]`;
2124
+
2125
+ // src/cli.ts
2126
+ init_esm_shims();
2127
+ import { Command as Command3 } from "commander";
2128
+ var BUILTIN_COMMANDS = ["completion", "describe", "exec", "init", "list", "man"];
2129
+ var LazyModuleGroup = class {
2130
+ registry;
2131
+ executor;
2132
+ helpTextMaxLength;
2133
+ commandCache = /* @__PURE__ */ new Map();
2134
+ /** alias -> canonical module_id (populated lazily) */
2135
+ aliasMap = /* @__PURE__ */ new Map();
2136
+ /** module_id -> descriptor cache (populated during alias map build) */
2137
+ descriptorCache = /* @__PURE__ */ new Map();
2138
+ aliasMapBuilt = false;
2139
+ constructor(registry, executor, helpTextMaxLength = 1e3) {
2140
+ this.registry = registry;
2141
+ this.executor = executor;
2142
+ this.helpTextMaxLength = helpTextMaxLength;
2066
2143
  }
2067
- const parts = [`\\fB${progName} ${commandName}\\fR`];
2068
- for (const opt of command.options) {
2069
- const flag = opt.long ?? opt.short ?? "";
2070
- if (opt.isBoolean?.()) {
2071
- parts.push(`[${flag}]`);
2072
- } else if (opt.required) {
2073
- const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
2074
- parts.push(`${flag} \\fI${typeName}\\fR`);
2075
- } else {
2076
- const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
2077
- parts.push(`[${flag} \\fI${typeName}\\fR]`);
2144
+ /**
2145
+ * Build alias->module_id map from display overlay metadata.
2146
+ */
2147
+ buildAliasMap() {
2148
+ if (this.aliasMapBuilt) {
2149
+ return;
2150
+ }
2151
+ try {
2152
+ for (const descriptor of this.registry.listModules()) {
2153
+ const moduleId = descriptor.id;
2154
+ this.descriptorCache.set(moduleId, descriptor);
2155
+ const display = getDisplay(descriptor);
2156
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
2157
+ const cliAlias = cliDisplay.alias;
2158
+ if (cliAlias && cliAlias !== moduleId) {
2159
+ this.aliasMap.set(cliAlias, moduleId);
2160
+ }
2161
+ }
2162
+ this.aliasMapBuilt = true;
2163
+ } catch {
2164
+ warn("Failed to build alias map from registry");
2078
2165
  }
2079
2166
  }
2080
- for (const arg of command.registeredArguments ?? []) {
2081
- const meta = arg.name().toUpperCase();
2082
- if (arg.required) {
2083
- parts.push(`\\fI${meta}\\fR`);
2084
- } else {
2085
- parts.push(`[\\fI${meta}\\fR]`);
2167
+ /**
2168
+ * List all available command names from the Registry.
2169
+ */
2170
+ listCommands() {
2171
+ this.buildAliasMap();
2172
+ const reverse = /* @__PURE__ */ new Map();
2173
+ for (const [alias, moduleId] of this.aliasMap) {
2174
+ reverse.set(moduleId, alias);
2086
2175
  }
2176
+ const moduleIds = this.registry.listModules().map((m) => m.id);
2177
+ const names = moduleIds.map((mid) => reverse.get(mid) ?? mid);
2178
+ return [...new Set(names)].sort();
2087
2179
  }
2088
- return parts.join(" ");
2089
- }
2090
- function generateManPage(commandName, command, progName, version = SHELL_VERSION) {
2091
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2092
- const title = `${progName}-${commandName}`.toUpperCase();
2093
- const pkgLabel = `${progName} ${version}`;
2094
- const manualLabel = `${progName} Manual`;
2095
- const sections = [];
2096
- sections.push(`.TH "${title}" "1" "${today}" "${pkgLabel}" "${manualLabel}"`);
2097
- sections.push(".SH NAME");
2098
- const desc = command?.description() ?? commandName;
2099
- const nameDesc = desc.split("\n")[0].replace(/\.$/, "");
2100
- sections.push(`${progName}-${commandName} \\- ${nameDesc}`);
2101
- sections.push(".SH SYNOPSIS");
2102
- sections.push(buildSynopsis(command, progName, commandName));
2103
- if (command?.description()) {
2104
- sections.push(".SH DESCRIPTION");
2105
- sections.push(
2106
- command.description().replace(/\\/g, "\\\\").replace(/-/g, "\\-")
2107
- );
2180
+ /**
2181
+ * Get or lazily build a Commander Command for the given module.
2182
+ */
2183
+ getCommand(cmdName) {
2184
+ if (this.commandCache.has(cmdName)) {
2185
+ return this.commandCache.get(cmdName);
2186
+ }
2187
+ this.buildAliasMap();
2188
+ const moduleId = this.aliasMap.get(cmdName) ?? cmdName;
2189
+ let moduleDef = this.descriptorCache.get(moduleId);
2190
+ if (!moduleDef) {
2191
+ moduleDef = this.registry.getModule(moduleId) ?? void 0;
2192
+ }
2193
+ if (!moduleDef) {
2194
+ return null;
2195
+ }
2196
+ const cmd = buildModuleCommand(moduleDef, this.executor, this.helpTextMaxLength, cmdName);
2197
+ this.commandCache.set(cmdName, cmd);
2198
+ return cmd;
2108
2199
  }
2109
- if (command && command.options.length > 0) {
2110
- sections.push(".SH OPTIONS");
2111
- for (const opt of command.options) {
2112
- const flag = [opt.short, opt.long].filter(Boolean).join(", ");
2113
- sections.push(".TP");
2114
- if (opt.isBoolean?.()) {
2115
- sections.push(`\\fB${flag}\\fR`);
2116
- } else {
2117
- sections.push(`\\fB${flag}\\fR \\fIVALUE\\fR`);
2118
- }
2119
- if (opt.description) {
2120
- sections.push(opt.description);
2121
- }
2122
- if (opt.defaultValue !== void 0 && !opt.isBoolean?.()) {
2123
- sections.push(`Default: ${opt.defaultValue}.`);
2124
- }
2200
+ };
2201
+ var LazyGroup = class {
2202
+ members;
2203
+ _executor;
2204
+ _helpTextMaxLength;
2205
+ _cmdCache = /* @__PURE__ */ new Map();
2206
+ command;
2207
+ constructor(members, executor, name, helpTextMaxLength = 1e3) {
2208
+ this.members = members;
2209
+ this._executor = executor;
2210
+ this._helpTextMaxLength = helpTextMaxLength;
2211
+ this.command = new Command3(name).description(`${name} commands`);
2212
+ for (const [cmdName, [, descriptor]] of this.members) {
2213
+ const cmd = buildModuleCommand(
2214
+ descriptor,
2215
+ this._executor,
2216
+ this._helpTextMaxLength,
2217
+ cmdName
2218
+ );
2219
+ this._cmdCache.set(cmdName, cmd);
2220
+ this.command.addCommand(cmd);
2125
2221
  }
2126
2222
  }
2127
- sections.push(".SH ENVIRONMENT");
2128
- sections.push(".TP");
2129
- sections.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
2130
- sections.push(
2131
- "Path to the apcore extensions directory. Overrides the default \\fI./extensions\\fR."
2132
- );
2133
- sections.push(".TP");
2134
- sections.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
2135
- sections.push(
2136
- "Set to \\fB1\\fR to bypass approval prompts for modules that require human-in-the-loop confirmation."
2137
- );
2138
- sections.push(".TP");
2139
- sections.push("\\fBAPCORE_CLI_LOGGING_LEVEL\\fR");
2140
- sections.push(
2141
- "CLI-specific logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Takes priority over \\fBAPCORE_LOGGING_LEVEL\\fR. Default: WARNING."
2142
- );
2143
- sections.push(".TP");
2144
- sections.push("\\fBAPCORE_LOGGING_LEVEL\\fR");
2145
- sections.push(
2146
- "Global apcore logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Used as fallback when \\fBAPCORE_CLI_LOGGING_LEVEL\\fR is not set. Default: WARNING."
2147
- );
2148
- sections.push(".TP");
2149
- sections.push("\\fBAPCORE_AUTH_API_KEY\\fR");
2150
- sections.push(
2151
- "API key for authenticating with the apcore registry."
2152
- );
2153
- sections.push(".SH EXIT CODES");
2154
- const exitCodes = [
2155
- ["0", "Success."],
2156
- ["1", "Module execution error."],
2157
- ["2", "Invalid CLI input or missing argument."],
2158
- ["44", "Module not found, disabled, or failed to load."],
2159
- ["45", "Input failed JSON Schema validation."],
2160
- [
2161
- "46",
2162
- "Approval denied, timed out, or no interactive terminal available."
2163
- ],
2164
- [
2165
- "47",
2166
- "Configuration error (extensions directory not found or unreadable)."
2167
- ],
2168
- ["48", "Schema contains a circular \\fB$ref\\fR."],
2169
- ["77", "ACL denied \u2014 insufficient permissions for this module."],
2170
- ["130", "Execution cancelled by user (SIGINT / Ctrl\\-C)."]
2171
- ];
2172
- for (const [code, meaning] of exitCodes) {
2173
- sections.push(`.TP
2174
- \\fB${code}\\fR
2175
- ${meaning}`);
2223
+ listCommands() {
2224
+ return [...this.members.keys()].sort();
2176
2225
  }
2177
- sections.push(".SH SEE ALSO");
2178
- sections.push(
2179
- [
2180
- `\\fB${progName}\\fR(1)`,
2181
- `\\fB${progName}\\-list\\fR(1)`,
2182
- `\\fB${progName}\\-describe\\fR(1)`,
2183
- `\\fB${progName}\\-completion\\fR(1)`
2184
- ].join(", ")
2185
- );
2186
- return sections.join("\n");
2187
- }
2188
- function roffEscape(s) {
2189
- return s.replace(/\\/g, "\\\\").replace(/-/g, "\\-").replace(/'/g, "\\(aq");
2190
- }
2191
- function buildProgramManPage(program, progName, version, description, docsUrl2) {
2192
- const help = new Help();
2193
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2194
- const s = [];
2195
- const resolvedDesc = description ?? program.description() ?? `${progName} CLI`;
2196
- s.push(`.TH "${progName.toUpperCase()}" "1" "${today}" "${progName} ${version}" "${progName} Manual"`);
2197
- s.push(".SH NAME");
2198
- s.push(`${progName} \\- ${roffEscape(resolvedDesc)}`);
2199
- s.push(".SH SYNOPSIS");
2200
- s.push(`\\fB${progName}\\fR [\\fIglobal\\-options\\fR] \\fIcommand\\fR [\\fIcommand\\-options\\fR]`);
2201
- if (resolvedDesc) {
2202
- s.push(".SH DESCRIPTION");
2203
- s.push(roffEscape(resolvedDesc));
2226
+ getCommand(cmdName) {
2227
+ if (this._cmdCache.has(cmdName)) {
2228
+ return this._cmdCache.get(cmdName);
2229
+ }
2230
+ const entry = this.members.get(cmdName);
2231
+ if (!entry) {
2232
+ return null;
2233
+ }
2234
+ const [, descriptor] = entry;
2235
+ const cmd = buildModuleCommand(
2236
+ descriptor,
2237
+ this._executor,
2238
+ this._helpTextMaxLength,
2239
+ cmdName
2240
+ );
2241
+ this._cmdCache.set(cmdName, cmd);
2242
+ return cmd;
2204
2243
  }
2205
- const globalOpts = help.visibleOptions(program).filter((o) => !["help", "version", "all", "man"].includes(o.long?.replace("--", "") ?? ""));
2206
- if (globalOpts.length > 0) {
2207
- s.push(".SH GLOBAL OPTIONS");
2208
- for (const opt of globalOpts) {
2209
- const flag = [opt.short, opt.long].filter(Boolean).join(", ");
2210
- s.push(".TP");
2211
- s.push(`\\fB${roffEscape(flag)}\\fR`);
2212
- if (opt.description) s.push(roffEscape(opt.description));
2244
+ };
2245
+ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
2246
+ /** groupName -> { cmdName -> [moduleId, descriptor] } */
2247
+ groupMap = /* @__PURE__ */ new Map();
2248
+ /** cmdName -> [moduleId, descriptor] for top-level (ungrouped) modules */
2249
+ topLevelModules = /* @__PURE__ */ new Map();
2250
+ /** Cached LazyGroup instances */
2251
+ groupCache = /* @__PURE__ */ new Map();
2252
+ groupMapBuilt = false;
2253
+ /**
2254
+ * Determine (groupName | null, commandName) for a module from its display overlay.
2255
+ */
2256
+ static resolveGroup(moduleId, descriptor) {
2257
+ if (!moduleId) {
2258
+ warn("Empty module_id encountered in resolveGroup");
2259
+ return [null, ""];
2260
+ }
2261
+ const display = getDisplay(descriptor);
2262
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
2263
+ const explicitGroup = cliDisplay.group;
2264
+ if (typeof explicitGroup === "string" && explicitGroup !== "") {
2265
+ return [explicitGroup, cliDisplay.alias ?? moduleId];
2266
+ }
2267
+ if (explicitGroup === "") {
2268
+ return [null, cliDisplay.alias ?? moduleId];
2269
+ }
2270
+ const cliName = cliDisplay.alias ?? moduleId;
2271
+ if (cliName.includes(".")) {
2272
+ const dotIdx = cliName.indexOf(".");
2273
+ const group = cliName.substring(0, dotIdx);
2274
+ const cmd = cliName.substring(dotIdx + 1);
2275
+ return [group, cmd];
2213
2276
  }
2277
+ return [null, cliName];
2214
2278
  }
2215
- const allCommands = help.visibleCommands(program);
2216
- if (allCommands.length > 0) {
2217
- s.push(".SH COMMANDS");
2218
- for (const cmd of allCommands) {
2219
- if (cmd.name() === "help") continue;
2220
- const desc = help.subcommandDescription(cmd);
2221
- s.push(".TP");
2222
- s.push(`\\fB${progName} ${roffEscape(cmd.name())}\\fR`);
2223
- if (desc) s.push(roffEscape(desc));
2224
- const cmdHelp = new Help();
2225
- const opts = cmdHelp.visibleOptions(cmd).filter((o) => !["help", "version"].includes(o.long?.replace("--", "") ?? ""));
2226
- for (const opt of opts) {
2227
- const flag = [opt.short, opt.long].filter(Boolean).join(", ");
2228
- s.push(".RS");
2229
- s.push(".TP");
2230
- s.push(`\\fB${roffEscape(flag)}\\fR`);
2231
- if (opt.description) s.push(roffEscape(opt.description));
2232
- s.push(".RE");
2279
+ /**
2280
+ * Build the group map from registry modules.
2281
+ */
2282
+ buildGroupMap() {
2283
+ if (this.groupMapBuilt) {
2284
+ return;
2285
+ }
2286
+ try {
2287
+ this.buildAliasMap();
2288
+ for (const descriptor of this.registry.listModules()) {
2289
+ const moduleId = descriptor.id;
2290
+ const cached = this.descriptorCache.get(moduleId);
2291
+ if (!cached) {
2292
+ continue;
2293
+ }
2294
+ const [group, cmd] = _GroupedModuleGroup.resolveGroup(moduleId, cached);
2295
+ if (group === null) {
2296
+ this.topLevelModules.set(cmd, [moduleId, cached]);
2297
+ } else if (!/^[a-z][a-z0-9_-]*$/.test(group)) {
2298
+ warn(
2299
+ `Module '${moduleId}': group name '${group}' is not shell-safe \u2014 treating as top-level.`
2300
+ );
2301
+ this.topLevelModules.set(cmd, [moduleId, cached]);
2302
+ } else {
2303
+ if (!this.groupMap.has(group)) {
2304
+ this.groupMap.set(group, /* @__PURE__ */ new Map());
2305
+ }
2306
+ this.groupMap.get(group).set(cmd, [moduleId, cached]);
2307
+ }
2233
2308
  }
2234
- const subCmds = cmdHelp.visibleCommands(cmd).filter((c) => c.name() !== "help");
2235
- for (const sub of subCmds) {
2236
- const subDesc = help.subcommandDescription(sub);
2237
- s.push(".TP");
2238
- s.push(`\\fB${progName} ${roffEscape(cmd.name())} ${roffEscape(sub.name())}\\fR`);
2239
- if (subDesc) s.push(roffEscape(subDesc));
2240
- const subOpts = cmdHelp.visibleOptions(sub).filter((o) => !["help", "version"].includes(o.long?.replace("--", "") ?? ""));
2241
- for (const opt of subOpts) {
2242
- const flag = [opt.short, opt.long].filter(Boolean).join(", ");
2243
- s.push(".RS");
2244
- s.push(".TP");
2245
- s.push(`\\fB${roffEscape(flag)}\\fR`);
2246
- if (opt.description) s.push(roffEscape(opt.description));
2247
- s.push(".RE");
2309
+ for (const groupName of this.groupMap.keys()) {
2310
+ if (BUILTIN_COMMANDS.includes(groupName)) {
2311
+ warn(
2312
+ `Group name '${groupName}' collides with a built-in command and will be ignored`
2313
+ );
2248
2314
  }
2249
2315
  }
2316
+ this.groupMapBuilt = true;
2317
+ } catch {
2318
+ warn("Failed to build group map");
2250
2319
  }
2251
2320
  }
2252
- s.push(".SH ENVIRONMENT");
2253
- s.push(".TP");
2254
- s.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
2255
- s.push("Path to the apcore extensions directory.");
2256
- s.push(".TP");
2257
- s.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
2258
- s.push("Set to \\fB1\\fR to bypass approval prompts.");
2259
- s.push(".TP");
2260
- s.push("\\fBAPCORE_CLI_LOGGING_LEVEL\\fR");
2261
- s.push("CLI\\-specific logging verbosity (DEBUG|INFO|WARNING|ERROR).");
2262
- s.push(".SH EXIT CODES");
2263
- const exitCodes = [
2264
- ["0", "Success."],
2265
- ["1", "Module execution error."],
2266
- ["2", "Invalid CLI input or missing argument."],
2267
- ["44", "Module not found, disabled, or failed to load."],
2268
- ["45", "Input failed JSON Schema validation."],
2269
- ["46", "Approval denied or timed out."],
2270
- ["47", "Configuration error."],
2271
- ["77", "ACL denied."],
2272
- ["130", "Cancelled by user (SIGINT)."]
2273
- ];
2274
- for (const [code, meaning] of exitCodes) {
2275
- s.push(`.TP
2276
- \\fB${code}\\fR
2277
- ${meaning}`);
2321
+ /**
2322
+ * List all available command names: builtins + group names + top-level module names.
2323
+ */
2324
+ listCommands() {
2325
+ this.buildGroupMap();
2326
+ const groupNames = [...this.groupMap.keys()].filter(
2327
+ (g) => !BUILTIN_COMMANDS.includes(g)
2328
+ );
2329
+ const topNames = [...this.topLevelModules.keys()];
2330
+ return [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...groupNames, ...topNames])].sort();
2278
2331
  }
2279
- s.push(".SH SEE ALSO");
2280
- s.push(`\\fB${progName} \\-\\-help \\-\\-verbose\\fR for full option list.`);
2281
- if (docsUrl2) {
2282
- s.push(`.PP
2283
- Full documentation at \\fI${roffEscape(docsUrl2)}\\fR`);
2332
+ /**
2333
+ * Get a command by name: check builtins -> group cache -> group map -> top-level modules.
2334
+ */
2335
+ getCommand(cmdName) {
2336
+ this.buildGroupMap();
2337
+ if (this.groupCache.has(cmdName)) {
2338
+ return this.groupCache.get(cmdName).command;
2339
+ }
2340
+ if (this.groupMap.has(cmdName)) {
2341
+ const lazyGrp = new LazyGroup(
2342
+ this.groupMap.get(cmdName),
2343
+ this.executor,
2344
+ cmdName,
2345
+ this.helpTextMaxLength
2346
+ );
2347
+ this.groupCache.set(cmdName, lazyGrp);
2348
+ return lazyGrp.command;
2349
+ }
2350
+ if (this.topLevelModules.has(cmdName)) {
2351
+ if (this.commandCache.has(cmdName)) {
2352
+ return this.commandCache.get(cmdName);
2353
+ }
2354
+ const [, descriptor] = this.topLevelModules.get(cmdName);
2355
+ const cmd = buildModuleCommand(
2356
+ descriptor,
2357
+ this.executor,
2358
+ this.helpTextMaxLength,
2359
+ cmdName
2360
+ );
2361
+ this.commandCache.set(cmdName, cmd);
2362
+ return cmd;
2363
+ }
2364
+ return null;
2365
+ }
2366
+ /** Expose groupMap for testing. */
2367
+ getGroupMap() {
2368
+ return this.groupMap;
2369
+ }
2370
+ /** Expose topLevelModules for testing. */
2371
+ getTopLevelModules() {
2372
+ return this.topLevelModules;
2373
+ }
2374
+ /** Expose groupMapBuilt for testing. */
2375
+ isGroupMapBuilt() {
2376
+ return this.groupMapBuilt;
2377
+ }
2378
+ };
2379
+
2380
+ // src/discovery.ts
2381
+ init_esm_shims();
2382
+ init_errors();
2383
+ import { Command as Command4 } from "commander";
2384
+ var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
2385
+ function validateTag(tag) {
2386
+ if (!TAG_PATTERN.test(tag)) {
2387
+ process.stderr.write(
2388
+ `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
2389
+ `
2390
+ );
2391
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2284
2392
  }
2285
- return s.join("\n");
2286
2393
  }
2287
- function configureManHelp(program, progName, version, description, docsUrl2) {
2288
- const manOpt = new Option2("--man", "Output man page in roff format (use with --help)").hideHelp();
2289
- program.addOption(manOpt);
2290
- program.addHelpText("beforeAll", () => {
2291
- if (program.opts().man) {
2292
- process.stdout.write(buildProgramManPage(program, progName, version, description, docsUrl2));
2293
- process.stdout.write("\n");
2294
- process.exit(0);
2295
- }
2296
- return "";
2297
- });
2394
+ function collectTag(value, previous) {
2395
+ return previous.concat([value]);
2298
2396
  }
2299
- function registerShellCommands(cli, progName = "apcore-cli") {
2300
- const completionCmd = new Command4("completion").description(
2301
- "Generate a shell completion script and print it to stdout."
2302
- ).argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
2303
- const validShells = ["bash", "zsh", "fish"];
2304
- if (!validShells.includes(shell)) {
2305
- process.stderr.write(
2306
- `Error: Unknown shell '${shell}'. Expected: bash, zsh, or fish.
2307
- `
2308
- );
2309
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2397
+ function registerDiscoveryCommands(cli, registry) {
2398
+ const listCmd = new Command4("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--format <format>", "Output format.", void 0).action((opts) => {
2399
+ for (const t of opts.tag) {
2400
+ validateTag(t);
2310
2401
  }
2311
- const resolved = cli.name() || progName;
2312
- const generators = {
2313
- bash: () => generateBashCompletion(resolved),
2314
- zsh: () => generateZshCompletion(resolved),
2315
- fish: () => generateFishCompletion(resolved)
2316
- };
2317
- process.stdout.write(generators[shell]());
2402
+ const modules = [];
2403
+ for (const m of registry.listModules()) {
2404
+ modules.push(m);
2405
+ }
2406
+ let filtered = modules;
2407
+ if (opts.tag.length > 0) {
2408
+ const filterTags = new Set(opts.tag);
2409
+ filtered = modules.filter((m) => {
2410
+ const mTags = m.tags ?? [];
2411
+ return [...filterTags].every((t) => mTags.includes(t));
2412
+ });
2413
+ }
2414
+ const fmt = resolveFormat(opts.format);
2415
+ formatModuleList(filtered, fmt, opts.tag.length > 0 ? opts.tag : void 0);
2318
2416
  });
2319
- cli.addCommand(completionCmd);
2320
- const manCmd = new Command4("man").description("Generate a roff man page for COMMAND and print it to stdout.").argument("<command>", "Command to generate man page for").action((commandName) => {
2321
- const knownBuiltins = /* @__PURE__ */ new Set(["completion", "describe", "exec", "init", "list", "man"]);
2322
- const cmd = cli.commands.find((c) => c.name() === commandName) ?? null;
2323
- if (!cmd && !knownBuiltins.has(commandName)) {
2417
+ cli.addCommand(listCmd);
2418
+ const describeCmd = new Command4("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").option("--format <format>", "Output format.", void 0).action((moduleId, opts) => {
2419
+ validateModuleId(moduleId);
2420
+ const moduleDef = registry.getModule(moduleId);
2421
+ if (!moduleDef) {
2324
2422
  process.stderr.write(
2325
- `Error: Unknown command '${commandName}'.
2423
+ `Error: Module '${moduleId}' not found.
2326
2424
  `
2327
2425
  );
2328
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2426
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2329
2427
  }
2330
- const resolved = cli.name() || progName;
2331
- const roff = generateManPage(commandName, cmd, resolved);
2332
- process.stdout.write(roff);
2428
+ const fmt = resolveFormat(opts.format);
2429
+ formatModuleDetail(moduleDef, fmt);
2333
2430
  });
2334
- cli.addCommand(manCmd);
2431
+ cli.addCommand(describeCmd);
2335
2432
  }
2336
2433
 
2337
2434
  // src/index.ts
@@ -2379,6 +2476,7 @@ export {
2379
2476
  main,
2380
2477
  mapType,
2381
2478
  reconvertEnumValues,
2479
+ registerConfigNamespace,
2382
2480
  registerDiscoveryCommands,
2383
2481
  registerInitCommand,
2384
2482
  registerShellCommands,