apcore-cli 0.3.2 → 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 } 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,975 +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 VERSION = "0.0.0";
1224
- try {
1225
- const pkg = JSON.parse(readFileSync(path5.resolve(__dirname2, "../package.json"), "utf-8"));
1226
- VERSION = pkg.version;
1227
- } catch {
1228
- }
1229
- function createCli(extensionsDir, progName) {
1230
- const resolvedProgName = progName ?? path5.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1231
- const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
1232
- setLogLevel(cliLogLevel);
1233
- 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");
1234
- const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
1235
- void resolvedExtDir;
1236
- registerInitCommand(program);
1237
- program.hook("preAction", async (thisCommand) => {
1238
- const opts = thisCommand.opts();
1239
- const commandsDir = opts.commandsDir;
1240
- const bindingPath = opts.binding;
1241
- await applyToolkitIntegration(commandsDir, bindingPath);
1242
- });
1243
- return program;
1244
- }
1245
- async function applyToolkitIntegration(commandsDir, bindingPath) {
1246
- if (!commandsDir && !bindingPath) {
1247
- return;
1248
- }
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() {
1249
1269
  try {
1250
- const toolkitModule = "apcore-toolkit";
1251
- const toolkit = await import(
1252
- /* @vite-ignore */
1253
- toolkitModule
1254
- );
1255
- if (commandsDir) {
1256
- console.warn("Convention scanning not yet available in TypeScript toolkit");
1257
- }
1258
- if (bindingPath) {
1259
- const resolver = new toolkit.DisplayResolver();
1260
- 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
+ });
1261
1282
  }
1262
1283
  } catch {
1263
- console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
1264
- }
1265
- }
1266
- function main(progName) {
1267
- const program = createCli(void 0, progName);
1268
- try {
1269
- program.parse(process.argv);
1270
- } catch (error2) {
1271
- if (error2 instanceof CommanderError) {
1272
- process.exit(error2.exitCode);
1273
- }
1274
- const code = exitCodeForError(error2);
1275
- if (error2 instanceof Error) {
1276
- process.stderr.write(`Error: ${error2.message}
1277
- `);
1278
- }
1279
- process.exit(code);
1280
1284
  }
1281
1285
  }
1282
- function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName) {
1283
- const moduleId = moduleDef.id;
1284
- let resolvedSchema = {};
1285
- let schemaOptions = [];
1286
- const display = getDisplay(moduleDef);
1287
- const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1288
- const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
1289
- const cmdHelp = cliDisplay.description ?? moduleDef.description;
1290
- const inputSchema = moduleDef.inputSchema;
1291
- if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
1292
- try {
1293
- resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
1294
- } catch {
1295
- resolvedSchema = inputSchema;
1296
- }
1297
- schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
1298
- }
1299
- const cmd = new Command(effectiveCmdName).description(cmdHelp);
1300
- cmd.option("--input <source>", "Read input from STDIN ('-')");
1301
- cmd.option("-y, --yes", "Bypass approval prompts", false);
1302
- cmd.option("--large-input", "Allow STDIN input larger than 10MB", false);
1303
- cmd.option("--format <format>", "Output format (json|table)");
1304
- cmd.option("--sandbox", "Run module in subprocess sandbox", false);
1305
- for (const opt of schemaOptions) {
1306
- if (opt.parseArg) {
1307
- cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
1308
- } else {
1309
- cmd.option(opt.flags, opt.description, opt.defaultValue);
1310
- }
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";
1311
1294
  }
1312
- cmd.action(async (options) => {
1313
- const stdinFlag = options.input;
1314
- const autoApprove = options.yes;
1315
- const largeInput = options.largeInput;
1316
- const outputFormat = options.format;
1317
- const sandboxEnabled = options.sandbox;
1318
- const schemaKwargs = {};
1319
- const builtinKeys = /* @__PURE__ */ new Set(["input", "yes", "largeInput", "format", "sandbox"]);
1320
- for (const [k, v] of Object.entries(options)) {
1321
- if (!builtinKeys.has(k)) {
1322
- schemaKwargs[k] = v;
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;
1323
1304
  }
1324
1305
  }
1325
- try {
1326
- const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
1327
- const reconverted = reconvertEnumValues(merged, schemaOptions);
1328
- await checkApproval(moduleDef, autoApprove);
1329
- const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
1330
- const sandbox = new Sandbox2(sandboxEnabled);
1331
- const startTime = performance.now();
1332
- const result = await sandbox.execute(moduleId, reconverted, executor);
1333
- const durationMs = Math.round(performance.now() - startTime);
1334
- const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
1335
- const auditLogger = getAuditLogger2();
1336
- if (auditLogger) {
1337
- auditLogger.logExecution(moduleId, reconverted, "success", 0, durationMs);
1338
- }
1339
- formatExecResult(result, outputFormat);
1340
- } catch (err) {
1341
- const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
1342
- const auditLogger = getAuditLogger2();
1343
- const code = exitCodeForError(err);
1344
- if (auditLogger) {
1345
- auditLogger.logExecution(moduleId, {}, "error", code, 0);
1306
+ if (envVar) {
1307
+ const envValue = process.env[envVar];
1308
+ if (envValue !== void 0 && envValue !== "") {
1309
+ return envValue;
1346
1310
  }
1347
- if (err instanceof Error) {
1348
- process.stderr.write(`Error: ${err.message}
1349
- `);
1311
+ }
1312
+ const fileValue = this.resolveFromFile(key);
1313
+ if (fileValue !== void 0) {
1314
+ return fileValue;
1315
+ }
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;
1350
1321
  }
1351
- process.exit(code);
1352
1322
  }
1353
- });
1354
- return cmd;
1355
- }
1356
- function validateModuleId(moduleId) {
1357
- if (moduleId.length > 128) {
1358
- process.stderr.write(
1359
- `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
1360
- `
1361
- );
1362
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1363
- }
1364
- if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
1365
- process.stderr.write(
1366
- `Error: Invalid module ID format: '${moduleId}'.
1367
- `
1368
- );
1369
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1323
+ return DEFAULTS[key];
1370
1324
  }
