apcore-cli 0.2.0 → 0.3.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/src/index.js CHANGED
@@ -137,9 +137,9 @@ __export(audit_exports, {
137
137
  setAuditLogger: () => setAuditLogger
138
138
  });
139
139
  import * as crypto from "crypto";
140
- import * as fs from "fs";
140
+ import * as fs2 from "fs";
141
141
  import * as os from "os";
142
- import * as path2 from "path";
142
+ import * as path3 from "path";
143
143
  function setAuditLogger(auditLogger) {
144
144
  _auditLogger = auditLogger;
145
145
  }
@@ -153,19 +153,19 @@ var init_audit = __esm({
153
153
  init_esm_shims();
154
154
  _auditLogger = null;
155
155
  AuditLogger = class _AuditLogger {
156
- static DEFAULT_PATH = path2.join(
156
+ static DEFAULT_PATH = path3.join(
157
157
  os.homedir(),
158
158
  ".apcore-cli",
159
159
  "audit.jsonl"
160
160
  );
161
161
  logPath;
162
- constructor(path6) {
163
- this.logPath = path6 ?? _AuditLogger.DEFAULT_PATH;
162
+ constructor(path7) {
163
+ this.logPath = path7 ?? _AuditLogger.DEFAULT_PATH;
164
164
  this.ensureDirectory();
165
165
  }
166
166
  ensureDirectory() {
167
167
  try {
168
- fs.mkdirSync(path2.dirname(this.logPath), { recursive: true });
168
+ fs2.mkdirSync(path3.dirname(this.logPath), { recursive: true });
169
169
  } catch {
170
170
  }
171
171
  }
@@ -180,7 +180,7 @@ var init_audit = __esm({
180
180
  duration_ms: durationMs
181
181
  };
182
182
  try {
183
- fs.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
183
+ fs2.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
184
184
  } catch (err) {
185
185
  console.warn(`Could not write audit log: ${err}`);
186
186
  }
@@ -381,9 +381,9 @@ var init_auth = __esm({
381
381
 
382
382
  // src/security/sandbox.ts
383
383
  import * as child_process from "child_process";
384
- import * as fs2 from "fs";
384
+ import * as fs3 from "fs";
385
385
  import * as os3 from "os";
386
- import * as path3 from "path";
386
+ import * as path4 from "path";
387
387
  var Sandbox;
388
388
  var init_sandbox = __esm({
389
389
  "src/security/sandbox.ts"() {
@@ -416,8 +416,8 @@ var init_sandbox = __esm({
416
416
  env[key] = value;
417
417
  }
418
418
  }
419
- const tmpDir = fs2.mkdtempSync(
420
- path3.join(os3.tmpdir(), "apcore_sandbox_")
419
+ const tmpDir = fs3.mkdtempSync(
420
+ path4.join(os3.tmpdir(), "apcore_sandbox_")
421
421
  );
422
422
  try {
423
423
  env.HOME = tmpDir;
@@ -455,7 +455,7 @@ var init_sandbox = __esm({
455
455
  );
456
456
  } finally {
457
457
  try {
458
- fs2.rmSync(tmpDir, { recursive: true, force: true });
458
+ fs3.rmSync(tmpDir, { recursive: true, force: true });
459
459
  } catch {
460
460
  }
461
461
  }
@@ -493,7 +493,7 @@ init_esm_shims();
493
493
  init_errors();
494
494
  import { readFileSync } from "fs";
495
495
  import { fileURLToPath as fileURLToPath2 } from "url";
496
- import * as path4 from "path";
496
+ import * as path5 from "path";
497
497
  import { Command, CommanderError } from "commander";
498
498
 
499
499
  // src/ref-resolver.ts
@@ -663,7 +663,7 @@ function mapType(propName, propSchema) {
663
663
  }
664
664
  return typeMap[schemaType] ?? "string";
665
665
  }
666
- function extractHelp(propSchema) {
666
+ function extractHelp(propSchema, maxLength = 1e3) {
667
667
  let text = propSchema["x-llm-description"];
668
668
  if (!text) {
669
669
  text = propSchema.description;
@@ -671,13 +671,13 @@ function extractHelp(propSchema) {
671
671
  if (!text) {
672
672
  return void 0;
673
673
  }
674
- if (text.length > 200) {
675
- return text.slice(0, 197) + "...";
674
+ if (maxLength > 0 && text.length > maxLength) {
675
+ return text.slice(0, maxLength - 3) + "...";
676
676
  }
677
677
  return text;
678
678
  }
679
679
  var RESERVED_NAMES = /* @__PURE__ */ new Set(["input", "yes", "large_input", "format", "sandbox"]);
680
- function schemaToCliOptions(schema) {
680
+ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
681
681
  const properties = schema.properties ?? {};
682
682
  const requiredList = schema.required ?? [];
683
683
  const options = [];
@@ -701,7 +701,7 @@ function schemaToCliOptions(schema) {
701
701
  }
702
702
  const typeResult = mapType(propName, propSchema);
703
703
  const isRequired = requiredList.includes(propName);
704
- const helpBase = extractHelp(propSchema);
704
+ const helpBase = extractHelp(propSchema, maxHelpLength);
705
705
  const helpText = isRequired ? (helpBase ? helpBase + " " : "") + "[required]" : helpBase ?? "";
706
706
  const defaultValue = propSchema.default;
707
707
  if (typeResult === BOOLEAN_FLAG) {
@@ -884,12 +884,12 @@ function formatTable(headers, rows) {
884
884
  const colWidths = headers.map(
885
885
  (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
886
886
  );
887
- const sep = colWidths.map((w) => "-".repeat(w)).join(" ");
887
+ const sep2 = colWidths.map((w) => "-".repeat(w)).join(" ");
888
888
  const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(" ");
889
889
  const dataLines = rows.map(
890
890
  (row) => row.map((cell, i) => (cell ?? "").padEnd(colWidths[i])).join(" ")
891
891
  );
892
- return [headerLine, sep, ...dataLines].join("\n") + "\n";
892
+ return [headerLine, sep2, ...dataLines].join("\n") + "\n";
893
893
  }
894
894
  function formatModuleList(modules, format, filterTags) {
895
895
  if (format === "table") {
@@ -1056,18 +1056,209 @@ function error(message) {
1056
1056
  `);
1057
1057
  }
1058
1058
 
1059
+ // src/init-cmd.ts
1060
+ init_esm_shims();
1061
+ import * as fs from "fs";
1062
+ import * as path2 from "path";
1063
+ var DECORATOR_TEMPLATE = `import { module } from "apcore-js";
1064
+ import { Type } from "@sinclair/typebox";
1065
+
1066
+ export const {varName} = module({
1067
+ id: "{moduleId}",
1068
+ description: "{description}",
1069
+ inputSchema: Type.Object({}),
1070
+ outputSchema: Type.Object({ status: Type.String() }),
1071
+ execute: (_inputs) => {
1072
+ // TODO: implement
1073
+ return { status: "ok" };
1074
+ },
1075
+ });
1076
+ `;
1077
+ var CONVENTION_TEMPLATE = `/**
1078
+ * {description}
1079
+ */
1080
+ {cliGroupLine}
1081
+ export function {funcName}(): Record<string, unknown> {
1082
+ // TODO: implement
1083
+ return { status: "ok" };
1084
+ }
1085
+ `;
1086
+ var BINDING_TEMPLATE = `bindings:
1087
+ - module_id: "{moduleId}"
1088
+ target: "{target}"
1089
+ description: "{description}"
1090
+ auto_schema: true
1091
+ `;
1092
+ function renderTemplate(template, context) {
1093
+ let result = template;
1094
+ for (const [key, value] of Object.entries(context)) {
1095
+ result = result.split(`{${key}}`).join(value);
1096
+ }
1097
+ return result;
1098
+ }
1099
+ function registerInitCommand(cli) {
1100
+ const initGroup = cli.command("init").description("Scaffold new apcore modules.");
1101
+ initGroup.command("module <module-id>").description("Create a new module from a template.\n\nMODULE_ID is the module identifier (e.g., ops.deploy, user.create).").option(
1102
+ "--style <style>",
1103
+ "Module style: decorator (@module), convention (plain function), or binding (YAML).",
1104
+ "convention"
1105
+ ).option("--dir <path>", "Output directory. Default: extensions/ or commands/.").option("-d, --description <text>", "Module description.", "TODO: add description").action((moduleId, opts) => {
1106
+ const lastDot = moduleId.lastIndexOf(".");
1107
+ const prefix = lastDot >= 0 ? moduleId.substring(0, lastDot) : moduleId;
1108
+ const funcName = lastDot >= 0 ? moduleId.substring(lastDot + 1) : moduleId;
1109
+ const style = opts.style;
1110
+ const description = opts.description;
1111
+ const dir = opts.dir ?? (style === "decorator" ? "extensions" : style === "binding" ? "bindings" : "commands");
1112
+ if (dir.split(path2.sep).includes("..") || dir.split("/").includes("..")) {
1113
+ process.stderr.write(`Error: Output directory must not contain '..' path components.
1114
+ `);
1115
+ process.exit(2);
1116
+ }
1117
+ switch (style) {
1118
+ case "decorator":
1119
+ createDecoratorModule(moduleId, prefix, funcName, description, dir);
1120
+ break;
1121
+ case "convention":
1122
+ createConventionModule(moduleId, prefix, funcName, description, dir);
1123
+ break;
1124
+ case "binding":
1125
+ createBindingModule(moduleId, prefix, funcName, description, dir);
1126
+ break;
1127
+ default:
1128
+ process.stderr.write(`Error: Unknown style '${style}'
1129
+ `);
1130
+ process.exit(2);
1131
+ }
1132
+ });
1133
+ }
1134
+ function createDecoratorModule(moduleId, _prefix, funcName, description, outputDir) {
1135
+ fs.mkdirSync(outputDir, { recursive: true });
1136
+ const filename = moduleId.replace(/\./g, "_") + ".ts";
1137
+ const filepath = path2.join(outputDir, filename);
1138
+ const varName = funcName + "Module";
1139
+ const content = renderTemplate(DECORATOR_TEMPLATE, {
1140
+ moduleId,
1141
+ varName,
1142
+ funcName,
1143
+ description
1144
+ });
1145
+ fs.writeFileSync(filepath, content);
1146
+ process.stdout.write(`Created ${filepath}
1147
+ `);
1148
+ }
1149
+ function createConventionModule(moduleId, prefix, funcName, description, outputDir) {
1150
+ const prefixParts = prefix.split(".");
1151
+ const dirPath = prefixParts.length > 1 ? path2.join(outputDir, ...prefixParts.slice(0, -1)) : outputDir;
1152
+ fs.mkdirSync(dirPath, { recursive: true });
1153
+ let filename;
1154
+ if (prefixParts.length > 1) {
1155
+ filename = prefixParts[prefixParts.length - 1] + ".ts";
1156
+ } else {
1157
+ filename = prefix + ".ts";
1158
+ }
1159
+ if (prefix === funcName) {
1160
+ filename = prefix + ".ts";
1161
+ }
1162
+ const filepath = path2.join(dirPath, filename);
1163
+ const cliGroupLine = moduleId.includes(".") ? `export const CLI_GROUP = "${prefixParts[0]}";
1164
+ ` : "";
1165
+ const content = renderTemplate(CONVENTION_TEMPLATE, {
1166
+ funcName,
1167
+ description,
1168
+ cliGroupLine
1169
+ });
1170
+ fs.writeFileSync(filepath, content);
1171
+ process.stdout.write(`Created ${filepath}
1172
+ `);
1173
+ }
1174
+ function createBindingModule(moduleId, prefix, funcName, description, outputDir) {
1175
+ fs.mkdirSync(outputDir, { recursive: true });
1176
+ const yamlFile = path2.join(outputDir, moduleId.replace(/\./g, "_") + ".binding.yaml");
1177
+ const target = `commands.${prefix}:${funcName}`;
1178
+ const yamlContent = renderTemplate(BINDING_TEMPLATE, {
1179
+ moduleId,
1180
+ target,
1181
+ description
1182
+ });
1183
+ fs.writeFileSync(yamlFile, yamlContent);
1184
+ process.stdout.write(`Created ${yamlFile}
1185
+ `);
1186
+ const baseSrc = "commands";
1187
+ fs.mkdirSync(baseSrc, { recursive: true });
1188
+ const srcFile = path2.join(baseSrc, prefix.replace(/\./g, "_") + ".ts");
1189
+ if (!fs.existsSync(srcFile)) {
1190
+ const srcContent = `export function ${funcName}(): Record<string, unknown> {
1191
+ /** ${description} */
1192
+ // TODO: implement
1193
+ return { status: "ok" };
1194
+ }
1195
+ `;
1196
+ fs.writeFileSync(srcFile, srcContent);
1197
+ process.stdout.write(`Created ${srcFile}
1198
+ `);
1199
+ }
1200
+ }
1201
+
1202
+ // src/display-helpers.ts
1203
+ init_esm_shims();
1204
+ function getDisplay(descriptor) {
1205
+ const metadata = descriptor.metadata ?? {};
1206
+ const display = metadata.display;
1207
+ if (display && typeof display === "object" && !Array.isArray(display)) {
1208
+ return display;
1209
+ }
1210
+ return {};
1211
+ }
1212
+ function getCliDisplayFields(descriptor) {
1213
+ const display = getDisplay(descriptor);
1214
+ const cli = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1215
+ const name = cli.alias ?? display.alias ?? descriptor.id;
1216
+ const desc = cli.description ?? descriptor.description;
1217
+ const tags = display.tags ?? descriptor.tags ?? [];
1218
+ return [name, desc, tags];
1219
+ }
1220
+
1059
1221
  // src/main.ts
1060
- var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
1061
- var pkg = JSON.parse(readFileSync(path4.resolve(__dirname2, "../package.json"), "utf-8"));
1222
+ var __dirname2 = path5.dirname(fileURLToPath2(import.meta.url));
1223
+ var pkg = JSON.parse(readFileSync(path5.resolve(__dirname2, "../package.json"), "utf-8"));
1062
1224
  var VERSION = pkg.version;
1063
1225
  function createCli(extensionsDir, progName) {
1064
- const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1226
+ const resolvedProgName = progName ?? path5.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1065
1227
  const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
1066
1228
  setLogLevel(cliLogLevel);
1067
- 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("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING");
1068
- void extensionsDir;
1229
+ 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");
1230
+ const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
1231
+ void resolvedExtDir;
1232
+ registerInitCommand(program);
1233
+ program.hook("preAction", async (thisCommand) => {
1234
+ const opts = thisCommand.opts();
1235
+ const commandsDir = opts.commandsDir;
1236
+ const bindingPath = opts.binding;
1237
+ await applyToolkitIntegration(commandsDir, bindingPath);
1238
+ });
1069
1239
  return program;
1070
1240
  }
1241
+ async function applyToolkitIntegration(commandsDir, bindingPath) {
1242
+ if (!commandsDir && !bindingPath) {
1243
+ return;
1244
+ }
1245
+ try {
1246
+ const toolkitModule = "apcore-toolkit";
1247
+ const toolkit = await import(
1248
+ /* @vite-ignore */
1249
+ toolkitModule
1250
+ );
1251
+ if (commandsDir) {
1252
+ console.warn("Convention scanning not yet available in TypeScript toolkit");
1253
+ }
1254
+ if (bindingPath) {
1255
+ const resolver = new toolkit.DisplayResolver();
1256
+ void resolver;
1257
+ }
1258
+ } catch {
1259
+ console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
1260
+ }
1261
+ }
1071
1262
  function main(progName) {
1072
1263
  const program = createCli(void 0, progName);
1073
1264
  try {
@@ -1084,10 +1275,14 @@ function main(progName) {
1084
1275
  process.exit(code);
1085
1276
  }
1086
1277
  }
1087
- function buildModuleCommand(moduleDef, executor) {
1278
+ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdName) {
1088
1279
  const moduleId = moduleDef.id;
1089
1280
  let resolvedSchema = {};
1090
1281
  let schemaOptions = [];
1282
+ const display = getDisplay(moduleDef);
1283
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1284
+ const effectiveCmdName = cmdName ?? cliDisplay.alias ?? moduleId;
1285
+ const cmdHelp = cliDisplay.description ?? moduleDef.description;
1091
1286
  const inputSchema = moduleDef.inputSchema;
1092
1287
  if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
1093
1288
  try {
@@ -1095,9 +1290,9 @@ function buildModuleCommand(moduleDef, executor) {
1095
1290
  } catch {
1096
1291
  resolvedSchema = inputSchema;
1097
1292
  }
1098
- schemaOptions = schemaToCliOptions(resolvedSchema);
1293
+ schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
1099
1294
  }
1100
- const cmd = new Command(moduleId).description(moduleDef.description);
1295
+ const cmd = new Command(effectiveCmdName).description(cmdHelp);
1101
1296
  cmd.option("--input <source>", "Read input from STDIN ('-')");
1102
1297
  cmd.option("-y, --yes", "Bypass approval prompts", false);
1103
1298
  cmd.option("--large-input", "Allow STDIN input larger than 10MB", false);
@@ -1258,51 +1453,270 @@ function reconvertEnumValues(kwargs, options) {
1258
1453
 
1259
1454
  // src/cli.ts
1260
1455
  init_esm_shims();
1456
+ import { Command as Command2 } from "commander";
1457
+ var BUILTIN_COMMANDS = ["completion", "describe", "exec", "init", "list", "man"];
1261
1458
  var LazyModuleGroup = class {
1262
1459
  registry;
1263
1460
  executor;
1461
+ helpTextMaxLength;
1264
1462
  commandCache = /* @__PURE__ */ new Map();
1265
- constructor(registry, executor) {
1463
+ /** alias -> canonical module_id (populated lazily) */
1464
+ aliasMap = /* @__PURE__ */ new Map();
1465
+ /** module_id -> descriptor cache (populated during alias map build) */
1466
+ descriptorCache = /* @__PURE__ */ new Map();
1467
+ aliasMapBuilt = false;
1468
+ constructor(registry, executor, helpTextMaxLength = 1e3) {
1266
1469
  this.registry = registry;
1267
1470
  this.executor = executor;
1471
+ this.helpTextMaxLength = helpTextMaxLength;
1472
+ }
1473
+ /**
1474
+ * Build alias->module_id map from display overlay metadata.
1475
+ */
1476
+ buildAliasMap() {
1477
+ if (this.aliasMapBuilt) {
1478
+ return;
1479
+ }
1480
+ try {
1481
+ for (const descriptor of this.registry.listModules()) {
1482
+ const moduleId = descriptor.id;
1483
+ this.descriptorCache.set(moduleId, descriptor);
1484
+ const display = getDisplay(descriptor);
1485
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1486
+ const cliAlias = cliDisplay.alias;
1487
+ if (cliAlias && cliAlias !== moduleId) {
1488
+ this.aliasMap.set(cliAlias, moduleId);
1489
+ }
1490
+ }
1491
+ this.aliasMapBuilt = true;
1492
+ } catch {
1493
+ warn("Failed to build alias map from registry");
1494
+ }
1268
1495
  }
1269
1496
  /**
1270
1497
  * List all available command names from the Registry.
1271
- *
1272
- * TODO: Implement registry enumeration.
1273
1498
  */
1274
1499
  listCommands() {
1275
- return this.registry.listModules().map((m) => m.id);
1500
+ this.buildAliasMap();
1501
+ const reverse = /* @__PURE__ */ new Map();
1502
+ for (const [alias, moduleId] of this.aliasMap) {
1503
+ reverse.set(moduleId, alias);
1504
+ }
1505
+ const moduleIds = this.registry.listModules().map((m) => m.id);
1506
+ const names = moduleIds.map((mid) => reverse.get(mid) ?? mid);
1507
+ return [...new Set(names)].sort();
1276
1508
  }
1277
1509
  /**
1278
1510
  * Get or lazily build a Commander Command for the given module.
1279
- *
1280
- * TODO: Implement lazy command construction with schema-based options.
1281
1511
  */
1282
1512
  getCommand(cmdName) {
1283
1513
  if (this.commandCache.has(cmdName)) {
1284
1514
  return this.commandCache.get(cmdName);
1285
1515
  }
1286
- const moduleDef = this.registry.getModule(cmdName);
1516
+ this.buildAliasMap();
1517
+ const moduleId = this.aliasMap.get(cmdName) ?? cmdName;
1518
+ let moduleDef = this.descriptorCache.get(moduleId);
1519
+ if (!moduleDef) {
1520
+ moduleDef = this.registry.getModule(moduleId) ?? void 0;
1521
+ }
1287
1522
  if (!moduleDef) {
1288
1523
  return null;
1289
1524
  }
1290
- const cmd = buildModuleCommand(moduleDef, this.executor);
1525
+ const cmd = buildModuleCommand(moduleDef, this.executor, this.helpTextMaxLength, cmdName);
1291
1526
  this.commandCache.set(cmdName, cmd);
1292
1527
  return cmd;
1293
1528
  }
1294
1529
  };
1530
+ var LazyGroup = class {
1531
+ members;
1532
+ _executor;
1533
+ _helpTextMaxLength;
1534
+ _cmdCache = /* @__PURE__ */ new Map();
1535
+ command;
1536
+ constructor(members, executor, name, helpTextMaxLength = 1e3) {
1537
+ this.members = members;
1538
+ this._executor = executor;
1539
+ this._helpTextMaxLength = helpTextMaxLength;
1540
+ this.command = new Command2(name).description(`${name} commands`);
1541
+ for (const [cmdName, [, descriptor]] of this.members) {
1542
+ const cmd = buildModuleCommand(
1543
+ descriptor,
1544
+ this._executor,
1545
+ this._helpTextMaxLength,
1546
+ cmdName
1547
+ );
1548
+ this._cmdCache.set(cmdName, cmd);
1549
+ this.command.addCommand(cmd);
1550
+ }
1551
+ }
1552
+ listCommands() {
1553
+ return [...this.members.keys()].sort();
1554
+ }
1555
+ getCommand(cmdName) {
1556
+ if (this._cmdCache.has(cmdName)) {
1557
+ return this._cmdCache.get(cmdName);
1558
+ }
1559
+ const entry = this.members.get(cmdName);
1560
+ if (!entry) {
1561
+ return null;
1562
+ }
1563
+ const [, descriptor] = entry;
1564
+ const cmd = buildModuleCommand(
1565
+ descriptor,
1566
+ this._executor,
1567
+ this._helpTextMaxLength,
1568
+ cmdName
1569
+ );
1570
+ this._cmdCache.set(cmdName, cmd);
1571
+ return cmd;
1572
+ }
1573
+ };
1574
+ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
1575
+ /** groupName -> { cmdName -> [moduleId, descriptor] } */
1576
+ groupMap = /* @__PURE__ */ new Map();
1577
+ /** cmdName -> [moduleId, descriptor] for top-level (ungrouped) modules */
1578
+ topLevelModules = /* @__PURE__ */ new Map();
1579
+ /** Cached LazyGroup instances */
1580
+ groupCache = /* @__PURE__ */ new Map();
1581
+ groupMapBuilt = false;
1582
+ /**
1583
+ * Determine (groupName | null, commandName) for a module from its display overlay.
1584
+ */
1585
+ static resolveGroup(moduleId, descriptor) {
1586
+ if (!moduleId) {
1587
+ warn("Empty module_id encountered in resolveGroup");
1588
+ return [null, ""];
1589
+ }
1590
+ const display = getDisplay(descriptor);
1591
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1592
+ const explicitGroup = cliDisplay.group;
1593
+ if (typeof explicitGroup === "string" && explicitGroup !== "") {
1594
+ return [explicitGroup, cliDisplay.alias ?? moduleId];
1595
+ }
1596
+ if (explicitGroup === "") {
1597
+ return [null, cliDisplay.alias ?? moduleId];
1598
+ }
1599
+ const cliName = cliDisplay.alias ?? moduleId;
1600
+ if (cliName.includes(".")) {
1601
+ const dotIdx = cliName.indexOf(".");
1602
+ const group = cliName.substring(0, dotIdx);
1603
+ const cmd = cliName.substring(dotIdx + 1);
1604
+ return [group, cmd];
1605
+ }
1606
+ return [null, cliName];
1607
+ }
1608
+ /**
1609
+ * Build the group map from registry modules.
1610
+ */
1611
+ buildGroupMap() {
1612
+ if (this.groupMapBuilt) {
1613
+ return;
1614
+ }
1615
+ try {
1616
+ this.buildAliasMap();
1617
+ for (const descriptor of this.registry.listModules()) {
1618
+ const moduleId = descriptor.id;
1619
+ const cached = this.descriptorCache.get(moduleId);
1620
+ if (!cached) {
1621
+ continue;
1622
+ }
1623
+ const [group, cmd] = _GroupedModuleGroup.resolveGroup(moduleId, cached);
1624
+ if (group === null) {
1625
+ this.topLevelModules.set(cmd, [moduleId, cached]);
1626
+ } else if (!/^[a-z][a-z0-9_-]*$/.test(group)) {
1627
+ warn(
1628
+ `Module '${moduleId}': group name '${group}' is not shell-safe \u2014 treating as top-level.`
1629
+ );
1630
+ this.topLevelModules.set(cmd, [moduleId, cached]);
1631
+ } else {
1632
+ if (!this.groupMap.has(group)) {
1633
+ this.groupMap.set(group, /* @__PURE__ */ new Map());
1634
+ }
1635
+ this.groupMap.get(group).set(cmd, [moduleId, cached]);
1636
+ }
1637
+ }
1638
+ for (const groupName of this.groupMap.keys()) {
1639
+ if (BUILTIN_COMMANDS.includes(groupName)) {
1640
+ warn(
1641
+ `Group name '${groupName}' collides with a built-in command and will be ignored`
1642
+ );
1643
+ }
1644
+ }
1645
+ this.groupMapBuilt = true;
1646
+ } catch {
1647
+ warn("Failed to build group map");
1648
+ }
1649
+ }
1650
+ /**
1651
+ * List all available command names: builtins + group names + top-level module names.
1652
+ */
1653
+ listCommands() {
1654
+ this.buildGroupMap();
1655
+ const groupNames = [...this.groupMap.keys()].filter(
1656
+ (g) => !BUILTIN_COMMANDS.includes(g)
1657
+ );
1658
+ const topNames = [...this.topLevelModules.keys()];
1659
+ return [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...groupNames, ...topNames])].sort();
1660
+ }
1661
+ /**
1662
+ * Get a command by name: check builtins -> group cache -> group map -> top-level modules.
1663
+ */
1664
+ getCommand(cmdName) {
1665
+ this.buildGroupMap();
1666
+ if (this.groupCache.has(cmdName)) {
1667
+ return this.groupCache.get(cmdName).command;
1668
+ }
1669
+ if (this.groupMap.has(cmdName)) {
1670
+ const lazyGrp = new LazyGroup(
1671
+ this.groupMap.get(cmdName),
1672
+ this.executor,
1673
+ cmdName,
1674
+ this.helpTextMaxLength
1675
+ );
1676
+ this.groupCache.set(cmdName, lazyGrp);
1677
+ return lazyGrp.command;
1678
+ }
1679
+ if (this.topLevelModules.has(cmdName)) {
1680
+ if (this.commandCache.has(cmdName)) {
1681
+ return this.commandCache.get(cmdName);
1682
+ }
1683
+ const [, descriptor] = this.topLevelModules.get(cmdName);
1684
+ const cmd = buildModuleCommand(
1685
+ descriptor,
1686
+ this.executor,
1687
+ this.helpTextMaxLength,
1688
+ cmdName
1689
+ );
1690
+ this.commandCache.set(cmdName, cmd);
1691
+ return cmd;
1692
+ }
1693
+ return null;
1694
+ }
1695
+ /** Expose groupMap for testing. */
1696
+ getGroupMap() {
1697
+ return this.groupMap;
1698
+ }
1699
+ /** Expose topLevelModules for testing. */
1700
+ getTopLevelModules() {
1701
+ return this.topLevelModules;
1702
+ }
1703
+ /** Expose groupMapBuilt for testing. */
1704
+ isGroupMapBuilt() {
1705
+ return this.groupMapBuilt;
1706
+ }
1707
+ };
1295
1708
 
1296
1709
  // src/config.ts
1297
1710
  init_esm_shims();
1298
- import * as fs3 from "fs";
1711
+ import * as fs4 from "fs";
1299
1712
  import yaml from "js-yaml";
1300
1713
  var DEFAULTS = {
1301
1714
  "extensions.root": "./extensions",
1302
1715
  "logging.level": "WARNING",
1303
1716
  "sandbox.enabled": false,
1304
1717
  "cli.stdin_buffer_limit": 10485760,
1305
- "cli.auto_approve": false
1718
+ "cli.auto_approve": false,
1719
+ "cli.help_text_max_length": 1e3
1306
1720
  };
1307
1721
  var ConfigResolver = class {
1308
1722
  cliFlags;
@@ -1355,7 +1769,7 @@ var ConfigResolver = class {
1355
1769
  loadConfigFile() {
1356
1770
  let content;
1357
1771
  try {
1358
- content = fs3.readFileSync(this.configPath, "utf-8");
1772
+ content = fs4.readFileSync(this.configPath, "utf-8");
1359
1773
  } catch (err) {
1360
1774
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1361
1775
  return null;
@@ -1405,7 +1819,7 @@ var ConfigResolver = class {
1405
1819
  // src/discovery.ts
1406
1820
  init_esm_shims();
1407
1821
  init_errors();
1408
- import { Command as Command2 } from "commander";
1822
+ import { Command as Command3 } from "commander";
1409
1823
  var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
1410
1824
  function validateTag(tag) {
1411
1825
  if (!TAG_PATTERN.test(tag)) {
@@ -1420,7 +1834,7 @@ function collectTag(value, previous) {
1420
1834
  return previous.concat([value]);
1421
1835
  }
1422
1836
  function registerDiscoveryCommands(cli, registry) {
1423
- const listCmd = new Command2("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) => {
1837
+ 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) => {
1424
1838
  for (const t of opts.tag) {
1425
1839
  validateTag(t);
1426
1840
  }
@@ -1440,7 +1854,7 @@ function registerDiscoveryCommands(cli, registry) {
1440
1854
  formatModuleList(filtered, fmt, opts.tag.length > 0 ? opts.tag : void 0);
1441
1855
  });
1442
1856
  cli.addCommand(listCmd);
1443
- const describeCmd = new Command2("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) => {
1857
+ 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) => {
1444
1858
  validateModuleId(moduleId);
1445
1859
  const moduleDef = registry.getModule(moduleId);
1446
1860
  if (!moduleDef) {
@@ -1461,10 +1875,10 @@ init_esm_shims();
1461
1875
  init_errors();
1462
1876
  import { readFileSync as readFileSync3 } from "fs";
1463
1877
  import { fileURLToPath as fileURLToPath3 } from "url";
1464
- import * as path5 from "path";
1465
- import { Command as Command3 } from "commander";
1466
- var __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
1467
- var pkg2 = JSON.parse(readFileSync3(path5.resolve(__dirname3, "../package.json"), "utf-8"));
1878
+ import * as path6 from "path";
1879
+ import { Command as Command4 } from "commander";
1880
+ var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
1881
+ var pkg2 = JSON.parse(readFileSync3(path6.resolve(__dirname3, "../package.json"), "utf-8"));
1468
1882
  var SHELL_VERSION = pkg2.version;
1469
1883
  function makeFunctionName(progName) {
1470
1884
  return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
@@ -1476,6 +1890,18 @@ function generateBashCompletion(progName) {
1476
1890
  const fn = makeFunctionName(progName);
1477
1891
  const quoted = shellQuote(progName);
1478
1892
  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`;
1893
+ const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1894
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1895
+ const ids=JSON.parse(m).map(x=>x.id);
1896
+ const g=new Set(),t=[];
1897
+ ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1898
+ console.log([...g].sort().concat(t.sort()).join(' '))
1899
+ " 2>/dev/null`;
1900
+ const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1901
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1902
+ const g=process.env._APCORE_GRP;
1903
+ JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1904
+ " 2>/dev/null`;
1479
1905
  return `${fn}() {
1480
1906
  local cur prev opts
1481
1907
  COMPREPLY=()
@@ -1483,14 +1909,21 @@ function generateBashCompletion(progName) {
1483
1909
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
1484
1910
 
1485
1911
  if [[ \${COMP_CWORD} -eq 1 ]]; then
1486
- opts="list describe completion man"
1487
- COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
1912
+ opts="completion describe exec init list man"
1913
+ local groups_and_top=$(${groupsAndTopCmd})
1914
+ COMPREPLY=( $(compgen -W "\${opts} \${groups_and_top}" -- \${cur}) )
1488
1915
  return 0
1489
1916
  fi
1490
1917
 
1491
- if [[ "\${COMP_WORDS[1]}" == "exec" && \${COMP_CWORD} -eq 2 ]]; then
1492
- local modules=$(${moduleListCmd})
1493
- COMPREPLY=( $(compgen -W "\${modules}" -- \${cur}) )
1918
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
1919
+ if [[ "\${COMP_WORDS[1]}" == "exec" ]]; then
1920
+ local modules=$(${moduleListCmd})
1921
+ COMPREPLY=( $(compgen -W "\${modules}" -- \${cur}) )
1922
+ return 0
1923
+ fi
1924
+ export _APCORE_GRP="\${COMP_WORDS[1]}"
1925
+ local group_cmds=$(${groupCmdsCmd})
1926
+ COMPREPLY=( $(compgen -W "\${group_cmds}" -- \${cur}) )
1494
1927
  return 0
1495
1928
  fi
1496
1929
  }
@@ -1501,6 +1934,18 @@ function generateZshCompletion(progName) {
1501
1934
  const fn = makeFunctionName(progName);
1502
1935
  const quoted = shellQuote(progName);
1503
1936
  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`;
1937
+ const groupsAndTopCmd = `${quoted} list --format json 2>/dev/null | node -e "
1938
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1939
+ const ids=JSON.parse(m).map(x=>x.id);
1940
+ const g=new Set(),t=[];
1941
+ ids.forEach(i=>{if(i.includes('.'))g.add(i.split('.')[0]);else t.push(i)});
1942
+ console.log([...g].sort().concat(t.sort()).join(' '))
1943
+ " 2>/dev/null`;
1944
+ const groupCmdsCmd = `${quoted} list --format json 2>/dev/null | node -e "
1945
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
1946
+ const g=process.env._APCORE_GRP;
1947
+ JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
1948
+ " 2>/dev/null`;
1504
1949
  return `#compdef ${progName}
1505
1950
 
1506
1951
  ${fn}() {
@@ -1509,6 +1954,7 @@ ${fn}() {
1509
1954
  'list:List available modules'
1510
1955
  'describe:Show module metadata and schema'
1511
1956
  'completion:Generate shell completion script'
1957
+ 'init:Scaffolding commands'
1512
1958
  'man:Generate man page'
1513
1959
  )
1514
1960
 
@@ -1519,6 +1965,9 @@ ${fn}() {
1519
1965
  case "$state" in
1520
1966
  command)
1521
1967
  _describe -t commands '${progName} commands' commands
1968
+ local -a groups_and_top
1969
+ groups_and_top=($(${groupsAndTopCmd}))
1970
+ compadd -a groups_and_top
1522
1971
  ;;
1523
1972
  args)
1524
1973
  case "\${words[1]}" in
@@ -1527,6 +1976,12 @@ ${fn}() {
1527
1976
  modules=($(${moduleListCmd}))
1528
1977
  compadd -a modules
1529
1978
  ;;
1979
+ *)
1980
+ export _APCORE_GRP="\${words[1]}"
1981
+ local -a group_cmds
1982
+ group_cmds=($(${groupCmdsCmd}))
1983
+ compadd -a group_cmds
1984
+ ;;
1530
1985
  esac
1531
1986
  ;;
1532
1987
  esac
@@ -1538,13 +1993,29 @@ compdef ${fn} ${quoted}
1538
1993
  function generateFishCompletion(progName) {
1539
1994
  const quoted = shellQuote(progName);
1540
1995
  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`;
1996
+ 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`;
1541
1997
  return `# Fish completions for ${progName}
1542
1998
  complete -c ${quoted} -n "__fish_use_subcommand" -a list -d "List available modules"
1543
1999
  complete -c ${quoted} -n "__fish_use_subcommand" -a describe -d "Show module metadata and schema"
1544
2000
  complete -c ${quoted} -n "__fish_use_subcommand" -a completion -d "Generate shell completion script"
2001
+ complete -c ${quoted} -n "__fish_use_subcommand" -a init -d "Scaffolding commands"
1545
2002
  complete -c ${quoted} -n "__fish_use_subcommand" -a man -d "Generate man page"
2003
+ complete -c ${quoted} -n "__fish_use_subcommand" -a "(${groupsAndTopCmd})" -d "Module group"
1546
2004
 
1547
2005
  complete -c ${quoted} -n "__fish_seen_subcommand_from exec" -a "(${moduleListCmd})"
2006
+
2007
+ function __apcore_group_cmds
2008
+ set -l grp (commandline -opc)[2]
2009
+ set -x _APCORE_GRP $grp
2010
+ ${quoted} list --format json 2>/dev/null | node -e "
2011
+ const m=require('fs').readFileSync('/dev/stdin','utf8');
2012
+ const g=process.env._APCORE_GRP;
2013
+ JSON.parse(m).map(x=>x.id).filter(i=>i.includes('.')&&i.split('.')[0]===g).forEach(i=>console.log(i.split('.',2)[1]))
2014
+ " 2>/dev/null
2015
+ end
2016
+
2017
+ # Group subcommand completion \u2014 matches when position 1 is not a builtin
2018
+ complete -c ${quoted} -n "not __fish_use_subcommand; and not __fish_seen_subcommand_from list describe completion init man exec" -a "(__apcore_group_cmds)"
1548
2019
  `;
1549
2020
  }
1550
2021
  function buildSynopsis(command, progName, commandName) {
@@ -1632,6 +2103,11 @@ function generateManPage(commandName, command, progName, version = SHELL_VERSION
1632
2103
  sections.push(
1633
2104
  "Global apcore logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Used as fallback when \\fBAPCORE_CLI_LOGGING_LEVEL\\fR is not set. Default: WARNING."
1634
2105
  );
2106
+ sections.push(".TP");
2107
+ sections.push("\\fBAPCORE_AUTH_API_KEY\\fR");
2108
+ sections.push(
2109
+ "API key for authenticating with the apcore registry."
2110
+ );
1635
2111
  sections.push(".SH EXIT CODES");
1636
2112
  const exitCodes = [
1637
2113
  ["0", "Success."],
@@ -1668,7 +2144,7 @@ ${meaning}`);
1668
2144
  return sections.join("\n");
1669
2145
  }
1670
2146
  function registerShellCommands(cli, progName = "apcore-cli") {
1671
- const completionCmd = new Command3("completion").description(
2147
+ const completionCmd = new Command4("completion").description(
1672
2148
  "Generate a shell completion script and print it to stdout."
1673
2149
  ).argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
1674
2150
  const validShells = ["bash", "zsh", "fish"];
@@ -1688,8 +2164,8 @@ function registerShellCommands(cli, progName = "apcore-cli") {
1688
2164
  process.stdout.write(generators[shell]());
1689
2165
  });
1690
2166
  cli.addCommand(completionCmd);
1691
- const manCmd = new Command3("man").description("Generate a roff man page for COMMAND and print it to stdout.").argument("<command>", "Command to generate man page for").action((commandName) => {
1692
- const knownBuiltins = /* @__PURE__ */ new Set(["list", "describe", "completion", "man"]);
2167
+ 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) => {
2168
+ const knownBuiltins = /* @__PURE__ */ new Set(["completion", "describe", "exec", "init", "list", "man"]);
1693
2169
  const cmd = cli.commands.find((c) => c.name() === commandName) ?? null;
1694
2170
  if (!cmd && !knownBuiltins.has(commandName)) {
1695
2171
  process.stderr.write(
@@ -1714,16 +2190,20 @@ export {
1714
2190
  AuditLogger,
1715
2191
  AuthProvider,
1716
2192
  AuthenticationError,
2193
+ BUILTIN_COMMANDS,
1717
2194
  ConfigDecryptionError,
1718
2195
  ConfigEncryptor,
1719
2196
  ConfigResolver,
1720
2197
  DEFAULTS,
1721
2198
  EXIT_CODES,
2199
+ GroupedModuleGroup,
2200
+ LazyGroup,
1722
2201
  LazyModuleGroup,
1723
2202
  ModuleExecutionError,
1724
2203
  ModuleNotFoundError,
1725
2204
  Sandbox,
1726
2205
  SchemaValidationError,
2206
+ applyToolkitIntegration,
1727
2207
  buildModuleCommand,
1728
2208
  checkApproval,
1729
2209
  collectInput,
@@ -1736,12 +2216,15 @@ export {
1736
2216
  formatModuleDetail,
1737
2217
  formatModuleList,
1738
2218
  getAuditLogger,
2219
+ getCliDisplayFields,
2220
+ getDisplay,
1739
2221
  getLogLevel,
1740
2222
  info,
1741
2223
  main,
1742
2224
  mapType,
1743
2225
  reconvertEnumValues,
1744
2226
  registerDiscoveryCommands,
2227
+ registerInitCommand,
1745
2228
  registerShellCommands,
1746
2229
  resolveFormat,
1747
2230
  resolveRefs,