apcore-cli 0.2.1 → 0.3.1

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.
@@ -8,11 +8,11 @@ var __export = (target, all) => {
8
8
  __defProp(target, name, { get: all[name], enumerable: true });
9
9
  };
10
10
 
11
- // node_modules/tsup/assets/esm_shims.js
11
+ // node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
12
12
  import path from "path";
13
13
  import { fileURLToPath } from "url";
14
14
  var init_esm_shims = __esm({
15
- "node_modules/tsup/assets/esm_shims.js"() {
15
+ "node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
16
16
  "use strict";
17
17
  }
18
18
  });
@@ -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
@@ -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, helpTextMaxLength = 1e3) {
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 {
@@ -1097,7 +1292,7 @@ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3) {
1097
1292
  }
1098
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,46 +1453,262 @@ 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;
1264
1461
  helpTextMaxLength;
1265
1462
  commandCache = /* @__PURE__ */ new Map();
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;
1266
1468
  constructor(registry, executor, helpTextMaxLength = 1e3) {
1267
1469
  this.registry = registry;
1268
1470
  this.executor = executor;
1269
1471
  this.helpTextMaxLength = helpTextMaxLength;
1270
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
+ }
1495
+ }
1271
1496
  /**
1272
1497
  * List all available command names from the Registry.
1273
- *
1274
- * TODO: Implement registry enumeration.
1275
1498
  */
1276
1499
  listCommands() {
1277
- 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();
1278
1508
  }
1279
1509
  /**
1280
1510
  * Get or lazily build a Commander Command for the given module.
1281
- *
1282
- * TODO: Implement lazy command construction with schema-based options.
1283
1511
  */
1284
1512
  getCommand(cmdName) {
1285
1513
  if (this.commandCache.has(cmdName)) {
1286
1514
  return this.commandCache.get(cmdName);
1287
1515
  }
1288
- 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
+ }
1289
1522
  if (!moduleDef) {
1290
1523
  return null;
1291
1524
  }
1292
- const cmd = buildModuleCommand(moduleDef, this.executor, this.helpTextMaxLength);
1525
+ const cmd = buildModuleCommand(moduleDef, this.executor, this.helpTextMaxLength, cmdName);
1293
1526
  this.commandCache.set(cmdName, cmd);
1294
1527
  return cmd;
1295
1528
  }
1296
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
+ };
1297
1708
 
1298
1709
  // src/config.ts
1299
1710
  init_esm_shims();
1300
- import * as fs3 from "fs";
1711
+ import * as fs4 from "fs";
1301
1712
  import yaml from "js-yaml";