1371
- }
1372
- async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
1373
- const cliKwargsNonNull = {};
1374
- for (const [k, v] of Object.entries(cliKwargs)) {
1375
- if (v !== null && v !== void 0) {
1376
- cliKwargsNonNull[k] = v;
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;
1377
1332
  }
1333
+ if (this.fileCache === null) {
1334
+ return void 0;
1335
+ }
1336
+ return this.fileCache[key];
1378
1337
  }
1379
- if (!stdinFlag) {
1380
- return cliKwargsNonNull;
1381
- }
1382
- if (stdinFlag === "-") {
1383
- const raw = await readStdin();
1384
- const rawSize = Buffer.byteLength(raw, "utf-8");
1385
- if (rawSize > 10485760 && !largeInput) {
1386
- process.stderr.write(
1387
- "Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
1338
+ /**
1339
+ * Load and flatten a YAML config file.
1340
+ */
1341
+ loadConfigFile() {
1342
+ let content;
1343
+ try {
1344
+ content = fs2.readFileSync(this.configPath, "utf-8");
1345
+ } catch (err) {
1346
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1347
+ return null;
1348
+ }
1349
+ console.warn(
1350
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1388
1351
  );
1389
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1390
- }
1391
- if (!raw) {
1392
- return cliKwargsNonNull;
1352
+ return null;
1393
1353
  }
1394
- let stdinData;
1354
+ let parsed;
1395
1355
  try {
1396
- stdinData = JSON.parse(raw);
1356
+ parsed = yaml.load(content);
1397
1357
  } catch {
1398
- process.stderr.write(
1399
- "Error: STDIN does not contain valid JSON.\n"
1358
+ console.warn(
1359
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1400
1360
  );
1401
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1361
+ return null;
1402
1362
  }
1403
- if (typeof stdinData !== "object" || stdinData === null || Array.isArray(stdinData)) {
1404
- process.stderr.write(
1405
- `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? "array" : typeof stdinData}.
1406
- `
1363
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1364
+ console.warn(
1365
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1407
1366
  );
1408
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1367
+ return null;
1409
1368
  }
1410
- return { ...stdinData, ...cliKwargsNonNull };
1369
+ return this.flattenDict(parsed);
1411
1370
  }
1412
- return cliKwargsNonNull;
1413
- }
1414
- function readStdin() {
1415
- return new Promise((resolve3, reject) => {
1416
- const chunks = [];
1417
- const onData = (chunk) => chunks.push(chunk);
1418
- const onEnd = () => {
1419
- cleanup();
1420
- resolve3(Buffer.concat(chunks).toString("utf-8"));
1421
- };
1422
- const onError = (err) => {
1423
- cleanup();
1424
- reject(err);
1425
- };
1426
- const cleanup = () => {
1427
- process.stdin.removeListener("data", onData);
1428
- process.stdin.removeListener("end", onEnd);
1429
- process.stdin.removeListener("error", onError);
1430
- };
1431
- process.stdin.on("data", onData);
1432
- process.stdin.on("end", onEnd);
1433
- process.stdin.on("error", onError);
1434
- process.stdin.resume();
1435
- });
1436
- }
1437
- function reconvertEnumValues(kwargs, options) {
1438
- const result = { ...kwargs };
1439
- for (const opt of options) {
1440
- if (!opt.enumOriginalTypes) continue;
1441
- const paramName = opt.name;
1442
- if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
1443
- continue;
1444
- }
1445
- const strVal = String(result[paramName]);
1446
- const origType = opt.enumOriginalTypes[strVal];
1447
- if (origType === "int") {
1448
- result[paramName] = parseInt(strVal, 10);
1449
- } else if (origType === "float") {
1450
- result[paramName] = parseFloat(strVal);
1451
- } else if (origType === "bool") {
1452
- result[paramName] = strVal.toLowerCase() === "true";
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
+ }
1453
1386
  }
1387
+ return result;
1454
1388
  }
1455
- return result;
1456
- }
1389
+ };
1457
1390
 
1458
- // src/cli.ts
1391
+ // src/shell.ts
1459
1392
  init_esm_shims();
1460
- import { Command as Command2 } from "commander";
1461
- var BUILTIN_COMMANDS = ["completion", "describe", "exec", "init", "list", "man"];
1462
- var LazyModuleGroup = class {
1463
- registry;
1464
- executor;
1465
- helpTextMaxLength;
1466
- commandCache = /* @__PURE__ */ new Map();
1467
- /** alias -> canonical module_id (populated lazily) */
1468
- aliasMap = /* @__PURE__ */ new Map();
1469
- /** module_id -> descriptor cache (populated during alias map build) */
1470
- descriptorCache = /* @__PURE__ */ new Map();
1471
- aliasMapBuilt = false;
1472
- constructor(registry, executor, helpTextMaxLength = 1e3) {
1473
- this.registry = registry;
1474
- this.executor = executor;
1475
- this.helpTextMaxLength = helpTextMaxLength;
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 {
1405
+ }
1406
+ function makeFunctionName(progName) {
1407
+ return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
1408
+ }
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
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}
1473
+
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]`;
1476
1547
  }
1477
- /**
1478
- * Build alias->module_id map from display overlay metadata.
1479
- */
1480
- buildAliasMap() {
1481
- if (this.aliasMapBuilt) {
1482
- return;
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]`);
1483
1559
  }
1484
- try {
1485
- for (const descriptor of this.registry.listModules()) {
1486
- const moduleId = descriptor.id;
1487
- this.descriptorCache.set(moduleId, descriptor);
1488
- const display = getDisplay(descriptor);
1489
- const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1490
- const cliAlias = cliDisplay.alias;
1491
- if (cliAlias && cliAlias !== moduleId) {
1492
- this.aliasMap.set(cliAlias, moduleId);
1493
- }
1494
- }
1495
- this.aliasMapBuilt = true;
1496
- } catch {
1497
- warn("Failed to build alias map from registry");
1560
+ }
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]`);
1498
1567
  }
1499
1568
  }
1500
- /**
1501
- * List all available command names from the Registry.
1502
- */
1503
- listCommands() {
1504
- this.buildAliasMap();
1505
- const reverse = /* @__PURE__ */ new Map();
1506
- for (const [alias, moduleId] of this.aliasMap) {
1507
- reverse.set(moduleId, alias);
1508
- }
1509
- const moduleIds = this.registry.listModules().map((m) => m.id);
1510
- const names = moduleIds.map((mid) => reverse.get(mid) ?? mid);
1511
- return [...new Set(names)].sort();
1512
- }
1513
- /**
1514
- * Get or lazily build a Commander Command for the given module.
1515
- */
1516
- getCommand(cmdName) {
1517
- if (this.commandCache.has(cmdName)) {
1518
- return this.commandCache.get(cmdName);
1519
- }
1520
- this.buildAliasMap();
1521
- const moduleId = this.aliasMap.get(cmdName) ?? cmdName;
1522
- let moduleDef = this.descriptorCache.get(moduleId);
1523
- if (!moduleDef) {
1524
- moduleDef = this.registry.getModule(moduleId) ?? void 0;
1525
- }
1526
- if (!moduleDef) {
1527
- return null;
1528
- }
1529
- const cmd = buildModuleCommand(moduleDef, this.executor, this.helpTextMaxLength, cmdName);
1530
- this.commandCache.set(cmdName, cmd);
1531
- 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
+ );
1532
1589
  }
1533
- };
1534
- var LazyGroup = class {
1535
- members;
1536
- _executor;
1537
- _helpTextMaxLength;
1538
- _cmdCache = /* @__PURE__ */ new Map();
1539
- command;
1540
- constructor(members, executor, name, helpTextMaxLength = 1e3) {
1541
- this.members = members;
1542
- this._executor = executor;
1543
- this._helpTextMaxLength = helpTextMaxLength;
1544
- this.command = new Command2(name).description(`${name} commands`);
1545
- for (const [cmdName, [, descriptor]] of this.members) {
1546
- const cmd = buildModuleCommand(
1547
- descriptor,
1548
- this._executor,
1549
- this._helpTextMaxLength,
1550
- cmdName
1551
- );
1552
- this._cmdCache.set(cmdName, cmd);
1553
- 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
+ }
1554
1606
  }
1555
1607
  }
1556
- listCommands() {
1557
- 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}`);
1558
1657
  }
1559
- getCommand(cmdName) {
1560
- if (this._cmdCache.has(cmdName)) {
1561
- return this._cmdCache.get(cmdName);
1562
- }
1563
- const entry = this.members.get(cmdName);
1564
- if (!entry) {
1565
- return null;
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));
1685
+ }
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));
1694
+ }
1695
+ }
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");
1729
+ }
1730
+ }
1566
1731
  }
1567
- const [, descriptor] = entry;
1568
- const cmd = buildModuleCommand(
1569
- descriptor,
1570
- this._executor,
1571
- this._helpTextMaxLength,
1572
- cmdName
1573
- );
1574
- this._cmdCache.set(cmdName, cmd);
1575
- return cmd;
1576
1732
  }
1577
- };
1578
- var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
1579
- /** groupName -> { cmdName -> [moduleId, descriptor] } */
1580
- groupMap = /* @__PURE__ */ new Map();
1581
- /** cmdName -> [moduleId, descriptor] for top-level (ungrouped) modules */
1582
- topLevelModules = /* @__PURE__ */ new Map();
1583
- /** Cached LazyGroup instances */
1584
- groupCache = /* @__PURE__ */ new Map();
1585
- groupMapBuilt = false;
1586
- /**
1587
- * Determine (groupName | null, commandName) for a module from its display overlay.
1588
- */
1589
- static resolveGroup(moduleId, descriptor) {
1590
- if (!moduleId) {
1591
- warn("Empty module_id encountered in resolveGroup");
1592
- return [null, ""];
1593
- }
1594
- const display = getDisplay(descriptor);
1595
- const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1596
- const explicitGroup = cliDisplay.group;
1597
- if (typeof explicitGroup === "string" && explicitGroup !== "") {
1598
- return [explicitGroup, cliDisplay.alias ?? moduleId];
1599
- }
1600
- if (explicitGroup === "") {
1601
- return [null, cliDisplay.alias ?? moduleId];
1602
- }
1603
- const cliName = cliDisplay.alias ?? moduleId;
1604
- if (cliName.includes(".")) {
1605
- const dotIdx = cliName.indexOf(".");
1606
- const group = cliName.substring(0, dotIdx);
1607
- const cmd = cliName.substring(dotIdx + 1);
1608
- return [group, cmd];
1609
- }
1610
- return [null, cliName];
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}`);
1611
1759
  }
1612
- /**
1613
- * Build the group map from registry modules.
1614
- */
1615
- buildGroupMap() {
1616
- if (this.groupMapBuilt) {
1617
- return;
1618
- }
1619
- try {
1620
- this.buildAliasMap();
1621
- for (const descriptor of this.registry.listModules()) {
1622
- const moduleId = descriptor.id;
1623
- const cached = this.descriptorCache.get(moduleId);
1624
- if (!cached) {
1625
- continue;
1626
- }
1627
- const [group, cmd] = _GroupedModuleGroup.resolveGroup(moduleId, cached);
1628
- if (group === null) {
1629
- this.topLevelModules.set(cmd, [moduleId, cached]);
1630
- } else if (!/^[a-z][a-z0-9_-]*$/.test(group)) {
1631
- warn(
1632
- `Module '${moduleId}': group name '${group}' is not shell-safe \u2014 treating as top-level.`
1633
- );
1634
- this.topLevelModules.set(cmd, [moduleId, cached]);
1635
- } else {
1636
- if (!this.groupMap.has(group)) {
1637
- this.groupMap.set(group, /* @__PURE__ */ new Map());
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
+ }
1638
1796
  }
1639
- this.groupMap.get(group).set(cmd, [moduleId, cached]);
1640
1797
  }
1641
- }
1642
- for (const groupName of this.groupMap.keys()) {
1643
- if (BUILTIN_COMMANDS.includes(groupName)) {
1644
- warn(
1645
- `Group name '${groupName}' collides with a built-in command and will be ignored`
1646
- );
1798
+ if (!rendered) {
1799
+ process.stdout.write(roff);
1647
1800
  }
1801
+ } else {
1802
+ process.stdout.write(roff);
1648
1803
  }
1649
- this.groupMapBuilt = true;
1650
- } catch {
1651
- warn("Failed to build group map");
1804
+ process.exit(0);
1652
1805
  }
1653
- }
1654
- /**
1655
- * List all available command names: builtins + group names + top-level module names.
1656
- */
1657
- listCommands() {
1658
- this.buildGroupMap();
1659
- const groupNames = [...this.groupMap.keys()].filter(
1660
- (g) => !BUILTIN_COMMANDS.includes(g)
1661
- );
1662
- const topNames = [...this.topLevelModules.keys()];
1663
- return [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...groupNames, ...topNames])].sort();
1664
- }
1665
- /**
1666
- * Get a command by name: check builtins -> group cache -> group map -> top-level modules.
1667
- */
1668
- getCommand(cmdName) {
1669
- this.buildGroupMap();
1670
- if (this.groupCache.has(cmdName)) {
1671
- return this.groupCache.get(cmdName).command;
1672
- }
1673
- if (this.groupMap.has(cmdName)) {
1674
- const lazyGrp = new LazyGroup(
1675
- this.groupMap.get(cmdName),
1676
- this.executor,
1677
- cmdName,
1678
- this.helpTextMaxLength
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
+ `
1679
1818
  );