1302
1713
  var DEFAULTS = {
1303
1714
  "extensions.root": "./extensions",
@@ -1358,7 +1769,7 @@ var ConfigResolver = class {
1358
1769
  loadConfigFile() {
1359
1770
  let content;
1360
1771
  try {
1361
- content = fs3.readFileSync(this.configPath, "utf-8");
1772
+ content = fs4.readFileSync(this.configPath, "utf-8");
1362
1773
  } catch (err) {
1363
1774
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1364
1775
  return null;
@@ -1408,7 +1819,7 @@ var ConfigResolver = class {
1408
1819
  // src/discovery.ts
1409
1820
  init_esm_shims();
1410
1821
  init_errors();
1411
- import { Command as Command2 } from "commander";
1822
+ import { Command as Command3 } from "commander";
1412
1823
  var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
1413
1824
  function validateTag(tag) {
1414
1825
  if (!TAG_PATTERN.test(tag)) {
@@ -1423,7 +1834,7 @@ function collectTag(value, previous) {
1423
1834
  return previous.concat([value]);
1424
1835
  }
1425
1836
  function registerDiscoveryCommands(cli, registry) {
1426
- 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) => {
1427
1838
  for (const t of opts.tag) {
1428
1839
  validateTag(t);
1429
1840
  }
@@ -1443,7 +1854,7 @@ function registerDiscoveryCommands(cli, registry) {
1443
1854
  formatModuleList(filtered, fmt, opts.tag.length > 0 ? opts.tag : void 0);
1444
1855
  });
1445
1856
  cli.addCommand(listCmd);
1446
- 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) => {
1447
1858
  validateModuleId(moduleId);
1448
1859
  const moduleDef = registry.getModule(moduleId);
1449
1860
  if (!moduleDef) {
@@ -1464,10 +1875,10 @@ init_esm_shims();
1464
1875
  init_errors();
1465
1876
  import { readFileSync as readFileSync3 } from "fs";
1466
1877
  import { fileURLToPath as fileURLToPath3 } from "url";
1467
- import * as path5 from "path";
1468
- import { Command as Command3 } from "commander";
1469
- var __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
1470
- 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"));
1471
1882
  var SHELL_VERSION = pkg2.version;
1472
1883
  function makeFunctionName(progName) {
1473
1884
  return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
@@ -1479,6 +1890,18 @@ function generateBashCompletion(progName) {
1479
1890
  const fn = makeFunctionName(progName);
1480
1891
  const quoted = shellQuote(progName);
1481
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`;
1482
1905
  return `${fn}() {
1483
1906
  local cur prev opts
1484
1907
  COMPREPLY=()
@@ -1486,14 +1909,21 @@ function generateBashCompletion(progName) {
1486
1909
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
1487
1910
 
1488
1911
  if [[ \${COMP_CWORD} -eq 1 ]]; then
1489
- opts="list describe completion man"
1490
- 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}) )
1491
1915
  return 0
1492
1916
  fi
1493
1917
 
1494
- if [[ "\${COMP_WORDS[1]}" == "exec" && \${COMP_CWORD} -eq 2 ]]; then
1495
- local modules=$(${moduleListCmd})
1496
- 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}) )
1497
1927
  return 0
1498
1928
  fi
1499
1929
  }
@@ -1504,6 +1934,18 @@ function generateZshCompletion(progName) {
1504
1934
  const fn = makeFunctionName(progName);
1505
1935
  const quoted = shellQuote(progName);
1506
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`;
1507
1949
  return `#compdef ${progName}
1508
1950
 
1509
1951
  ${fn}() {
@@ -1512,6 +1954,7 @@ ${fn}() {
1512
1954
  'list:List available modules'
1513
1955
  'describe:Show module metadata and schema'
1514
1956
  'completion:Generate shell completion script'
1957
+ 'init:Scaffolding commands'
1515
1958
  'man:Generate man page'
1516
1959
  )
1517
1960
 
@@ -1522,6 +1965,9 @@ ${fn}() {
1522
1965
  case "$state" in
1523
1966
  command)
1524
1967
  _describe -t commands '${progName} commands' commands
1968
+ local -a groups_and_top
1969
+ groups_and_top=($(${groupsAndTopCmd}))
1970
+ compadd -a groups_and_top
1525
1971
  ;;
1526
1972
  args)
1527
1973
  case "\${words[1]}" in
@@ -1530,6 +1976,12 @@ ${fn}() {
1530
1976
  modules=($(${moduleListCmd}))
1531
1977
  compadd -a modules
1532
1978
  ;;
1979
+ *)
1980
+ export _APCORE_GRP="\${words[1]}"
1981
+ local -a group_cmds
1982
+ group_cmds=($(${groupCmdsCmd}))
1983
+ compadd -a group_cmds
1984
+ ;;
1533
1985
  esac
1534
1986
  ;;
1535
1987
  esac
@@ -1541,13 +1993,29 @@ compdef ${fn} ${quoted}
1541
1993
  function generateFishCompletion(progName) {
1542
1994
  const quoted = shellQuote(progName);
1543
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`;
1544
1997
  return `# Fish completions for ${progName}
1545
1998
  complete -c ${quoted} -n "__fish_use_subcommand" -a list -d "List available modules"
1546
1999
  complete -c ${quoted} -n "__fish_use_subcommand" -a describe -d "Show module metadata and schema"
1547
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"
1548
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"
1549
2004
 
1550
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)"
1551
2019
  `;
1552
2020
  }
1553
2021
  function buildSynopsis(command, progName, commandName) {
@@ -1635,6 +2103,11 @@ function generateManPage(commandName, command, progName, version = SHELL_VERSION
1635
2103
  sections.push(
1636
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."
1637
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
+ );
1638
2111
  sections.push(".SH EXIT CODES");
1639
2112
  const exitCodes = [
1640
2113
  ["0", "Success."],
@@ -1671,7 +2144,7 @@ ${meaning}`);
1671
2144
  return sections.join("\n");
1672
2145
  }
1673
2146
  function registerShellCommands(cli, progName = "apcore-cli") {
1674
- const completionCmd = new Command3("completion").description(
2147
+ const completionCmd = new Command4("completion").description(
1675
2148
  "Generate a shell completion script and print it to stdout."
1676
2149
  ).argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
1677
2150
  const validShells = ["bash", "zsh", "fish"];
@@ -1691,8 +2164,8 @@ function registerShellCommands(cli, progName = "apcore-cli") {
1691
2164
  process.stdout.write(generators[shell]());
1692
2165
  });
1693
2166
  cli.addCommand(completionCmd);
1694
- 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) => {
1695
- 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"]);
1696
2169
  const cmd = cli.commands.find((c) => c.name() === commandName) ?? null;
1697
2170
  if (!cmd && !knownBuiltins.has(commandName)) {
1698
2171
  process.stderr.write(
@@ -1717,16 +2190,20 @@ export {
1717
2190
  AuditLogger,
1718
2191
  AuthProvider,
1719
2192
  AuthenticationError,
2193
+ BUILTIN_COMMANDS,
1720
2194
  ConfigDecryptionError,
1721
2195
  ConfigEncryptor,
1722
2196
  ConfigResolver,
1723
2197
  DEFAULTS,
1724
2198
  EXIT_CODES,
2199
+ GroupedModuleGroup,
2200
+ LazyGroup,
1725
2201
  LazyModuleGroup,
1726
2202
  ModuleExecutionError,
1727
2203
  ModuleNotFoundError,
1728
2204
  Sandbox,
1729
2205
  SchemaValidationError,
2206
+ applyToolkitIntegration,
1730
2207
  buildModuleCommand,
1731
2208
  checkApproval,
1732
2209
  collectInput,
@@ -1739,12 +2216,15 @@ export {
1739
2216
  formatModuleDetail,
1740
2217
  formatModuleList,
1741
2218
  getAuditLogger,
2219
+ getCliDisplayFields,
2220
+ getDisplay,
1742
2221
  getLogLevel,
1743
2222
  info,
1744
2223
  main,
1745
2224
  mapType,
1746
2225
  reconvertEnumValues,
1747
2226
  registerDiscoveryCommands,
2227
+ registerInitCommand,
1748
2228
  registerShellCommands,
1749
2229
  resolveFormat,
1750
2230
  resolveRefs,