1680
- this.groupCache.set(cmdName, lazyGrp);
1681
- return lazyGrp.command;
1819
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1682
1820
  }
1683
- if (this.topLevelModules.has(cmdName)) {
1684
- if (this.commandCache.has(cmdName)) {
1685
- return this.commandCache.get(cmdName);
1686
- }
1687
- const [, descriptor] = this.topLevelModules.get(cmdName);
1688
- const cmd = buildModuleCommand(
1689
- descriptor,
1690
- this.executor,
1691
- this.helpTextMaxLength,
1692
- cmdName
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
+ `
1693
1837
  );
1694
- this.commandCache.set(cmdName, cmd);
1695
- return cmd;
1838
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1696
1839
  }
1697
- return null;
1698
- }
1699
- /** Expose groupMap for testing. */
1700
- getGroupMap() {
1701
- return this.groupMap;
1702
- }
1703
- /** Expose topLevelModules for testing. */
1704
- getTopLevelModules() {
1705
- return this.topLevelModules;
1706
- }
1707
- /** Expose groupMapBuilt for testing. */
1708
- isGroupMapBuilt() {
1709
- return this.groupMapBuilt;
1710
- }
1711
- };
1840
+ const resolved = cli.name() || progName;
1841
+ const roff = generateManPage(commandName, cmd, resolved);
1842
+ process.stdout.write(roff);
1843
+ });
1844
+ cli.addCommand(manCmd);
1845
+ }
1712
1846
 
1713
- // src/config.ts
1714
- init_esm_shims();
1715
- import * as fs4 from "fs";
1716
- import yaml from "js-yaml";
1717
- var DEFAULTS = {
1718
- "extensions.root": "./extensions",
1719
- "logging.level": "WARNING",
1720
- "sandbox.enabled": false,
1721
- "cli.stdin_buffer_limit": 10485760,
1722
- "cli.auto_approve": false,
1723
- "cli.help_text_max_length": 1e3
1724
- };
1725
- var ConfigResolver = class {
1726
- cliFlags;
1727
- configPath;
1728
- fileCache = null;
1729
- fileCacheLoaded = false;
1730
- constructor(cliFlags, configPath) {
1731
- this.cliFlags = cliFlags ?? {};
1732
- this.configPath = configPath ?? "apcore.yaml";
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;
1733
1893
  }
1734
- /**
1735
- * Resolve a single configuration key across all four tiers.
1736
- */
1737
- resolve(key, cliFlag, envVar) {
1738
- const flagKey = cliFlag ?? key;
1739
- if (flagKey in this.cliFlags) {
1740
- const value = this.cliFlags[flagKey];
1741
- if (value !== null && value !== void 0) {
1742
- return value;
1743
- }
1744
- }
1745
- if (envVar) {
1746
- const envValue = process.env[envVar];
1747
- if (envValue !== void 0 && envValue !== "") {
1748
- return envValue;
1749
- }
1894
+ try {
1895
+ const toolkitModule = "apcore-toolkit";
1896
+ const toolkit = await import(
1897
+ /* @vite-ignore */
1898
+ toolkitModule
1899
+ );
1900
+ if (commandsDir) {
1901
+ console.warn("Convention scanning not yet available in TypeScript toolkit");
1750
1902
  }
1751
- const fileValue = this.resolveFromFile(key);
1752
- if (fileValue !== void 0) {
1753
- return fileValue;
1903
+ if (bindingPath) {
1904
+ const resolver = new toolkit.DisplayResolver();
1905
+ void resolver;
1754
1906
  }
1755
- return DEFAULTS[key];
1907
+ } catch {
1908
+ console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
1756
1909
  }
1757
- /**
1758
- * Load a value from the config file using a dot-separated key path.
1759
- */
1760
- resolveFromFile(key) {
1761
- if (!this.fileCacheLoaded) {
1762
- this.fileCache = this.loadConfigFile();
1763
- this.fileCacheLoaded = true;
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);
1764
1919
  }
1765
- if (this.fileCache === null) {
1766
- return void 0;
1920
+ const code = exitCodeForError(error2);
1921
+ if (error2 instanceof Error) {
1922
+ process.stderr.write(`Error: ${error2.message}
1923
+ `);
1767
1924
  }
1768
- return this.fileCache[key];
1925
+ process.exit(code);
1769
1926
  }
1770
- /**
1771
- * Load and flatten a YAML config file.
1772
- */
1773
- loadConfigFile() {
1774
- let content;
1775
- try {
1776
- content = fs4.readFileSync(this.configPath, "utf-8");
1777
- } catch (err) {
1778
- if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1779
- return null;
1780
- }
1781
- console.warn(
1782
- `Configuration file '${this.configPath}' is malformed, using defaults.`
1783
- );
1784
- return null;
1785
- }
1786
- let parsed;
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) {
1787
1938
  try {
1788
- parsed = yaml.load(content);
1939
+ resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
1789
1940
  } catch {
1790
- console.warn(
1791
- `Configuration file '${this.configPath}' is malformed, using defaults.`
1792
- );
1793
- return null;
1941
+ resolvedSchema = inputSchema;
1794
1942
  }
1795
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1796
- console.warn(
1797
- `Configuration file '${this.configPath}' is malformed, using defaults.`
1798
- );
1799
- return null;
1943
+ schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
1944
+ }
1945
+ const cmd = new Command2(effectiveCmdName).description(cmdHelp);
1946
+ const inputOpt = new Option2("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
1947
+ const yesOpt = new Option2("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
1948
+ const largeInputOpt = new Option2("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
1949
+ const formatOpt = new Option2("--format <format>", "Set output format: 'json' for machine-readable, 'table' for human-readable");
1950
+ const sandboxOpt = new Option2("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
1951
+ if (!verbose) {
1952
+ inputOpt.hideHelp();
1953
+ yesOpt.hideHelp();
1954
+ largeInputOpt.hideHelp();
1955
+ formatOpt.hideHelp();
1956
+ }
1957
+ cmd.addOption(inputOpt);
1958
+ cmd.addOption(yesOpt);
1959
+ cmd.addOption(largeInputOpt);
1960
+ cmd.addOption(formatOpt);
1961
+ cmd.addOption(sandboxOpt);
1962
+ const footerParts = [];
1963
+ if (!verbose) {
1964
+ footerParts.push("Use --verbose to show all options (including built-in apcore options).");
1965
+ }
1966
+ if (docsUrl) {
1967
+ footerParts.push(`Docs: ${docsUrl}/commands/${effectiveCmdName}`);
1968
+ }
1969
+ if (footerParts.length > 0) {
1970
+ cmd.addHelpText("after", "\n" + footerParts.join("\n") + "\n");
1971
+ }
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);
1800
1977
  }
1801
- return this.flattenDict(parsed);
1802
1978
  }
1803
- /**
1804
- * Flatten nested dict to dot-notation keys.
1805
- */
1806
- flattenDict(d, prefix = "") {
1807
- const result = {};
1808
- for (const [key, value] of Object.entries(d)) {
1809
- const fullKey = prefix ? `${prefix}.${key}` : key;
1810
- if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1811
- Object.assign(
1812
- result,
1813
- this.flattenDict(value, fullKey)
1814
- );
1815
- } else {
1816
- result[fullKey] = value;
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;
1817
1990
  }
1818
1991
  }
1819
- return result;
1992
+ try {
1993
+ const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
1994
+ const reconverted = reconvertEnumValues(merged, schemaOptions);
1995
+ await checkApproval(moduleDef, autoApprove);
1996
+ const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
1997
+ const sandbox = new Sandbox2(sandboxEnabled);
1998
+ const startTime = performance.now();
1999
+ const result = await sandbox.execute(moduleId, reconverted, executor);
2000
+ const durationMs = Math.round(performance.now() - startTime);
2001
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2002
+ const auditLogger = getAuditLogger2();
2003
+ if (auditLogger) {
2004
+ auditLogger.logExecution(moduleId, reconverted, "success", 0, durationMs);
2005
+ }
2006
+ formatExecResult(result, outputFormat);
2007
+ } catch (err) {
2008
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2009
+ const auditLogger = getAuditLogger2();
2010
+ const code = exitCodeForError(err);
2011
+ if (auditLogger) {
2012
+ auditLogger.logExecution(moduleId, {}, "error", code, 0);
2013
+ }
2014
+ if (err instanceof Error) {
2015
+ process.stderr.write(`Error: ${err.message}
2016
+ `);
2017
+ }
2018
+ process.exit(code);
2019
+ }
2020
+ });
2021
+ return cmd;
2022
+ }
2023
+ function validateModuleId(moduleId) {
2024
+ if (moduleId.length > 128) {
2025
+ process.stderr.write(
2026
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
2027
+ `
2028
+ );
2029
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1820
2030
  }
1821
- };
1822
-
1823
- // src/discovery.ts
1824
- init_esm_shims();
1825
- init_errors();
1826
- import { Command as Command3 } from "commander";
1827
- var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
1828
- function validateTag(tag) {
1829
- if (!TAG_PATTERN.test(tag)) {
2031
+ if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
1830
2032
  process.stderr.write(
1831
- `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
2033
+ `Error: Invalid module ID format: '${moduleId}'.
1832
2034
  `
1833
2035
  );
1834
2036
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1835
2037
  }
1836
2038
  }
1837
- function collectTag(value, previous) {
1838
- return previous.concat([value]);
1839
- }
1840
- function registerDiscoveryCommands(cli, registry) {
1841
- 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) => {
1842
- for (const t of opts.tag) {
1843
- 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;
1844
2044
  }
1845
- const modules = [];
1846
- for (const m of registry.listModules()) {
1847
- 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);
1848
2057
  }
1849
- let filtered = modules;
1850
- if (opts.tag.length > 0) {
1851
- const filterTags = new Set(opts.tag);
1852
- filtered = modules.filter((m) => {
1853
- const mTags = m.tags ?? [];
1854
- return [...filterTags].every((t) => mTags.includes(t));
1855
- });
2058
+ if (!raw) {
2059
+ return cliKwargsNonNull;
1856
2060
  }
1857
- const fmt = resolveFormat(opts.format);
1858
- formatModuleList(filtered, fmt, opts.tag.length > 0 ? opts.tag : void 0);
1859
- });
1860
- cli.addCommand(listCmd);
1861
- 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) => {
1862
- validateModuleId(moduleId);
1863
- const moduleDef = registry.getModule(moduleId);
1864
- if (!moduleDef) {
2061
+ let stdinData;
2062
+ try {
2063
+ stdinData = JSON.parse(raw);
2064
+ } catch {
1865
2065
  process.stderr.write(
1866
- `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}.
1867
2073
  `
1868
2074
  );
1869
- process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2075
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1870
2076
  }
1871
- const fmt = resolveFormat(opts.format);
1872
- formatModuleDetail(moduleDef, fmt);
1873
- });
1874
- cli.addCommand(describeCmd);
1875
- }
1876
-
1877
- // src/shell.ts
1878
- init_esm_shims();
1879
- init_errors();
1880
- import { readFileSync as readFileSync3 } from "fs";
1881
- import { fileURLToPath as fileURLToPath3 } from "url";
1882
- import * as path6 from "path";
1883
- import { Command as Command4 } from "commander";
1884
- var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
1885
- var SHELL_VERSION = "0.0.0";
1886
- try {
1887
- const pkg = JSON.parse(readFileSync3(path6.resolve(__dirname3, "../package.json"), "utf-8"));
1888
- SHELL_VERSION = pkg.version;
1889
- } catch {
1890
- }
1891
- function makeFunctionName(progName) {
1892
- return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
1893
- }
1894
- function shellQuote(s) {
1895
- return "'" + s.replace(/'/g, "'\\''") + "'";
1896
- }
1897
- function generateBashCompletion(progName) {
1898
- const fn = makeFunctionName(progName);
1899
- const quoted = shellQuote(progName);
1900
- 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`;
1901
- const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1902
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1903
- const ids=JSON.parse(m).map(x=>x.id);
1904
- const g=new Set(),t=[];
1905
- ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1906
- console.log([...g].sort().concat(t.sort()).join(' '))
1907
- " 2>/dev/null`;
1908
- const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1909
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1910
- const g=process.env._APCORE_GRP;
1911
- JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1912
- " 2>/dev/null`;
1913
- return `${fn}() {
1914
- local cur prev opts
1915
- COMPREPLY=()
1916
- cur="\${COMP_WORDS[COMP_CWORD]}"
1917
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
1918
-
1919
- if [[ \${COMP_CWORD} -eq 1 ]]; then
1920
- opts="completion describe exec init list man"
1921
- local groups_and_top=$(${groupsAndTopCmd})
1922
- COMPREPLY=( $(compgen -W "\${opts} \${groups_and_top}" -- \${cur}) )
1923
- return 0
1924
- fi
1925
-
1926
- if [[ \${COMP_CWORD} -eq 2 ]]; then
1927
- if [[ "\${COMP_WORDS[1]}" == "exec" ]]; then
1928
- local modules=$(${moduleListCmd})
1929
- COMPREPLY=( $(compgen -W "\${modules}" -- \${cur}) )
1930
- return 0
1931
- fi
1932
- export _APCORE_GRP="\${COMP_WORDS[1]}"
1933
- local group_cmds=$(${groupCmdsCmd})
1934
- COMPREPLY=( $(compgen -W "\${group_cmds}" -- \${cur}) )
1935
- return 0
1936
- fi
1937
- }
1938
- complete -F ${fn} ${quoted}
1939
- `;
2077
+ return { ...stdinData, ...cliKwargsNonNull };
2078
+ }
2079
+ return cliKwargsNonNull;
1940
2080
  }
1941
- function generateZshCompletion(progName) {
1942
- const fn = makeFunctionName(progName);
1943
- const quoted = shellQuote(progName);
1944
- 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`;
1945
- const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1946
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1947
- const ids=JSON.parse(m).map(x=>x.id);
1948
- const g=new Set(),t=[];
1949
- ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1950
- console.log([...g].sort().concat(t.sort()).join(' '))
1951
- " 2>/dev/null`;
1952
- const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1953
- const m=require('fs').readFileSync('/dev/stdin','utf8');
1954
- const g=process.env._APCORE_GRP;
1955
- JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1956
- " 2>/dev/null`;
1957
- return `#compdef ${progName}
1958
-
1959
- ${fn}() {
1960
- local -a commands
1961
- commands=(
1962
- 'list:List available modules'
1963
- 'describe:Show module metadata and schema'
1964
- 'completion:Generate shell completion script'
1965
- 'init:Scaffolding commands'
1966
- 'man:Generate man page'
1967
- )
1968
-
1969
- _arguments -C \\
1970
- '1:command:->command' \\
1971
- '*::arg:->args'
1972
-
1973
- case "$state" in
1974
- command)
1975
- _describe -t commands '${progName} commands' commands
1976
- local -a groups_and_top
1977
- groups_and_top=($(${groupsAndTopCmd}))
1978
- compadd -a groups_and_top
1979
- ;;
1980
- args)
1981
- case "\${words[1]}" in
1982
- exec)
1983
- local modules
1984
- modules=($(${moduleListCmd}))
1985
- compadd -a modules
1986
- ;;
1987
- *)
1988
- export _APCORE_GRP="\${words[1]}"
1989
- local -a group_cmds
1990
- group_cmds=($(${groupCmdsCmd}))
1991
- compadd -a group_cmds
1992
- ;;
1993
- esac
1994
- ;;
1995
- esac
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
+ });
1996
2103
  }
1997
-
1998
- compdef ${fn} ${quoted}
1999
- `;
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;
2000
2123
  }
2001
- function generateFishCompletion(progName) {
2002
- const quoted = shellQuote(progName);
2003
- 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`;
2004
- 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`;
2005
- return `# Fish completions for ${progName}
2006
- complete -c ${quoted} -n "__fish_use_subcommand" -a list -d "List available modules"
2007
- complete -c ${quoted} -n "__fish_use_subcommand" -a describe -d "Show module metadata and schema"
2008
- complete -c ${quoted} -n "__fish_use_subcommand" -a completion -d "Generate shell completion script"
2009
- complete -c ${quoted} -n "__fish_use_subcommand" -a init -d "Scaffolding commands"
2010
- complete -c ${quoted} -n "__fish_use_subcommand" -a man -d "Generate man page"
2011
- complete -c ${quoted} -n "__fish_use_subcommand" -a "(${groupsAndTopCmd})" -d "Module group"
2012
-
2013
- complete -c ${quoted} -n "__fish_seen_subcommand_from exec" -a "(${moduleListCmd})"
2014
2124
 
2015
- function __apcore_group_cmds
2016
- set -l grp (commandline -opc)[2]
2017
- set -x _APCORE_GRP $grp
2018
- ${quoted} list --format json 2>/dev/null | node -e "
2019
- const m=require('fs').readFileSync('/dev/stdin','utf8');
2020
- const g=process.env._APCORE_GRP;
2021
- JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
2022
- " 2>/dev/null
2023
- end
2024
-
2025
- # Group subcommand completion \u2014 matches when position 1 is not a builtin
2026
- complete -c ${quoted} -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from list describe completion init man exec" -a "(__apcore_group_cmds)"
2027
- `;
2028
- }
2029
- function buildSynopsis(command, progName, commandName) {
2030
- if (!command) {
2031
- return `\\fB${progName} ${commandName}\\fR [OPTIONS]`;
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;
2032
2143
  }
2033
- const parts = [`\\fB${progName} ${commandName}\\fR`];
2034
- for (const opt of command.options) {
2035
- const flag = opt.long ?? opt.short ?? "";
2036
- if (opt.isBoolean?.()) {
2037
- parts.push(`[${flag}]`);
2038
- } else if (opt.required) {
2039
- const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
2040
- parts.push(`${flag} \\fI${typeName}\\fR`);
2041
- } else {
2042
- const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
2043
- 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");
2165
+ }
2166
+ }
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);
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();
2179
+ }
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;
2199
+ }
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);
2221
+ }
2222
+ }
2223
+ listCommands() {
2224
+ return [...this.members.keys()].sort();
2225
+ }
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;
2243
+ }
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];
2276
+ }
2277
+ return [null, cliName];
2278
+ }
2279
+ /**
2280
+ * Build the group map from registry modules.
2281
+ */
2282
+ buildGroupMap() {
2283
+ if (this.groupMapBuilt) {
2284
+ return;
2044
2285
  }
2045
- }
2046
- for (const arg of command.registeredArguments ?? []) {
2047
- const meta = arg.name().toUpperCase();
2048
- if (arg.required) {
2049
- parts.push(`\\fI${meta}\\fR`);
2050
- } else {
2051
- parts.push(`[\\fI${meta}\\fR]`);
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
+ }
2308
+ }
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
+ );
2314
+ }
2315
+ }
2316
+ this.groupMapBuilt = true;
2317
+ } catch {
2318
+ warn("Failed to build group map");
2052
2319
  }
2053
2320
  }
2054
- return parts.join(" ");
2055
- }
2056
- function generateManPage(commandName, command, progName, version = SHELL_VERSION) {
2057
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2058
- const title = `${progName}-${commandName}`.toUpperCase();
2059
- const pkgLabel = `${progName} ${version}`;
2060
- const manualLabel = `${progName} Manual`;
2061
- const sections = [];
2062
- sections.push(`.TH "${title}" "1" "${today}" "${pkgLabel}" "${manualLabel}"`);
2063
- sections.push(".SH NAME");
2064
- const desc = command?.description() ?? commandName;
2065
- const nameDesc = desc.split("\n")[0].replace(/\.$/, "");
2066
- sections.push(`${progName}-${commandName} \\- ${nameDesc}`);
2067
- sections.push(".SH SYNOPSIS");
2068
- sections.push(buildSynopsis(command, progName, commandName));
2069
- if (command?.description()) {
2070
- sections.push(".SH DESCRIPTION");
2071
- sections.push(
2072
- command.description().replace(/\\/g, "\\\\").replace(/-/g, "\\-")
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)
2073
2328
  );
2329
+ const topNames = [...this.topLevelModules.keys()];
2330
+ return [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...groupNames, ...topNames])].sort();
2074
2331
  }
2075
- if (command && command.options.length > 0) {
2076
- sections.push(".SH OPTIONS");
2077
- for (const opt of command.options) {
2078
- const flag = [opt.short, opt.long].filter(Boolean).join(", ");
2079
- sections.push(".TP");
2080
- if (opt.isBoolean?.()) {
2081
- sections.push(`\\fB${flag}\\fR`);
2082
- } else {
2083
- sections.push(`\\fB${flag}\\fR \\fIVALUE\\fR`);
2084
- }
2085
- if (opt.description) {
2086
- sections.push(opt.description);
2087
- }
2088
- if (opt.defaultValue !== void 0 && !opt.isBoolean?.()) {
2089
- sections.push(`Default: ${opt.defaultValue}.`);
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);
2090
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;
2091
2363
  }
2364
+ return null;
2092
2365
  }
2093
- sections.push(".SH ENVIRONMENT");
2094
- sections.push(".TP");
2095
- sections.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
2096
- sections.push(
2097
- "Path to the apcore extensions directory. Overrides the default \\fI./extensions\\fR."
2098
- );
2099
- sections.push(".TP");
2100
- sections.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
2101
- sections.push(
2102
- "Set to \\fB1\\fR to bypass approval prompts for modules that require human-in-the-loop confirmation."
2103
- );
2104
- sections.push(".TP");
2105
- sections.push("\\fBAPCORE_CLI_LOGGING_LEVEL\\fR");
2106
- sections.push(
2107
- "CLI-specific logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Takes priority over \\fBAPCORE_LOGGING_LEVEL\\fR. Default: WARNING."
2108
- );
2109
- sections.push(".TP");
2110
- sections.push("\\fBAPCORE_LOGGING_LEVEL\\fR");
2111
- sections.push(
2112
- "Global apcore logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Used as fallback when \\fBAPCORE_CLI_LOGGING_LEVEL\\fR is not set. Default: WARNING."
2113
- );
2114
- sections.push(".TP");
2115
- sections.push("\\fBAPCORE_AUTH_API_KEY\\fR");
2116
- sections.push(
2117
- "API key for authenticating with the apcore registry."
2118
- );
2119
- sections.push(".SH EXIT CODES");
2120
- const exitCodes = [
2121
- ["0", "Success."],
2122
- ["1", "Module execution error."],
2123
- ["2", "Invalid CLI input or missing argument."],
2124
- ["44", "Module not found, disabled, or failed to load."],
2125
- ["45", "Input failed JSON Schema validation."],
2126
- [
2127
- "46",
2128
- "Approval denied, timed out, or no interactive terminal available."
2129
- ],
2130
- [
2131
- "47",
2132
- "Configuration error (extensions directory not found or unreadable)."
2133
- ],
2134
- ["48", "Schema contains a circular \\fB$ref\\fR."],
2135
- ["77", "ACL denied \u2014 insufficient permissions for this module."],
2136
- ["130", "Execution cancelled by user (SIGINT / Ctrl\\-C)."]
2137
- ];
2138
- for (const [code, meaning] of exitCodes) {
2139
- sections.push(`.TP
2140
- \\fB${code}\\fR
2141
- ${meaning}`);
2366
+ /** Expose groupMap for testing. */
2367
+ getGroupMap() {
2368
+ return this.groupMap;
2142
2369
  }
2143
- sections.push(".SH SEE ALSO");
2144
- sections.push(
2145
- [
2146
- `\\fB${progName}\\fR(1)`,
2147
- `\\fB${progName}\\-list\\fR(1)`,
2148
- `\\fB${progName}\\-describe\\fR(1)`,
2149
- `\\fB${progName}\\-completion\\fR(1)`
2150
- ].join(", ")
2151
- );
2152
- return sections.join("\n");
2153
- }
2154
- function registerShellCommands(cli, progName = "apcore-cli") {
2155
- const completionCmd = new Command4("completion").description(
2156
- "Generate a shell completion script and print it to stdout."
2157
- ).argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
2158
- const validShells = ["bash", "zsh", "fish"];
2159
- if (!validShells.includes(shell)) {
2160
- process.stderr.write(
2161
- `Error: Unknown shell '${shell}'. Expected: bash, zsh, or fish.
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_-]*.
2162
2389
  `
2163
- );
2164
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2390
+ );
2391
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2392
+ }
2393
+ }
2394
+ function collectTag(value, previous) {
2395
+ return previous.concat([value]);
2396
+ }
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);
2165
2401
  }
2166
- const resolved = cli.name() || progName;
2167
- const generators = {
2168
- bash: () => generateBashCompletion(resolved),
2169
- zsh: () => generateZshCompletion(resolved),
2170
- fish: () => generateFishCompletion(resolved)
2171
- };
2172
- 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);
2173
2416
  });
2174
- cli.addCommand(completionCmd);
2175
- 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) => {
2176
- const knownBuiltins = /* @__PURE__ */ new Set(["completion", "describe", "exec", "init", "list", "man"]);
2177
- const cmd = cli.commands.find((c) => c.name() === commandName) ?? null;
2178
- 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) {
2179
2422
  process.stderr.write(
2180
- `Error: Unknown command '${commandName}'.
2423
+ `Error: Module '${moduleId}' not found.
2181
2424
  `
2182
2425
  );
2183
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2426
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2184
2427
  }
2185
- const resolved = cli.name() || progName;
2186
- const roff = generateManPage(commandName, cmd, resolved);
2187
- process.stdout.write(roff);
2428
+ const fmt = resolveFormat(opts.format);
2429
+ formatModuleDetail(moduleDef, fmt);
2188
2430
  });
2189
- cli.addCommand(manCmd);
2431
+ cli.addCommand(describeCmd);
2190
2432
  }
2191
2433
 
2192
2434
  // src/index.ts
@@ -2213,10 +2455,13 @@ export {
2213
2455
  SchemaValidationError,
2214
2456
  applyToolkitIntegration,
2215
2457
  buildModuleCommand,
2458
+ buildProgramManPage,
2216
2459
  checkApproval,
2217
2460
  collectInput,
2461
+ configureManHelp,
2218
2462
  createCli,
2219
2463
  debug,
2464
+ docsUrl,
2220
2465
  error,
2221
2466
  exitCodeForError,
2222
2467
  extractHelp,
@@ -2231,6 +2476,7 @@ export {
2231
2476
  main,
2232
2477
  mapType,
2233
2478
  reconvertEnumValues,
2479
+ registerConfigNamespace,
2234
2480
  registerDiscoveryCommands,
2235
2481
  registerInitCommand,
2236
2482
  registerShellCommands,
@@ -2238,9 +2484,12 @@ export {
2238
2484
  resolveRefs,
2239
2485
  schemaToCliOptions,
2240
2486
  setAuditLogger,
2487
+ setDocsUrl,
2241
2488
  setLogLevel,
2489
+ setVerboseHelp,
2242
2490
  truncate,
2243
2491
  validateModuleId,
2492
+ verboseHelp,
2244
2493
  warn
2245
2494
  };
2246
2495
  //# sourceMappingURL=index.js.map