apcore-cli 0.5.0 → 0.7.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,11 +1,5 @@
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
- });
9
3
  var __esm = (fn, res) => function __init() {
10
4
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
5
  };
@@ -24,34 +18,36 @@ var init_esm_shims = __esm({
24
18
  });
25
19
 
26
20
  // src/errors.ts
27
- function exitCodeForError(error2) {
28
- if (error2 instanceof ApprovalTimeoutError) {
21
+ function exitCodeForError(error) {
22
+ if (error instanceof ApprovalTimeoutError) {
29
23
  return EXIT_CODES.APPROVAL_TIMEOUT;
30
24
  }
31
- if (error2 instanceof ApprovalDeniedError) {
25
+ if (error instanceof ApprovalDeniedError) {
32
26
  return EXIT_CODES.APPROVAL_DENIED;
33
27
  }
34
- if (error2 instanceof AuthenticationError) {
28
+ if (error instanceof AuthenticationError) {
35
29
  return EXIT_CODES.ACL_DENIED;
36
30
  }
37
- if (error2 instanceof ConfigDecryptionError) {
31
+ if (error instanceof ConfigDecryptionError) {
38
32
  return EXIT_CODES.CONFIG_INVALID;
39
33
  }
40
- if (error2 instanceof SchemaValidationError) {
34
+ if (error instanceof SchemaValidationError) {
41
35
  return EXIT_CODES.SCHEMA_VALIDATION_ERROR;
42
36
  }
43
- if (error2 instanceof ModuleNotFoundError) {
37
+ if (error instanceof ModuleNotFoundError) {
44
38
  return EXIT_CODES.MODULE_NOT_FOUND;
45
39
  }
46
- if (error2 instanceof ModuleExecutionError) {
40
+ if (error instanceof ModuleExecutionError) {
47
41
  return EXIT_CODES.MODULE_EXECUTE_ERROR;
48
42
  }
49
- if (error2 instanceof Error) {
50
- const code = error2.code;
43
+ if (error instanceof Error) {
44
+ const code = error.code;
51
45
  const codeMap = {
52
46
  MODULE_NOT_FOUND: EXIT_CODES.MODULE_NOT_FOUND,
53
47
  MODULE_LOAD_ERROR: EXIT_CODES.MODULE_LOAD_ERROR,
54
48
  MODULE_DISABLED: EXIT_CODES.MODULE_DISABLED,
49
+ DEPENDENCY_NOT_FOUND: EXIT_CODES.DEPENDENCY_NOT_FOUND,
50
+ DEPENDENCY_VERSION_MISMATCH: EXIT_CODES.DEPENDENCY_VERSION_MISMATCH,
55
51
  SCHEMA_VALIDATION_ERROR: EXIT_CODES.SCHEMA_VALIDATION_ERROR,
56
52
  SCHEMA_CIRCULAR_REF: EXIT_CODES.SCHEMA_CIRCULAR_REF,
57
53
  APPROVAL_DENIED: EXIT_CODES.APPROVAL_DENIED,
@@ -66,6 +62,7 @@ function exitCodeForError(error2) {
66
62
  CONFIG_NAMESPACE_RESERVED: EXIT_CODES.CONFIG_NAMESPACE_RESERVED,
67
63
  CONFIG_NAMESPACE_DUPLICATE: EXIT_CODES.CONFIG_NAMESPACE_DUPLICATE,
68
64
  CONFIG_ENV_PREFIX_CONFLICT: EXIT_CODES.CONFIG_ENV_PREFIX_CONFLICT,
65
+ CONFIG_ENV_MAP_CONFLICT: EXIT_CODES.CONFIG_ENV_MAP_CONFLICT,
69
66
  CONFIG_MOUNT_ERROR: EXIT_CODES.CONFIG_MOUNT_ERROR,
70
67
  CONFIG_BIND_ERROR: EXIT_CODES.CONFIG_BIND_ERROR,
71
68
  ERROR_FORMATTER_DUPLICATE: EXIT_CODES.ERROR_FORMATTER_DUPLICATE
@@ -131,6 +128,8 @@ var init_errors = __esm({
131
128
  MODULE_NOT_FOUND: 44,
132
129
  MODULE_LOAD_ERROR: 44,
133
130
  MODULE_DISABLED: 44,
131
+ DEPENDENCY_NOT_FOUND: 44,
132
+ DEPENDENCY_VERSION_MISMATCH: 44,
134
133
  SCHEMA_VALIDATION_ERROR: 45,
135
134
  APPROVAL_DENIED: 46,
136
135
  APPROVAL_TIMEOUT: 46,
@@ -142,6 +141,7 @@ var init_errors = __esm({
142
141
  CONFIG_NAMESPACE_RESERVED: 78,
143
142
  CONFIG_NAMESPACE_DUPLICATE: 78,
144
143
  CONFIG_ENV_PREFIX_CONFLICT: 78,
144
+ CONFIG_ENV_MAP_CONFLICT: 78,
145
145
  CONFIG_MOUNT_ERROR: 66,
146
146
  CONFIG_BIND_ERROR: 65,
147
147
  ERROR_FORMATTER_DUPLICATE: 70,
@@ -150,43 +150,88 @@ var init_errors = __esm({
150
150
  }
151
151
  });
152
152
 
153
+ // src/logger.ts
154
+ function setLogLevel(level) {
155
+ const upper = level.toUpperCase();
156
+ if (upper in LEVELS) {
157
+ currentLevel = upper;
158
+ }
159
+ }
160
+ function getLogLevel() {
161
+ return currentLevel;
162
+ }
163
+ function shouldLog(level) {
164
+ return LEVELS[level] >= LEVELS[currentLevel];
165
+ }
166
+ function warn(message) {
167
+ if (shouldLog("WARNING")) process.stderr.write(`WARNING: ${message}
168
+ `);
169
+ }
170
+ var LEVELS, currentLevel;
171
+ var init_logger = __esm({
172
+ "src/logger.ts"() {
173
+ "use strict";
174
+ init_esm_shims();
175
+ LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 };
176
+ currentLevel = "WARNING";
177
+ }
178
+ });
179
+
153
180
  // src/security/audit.ts
154
181
  var audit_exports = {};
155
182
  __export(audit_exports, {
156
183
  AuditLogger: () => AuditLogger,
184
+ canonicalizeForHash: () => canonicalizeForHash,
157
185
  getAuditLogger: () => getAuditLogger,
158
186
  setAuditLogger: () => setAuditLogger
159
187
  });
160
188
  import * as crypto from "crypto";
161
189
  import * as fs3 from "fs";
162
190
  import * as os from "os";
163
- import * as path4 from "path";
191
+ import * as path3 from "path";
164
192
  function setAuditLogger(auditLogger) {
165
193
  _auditLogger = auditLogger;
166
194
  }
167
195
  function getAuditLogger() {
168
196
  return _auditLogger;
169
197
  }
198
+ function canonicalizeForHash(value) {
199
+ if (value === null || typeof value !== "object") return value;
200
+ if (Array.isArray(value)) return value.map(canonicalizeForHash);
201
+ const src = value;
202
+ const sorted = {};
203
+ for (const key of Object.keys(src).sort()) {
204
+ sorted[key] = canonicalizeForHash(src[key]);
205
+ }
206
+ return sorted;
207
+ }
170
208
  var _auditLogger, AuditLogger;
171
209
  var init_audit = __esm({
172
210
  "src/security/audit.ts"() {
173
211
  "use strict";
174
212
  init_esm_shims();
213
+ init_logger();
175
214
  _auditLogger = null;
176
215
  AuditLogger = class _AuditLogger {
177
- static DEFAULT_PATH = path4.join(
216
+ static DEFAULT_PATH = path3.join(
178
217
  os.homedir(),
179
218
  ".apcore-cli",
180
219
  "audit.jsonl"
181
220
  );
182
221
  logPath;
183
- constructor(path7) {
184
- this.logPath = path7 ?? _AuditLogger.DEFAULT_PATH;
222
+ writeFailureWarned = false;
223
+ constructor(path5) {
224
+ this.logPath = path5 ?? _AuditLogger.DEFAULT_PATH;
185
225
  this.ensureDirectory();
186
226
  }
187
227
  ensureDirectory() {
228
+ const dir = path3.dirname(this.logPath);
188
229
  try {
189
- fs3.mkdirSync(path4.dirname(this.logPath), { recursive: true });
230
+ fs3.mkdirSync(dir, { recursive: true });
231
+ try {
232
+ fs3.chmodSync(dir, 448);
233
+ } catch {
234
+ }
190
235
  } catch {
191
236
  }
192
237
  }
@@ -202,14 +247,20 @@ var init_audit = __esm({
202
247
  };
203
248
  try {
204
249
  fs3.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
250
+ try {
251
+ fs3.chmodSync(this.logPath, 384);
252
+ } catch {
253
+ }
205
254
  } catch (err) {
206
- console.warn(`Could not write audit log: ${err}`);
255
+ if (!this.writeFailureWarned) {
256
+ this.writeFailureWarned = true;
257
+ warn(`Could not write audit log: ${err}`);
258
+ }
207
259
  }
208
260
  }
209
261
  hashInput(inputData) {
210
262
  const salt = crypto.randomBytes(16);
211
- const sortedKeys = Object.keys(inputData).sort();
212
- const payload = JSON.stringify(inputData, sortedKeys);
263
+ const payload = JSON.stringify(canonicalizeForHash(inputData));
213
264
  return crypto.createHash("sha256").update(Buffer.concat([salt, Buffer.from(payload, "utf-8")])).digest("hex");
214
265
  }
215
266
  getUser() {
@@ -235,17 +286,34 @@ async function getKeytar() {
235
286
  return null;
236
287
  }
237
288
  }
238
- var keytarModule, ConfigEncryptor;
289
+ var PBKDF2_ITERATIONS, V1_STATIC_SALT, keytarModule, ConfigEncryptor;
239
290
  var init_config_encryptor = __esm({
240
291
  "src/security/config-encryptor.ts"() {
241
292
  "use strict";
242
293
  init_esm_shims();
243
294
  init_errors();
295
+ init_logger();
296
+ PBKDF2_ITERATIONS = 6e5;
297
+ V1_STATIC_SALT = Buffer.from("apcore-cli-config-v1");
244
298
  keytarModule = null;
245
299
  ConfigEncryptor = class _ConfigEncryptor {
246
300
  static SERVICE_NAME = "apcore-cli";
301
+ // One-shot flag so the "obfuscation only" warning fires exactly once
302
+ // per process instead of once per encrypt/decrypt call.
303
+ static weakFallbackWarned = false;
247
304
  /**
248
305
  * Encrypt and store a configuration value.
306
+ *
307
+ * Cross-SDK contract (D10-003, 2026-04-26): when the OS keyring is
308
+ * detected as available but `setPassword` then throws (locked keyring,
309
+ * transient backend failure, permission revoked, etc.), the error is
310
+ * propagated wrapped in a `ConfigDecryptionError`. Previously TS
311
+ * caught the exception and silently fell through to AES file encryption
312
+ * — a quiet downgrade that surprised users who expected a hard failure.
313
+ * Python lets the keyring exception propagate raw; Rust returns
314
+ * `ConfigDecryptionError::KeyringError`. The fall-through to AES is
315
+ * still reached when `getKeytar()` returns `null` (keyring
316
+ * genuinely unavailable on this platform / install).
249
317
  */
250
318
  async store(key, value) {
251
319
  const keytar = await getKeytar();
@@ -253,12 +321,16 @@ var init_config_encryptor = __esm({
253
321
  try {
254
322
  await keytar.setPassword(_ConfigEncryptor.SERVICE_NAME, key, value);
255
323
  return `keyring:${key}`;
256
- } catch {
324
+ } catch (err) {
325
+ const detail = err instanceof Error ? err.message : String(err);
326
+ throw new ConfigDecryptionError(
327
+ `Failed to store '${key}' in OS keyring: ${detail}. Unset APCORE_CLI_CONFIG_PASSPHRASE-aware backends or unlock the keyring before retrying.`
328
+ );
257
329
  }
258
330
  }
259
- console.warn("OS keyring unavailable. Using file-based encryption.");
331
+ warn("OS keyring unavailable. Using file-based encryption.");
260
332
  const ciphertext = this.aesEncrypt(value);
261
- return `enc:${Buffer.from(ciphertext).toString("base64")}`;
333
+ return `enc:v2:${ciphertext.toString("base64")}`;
262
334
  }
263
335
  /**
264
336
  * Retrieve and decrypt a configuration value.
@@ -290,13 +362,20 @@ var init_config_encryptor = __esm({
290
362
  );
291
363
  }
292
364
  }
365
+ if (configValue.startsWith("enc:v2:")) {
366
+ const data = Buffer.from(configValue.slice("enc:v2:".length), "base64");
367
+ try {
368
+ return this.aesDecrypt(data);
369
+ } catch {
370
+ throw new ConfigDecryptionError(
371
+ `Failed to decrypt configuration value '${key}'. Re-configure with 'apcore-cli config set ${key}'.`
372
+ );
373
+ }
374
+ }
293
375
  if (configValue.startsWith("enc:")) {
294
- const ciphertext = Buffer.from(
295
- configValue.slice("enc:".length),
296
- "base64"
297
- );
376
+ const data = Buffer.from(configValue.slice("enc:".length), "base64");
298
377
  try {
299
- return this.aesDecrypt(ciphertext);
378
+ return this.aesDecryptV1(data);
300
379
  } catch {
301
380
  throw new ConfigDecryptionError(
302
381
  `Failed to decrypt configuration value '${key}'. Re-configure with 'apcore-cli config set ${key}'.`
@@ -305,18 +384,35 @@ var init_config_encryptor = __esm({
305
384
  }
306
385
  return configValue;
307
386
  }
308
- // NOTE: Best-effort fallback when OS keyring is unavailable.
309
- // The key is derived from hostname + username (non-secret inputs).
310
- // For production security, ensure the OS keyring is accessible.
311
- deriveKey() {
387
+ // Derive an AES-256 key with a provided salt (v2 format).
388
+ //
389
+ // Order of preference:
390
+ // 1. APCORE_CLI_CONFIG_PASSPHRASE env var — a real secret supplied by
391
+ // the user; produces a key an attacker with filesystem read cannot
392
+ // reconstruct without also knowing the passphrase.
393
+ // 2. hostname + username — obfuscation-only derivation for backward
394
+ // compatibility. Emits a loud stderr warning on first use so
395
+ // operators know the stored value is NOT protected against a
396
+ // filesystem-read attacker.
397
+ deriveKey(salt) {
398
+ const passphrase = process.env.APCORE_CLI_CONFIG_PASSPHRASE;
399
+ if (passphrase && passphrase.length > 0) {
400
+ return crypto2.pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, 32, "sha256");
401
+ }
402
+ if (!_ConfigEncryptor.weakFallbackWarned) {
403
+ warn(
404
+ "APCORE_CLI_CONFIG_PASSPHRASE is not set. The `enc:v2:` fallback uses a key derived from hostname+username (non-secret inputs) and is OBFUSCATION ONLY \u2014 an attacker with filesystem read access can reconstruct the key. Set APCORE_CLI_CONFIG_PASSPHRASE or ensure the OS keyring is available for real encryption."
405
+ );
406
+ _ConfigEncryptor.weakFallbackWarned = true;
407
+ }
312
408
  const hostname2 = os2.hostname();
313
409
  const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
314
- const salt = Buffer.from("apcore-cli-config-v1");
315
410
  const material = `${hostname2}:${username}`;
316
- return crypto2.pbkdf2Sync(material, salt, 1e5, 32, "sha256");
411
+ return crypto2.pbkdf2Sync(material, salt, PBKDF2_ITERATIONS, 32, "sha256");
317
412
  }
318
413
  aesEncrypt(plaintext) {
319
- const key = this.deriveKey();
414
+ const salt = crypto2.randomBytes(16);
415
+ const key = this.deriveKey(salt);
320
416
  const nonce = crypto2.randomBytes(12);
321
417
  const cipher = crypto2.createCipheriv("aes-256-gcm", key, nonce);
322
418
  const ct = Buffer.concat([
@@ -324,17 +420,39 @@ var init_config_encryptor = __esm({
324
420
  cipher.final()
325
421
  ]);
326
422
  const tag = cipher.getAuthTag();
327
- return Buffer.concat([nonce, tag, ct]);
423
+ return Buffer.concat([salt, nonce, tag, ct]);
328
424
  }
329
425
  aesDecrypt(data) {
330
- const key = this.deriveKey();
426
+ const salt = data.subarray(0, 16);
427
+ const nonce = data.subarray(16, 28);
428
+ const tag = data.subarray(28, 44);
429
+ const ct = data.subarray(44);
430
+ const key = this.deriveKey(salt);
431
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
432
+ decipher.setAuthTag(tag);
433
+ return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf-8");
434
+ }
435
+ /** Decrypt legacy v1-format values: nonce(12)+tag(16)+ct, static salt. */
436
+ aesDecryptV1(data) {
331
437
  const nonce = data.subarray(0, 12);
332
438
  const tag = data.subarray(12, 28);
333
439
  const ct = data.subarray(28);
334
- const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
335
- decipher.setAuthTag(tag);
336
- const plaintext = Buffer.concat([decipher.update(ct), decipher.final()]);
337
- return plaintext.toString("utf-8");
440
+ const hostname2 = os2.hostname();
441
+ const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
442
+ const passphrase = process.env.APCORE_CLI_CONFIG_PASSPHRASE;
443
+ const materials = passphrase ? [passphrase, `${hostname2}:${username}`] : [`${hostname2}:${username}`];
444
+ for (const material of materials) {
445
+ for (const iterations of [6e5, 1e5]) {
446
+ try {
447
+ const key = crypto2.pbkdf2Sync(material, V1_STATIC_SALT, iterations, 32, "sha256");
448
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
449
+ decipher.setAuthTag(tag);
450
+ return Buffer.concat([decipher.update(ct), decipher.final()]).toString("utf-8");
451
+ } catch {
452
+ }
453
+ }
454
+ }
455
+ throw new Error("v1 decryption failed with all material+iteration combinations");
338
456
  }
339
457
  };
340
458
  }
@@ -370,12 +488,30 @@ var init_auth = __esm({
370
488
  }
371
489
  const strResult = String(result);
372
490
  if (strResult.startsWith("keyring:") || strResult.startsWith("enc:")) {
373
- return this.encryptor.retrieve(strResult, "auth.api_key");
491
+ try {
492
+ return await this.encryptor.retrieve(strResult, "auth.api_key");
493
+ } catch (err) {
494
+ if (err instanceof ConfigDecryptionError) {
495
+ throw new AuthenticationError(
496
+ "Failed to decrypt stored API key. Re-configure with 'apcore-cli config set auth.api_key'."
497
+ );
498
+ }
499
+ throw err;
500
+ }
374
501
  }
375
502
  return strResult;
376
503
  }
377
504
  /**
378
505
  * Add authentication headers to an outgoing request.
506
+ *
507
+ * Cross-SDK contract (D10-002, 2026-04-26): the input `headers` object
508
+ * is mutated **in place** and the same reference is returned. Callers
509
+ * that share the headers reference (the documented pattern in
510
+ * apcore-cli/docs/features/security.md §AuthProvider) can read
511
+ * `headers.Authorization` after the call without re-binding the
512
+ * return value. Python and Rust both mutate-and-return; TS previously
513
+ * spread into a new object, which silently broke shared-reference
514
+ * callers.
379
515
  */
380
516
  async authenticateRequest(headers) {
381
517
  const key = await this.getApiKey();
@@ -384,7 +520,13 @@ var init_auth = __esm({
384
520
  "Remote registry requires authentication. Set --api-key, APCORE_AUTH_API_KEY, or auth.api_key in config."
385
521
  );
386
522
  }
387
- return { ...headers, Authorization: `Bearer ${key}` };
523
+ if (/[\r\n]/.test(key)) {
524
+ throw new AuthenticationError(
525
+ "Malformed API key: contains invalid characters (CR/LF). Re-configure with 'apcore-cli config set auth.api_key'."
526
+ );
527
+ }
528
+ headers.Authorization = `Bearer ${key.trim()}`;
529
+ return headers;
388
530
  }
389
531
  /**
390
532
  * Handle an HTTP response status code for auth-related errors.
@@ -401,20 +543,37 @@ var init_auth = __esm({
401
543
  });
402
544
 
403
545
  // src/security/sandbox.ts
404
- import * as child_process from "child_process";
405
- import * as fs4 from "fs";
406
- import * as os3 from "os";
407
- import * as path5 from "path";
408
- var Sandbox;
546
+ function buildSandboxEnv(tmpDir) {
547
+ const env = {};
548
+ for (const key of SANDBOX_ALLOW_KEYS) {
549
+ if (process.env[key]) env[key] = process.env[key];
550
+ }
551
+ for (const [key, val] of Object.entries(process.env)) {
552
+ if (key.startsWith(SANDBOX_ALLOW_PREFIX) && !key.startsWith(SANDBOX_DENY_PREFIX) && !SANDBOX_DENY_KEYS.includes(key)) {
553
+ env[key] = val;
554
+ }
555
+ }
556
+ env.HOME = tmpDir;
557
+ env.TMPDIR = tmpDir;
558
+ return env;
559
+ }
560
+ var SANDBOX_ALLOW_KEYS, SANDBOX_ALLOW_PREFIX, SANDBOX_DENY_PREFIX, SANDBOX_DENY_KEYS, SANDBOX_OUTPUT_SIZE_LIMIT, Sandbox;
409
561
  var init_sandbox = __esm({
410
562
  "src/security/sandbox.ts"() {
411
563
  "use strict";
412
564
  init_esm_shims();
413
565
  init_errors();
566
+ SANDBOX_ALLOW_KEYS = ["PATH", "LANG", "LC_ALL"];
567
+ SANDBOX_ALLOW_PREFIX = "APCORE_";
568
+ SANDBOX_DENY_PREFIX = "APCORE_AUTH_";
569
+ SANDBOX_DENY_KEYS = ["APCORE_AUTH_API_KEY"];
570
+ SANDBOX_OUTPUT_SIZE_LIMIT = 64 * 1024 * 1024;
414
571
  Sandbox = class {
415
572
  enabled;
416
- constructor(enabled = false) {
573
+ timeoutSeconds;
574
+ constructor(enabled = false, timeoutSeconds = 300) {
417
575
  this.enabled = enabled;
576
+ this.timeoutSeconds = timeoutSeconds;
418
577
  }
419
578
  /**
420
579
  * Execute a module, optionally inside a sandboxed subprocess.
@@ -423,63 +582,77 @@ var init_sandbox = __esm({
423
582
  if (!this.enabled) {
424
583
  return executor.execute(moduleId, inputData);
425
584
  }
426
- return this.sandboxedExecute(moduleId, inputData);
585
+ return this._sandboxedExecute(moduleId, inputData);
427
586
  }
428
- sandboxedExecute(moduleId, inputData) {
429
- const env = {};
430
- for (const key of ["PATH", "NODE_PATH", "LANG", "LC_ALL"]) {
431
- if (process.env[key]) {
432
- env[key] = process.env[key];
433
- }
434
- }
435
- for (const [key, value] of Object.entries(process.env)) {
436
- if (key.startsWith("APCORE_") && value) {
437
- env[key] = value;
587
+ async _sandboxedExecute(moduleId, inputData) {
588
+ const { spawn } = await import("child_process");
589
+ const { tmpdir } = await import("os");
590
+ const { join: join3 } = await import("path");
591
+ const { mkdtempSync, rmSync } = await import("fs");
592
+ const tmpDir = mkdtempSync(join3(tmpdir(), "apcore_sandbox_"));
593
+ const env = buildSandboxEnv(tmpDir);
594
+ const binaryPath = process.argv[1];
595
+ const child = spawn(process.execPath, [binaryPath, "--internal-sandbox-runner", moduleId], {
596
+ env,
597
+ cwd: tmpDir,
598
+ stdio: ["pipe", "pipe", "pipe"]
599
+ });
600
+ let stdout = "";
601
+ let stderr = "";
602
+ let totalBytes = 0;
603
+ let sizeExceeded = false;
604
+ child.stdout.on("data", (chunk) => {
605
+ totalBytes += chunk.length;
606
+ if (totalBytes > SANDBOX_OUTPUT_SIZE_LIMIT) {
607
+ sizeExceeded = true;
608
+ child.kill("SIGKILL");
609
+ return;
438
610
  }
439
- }
440
- const tmpDir = fs4.mkdtempSync(
441
- path5.join(os3.tmpdir(), "apcore_sandbox_")
442
- );
443
- try {
444
- env.HOME = tmpDir;
445
- env.TMPDIR = tmpDir;
446
- const script = [
447
- "let d='';",
448
- "process.stdin.setEncoding('utf-8');",
449
- "process.stdin.on('data',c=>d+=c);",
450
- "process.stdin.on('end',()=>{",
451
- " const input=JSON.parse(d);",
452
- ` process.stdout.write(JSON.stringify({error:"Sandbox runner not yet implemented for module: ${moduleId}"}));`,
453
- "});"
454
- ].join("");
455
- const result = child_process.execFileSync(
456
- process.execPath,
457
- ["-e", script],
458
- {
459
- input: JSON.stringify(inputData),
460
- env,
461
- cwd: tmpDir,
462
- timeout: 3e5,
463
- maxBuffer: 10 * 1024 * 1024
464
- }
465
- );
466
- return JSON.parse(result.toString("utf-8"));
467
- } catch (err) {
468
- if (err instanceof Error && "killed" in err && err.killed) {
469
- throw new ModuleExecutionError(
470
- `Error: Module '${moduleId}' timed out in sandbox.`
611
+ stdout += chunk.toString();
612
+ });
613
+ child.stderr.on("data", (chunk) => {
614
+ stderr += chunk.toString();
615
+ });
616
+ child.stdin.write(JSON.stringify(inputData));
617
+ child.stdin.end();
618
+ return new Promise((resolve2, reject) => {
619
+ const timer = setTimeout(() => {
620
+ child.kill("SIGKILL");
621
+ reject(
622
+ new ModuleExecutionError(
623
+ `Sandbox module '${moduleId}' timed out after ${this.timeoutSeconds}s.`
624
+ )
471
625
  );
472
- }
473
- const stderr = err instanceof Error && "stderr" in err ? String(err.stderr) : String(err);
474
- throw new ModuleExecutionError(
475
- `Error: Module '${moduleId}' execution failed: ${stderr}`
476
- );
477
- } finally {
478
- try {
479
- fs4.rmSync(tmpDir, { recursive: true, force: true });
480
- } catch {
481
- }
482
- }
626
+ }, this.timeoutSeconds * 1e3);
627
+ child.on("close", (code) => {
628
+ clearTimeout(timer);
629
+ try {
630
+ rmSync(tmpDir, { recursive: true, force: true });
631
+ } catch {
632
+ }
633
+ if (sizeExceeded) {
634
+ reject(new ModuleExecutionError(`Sandbox module '${moduleId}' output exceeded 64MiB limit.`));
635
+ return;
636
+ }
637
+ if (code !== 0) {
638
+ reject(new ModuleExecutionError(
639
+ `Sandbox module '${moduleId}' exited with code ${code}.${stderr ? ` stderr: ${stderr}` : ""}`
640
+ ));
641
+ return;
642
+ }
643
+ try {
644
+ resolve2(JSON.parse(stdout));
645
+ } catch {
646
+ reject(new ModuleExecutionError(
647
+ `Sandbox module '${moduleId}' returned non-JSON output: ${stdout.slice(0, 200)}`
648
+ ));
649
+ }
650
+ });
651
+ child.on("error", (err) => {
652
+ clearTimeout(timer);
653
+ reject(new ModuleExecutionError(`Failed to spawn sandbox process: ${err.message}`));
654
+ });
655
+ });
483
656
  }
484
657
  };
485
658
  }
@@ -512,10 +685,10 @@ init_esm_shims();
512
685
  // src/main.ts
513
686
  init_esm_shims();
514
687
  init_errors();
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";
688
+ import { readFileSync as readFileSync2 } from "fs";
689
+ import { fileURLToPath as fileURLToPath2 } from "url";
690
+ import * as path4 from "path";
691
+ import { Command as Command5, CommanderError, Option as Option4 } from "commander";
519
692
 
520
693
  // src/ref-resolver.ts
521
694
  init_esm_shims();
@@ -574,6 +747,12 @@ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
574
747
  properties: {},
575
748
  required: []
576
749
  };
750
+ if (typeof obj.properties === "object" && obj.properties !== null) {
751
+ Object.assign(merged.properties, obj.properties);
752
+ }
753
+ if (Array.isArray(obj.required)) {
754
+ merged.required.push(...obj.required);
755
+ }
577
756
  for (const subSchema of obj.allOf) {
578
757
  const resolved = resolveNode(
579
758
  subSchema,
@@ -697,7 +876,21 @@ function extractHelp(propSchema, maxLength = 1e3) {
697
876
  }
698
877
  return text;
699
878
  }
700
- var RESERVED_NAMES = /* @__PURE__ */ new Set(["input", "yes", "large_input", "format", "sandbox"]);
879
+ var RESERVED_NAMES = /* @__PURE__ */ new Set([
880
+ "input",
881
+ "yes",
882
+ "large_input",
883
+ "format",
884
+ "fields",
885
+ "sandbox",
886
+ "verbose",
887
+ "dry_run",
888
+ "trace",
889
+ "stream",
890
+ "strategy",
891
+ "approval_timeout",
892
+ "approval_token"
893
+ ]);
701
894
  function schemaToCliOptions(schema, maxHelpLength = 1e3) {
702
895
  const properties = schema.properties ?? {};
703
896
  const requiredList = schema.required ?? [];
@@ -705,21 +898,21 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
705
898
  const flagNames = {};
706
899
  for (const [propName, propSchema] of Object.entries(properties)) {
707
900
  const flagName = "--" + propName.replace(/_/g, "-");
708
- if (flagName in flagNames) {
901
+ if (RESERVED_NAMES.has(propName)) {
709
902
  process.stderr.write(
710
- `Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.
903
+ `Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.
711
904
  `
712
905
  );
713
906
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
714
907
  }
715
- flagNames[flagName] = propName;
716
- if (RESERVED_NAMES.has(propName)) {
908
+ if (flagName in flagNames) {
717
909
  process.stderr.write(
718
- `Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.
910
+ `Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.
719
911
  `
720
912
  );
721
913
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
722
914
  }
915
+ flagNames[flagName] = propName;
723
916
  const typeResult = mapType(propName, propSchema);
724
917
  const isRequired = requiredList.includes(propName);
725
918
  const helpBase = extractHelp(propSchema, maxHelpLength);
@@ -727,13 +920,22 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
727
920
  const defaultValue = propSchema.default;
728
921
  if (typeResult === BOOLEAN_FLAG) {
729
922
  const flagBase = propName.replace(/_/g, "-");
923
+ const noFlag = `--no-${flagBase}`;
924
+ if (noFlag in flagNames) {
925
+ process.stderr.write(
926
+ `Error: Flag name collision: boolean property '${propName}' auto-generates '${noFlag}' which is already used by property '${flagNames[noFlag]}'.
927
+ `
928
+ );
929
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
930
+ }
931
+ flagNames[noFlag] = propName;
730
932
  const defaultVal = propSchema.default ?? false;
731
933
  options.push({
732
934
  name: propName,
733
935
  flags: `--${flagBase}, --no-${flagBase}`,
734
936
  description: helpText,
735
937
  defaultValue: defaultVal,
736
- required: false,
938
+ required: isRequired,
737
939
  isBooleanFlag: true
738
940
  });
739
941
  } else if ("enum" in propSchema && Array.isArray(propSchema.enum)) {
@@ -744,7 +946,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
744
946
  flags: `${flagName} <value>`,
745
947
  description: helpText,
746
948
  defaultValue,
747
- required: false
949
+ required: isRequired
748
950
  });
749
951
  } else {
750
952
  const stringValues = enumValues.map(String);
@@ -763,7 +965,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
763
965
  flags: `${flagName} <value>`,
764
966
  description: helpText,
765
967
  defaultValue: defaultValue !== void 0 ? String(defaultValue) : void 0,
766
- required: false,
968
+ required: isRequired,
767
969
  choices: stringValues,
768
970
  enumOriginalTypes: Object.keys(enumOriginalTypes).length > 0 ? enumOriginalTypes : void 0
769
971
  });
@@ -788,7 +990,7 @@ function schemaToCliOptions(schema, maxHelpLength = 1e3) {
788
990
  flags: `${flagName} <value>`,
789
991
  description: helpText,
790
992
  defaultValue,
791
- required: false,
993
+ required: isRequired,
792
994
  parseArg
793
995
  });
794
996
  }
@@ -805,7 +1007,55 @@ function getAnnotation(annotations, key, defaultValue = void 0) {
805
1007
  const ann = annotations;
806
1008
  return key in ann ? ann[key] : defaultValue;
807
1009
  }
808
- async function checkApproval(moduleDef, autoApprove) {
1010
+ function readTimeoutFromEnv() {
1011
+ const raw = process.env.APCORE_CLI_APPROVAL_TIMEOUT;
1012
+ if (raw === void 0 || raw === "") return void 0;
1013
+ const parsed = parseInt(raw, 10);
1014
+ if (!Number.isFinite(parsed) || parsed <= 0) return void 0;
1015
+ return parsed;
1016
+ }
1017
+ var CliApprovalHandler = class {
1018
+ autoApprove;
1019
+ timeout;
1020
+ constructor(autoApprove = false, timeout) {
1021
+ this.autoApprove = autoApprove;
1022
+ const resolved = timeout ?? readTimeoutFromEnv() ?? 60;
1023
+ this.timeout = Math.max(1, Math.min(resolved, 3600));
1024
+ }
1025
+ async requestApproval(request) {
1026
+ const moduleId = request.module_id ?? "unknown";
1027
+ if (this.autoApprove) {
1028
+ return { status: "approved", approved_by: "auto_approve" };
1029
+ }
1030
+ const envVal = process.env.APCORE_CLI_AUTO_APPROVE ?? "";
1031
+ if (envVal === "1") {
1032
+ return { status: "approved", approved_by: "env_auto_approve" };
1033
+ }
1034
+ if (envVal !== "" && envVal !== "1") {
1035
+ process.stderr.write(
1036
+ `Warning: APCORE_CLI_AUTO_APPROVE is set to '${envVal}', expected '1'. Ignoring.
1037
+ `
1038
+ );
1039
+ }
1040
+ if (!process.stdin.isTTY) {
1041
+ return { status: "rejected", reason: "Non-interactive session without --yes" };
1042
+ }
1043
+ const annotations = request.annotations;
1044
+ const extra = annotations?.extra ?? {};
1045
+ const message = extra.approval_message ?? `Module '${moduleId}' requires approval to execute.`;
1046
+ process.stderr.write(message + "\n");
1047
+ try {
1048
+ await promptWithTimeout({ id: moduleId }, this.timeout);
1049
+ return { status: "approved", approved_by: "tty_user" };
1050
+ } catch {
1051
+ return { status: "rejected", reason: "User rejected or timed out" };
1052
+ }
1053
+ }
1054
+ async checkApproval(_approvalId) {
1055
+ return { status: "rejected", reason: "CLI does not support async approval polling" };
1056
+ }
1057
+ };
1058
+ async function checkApproval(moduleDef, autoApprove, timeout) {
809
1059
  const annotations = moduleDef.annotations;
810
1060
  let requiresApproval;
811
1061
  if (moduleDef.requiresApproval !== void 0) {
@@ -833,13 +1083,12 @@ async function checkApproval(moduleDef, autoApprove) {
833
1083
  );
834
1084
  }
835
1085
  if (!process.stdin.isTTY) {
836
- process.stderr.write(
837
- `Error: Module '${moduleId}' requires approval but no interactive terminal is available. Use --yes or set APCORE_CLI_AUTO_APPROVE=1 to bypass.
838
- `
1086
+ throw new ApprovalDeniedError(
1087
+ `Module '${moduleId}' requires approval but no interactive terminal is available. Use --yes or set APCORE_CLI_AUTO_APPROVE=1 to bypass.`
839
1088
  );
840
- process.exit(EXIT_CODES.APPROVAL_DENIED);
841
1089
  }
842
- await promptWithTimeout(moduleDef, 60);
1090
+ const effectiveTimeout = timeout ?? readTimeoutFromEnv() ?? 60;
1091
+ await promptWithTimeout(moduleDef, effectiveTimeout);
843
1092
  }
844
1093
  async function promptWithTimeout(moduleDef, timeout) {
845
1094
  timeout = Math.max(1, Math.min(timeout, 3600));
@@ -854,8 +1103,8 @@ async function promptWithTimeout(moduleDef, timeout) {
854
1103
  let timer;
855
1104
  try {
856
1105
  const answer = await Promise.race([
857
- new Promise((resolve3) => {
858
- rl.question("Proceed? [y/N] ", (ans) => resolve3(ans));
1106
+ new Promise((resolve2) => {
1107
+ rl.question("Proceed? [y/N] ", (ans) => resolve2(ans));
859
1108
  }),
860
1109
  new Promise((_, reject) => {
861
1110
  timer = setTimeout(() => {
@@ -870,17 +1119,9 @@ async function promptWithTimeout(moduleDef, timeout) {
870
1119
  if (normalized === "y" || normalized === "yes") {
871
1120
  return;
872
1121
  }
873
- process.stderr.write("Error: Approval denied.\n");
874
- process.exit(EXIT_CODES.APPROVAL_DENIED);
1122
+ throw new ApprovalDeniedError("Approval denied");
875
1123
  } catch (err) {
876
1124
  if (timer) clearTimeout(timer);
877
- if (err instanceof ApprovalTimeoutError) {
878
- process.stderr.write(
879
- `Error: Approval prompt timed out after ${timeout} seconds.
880
- `
881
- );
882
- process.exit(EXIT_CODES.APPROVAL_TIMEOUT);
883
- }
884
1125
  throw err;
885
1126
  } finally {
886
1127
  rl.close();
@@ -889,6 +1130,13 @@ async function promptWithTimeout(moduleDef, timeout) {
889
1130
 
890
1131
  // src/output.ts
891
1132
  init_esm_shims();
1133
+ init_errors();
1134
+ import yaml from "js-yaml";
1135
+ function csvCellString(value) {
1136
+ if (value === null || value === void 0) return "";
1137
+ if (typeof value === "object") return JSON.stringify(value);
1138
+ return String(value);
1139
+ }
892
1140
  function resolveFormat(explicitFormat) {
893
1141
  if (explicitFormat !== void 0) {
894
1142
  return explicitFormat;
@@ -912,7 +1160,7 @@ function formatTable(headers, rows) {
912
1160
  );
913
1161
  return [headerLine, sep2, ...dataLines].join("\n") + "\n";
914
1162
  }
915
- function formatModuleList(modules, format, filterTags) {
1163
+ function formatModuleList(modules, format, filterTags, showDeps = false, exposureFilter) {
916
1164
  if (format === "table") {
917
1165
  if (modules.length === 0 && filterTags && filterTags.length > 0) {
918
1166
  process.stdout.write(
@@ -926,18 +1174,36 @@ function formatModuleList(modules, format, filterTags) {
926
1174
  return;
927
1175
  }
928
1176
  const headers = ["ID", "Description", "Tags"];
929
- const rows = modules.map((m) => [
930
- m.id,
931
- truncate(m.description, 80),
932
- (m.tags ?? []).join(", ")
933
- ]);
1177
+ if (showDeps) headers.push("Deps");
1178
+ if (exposureFilter) headers.push("Exposure");
1179
+ const rows = modules.map((m) => {
1180
+ const base = [m.id, truncate(m.description, 80), (m.tags ?? []).join(", ")];
1181
+ if (showDeps) {
1182
+ const deps = m.dependencies;
1183
+ base.push(String(Array.isArray(deps) ? deps.length : 0));
1184
+ }
1185
+ if (exposureFilter) {
1186
+ base.push(exposureFilter.isExposed(m.id ?? "") ? "\u2713" : "\u2014");
1187
+ }
1188
+ return base;
1189
+ });
934
1190
  process.stdout.write(formatTable(headers, rows));
935
1191
  } else if (format === "json") {
936
- const result = modules.map((m) => ({
937
- id: m.id,
938
- description: m.description,
939
- tags: m.tags ?? []
940
- }));
1192
+ const result = modules.map((m) => {
1193
+ const entry = {
1194
+ id: m.id,
1195
+ description: m.description,
1196
+ tags: m.tags ?? []
1197
+ };
1198
+ if (showDeps) {
1199
+ const deps = m.dependencies;
1200
+ entry.dependency_count = Array.isArray(deps) ? deps.length : 0;
1201
+ }
1202
+ if (exposureFilter) {
1203
+ entry.exposed = exposureFilter.isExposed(m.id ?? "");
1204
+ }
1205
+ return entry;
1206
+ });
941
1207
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
942
1208
  }
943
1209
  }
@@ -1025,62 +1291,177 @@ Tags: ${tags.join(", ")}
1025
1291
  process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1026
1292
  }
1027
1293
  }
1028
- function formatExecResult(result, format) {
1294
+ function selectFields(result, fields) {
1295
+ const selected = {};
1296
+ for (const f of fields.split(",")) {
1297
+ const key = f.trim();
1298
+ let val = result;
1299
+ for (const part of key.split(".")) {
1300
+ if (val && typeof val === "object" && !Array.isArray(val)) {
1301
+ val = val[part];
1302
+ } else {
1303
+ val = void 0;
1304
+ break;
1305
+ }
1306
+ }
1307
+ selected[key] = val;
1308
+ }
1309
+ return selected;
1310
+ }
1311
+ function formatExecResult(result, format, fields) {
1029
1312
  if (result === null || result === void 0) {
1030
1313
  return;
1031
1314
  }
1315
+ let effective_result = result;
1316
+ if (fields && typeof result === "object" && !Array.isArray(result) && result !== null) {
1317
+ effective_result = selectFields(result, fields);
1318
+ }
1032
1319
  const effective = resolveFormat(format);
1033
- if (effective === "table" && typeof result === "object" && !Array.isArray(result)) {
1034
- const entries = Object.entries(result);
1320
+ if (effective === "csv") {
1321
+ if (typeof effective_result === "object" && !Array.isArray(effective_result) && effective_result !== null) {
1322
+ const obj = effective_result;
1323
+ const keys = Object.keys(obj);
1324
+ const header = keys.map(escapeCsvField).join(",");
1325
+ const row = keys.map((k) => escapeCsvField(csvCellString(obj[k]))).join(",");
1326
+ process.stdout.write(header + "\n" + row + "\n");
1327
+ } else if (Array.isArray(effective_result) && effective_result.length > 0 && typeof effective_result[0] === "object") {
1328
+ const keys = Object.keys(effective_result[0]);
1329
+ const header = keys.map(escapeCsvField).join(",");
1330
+ const rows = effective_result.map((item) => {
1331
+ const obj = item;
1332
+ return keys.map((k) => escapeCsvField(csvCellString(obj[k]))).join(",");
1333
+ });
1334
+ process.stdout.write(header + "\n" + rows.join("\n") + "\n");
1335
+ } else {
1336
+ process.stdout.write(JSON.stringify(effective_result) + "\n");
1337
+ }
1338
+ } else if (effective === "yaml") {
1339
+ process.stdout.write(yaml.dump(effective_result, { lineWidth: -1 }));
1340
+ } else if (effective === "jsonl") {
1341
+ if (Array.isArray(effective_result)) {
1342
+ for (const item of effective_result) {
1343
+ process.stdout.write(JSON.stringify(item) + "\n");
1344
+ }
1345
+ } else {
1346
+ process.stdout.write(JSON.stringify(effective_result) + "\n");
1347
+ }
1348
+ } else if (effective === "table" && typeof effective_result === "object" && !Array.isArray(effective_result)) {
1349
+ const entries = Object.entries(effective_result);
1035
1350
  const headers = ["Key", "Value"];
1036
1351
  const rows = entries.map(([k, v]) => [String(k), String(v)]);
1037
1352
  process.stdout.write(formatTable(headers, rows));
1038
- } else if (typeof result === "object") {
1039
- process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1040
- } else if (typeof result === "string") {
1041
- process.stdout.write(result + "\n");
1353
+ } else if (typeof effective_result === "object") {
1354
+ process.stdout.write(JSON.stringify(effective_result, null, 2) + "\n");
1355
+ } else if (typeof effective_result === "string") {
1356
+ process.stdout.write(effective_result + "\n");
1042
1357
  } else {
1043
- process.stdout.write(String(result) + "\n");
1358
+ process.stdout.write(String(effective_result) + "\n");
1044
1359
  }
1045
1360
  }
1046
-
1047
- // src/logger.ts
1048
- init_esm_shims();
1049
- var LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 };
1050
- var currentLevel = "WARNING";
1051
- function setLogLevel(level) {
1052
- const upper = level.toUpperCase();
1053
- if (upper in LEVELS) {
1054
- currentLevel = upper;
1361
+ function escapeCsvField(value) {
1362
+ if (value.includes(",") || value.includes('"') || value.includes("\n") || value.includes("\r")) {
1363
+ return '"' + value.replace(/"/g, '""') + '"';
1055
1364
  }
1365
+ return value;
1056
1366
  }
1057
- function getLogLevel() {
1058
- return currentLevel;
1059
- }
1060
- function shouldLog(level) {
1061
- return LEVELS[level] >= LEVELS[currentLevel];
1062
- }
1063
- function debug(message) {
1064
- if (shouldLog("DEBUG")) process.stderr.write(`DEBUG: ${message}
1065
- `);
1066
- }
1067
- function info(message) {
1068
- if (shouldLog("INFO")) process.stderr.write(`INFO: ${message}
1367
+ function formatPreflightResult(result, format) {
1368
+ const resolved = resolveFormat(format);
1369
+ if (resolved === "json" || !process.stdout.isTTY) {
1370
+ const payload = {
1371
+ valid: result.valid,
1372
+ // JSON output key stays snake_case for cross-language CLI contract;
1373
+ // runtime read uses camelCase to match apcore-js PreflightResult.
1374
+ requires_approval: result.requiresApproval,
1375
+ checks: result.checks.map((c) => {
1376
+ const entry = { check: c.check, passed: c.passed };
1377
+ if (c.error !== void 0 && c.error !== null) {
1378
+ entry.error = c.error;
1379
+ }
1380
+ if (c.warnings && c.warnings.length > 0) {
1381
+ entry.warnings = c.warnings;
1382
+ }
1383
+ return entry;
1384
+ })
1385
+ };
1386
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
1387
+ } else {
1388
+ for (const c of result.checks) {
1389
+ const hasWarnings = (c.warnings?.length ?? 0) > 0;
1390
+ let sym;
1391
+ if (c.passed && hasWarnings) {
1392
+ sym = "\u26A0";
1393
+ } else if (c.passed) {
1394
+ sym = "\u2713";
1395
+ } else if (c.passed === false) {
1396
+ sym = "\u2717";
1397
+ } else {
1398
+ sym = "\u25CB";
1399
+ }
1400
+ let status = ` ${sym} ${c.check.padEnd(20)}`;
1401
+ if (c.error) {
1402
+ const detail = typeof c.error === "object" ? JSON.stringify(c.error) : String(c.error);
1403
+ status += ` ${detail}`;
1404
+ } else if (c.passed && !hasWarnings) {
1405
+ status += " OK";
1406
+ } else if (!c.passed) {
1407
+ status += " Skipped";
1408
+ }
1409
+ process.stdout.write(status + "\n");
1410
+ for (const w of c.warnings ?? []) {
1411
+ process.stdout.write(` Warning: ${w}
1069
1412
  `);
1070
- }
1071
- function warn(message) {
1072
- if (shouldLog("WARNING")) process.stderr.write(`WARNING: ${message}
1413
+ }
1414
+ }
1415
+ const errors = result.checks.filter((c) => !c.passed).length;
1416
+ const warnings = result.checks.reduce((sum, c) => sum + (c.warnings?.length ?? 0), 0);
1417
+ const tag = result.valid ? "PASS" : "FAIL";
1418
+ process.stdout.write(`
1419
+ Result: ${tag} (${errors} error(s), ${warnings} warning(s))
1073
1420
  `);
1421
+ }
1074
1422
  }
1075
- function error(message) {
1076
- if (shouldLog("ERROR")) process.stderr.write(`ERROR: ${message}
1077
- `);
1423
+ function firstFailedExitCode(result) {
1424
+ const checkToExit = {
1425
+ module_id: EXIT_CODES.INVALID_CLI_INPUT,
1426
+ module_lookup: EXIT_CODES.MODULE_NOT_FOUND,
1427
+ call_chain: EXIT_CODES.MODULE_EXECUTE_ERROR,
1428
+ acl: EXIT_CODES.ACL_DENIED,
1429
+ schema: EXIT_CODES.SCHEMA_VALIDATION_ERROR,
1430
+ approval: EXIT_CODES.APPROVAL_DENIED,
1431
+ module_preflight: EXIT_CODES.MODULE_EXECUTE_ERROR
1432
+ };
1433
+ for (const check of result.checks) {
1434
+ if (!check.passed) {
1435
+ return checkToExit[check.check] ?? EXIT_CODES.MODULE_EXECUTE_ERROR;
1436
+ }
1437
+ }
1438
+ return EXIT_CODES.MODULE_EXECUTE_ERROR;
1078
1439
  }
1079
1440
 
1441
+ // src/main.ts
1442
+ init_logger();
1443
+
1080
1444
  // src/init-cmd.ts
1081
1445
  init_esm_shims();
1446
+ init_errors();
1082
1447
  import * as fs from "fs";
1083
1448
  import * as path2 from "path";
1449
+ function runFsOp(op, targetPath, fn, partial) {
1450
+ try {
1451
+ return fn();
1452
+ } catch (err) {
1453
+ const msg = err instanceof Error ? err.message : String(err);
1454
+ process.stderr.write(`Error: failed to ${op} ${targetPath}: ${msg}
1455
+ `);
1456
+ if (partial && partial.length > 0) {
1457
+ process.stderr.write(
1458
+ ` Partial scaffold left on disk \u2014 you may want to remove: ${partial.join(", ")}
1459
+ `
1460
+ );
1461
+ }
1462
+ process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
1463
+ }
1464
+ }
1084
1465
  var DECORATOR_TEMPLATE = `import { module } from "apcore-js";
1085
1466
  import { Type } from "@sinclair/typebox";
1086
1467
 
@@ -1104,7 +1485,8 @@ export function {funcName}(): Record<string, unknown> {
1104
1485
  return { status: "ok" };
1105
1486
  }
1106
1487
  `;
1107
- var BINDING_TEMPLATE = `bindings:
1488
+ var BINDING_TEMPLATE = `spec_version: "1.0"
1489
+ bindings:
1108
1490
  - module_id: "{moduleId}"
1109
1491
  target: "{target}"
1110
1492
  description: "{description}"
@@ -1153,7 +1535,7 @@ function registerInitCommand(cli) {
1153
1535
  });
1154
1536
  }
1155
1537
  function createDecoratorModule(moduleId, _prefix, funcName, description, outputDir) {
1156
- fs.mkdirSync(outputDir, { recursive: true });
1538
+ runFsOp("create directory", outputDir, () => fs.mkdirSync(outputDir, { recursive: true }));
1157
1539
  const filename = moduleId.replace(/\./g, "_") + ".ts";
1158
1540
  const filepath = path2.join(outputDir, filename);
1159
1541
  const varName = funcName + "Module";
@@ -1163,14 +1545,14 @@ function createDecoratorModule(moduleId, _prefix, funcName, description, outputD
1163
1545
  funcName,
1164
1546
  description
1165
1547
  });
1166
- fs.writeFileSync(filepath, content);
1548
+ runFsOp("write file", filepath, () => fs.writeFileSync(filepath, content));
1167
1549
  process.stdout.write(`Created ${filepath}
1168
1550
  `);
1169
1551
  }
1170
1552
  function createConventionModule(moduleId, prefix, funcName, description, outputDir) {
1171
1553
  const prefixParts = prefix.split(".");
1172
1554
  const dirPath = prefixParts.length > 1 ? path2.join(outputDir, ...prefixParts.slice(0, -1)) : outputDir;
1173
- fs.mkdirSync(dirPath, { recursive: true });
1555
+ runFsOp("create directory", dirPath, () => fs.mkdirSync(dirPath, { recursive: true }));
1174
1556
  let filename;
1175
1557
  if (prefixParts.length > 1) {
1176
1558
  filename = prefixParts[prefixParts.length - 1] + ".ts";
@@ -1188,12 +1570,13 @@ function createConventionModule(moduleId, prefix, funcName, description, outputD
1188
1570
  description,
1189
1571
  cliGroupLine
1190
1572
  });
1191
- fs.writeFileSync(filepath, content);
1573
+ runFsOp("write file", filepath, () => fs.writeFileSync(filepath, content));
1192
1574
  process.stdout.write(`Created ${filepath}
1193
1575
  `);
1194
1576
  }
1195
1577
  function createBindingModule(moduleId, prefix, funcName, description, outputDir) {
1196
- fs.mkdirSync(outputDir, { recursive: true });
1578
+ const partial = [];
1579
+ runFsOp("create directory", outputDir, () => fs.mkdirSync(outputDir, { recursive: true }));
1197
1580
  const yamlFile = path2.join(outputDir, moduleId.replace(/\./g, "_") + ".binding.yaml");
1198
1581
  const target = `commands.${prefix}:${funcName}`;
1199
1582
  const yamlContent = renderTemplate(BINDING_TEMPLATE, {
@@ -1201,11 +1584,12 @@ function createBindingModule(moduleId, prefix, funcName, description, outputDir)
1201
1584
  target,
1202
1585
  description
1203
1586
  });
1204
- fs.writeFileSync(yamlFile, yamlContent);
1587
+ runFsOp("write file", yamlFile, () => fs.writeFileSync(yamlFile, yamlContent));
1588
+ partial.push(yamlFile);
1205
1589
  process.stdout.write(`Created ${yamlFile}
1206
1590
  `);
1207
1591
  const baseSrc = "commands";
1208
- fs.mkdirSync(baseSrc, { recursive: true });
1592
+ runFsOp("create directory", baseSrc, () => fs.mkdirSync(baseSrc, { recursive: true }), partial);
1209
1593
  const srcFile = path2.join(baseSrc, prefix.replace(/\./g, "_") + ".ts");
1210
1594
  if (!fs.existsSync(srcFile)) {
1211
1595
  const srcContent = `export function ${funcName}(): Record<string, unknown> {
@@ -1214,7 +1598,7 @@ function createBindingModule(moduleId, prefix, funcName, description, outputDir)
1214
1598
  return { status: "ok" };
1215
1599
  }
1216
1600
  `;
1217
- fs.writeFileSync(srcFile, srcContent);
1601
+ runFsOp("write file", srcFile, () => fs.writeFileSync(srcFile, srcContent), partial);
1218
1602
  process.stdout.write(`Created ${srcFile}
1219
1603
  `);
1220
1604
  }
@@ -1228,33 +1612,48 @@ function getDisplay(descriptor) {
1228
1612
  if (display && typeof display === "object" && !Array.isArray(display)) {
1229
1613
  return display;
1230
1614
  }
1231
- return {};
1232
- }
1233
- function getCliDisplayFields(descriptor) {
1234
- const display = getDisplay(descriptor);
1235
- const cli = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
1236
- const name = cli.alias ?? display.alias ?? descriptor.id;
1237
- const desc = cli.description ?? descriptor.description;
1238
- const tags = display.tags ?? descriptor.tags ?? [];
1239
- return [name, desc, tags];
1615
+ const overlay = lookupBindingDisplay(descriptor.id);
1616
+ return overlay ?? {};
1240
1617
  }
1241
1618
 
1242
1619
  // src/config.ts
1243
1620
  init_esm_shims();
1621
+ init_logger();
1244
1622
  import * as fs2 from "fs";
1245
- import yaml from "js-yaml";
1623
+ import { createRequire } from "module";
1624
+ import yaml2 from "js-yaml";
1246
1625
  var DEFAULTS = {
1247
1626
  "extensions.root": "./extensions",
1248
1627
  "logging.level": "WARNING",
1249
- "sandbox.enabled": false,
1250
- "cli.stdin_buffer_limit": 10485760,
1251
- "cli.auto_approve": false,
1252
1628
  "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"
1629
+ // FE-11 config keys
1630
+ "cli.approval_timeout": 60,
1631
+ "cli.strategy": "standard",
1632
+ "cli.group_depth": 1,
1633
+ // Exposure filtering (FE-12)
1634
+ "expose.mode": "all",
1635
+ "expose.include": [],
1636
+ "expose.exclude": []
1637
+ // Builtin group visibility (FE-13) — apcli.* keys are NOT in DEFAULTS.
1638
+ // The runtime reads them via resolveObject('apcli') (raw yaml walk) and
1639
+ // does not use the flat-key resolve() path. Python and Rust have no such
1640
+ // entries either. (D11-008 cleanup)
1641
+ };
1642
+ var NAMESPACE_DEFAULTS = {
1643
+ stdin_buffer_limit: 10485760,
1644
+ auto_approve: false,
1645
+ help_text_max_length: 1e3,
1646
+ logging_level: "WARNING",
1647
+ approval_timeout: 60,
1648
+ strategy: "standard",
1649
+ group_depth: 1,
1650
+ // FE-13 — builtin group visibility configuration
1651
+ apcli: {
1652
+ mode: null,
1653
+ include: [],
1654
+ exclude: [],
1655
+ disable_env: false
1656
+ }
1258
1657
  };
1259
1658
  var NAMESPACE_TO_LEGACY = {
1260
1659
  "apcore-cli.stdin_buffer_limit": "cli.stdin_buffer_limit",
@@ -1267,17 +1666,13 @@ var LEGACY_TO_NAMESPACE = Object.fromEntries(
1267
1666
  );
1268
1667
  function registerConfigNamespace() {
1269
1668
  try {
1270
- const { Config } = __require("apcore-js");
1669
+ const nodeRequire = createRequire(import.meta.url);
1670
+ const { Config } = nodeRequire("apcore-js");
1271
1671
  if (typeof Config?.registerNamespace === "function") {
1272
1672
  Config.registerNamespace({
1273
1673
  name: "apcore-cli",
1274
1674
  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
- }
1675
+ defaults: NAMESPACE_DEFAULTS
1281
1676
  });
1282
1677
  }
1283
1678
  } catch {
@@ -1288,6 +1683,13 @@ var ConfigResolver = class {
1288
1683
  configPath;
1289
1684
  fileCache = null;
1290
1685
  fileCacheLoaded = false;
1686
+ /**
1687
+ * Raw parsed yaml root (pre-flatten). Populated alongside `fileCache`
1688
+ * on load. Used by `resolveObject()` to walk nested paths without
1689
+ * invoking `flattenDict` — see FE-13 spec §4.8 M1 note.
1690
+ * `null` when no config file is present or parsing fails.
1691
+ */
1692
+ _rawConfig = null;
1291
1693
  constructor(cliFlags, configPath) {
1292
1694
  this.cliFlags = cliFlags ?? {};
1293
1695
  this.configPath = configPath ?? "apcore.yaml";
@@ -1296,9 +1698,8 @@ var ConfigResolver = class {
1296
1698
  * Resolve a single configuration key across all four tiers.
1297
1699
  */
1298
1700
  resolve(key, cliFlag, envVar) {
1299
- const flagKey = cliFlag ?? key;
1300
- if (flagKey in this.cliFlags) {
1301
- const value = this.cliFlags[flagKey];
1701
+ if (cliFlag !== void 0 && cliFlag in this.cliFlags) {
1702
+ const value = this.cliFlags[cliFlag];
1302
1703
  if (value !== null && value !== void 0) {
1303
1704
  return value;
1304
1705
  }
@@ -1335,6 +1736,44 @@ var ConfigResolver = class {
1335
1736
  }
1336
1737
  return this.fileCache[key];
1337
1738
  }
1739
+ /**
1740
+ * Resolve a configuration key to its raw nested value (FE-13).
1741
+ *
1742
+ * Unlike `resolve()`, this method does NOT flatten the yaml tree — it
1743
+ * walks the dot-separated path directly against the parsed yaml root.
1744
+ * This lets callers retrieve non-leaf structures (booleans, arrays,
1745
+ * objects) such as the `apcli` visibility config, which is naturally
1746
+ * shaped as a nested object in apcore.yaml.
1747
+ *
1748
+ * Semantics:
1749
+ * - Returns `null` when no config file is loaded or when the path is
1750
+ * not present / descends into a non-object node (including arrays).
1751
+ * - Returns the raw value (boolean / array / object / scalar) when the
1752
+ * full path resolves to a leaf or intermediate node.
1753
+ *
1754
+ * Intentionally DOES NOT consult DEFAULTS, env vars, or CLI flags — it is
1755
+ * strictly a yaml-tree accessor. Scalar `resolve()` semantics are
1756
+ * unaffected.
1757
+ */
1758
+ resolveObject(key) {
1759
+ if (!this.fileCacheLoaded) {
1760
+ this.fileCache = this.loadConfigFile();
1761
+ this.fileCacheLoaded = true;
1762
+ }
1763
+ if (this._rawConfig == null) {
1764
+ return null;
1765
+ }
1766
+ const parts = key.split(".");
1767
+ let cur = this._rawConfig;
1768
+ for (const p of parts) {
1769
+ if (cur != null && typeof cur === "object" && !Array.isArray(cur) && p in cur) {
1770
+ cur = cur[p];
1771
+ } else {
1772
+ return null;
1773
+ }
1774
+ }
1775
+ return cur;
1776
+ }
1338
1777
  /**
1339
1778
  * Load and flatten a YAML config file.
1340
1779
  */
@@ -1346,26 +1785,27 @@ var ConfigResolver = class {
1346
1785
  if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1347
1786
  return null;
1348
1787
  }
1349
- console.warn(
1788
+ warn(
1350
1789
  `Configuration file '${this.configPath}' is malformed, using defaults.`
1351
1790
  );
1352
1791
  return null;
1353
1792
  }
1354
1793
  let parsed;
1355
1794
  try {
1356
- parsed = yaml.load(content);
1795
+ parsed = yaml2.load(content);
1357
1796
  } catch {
1358
- console.warn(
1797
+ warn(
1359
1798
  `Configuration file '${this.configPath}' is malformed, using defaults.`
1360
1799
  );
1361
1800
  return null;
1362
1801
  }
1363
1802
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1364
- console.warn(
1803
+ warn(
1365
1804
  `Configuration file '${this.configPath}' is malformed, using defaults.`
1366
1805
  );
1367
1806
  return null;
1368
1807
  }
1808
+ this._rawConfig = parsed;
1369
1809
  return this.flattenDict(parsed);
1370
1810
  }
1371
1811
  /**
@@ -1391,95 +1831,89 @@ var ConfigResolver = class {
1391
1831
  // src/shell.ts
1392
1832
  init_esm_shims();
1393
1833
  init_errors();
1394
- import { readFileSync as readFileSync2 } from "fs";
1395
- import { fileURLToPath as fileURLToPath2 } from "url";
1396
- import * as path3 from "path";
1397
1834
  import { spawnSync } from "child_process";
1398
1835
  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
1836
  function makeFunctionName(progName) {
1407
1837
  return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
1408
1838
  }
1409
1839
  function shellQuote(s) {
1410
1840
  return "'" + s.replace(/'/g, "'\\''") + "'";
1411
1841
  }
1412
- function generateBashCompletion(progName) {
1842
+ function enumerateApcliSubcommands(apcliGroup) {
1843
+ if (!apcliGroup) return [];
1844
+ return apcliGroup.commands.filter((c) => !isCmdHidden(c)).map((c) => c.name());
1845
+ }
1846
+ function enumerateRootCommands(program) {
1847
+ return program.commands.filter((c) => !isCmdHidden(c)).map((c) => c.name());
1848
+ }
1849
+ function findApcliGroup(program) {
1850
+ return program.commands.find((c) => c.name() === "apcli");
1851
+ }
1852
+ function isCmdHidden(cmd) {
1853
+ const withHiddenFn = cmd;
1854
+ const withHiddenField = cmd;
1855
+ if (typeof withHiddenFn.hidden === "function") return !!withHiddenFn.hidden();
1856
+ return !!withHiddenField._hidden;
1857
+ }
1858
+ function generateBashCompletion(progName, program) {
1413
1859
  const fn = makeFunctionName(progName);
1414
1860
  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}() {
1861
+ const apcliGroup = program ? findApcliGroup(program) : void 0;
1862
+ const apcliVisible = apcliGroup !== void 0 && !isCmdHidden(apcliGroup);
1863
+ const rootCmds = program ? enumerateRootCommands(program).filter(
1864
+ (n) => n !== "apcli" || apcliVisible
1865
+ ) : [];
1866
+ const apcliCmds = apcliVisible ? enumerateApcliSubcommands(apcliGroup) : [];
1867
+ const rootOpts = rootCmds.join(" ");
1868
+ const apcliOpts = apcliCmds.join(" ");
1869
+ let body = `${fn}() {
1429
1870
  local cur prev opts
1430
1871
  COMPREPLY=()
1431
1872
  cur="\${COMP_WORDS[COMP_CWORD]}"
1432
1873
  prev="\${COMP_WORDS[COMP_CWORD-1]}"
1433
1874
 
1434
1875
  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}) )
1876
+ opts="${rootOpts}"
1877
+ COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
1438
1878
  return 0
1439
1879
  fi
1440
-
1880
+ `;
1881
+ if (apcliVisible) {
1882
+ body += `
1441
1883
  if [[ \${COMP_CWORD} -eq 2 ]]; then
1442
- if [[ "\${COMP_WORDS[1]}" == "exec" ]]; then
1443
- local modules=$(${moduleListCmd})
1444
- COMPREPLY=( $(compgen -W "\${modules}" -- \${cur}) )
1884
+ if [[ "\${COMP_WORDS[1]}" == "apcli" ]]; then
1885
+ local apcli_cmds="${apcliOpts}"
1886
+ COMPREPLY=( $(compgen -W "\${apcli_cmds}" -- \${cur}) )
1445
1887
  return 0
1446
1888
  fi
1447
- export _APCORE_GRP="\${COMP_WORDS[1]}"
1448
- local group_cmds=$(${groupCmdsCmd})
1449
- COMPREPLY=( $(compgen -W "\${group_cmds}" -- \${cur}) )
1450
- return 0
1451
1889
  fi
1452
- }
1890
+ `;
1891
+ }
1892
+ body += `}
1453
1893
  complete -F ${fn} ${quoted}
1454
1894
  `;
1895
+ return body;
1455
1896
  }
1456
- function generateZshCompletion(progName) {
1897
+ function generateZshCompletion(progName, program) {
1457
1898
  const fn = makeFunctionName(progName);
1458
1899
  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`;
1900
+ const apcliGroup = program ? findApcliGroup(program) : void 0;
1901
+ const apcliVisible = apcliGroup !== void 0 && !isCmdHidden(apcliGroup);
1902
+ const rootCmds = program ? enumerateRootCommands(program).filter(
1903
+ (n) => n !== "apcli" || apcliVisible
1904
+ ) : [];
1905
+ const apcliCmds = apcliVisible ? enumerateApcliSubcommands(apcliGroup) : [];
1906
+ const rootEntries = rootCmds.map((n) => ` '${n}:${n}'`).join("\n");
1907
+ const apcliEntries = apcliCmds.map((n) => ` '${n}:${n}'`).join("\n");
1472
1908
  return `#compdef ${progName}
1473
1909
 
1474
1910
  ${fn}() {
1475
1911
  local -a commands
1476
1912
  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
- )
1913
+ ` + (rootEntries ? rootEntries + "\n" : "") + ` )
1914
+ local -a apcli_cmds
1915
+ apcli_cmds=(
1916
+ ` + (apcliEntries ? apcliEntries + "\n" : "") + ` )
1483
1917
 
1484
1918
  _arguments -C \\
1485
1919
  '1:command:->command' \\
@@ -1488,22 +1922,11 @@ ${fn}() {
1488
1922
  case "$state" in
1489
1923
  command)
1490
1924
  _describe -t commands '${progName} commands' commands
1491
- local -a groups_and_top
1492
- groups_and_top=($(${groupsAndTopCmd}))
1493
- compadd -a groups_and_top
1494
1925
  ;;
1495
1926
  args)
1496
1927
  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
1928
+ apcli)
1929
+ _describe -t apcli_cmds '${progName} apcli commands' apcli_cmds
1507
1930
  ;;
1508
1931
  esac
1509
1932
  ;;
@@ -1513,158 +1936,30 @@ ${fn}() {
1513
1936
  compdef ${fn} ${quoted}
1514
1937
  `;
1515
1938
  }
1516
- function generateFishCompletion(progName) {
1939
+ function generateFishCompletion(progName, program) {
1517
1940
  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]`;
1547
- }
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]`);
1559
- }
1941
+ const apcliGroup = program ? findApcliGroup(program) : void 0;
1942
+ const apcliVisible = apcliGroup !== void 0 && !isCmdHidden(apcliGroup);
1943
+ const rootCmds = program ? enumerateRootCommands(program).filter(
1944
+ (n) => n !== "apcli" || apcliVisible
1945
+ ) : [];
1946
+ const apcliCmds = apcliVisible ? enumerateApcliSubcommands(apcliGroup) : [];
1947
+ const lines = [];
1948
+ lines.push(`# Fish completions for ${progName}`);
1949
+ for (const name of rootCmds) {
1950
+ lines.push(
1951
+ `complete -c ${quoted} -n "__fish_use_subcommand" -a ${name} -d "${name}"`
1952
+ );
1560
1953
  }
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]`);
1954
+ if (apcliVisible && apcliCmds.length > 0) {
1955
+ lines.push("");
1956
+ for (const name of apcliCmds) {
1957
+ lines.push(
1958
+ `complete -c ${quoted} -n "__fish_seen_subcommand_from apcli" -a ${name} -d "${name}"`
1959
+ );
1567
1960
  }
1568
1961
  }
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
- );
1589
- }
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
- }
1606
- }
1607
- }
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}`);
1657
- }
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");
1962
+ return lines.join("\n") + "\n";
1668
1963
  }
1669
1964
  function roffEscape(s) {
1670
1965
  return s.replace(/\\/g, "\\\\").replace(/-/g, "\\-").replace(/'/g, "\\(aq");
@@ -1806,10 +2101,13 @@ function configureManHelp(program, progName, version, description, docsUrl2) {
1806
2101
  return "";
1807
2102
  });
1808
2103
  }
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) => {
2104
+ function findRootProgram(host) {
2105
+ let cur = host;
2106
+ while (cur.parent) cur = cur.parent;
2107
+ return cur;
2108
+ }
2109
+ function registerCompletionCommand(host) {
2110
+ const completionCmd = new Command("completion").description("Generate a shell completion script and print it to stdout.").argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
1813
2111
  const validShells = ["bash", "zsh", "fish"];
1814
2112
  if (!validShells.includes(shell)) {
1815
2113
  process.stderr.write(
@@ -1818,34 +2116,1164 @@ function registerShellCommands(cli, progName = "apcore-cli") {
1818
2116
  );
1819
2117
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1820
2118
  }
1821
- const resolved = cli.name() || progName;
2119
+ const root = findRootProgram(host);
2120
+ const resolved = root.name() || "apcore-cli";
1822
2121
  const generators = {
1823
- bash: () => generateBashCompletion(resolved),
1824
- zsh: () => generateZshCompletion(resolved),
1825
- fish: () => generateFishCompletion(resolved)
2122
+ bash: () => generateBashCompletion(resolved, root),
2123
+ zsh: () => generateZshCompletion(resolved, root),
2124
+ fish: () => generateFishCompletion(resolved, root)
1826
2125
  };
1827
2126
  process.stdout.write(generators[shell]());
1828
2127
  });
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)) {
2128
+ host.addCommand(completionCmd);
2129
+ }
2130
+
2131
+ // src/discovery.ts
2132
+ init_esm_shims();
2133
+ import { Command as Command2, Option as Option2 } from "commander";
2134
+ init_errors();
2135
+ init_audit();
2136
+ var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
2137
+ function validateTag(tag) {
2138
+ if (!TAG_PATTERN.test(tag)) {
2139
+ process.stderr.write(
2140
+ `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
2141
+ `
2142
+ );
2143
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2144
+ }
2145
+ }
2146
+ function collectTag(value, previous) {
2147
+ return previous.concat([value]);
2148
+ }
2149
+ function collectAnnotation(value, previous) {
2150
+ return previous.concat([value]);
2151
+ }
2152
+ function getAnnotationFlag(moduleDef, flag) {
2153
+ const annotations = moduleDef.annotations;
2154
+ if (!annotations || typeof annotations !== "object") return false;
2155
+ const ann = annotations;
2156
+ const map = {
2157
+ "destructive": "destructive",
2158
+ "requires-approval": "requires_approval",
2159
+ "readonly": "readonly",
2160
+ "streaming": "streaming",
2161
+ "cacheable": "cacheable",
2162
+ "idempotent": "idempotent",
2163
+ "paginated": "paginated"
2164
+ };
2165
+ const attr = map[flag] ?? flag;
2166
+ return ann[attr] === true;
2167
+ }
2168
+ function registerListCommand(apcliGroup, registry, exposureFilter) {
2169
+ const listCmd = new Command2("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--flat", "Show flat list (no grouping).", false).option("--format <format>", "Output format.", void 0).option("-s, --search <query>", "Filter by substring match on ID and description.").addOption(
2170
+ new Option2("--status <status>", "Filter by module status.").choices(["enabled", "disabled", "all"]).default("enabled")
2171
+ ).option("-a, --annotation <flag>", "Filter by annotation flag (AND logic). Repeatable.", collectAnnotation, []).addOption(
2172
+ new Option2("--sort <field>", "Sort order.").choices(["id", "calls", "errors", "latency"]).default("id")
2173
+ ).option("--reverse", "Reverse sort order.", false).option("--deprecated", "Include deprecated modules.", false).option("--deps", "Show dependency count column.", false).addOption(
2174
+ new Option2("--exposure <mode>", "Filter by exposure status.").choices(["exposed", "hidden", "all"]).default("exposed")
2175
+ ).action((opts) => {
2176
+ for (const t of opts.tag) {
2177
+ validateTag(t);
2178
+ }
2179
+ let modules = [];
2180
+ for (const m of registry.listModules()) {
2181
+ modules.push(m);
2182
+ }
2183
+ if (opts.tag.length > 0) {
2184
+ const filterTags = new Set(opts.tag);
2185
+ modules = modules.filter((m) => {
2186
+ const mTags = m.tags ?? [];
2187
+ return [...filterTags].every((t) => mTags.includes(t));
2188
+ });
2189
+ }
2190
+ if (opts.search) {
2191
+ const query = opts.search.toLowerCase();
2192
+ modules = modules.filter(
2193
+ (m) => (m.id ?? "").toLowerCase().includes(query) || (m.description ?? "").toLowerCase().includes(query)
2194
+ );
2195
+ }
2196
+ if (opts.status === "enabled") {
2197
+ modules = modules.filter((m) => {
2198
+ const enabled = m.enabled;
2199
+ return enabled !== false;
2200
+ });
2201
+ } else if (opts.status === "disabled") {
2202
+ modules = modules.filter((m) => {
2203
+ const enabled = m.enabled;
2204
+ return enabled === false;
2205
+ });
2206
+ }
2207
+ if (!opts.deprecated) {
2208
+ modules = modules.filter((m) => {
2209
+ const deprecated = m.deprecated;
2210
+ return deprecated !== true;
2211
+ });
2212
+ }
2213
+ if (opts.annotation.length > 0) {
2214
+ for (const annFlag of opts.annotation) {
2215
+ modules = modules.filter((m) => getAnnotationFlag(m, annFlag));
2216
+ }
2217
+ }
2218
+ if (opts.sort === "calls" || opts.sort === "errors" || opts.sort === "latency") {
2219
+ process.stderr.write(
2220
+ `Warning: Usage data not available; sorting by id. Sort by ${opts.sort} requires system.usage modules.
2221
+ `
2222
+ );
2223
+ }
2224
+ modules.sort((a, b) => (a.id ?? "").localeCompare(b.id ?? ""));
2225
+ if (opts.reverse) {
2226
+ modules.reverse();
2227
+ }
2228
+ let showExposureCol = false;
2229
+ if (exposureFilter && opts.exposure !== "all") {
2230
+ if (opts.exposure === "exposed") {
2231
+ modules = modules.filter((m) => exposureFilter.isExposed(m.id ?? ""));
2232
+ } else if (opts.exposure === "hidden") {
2233
+ modules = modules.filter((m) => !exposureFilter.isExposed(m.id ?? ""));
2234
+ }
2235
+ }
2236
+ if (opts.exposure === "all" && exposureFilter) {
2237
+ showExposureCol = true;
2238
+ }
2239
+ const fmt = resolveFormat(opts.format);
2240
+ const filterTagsArg = opts.tag.length > 0 ? opts.tag : void 0;
2241
+ formatModuleList(modules, fmt, filterTagsArg, opts.deps, showExposureCol ? exposureFilter : void 0);
2242
+ });
2243
+ apcliGroup.addCommand(listCmd);
2244
+ }
2245
+ function registerDescribeCommand(apcliGroup, registry) {
2246
+ 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) => {
2247
+ validateModuleId(moduleId);
2248
+ const moduleDef = registry.getModule(moduleId);
2249
+ if (!moduleDef) {
2250
+ process.stderr.write(
2251
+ `Error: Module '${moduleId}' not found.
2252
+ `
2253
+ );
2254
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2255
+ }
2256
+ const fmt = resolveFormat(opts.format);
2257
+ formatModuleDetail(moduleDef, fmt);
2258
+ });
2259
+ apcliGroup.addCommand(describeCmd);
2260
+ }
2261
+ function registerExecCommand(apcliGroup, registry, executor) {
2262
+ const execCmd = new Command2("exec").description("Execute a module by ID with JSON input.").argument("<module-id>", "Module ID to execute").option("--format <format>", "Output format (json, table, csv, yaml, jsonl).").option("--fields <fields>", "Comma-separated dot-paths to select from the result.").option(
2263
+ "--input <json>",
2264
+ "JSON object passed as input to the module. Use '-' to read JSON from stdin."
2265
+ ).option("-y, --yes", "Auto-approve if the module declares requires_approval.", false).option(
2266
+ "--approval-timeout <seconds>",
2267
+ "Seconds to wait for interactive approval.",
2268
+ parseInt
2269
+ ).option("--sandbox", "Run module in an isolated subprocess with restricted env.", false).option("--strategy <name>", "Execution strategy (standard, parallel, sequential, etc.).").option("--trace", "Enable pipeline trace output.", false).option("--dry-run", "Validate inputs without executing the module.", false).option("--stream", "Stream output as JSONL instead of buffering.", false).action(async (moduleId, opts) => {
2270
+ validateModuleId(moduleId);
2271
+ const moduleDef = registry.getModule(moduleId);
2272
+ if (!moduleDef) {
2273
+ process.stderr.write(`Error: Module '${moduleId}' not found.
2274
+ `);
2275
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2276
+ }
2277
+ let merged = {};
2278
+ if (opts.input === "-") {
2279
+ merged = await collectInput("-", {}, false);
2280
+ } else if (opts.input !== void 0) {
2281
+ try {
2282
+ const parsed = JSON.parse(opts.input);
2283
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
2284
+ process.stderr.write("Error: --input JSON must be an object.\n");
2285
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2286
+ }
2287
+ merged = parsed;
2288
+ } catch (err) {
2289
+ const msg = err instanceof Error ? err.message : String(err);
2290
+ process.stderr.write(`Error: --input is not valid JSON: ${msg}
2291
+ `);
2292
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2293
+ }
2294
+ }
2295
+ const startTime = performance.now();
2296
+ try {
2297
+ await checkApproval(moduleDef, opts.yes, opts.approvalTimeout);
2298
+ if (opts.dryRun) {
2299
+ if (executor.validate) {
2300
+ const preflight = await executor.validate(moduleId, merged);
2301
+ formatPreflightResult(preflight, opts.format);
2302
+ } else {
2303
+ process.stdout.write(JSON.stringify({ valid: true }) + "\n");
2304
+ }
2305
+ return;
2306
+ }
2307
+ let result;
2308
+ if (opts.strategy && executor.callWithTrace) {
2309
+ const [res] = await executor.callWithTrace(moduleId, merged, { strategy: opts.strategy });
2310
+ result = res;
2311
+ } else {
2312
+ const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
2313
+ const sandbox = new Sandbox2(opts.sandbox);
2314
+ result = await sandbox.execute(moduleId, merged, executor);
2315
+ }
2316
+ const durationMs = Math.round(performance.now() - startTime);
2317
+ const fmt = resolveFormat(opts.format);
2318
+ formatExecResult(result, fmt, opts.fields);
2319
+ const auditLogger = getAuditLogger();
2320
+ if (auditLogger) {
2321
+ auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
2322
+ }
2323
+ } catch (err) {
2324
+ const exitCode = exitCodeForError(err);
2325
+ const durationMs = Math.round(performance.now() - startTime);
2326
+ try {
2327
+ const auditLogger = getAuditLogger();
2328
+ if (auditLogger) {
2329
+ auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
2330
+ }
2331
+ } catch {
2332
+ }
2333
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
2334
+ `);
2335
+ process.exit(exitCode);
2336
+ }
2337
+ });
2338
+ apcliGroup.addCommand(execCmd);
2339
+ }
2340
+ function registerValidateCommand(cli, registry, executor) {
2341
+ const validateCmd = new Command2("validate").description("Run preflight checks without executing a module.").argument("<module-id>", "Module ID to validate").option("--input <source>", "JSON input file or '-' for stdin.").option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2342
+ validateModuleId(moduleId);
2343
+ const moduleDef = registry.getModule(moduleId);
2344
+ if (!moduleDef) {
2345
+ process.stderr.write(`Error: Module '${moduleId}' not found.
2346
+ `);
2347
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2348
+ }
2349
+ const merged = opts.input ? await collectInput(opts.input, {}, false) : {};
2350
+ if (!executor.validate) {
2351
+ process.stderr.write("Error: Executor does not support validate.\n");
2352
+ process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
2353
+ }
2354
+ try {
2355
+ const preflight = await executor.validate(moduleId, merged);
2356
+ formatPreflightResult(preflight, opts.format);
2357
+ process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
2358
+ } catch (err) {
2359
+ const exitCode = exitCodeForError(err);
2360
+ try {
2361
+ const auditLogger = getAuditLogger();
2362
+ if (auditLogger) {
2363
+ auditLogger.logExecution(moduleId, merged, "error", exitCode, 0);
2364
+ }
2365
+ } catch {
2366
+ }
2367
+ process.stderr.write(`Error: ${err instanceof Error ? err.message : err}
2368
+ `);
2369
+ process.exit(exitCode);
2370
+ }
2371
+ });
2372
+ cli.addCommand(validateCmd);
2373
+ }
2374
+
2375
+ // src/system-cmd.ts
2376
+ init_esm_shims();
2377
+ init_errors();
2378
+ import { Command as Command3 } from "commander";
2379
+ async function callSystemModule(executor, moduleId, inputs) {
2380
+ if (executor.call) {
2381
+ return executor.call(moduleId, inputs);
2382
+ }
2383
+ return executor.execute(moduleId, inputs);
2384
+ }
2385
+ function emitResult(jsonPayload, fmt, ttyRender) {
2386
+ if (fmt === "json" || !process.stdout.isTTY) {
2387
+ process.stdout.write(JSON.stringify(jsonPayload, null, 2) + "\n");
2388
+ } else {
2389
+ ttyRender();
2390
+ }
2391
+ }
2392
+ function emitErrorAndExit(e) {
2393
+ process.stderr.write(`Error: ${e instanceof Error ? e.message : e}
2394
+ `);
2395
+ process.exit(exitCodeForError(e));
2396
+ }
2397
+ function formatHealthSummaryTty(result) {
2398
+ const summary = result.summary ?? {};
2399
+ const modules = result.modules ?? [];
2400
+ if (modules.length === 0) {
2401
+ process.stdout.write("No modules found.\n");
2402
+ return;
2403
+ }
2404
+ const total = summary.total_modules ?? modules.length;
2405
+ process.stdout.write(`Health Overview (${total} modules)
2406
+
2407
+ `);
2408
+ process.stdout.write(` ${"Module".padEnd(28)} ${"Status".padEnd(12)} ${"Error Rate".padEnd(12)} Top Error
2409
+ `);
2410
+ process.stdout.write(` ${"-".repeat(28)} ${"-".repeat(12)} ${"-".repeat(12)} ${"-".repeat(20)}
2411
+ `);
2412
+ for (const m of modules) {
2413
+ const top = m.top_error;
2414
+ const topStr = top ? `${top.code} (${top.count ?? "?"})` : "\u2014";
2415
+ const rate = `${((m.error_rate ?? 0) * 100).toFixed(1)}%`;
2416
+ process.stdout.write(
2417
+ ` ${String(m.module_id).padEnd(28)} ${String(m.status).padEnd(12)} ${rate.padEnd(12)} ${topStr}
2418
+ `
2419
+ );
2420
+ }
2421
+ const parts = [];
2422
+ for (const key of ["healthy", "degraded", "error"]) {
2423
+ const count = summary[key];
2424
+ if (count) parts.push(`${count} ${key}`);
2425
+ }
2426
+ process.stdout.write(`
2427
+ Summary: ${parts.join(", ") || "no data"}
2428
+ `);
2429
+ }
2430
+ function formatHealthModuleTty(result) {
2431
+ process.stdout.write(`Module: ${result.module_id ?? "?"}
2432
+ `);
2433
+ process.stdout.write(`Status: ${result.status ?? "unknown"}
2434
+ `);
2435
+ const total = result.total_calls ?? 0;
2436
+ const errors = result.error_count ?? 0;
2437
+ const rate = result.error_rate ?? 0;
2438
+ const avg = result.avg_latency_ms ?? 0;
2439
+ const p99 = result.p99_latency_ms ?? 0;
2440
+ process.stdout.write(`Calls: ${total.toLocaleString()} total | ${errors.toLocaleString()} errors | ${(rate * 100).toFixed(1)}% error rate
2441
+ `);
2442
+ process.stdout.write(`Latency: ${avg.toFixed(0)}ms avg | ${p99.toFixed(0)}ms p99
2443
+ `);
2444
+ const recent = result.recent_errors ?? [];
2445
+ if (recent.length > 0) {
2446
+ process.stdout.write(`
2447
+ Recent Errors (top ${recent.length}):
2448
+ `);
2449
+ for (const e of recent) {
2450
+ const count = e.count ?? "?";
2451
+ const last = e.last_occurred ?? "?";
2452
+ process.stdout.write(` ${String(e.code ?? "?").padEnd(24)} x${count} (last: ${last})
2453
+ `);
2454
+ }
2455
+ }
2456
+ }
2457
+ function formatUsageSummaryTty(result) {
2458
+ const modules = result.modules ?? [];
2459
+ const period = result.period ?? "?";
2460
+ if (modules.length === 0) {
2461
+ process.stdout.write(`No usage data for period ${period}.
2462
+ `);
2463
+ return;
2464
+ }
2465
+ process.stdout.write(`Usage Summary (last ${period})
2466
+
2467
+ `);
2468
+ process.stdout.write(` ${"Module".padEnd(24)} ${"Calls".padStart(8)} ${"Errors".padStart(8)} ${"Avg Latency".padStart(12)} ${"Trend".padStart(10)}
2469
+ `);
2470
+ process.stdout.write(` ${"-".repeat(24)} ${"-".repeat(8)} ${"-".repeat(8)} ${"-".repeat(12)} ${"-".repeat(10)}
2471
+ `);
2472
+ for (const m of modules) {
2473
+ const avg = `${(m.avg_latency_ms ?? 0).toFixed(0)}ms`;
2474
+ process.stdout.write(
2475
+ ` ${String(m.module_id).padEnd(24)} ${String(m.call_count ?? 0).padStart(8)} ${String(m.error_count ?? 0).padStart(8)} ${avg.padStart(12)} ${String(m.trend ?? "").padStart(10)}
2476
+ `
2477
+ );
2478
+ }
2479
+ const totalCalls = result.total_calls ?? modules.reduce((s, m) => s + (m.call_count ?? 0), 0);
2480
+ const totalErrors = result.total_errors ?? modules.reduce((s, m) => s + (m.error_count ?? 0), 0);
2481
+ process.stdout.write(`
2482
+ Total: ${totalCalls.toLocaleString()} calls | ${totalErrors.toLocaleString()} errors
2483
+ `);
2484
+ }
2485
+ function registerHealthCommand(apcliGroup, executor) {
2486
+ const healthCmd = new Command3("health").description("Show module health status. Optionally specify a module ID for details.").argument("[module-id]", "Module ID for detailed health").option("--threshold <number>", "Error rate threshold (default: 0.01).", parseFloat, 0.01).option("--all", "Include healthy modules.", false).option("--errors <count>", "Max recent errors (module detail only).", parseInt, 10).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2487
+ const fmt = resolveFormat(opts.format);
2488
+ try {
2489
+ if (moduleId) {
2490
+ const result = await callSystemModule(executor, "system.health.module", {
2491
+ module_id: moduleId,
2492
+ error_limit: opts.errors
2493
+ });
2494
+ emitResult(result, fmt, () => formatHealthModuleTty(result));
2495
+ } else {
2496
+ const result = await callSystemModule(executor, "system.health.summary", {
2497
+ error_rate_threshold: opts.threshold,
2498
+ include_healthy: opts.all
2499
+ });
2500
+ emitResult(result, fmt, () => formatHealthSummaryTty(result));
2501
+ }
2502
+ } catch (e) {
2503
+ emitErrorAndExit(e);
2504
+ }
2505
+ });
2506
+ apcliGroup.addCommand(healthCmd);
2507
+ }
2508
+ function registerUsageCommand(apcliGroup, executor) {
2509
+ const usageCmd = new Command3("usage").description("Show module usage statistics. Optionally specify a module ID for details.").argument("[module-id]", "Module ID for detailed usage").option("--period <period>", "Time window: 1h, 24h, 7d, 30d.", "24h").option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2510
+ const fmt = resolveFormat(opts.format);
2511
+ try {
2512
+ let result;
2513
+ if (moduleId) {
2514
+ result = await callSystemModule(executor, "system.usage.module", {
2515
+ module_id: moduleId,
2516
+ period: opts.period
2517
+ });
2518
+ } else {
2519
+ result = await callSystemModule(executor, "system.usage.summary", {
2520
+ period: opts.period
2521
+ });
2522
+ }
2523
+ emitResult(result, fmt, () => {
2524
+ if (moduleId) {
2525
+ formatExecResult(result, fmt);
2526
+ } else {
2527
+ formatUsageSummaryTty(result);
2528
+ }
2529
+ });
2530
+ } catch (e) {
2531
+ emitErrorAndExit(e);
2532
+ }
2533
+ });
2534
+ apcliGroup.addCommand(usageCmd);
2535
+ }
2536
+ function registerEnableCommand(apcliGroup, executor) {
2537
+ const enableCmd = new Command3("enable").description("Enable a disabled module at runtime.").argument("<module-id>", "Module ID to enable").requiredOption("--reason <reason>", "Reason for enabling (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2538
+ const fmt = resolveFormat(opts.format);
2539
+ try {
2540
+ const result = await callSystemModule(executor, "system.control.toggle_feature", {
2541
+ module_id: moduleId,
2542
+ enabled: true,
2543
+ reason: opts.reason
2544
+ });
2545
+ emitResult(result, fmt, () => {
2546
+ process.stdout.write(`Module '${moduleId}' enabled.
2547
+ Reason: ${opts.reason}
2548
+ `);
2549
+ });
2550
+ } catch (e) {
2551
+ emitErrorAndExit(e);
2552
+ }
2553
+ });
2554
+ apcliGroup.addCommand(enableCmd);
2555
+ }
2556
+ function registerDisableCommand(apcliGroup, executor) {
2557
+ const disableCmd = new Command3("disable").description("Disable a module at runtime (calls are rejected until re-enabled).").argument("<module-id>", "Module ID to disable").requiredOption("--reason <reason>", "Reason for disabling (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2558
+ const fmt = resolveFormat(opts.format);
2559
+ try {
2560
+ const result = await callSystemModule(executor, "system.control.toggle_feature", {
2561
+ module_id: moduleId,
2562
+ enabled: false,
2563
+ reason: opts.reason
2564
+ });
2565
+ emitResult(result, fmt, () => {
2566
+ process.stdout.write(`Module '${moduleId}' disabled.
2567
+ Reason: ${opts.reason}
2568
+ `);
2569
+ });
2570
+ } catch (e) {
2571
+ emitErrorAndExit(e);
2572
+ }
2573
+ });
2574
+ apcliGroup.addCommand(disableCmd);
2575
+ }
2576
+ function registerReloadCommand(apcliGroup, executor) {
2577
+ const reloadCmd = new Command3("reload").description("Hot-reload a module from disk.").argument("<module-id>", "Module ID to reload").requiredOption("--reason <reason>", "Reason for reload (required for audit).").option("-y, --yes", "Signal explicit intent (forwarded to server-side approval gate).", false).option("--format <format>", "Output format.").action(async (moduleId, opts) => {
2578
+ const fmt = resolveFormat(opts.format);
2579
+ try {
2580
+ const result = await callSystemModule(executor, "system.control.reload_module", {
2581
+ module_id: moduleId,
2582
+ reason: opts.reason
2583
+ });
2584
+ emitResult(result, fmt, () => {
2585
+ const prev = result.previous_version ?? "?";
2586
+ const newVer = result.new_version ?? "?";
2587
+ const dur = result.reload_duration_ms ?? "?";
2588
+ process.stdout.write(`Module '${moduleId}' reloaded.
2589
+ `);
2590
+ process.stdout.write(` Version: ${prev} -> ${newVer}
2591
+ `);
2592
+ process.stdout.write(` Duration: ${dur}ms
2593
+ `);
2594
+ });
2595
+ } catch (e) {
2596
+ emitErrorAndExit(e);
2597
+ }
2598
+ });
2599
+ apcliGroup.addCommand(reloadCmd);
2600
+ }
2601
+ function registerConfigCommand(apcliGroup, executor) {
2602
+ const configGroup = new Command3("config").description("Read or update runtime configuration.");
2603
+ const configGetCmd = new Command3("get").description("Read a configuration value by dot-path key.").argument("<key>", "Configuration key (dot-path)").option("--format <format>", "Output format.", "table").action(async (key, opts) => {
2604
+ const fmt = resolveFormat(opts.format);
2605
+ try {
2606
+ const result = await callSystemModule(executor, "system.config.get", { key });
2607
+ const value = result?.value ?? result;
2608
+ emitResult({ key, value }, fmt, () => {
2609
+ process.stdout.write(`${key} = ${JSON.stringify(value)}
2610
+ `);
2611
+ });
2612
+ } catch (e) {
2613
+ emitErrorAndExit(e);
2614
+ }
2615
+ });
2616
+ configGroup.addCommand(configGetCmd);
2617
+ const configSetCmd = new Command3("set").description("Update a runtime configuration value (audit-logged; server-side approval gate applies).").argument("<key>", "Configuration key (dot-path)").argument("<value>", "New value").requiredOption("--reason <reason>", "Reason for config change (required for audit).").option("--format <format>", "Output format.").action(async (key, value, opts) => {
2618
+ const fmt = resolveFormat(opts.format);
2619
+ let parsedValue;
2620
+ try {
2621
+ parsedValue = JSON.parse(value);
2622
+ } catch {
2623
+ parsedValue = value;
2624
+ }
2625
+ try {
2626
+ const result = await callSystemModule(executor, "system.control.update_config", {
2627
+ key,
2628
+ value: parsedValue,
2629
+ reason: opts.reason
2630
+ });
2631
+ emitResult(result, fmt, () => {
2632
+ const old = result.old_value ?? "?";
2633
+ const newVal = result.new_value ?? "?";
2634
+ process.stdout.write(`Config updated: ${key}
2635
+ `);
2636
+ process.stdout.write(` ${JSON.stringify(old)} -> ${JSON.stringify(newVal)}
2637
+ `);
2638
+ process.stdout.write(` Reason: ${opts.reason}
2639
+ `);
2640
+ });
2641
+ } catch (e) {
2642
+ emitErrorAndExit(e);
2643
+ }
2644
+ });
2645
+ configGroup.addCommand(configSetCmd);
2646
+ apcliGroup.addCommand(configGroup);
2647
+ }
2648
+
2649
+ // src/strategy.ts
2650
+ init_esm_shims();
2651
+ import { Command as Command4, Option as Option3 } from "commander";
2652
+ function lookupStrategyInfo(executor, strategyName) {
2653
+ if (typeof executor.describePipeline === "function") {
2654
+ try {
2655
+ const current = executor.describePipeline();
2656
+ if (current && current.name === strategyName) {
2657
+ return { info: current, isCurrent: true };
2658
+ }
2659
+ } catch {
2660
+ }
2661
+ }
2662
+ const ctor = executor.constructor;
2663
+ if (ctor && typeof ctor.listStrategies === "function") {
2664
+ try {
2665
+ const all = ctor.listStrategies();
2666
+ const info = all.find((s) => s.name === strategyName) ?? null;
2667
+ return { info, isCurrent: false };
2668
+ } catch {
2669
+ return { info: null, isCurrent: false };
2670
+ }
2671
+ }
2672
+ return { info: null, isCurrent: false };
2673
+ }
2674
+ var PRESET_STEPS = {
2675
+ standard: [
2676
+ "context_creation",
2677
+ "call_chain_guard",
2678
+ "module_lookup",
2679
+ "acl_check",
2680
+ "approval_gate",
2681
+ "middleware_before",
2682
+ "input_validation",
2683
+ "execute",
2684
+ "output_validation",
2685
+ "middleware_after",
2686
+ "return_result"
2687
+ ],
2688
+ internal: [
2689
+ "context_creation",
2690
+ "call_chain_guard",
2691
+ "module_lookup",
2692
+ "middleware_before",
2693
+ "input_validation",
2694
+ "execute",
2695
+ "output_validation",
2696
+ "middleware_after",
2697
+ "return_result"
2698
+ ],
2699
+ testing: [
2700
+ "context_creation",
2701
+ "module_lookup",
2702
+ "middleware_before",
2703
+ "input_validation",
2704
+ "execute",
2705
+ "output_validation",
2706
+ "middleware_after",
2707
+ "return_result"
2708
+ ],
2709
+ performance: [
2710
+ "context_creation",
2711
+ "call_chain_guard",
2712
+ "module_lookup",
2713
+ "acl_check",
2714
+ "approval_gate",
2715
+ "input_validation",
2716
+ "execute",
2717
+ "output_validation",
2718
+ "return_result"
2719
+ ],
2720
+ minimal: [
2721
+ "context_creation",
2722
+ "module_lookup",
2723
+ "execute",
2724
+ "return_result"
2725
+ ]
2726
+ };
2727
+ function registerPipelineCommand(cli, executor) {
2728
+ const pipelineCmd = new Command4("describe-pipeline").description("Show the execution pipeline steps for a strategy.").addOption(
2729
+ new Option3("--strategy <name>", "Strategy to describe (default: standard).").choices(["standard", "internal", "testing", "performance", "minimal"]).default("standard")
2730
+ ).option("--format <format>", "Output format.").action((opts) => {
2731
+ const fmt = resolveFormat(opts.format);
2732
+ const { info, isCurrent } = lookupStrategyInfo(executor, opts.strategy);
2733
+ if (info) {
2734
+ const strategySteps = isCurrent ? executor.currentStrategy?.steps ?? [] : [];
2735
+ const header = `Pipeline: ${info.name} (${info.stepCount} steps)`;
2736
+ if (fmt === "json" || !process.stdout.isTTY) {
2737
+ const payload = {
2738
+ strategy: info.name,
2739
+ step_count: info.stepCount,
2740
+ description: info.description,
2741
+ steps: info.stepNames.map((name, i) => {
2742
+ const stepMeta = strategySteps[i];
2743
+ return {
2744
+ index: i + 1,
2745
+ name,
2746
+ pure: stepMeta?.pure ?? false,
2747
+ removable: stepMeta?.removable ?? true,
2748
+ timeout_ms: stepMeta?.timeoutMs ?? null
2749
+ };
2750
+ })
2751
+ };
2752
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
2753
+ } else {
2754
+ process.stdout.write(`${header}
2755
+
2756
+ `);
2757
+ process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
2758
+ `);
2759
+ process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
2760
+ `);
2761
+ for (let i = 0; i < info.stepNames.length; i++) {
2762
+ const stepMeta = strategySteps[i];
2763
+ const pure = stepMeta?.pure ? "yes" : "no";
2764
+ const removable = stepMeta?.removable !== false ? "yes" : "no";
2765
+ const timeout = stepMeta?.timeoutMs ? `${stepMeta.timeoutMs}ms` : "\u2014";
2766
+ process.stdout.write(` ${String(i + 1).padEnd(4)} ${info.stepNames[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} ${timeout}
2767
+ `);
2768
+ }
2769
+ }
2770
+ return;
2771
+ }
2772
+ const steps = PRESET_STEPS[opts.strategy] ?? [];
2773
+ const pureSteps = /* @__PURE__ */ new Set([
2774
+ "context_creation",
2775
+ "call_chain_guard",
2776
+ "module_lookup",
2777
+ "acl_check",
2778
+ "input_validation"
2779
+ ]);
2780
+ const nonRemovable = /* @__PURE__ */ new Set([
2781
+ "context_creation",
2782
+ "module_lookup",
2783
+ "execute",
2784
+ "return_result"
2785
+ ]);
2786
+ if (fmt === "json" || !process.stdout.isTTY) {
2787
+ const payload = {
2788
+ strategy: opts.strategy,
2789
+ step_count: steps.length,
2790
+ steps: steps.map((s, i) => ({
2791
+ index: i + 1,
2792
+ name: s,
2793
+ pure: pureSteps.has(s),
2794
+ removable: !nonRemovable.has(s)
2795
+ }))
2796
+ };
2797
+ process.stdout.write(JSON.stringify(payload, null, 2) + "\n");
2798
+ } else {
2799
+ process.stdout.write(`Pipeline: ${opts.strategy} (${steps.length} steps)
2800
+
2801
+ `);
2802
+ process.stdout.write(` ${"#".padEnd(4)} ${"Step".padEnd(28)} ${"Pure".padEnd(6)} ${"Removable".padEnd(11)} Timeout
2803
+ `);
2804
+ process.stdout.write(` ${"-".repeat(4)} ${"-".repeat(28)} ${"-".repeat(6)} ${"-".repeat(11)} ${"-".repeat(8)}
2805
+ `);
2806
+ for (let i = 0; i < steps.length; i++) {
2807
+ const pure = pureSteps.has(steps[i]) ? "yes" : "no";
2808
+ const removable = nonRemovable.has(steps[i]) ? "no" : "yes";
2809
+ process.stdout.write(` ${String(i + 1).padEnd(4)} ${steps[i].padEnd(28)} ${pure.padEnd(6)} ${removable.padEnd(11)} \u2014
2810
+ `);
2811
+ }
2812
+ }
2813
+ });
2814
+ cli.addCommand(pipelineCmd);
2815
+ }
2816
+
2817
+ // src/builtin-group.ts
2818
+ init_esm_shims();
2819
+ init_errors();
2820
+ init_logger();
2821
+ var RESERVED_GROUP_NAMES = /* @__PURE__ */ new Set(["apcli"]);
2822
+ var VALID_USER_MODES = /* @__PURE__ */ new Set([
2823
+ "all",
2824
+ "none",
2825
+ "include",
2826
+ "exclude"
2827
+ ]);
2828
+ var APCLI_SUBCOMMAND_NAMES = /* @__PURE__ */ new Set([
2829
+ "list",
2830
+ "describe",
2831
+ "exec",
2832
+ "validate",
2833
+ "init",
2834
+ "health",
2835
+ "usage",
2836
+ "enable",
2837
+ "disable",
2838
+ "reload",
2839
+ "config",
2840
+ "completion",
2841
+ "describe-pipeline"
2842
+ ]);
2843
+ var ApcliGroup = class _ApcliGroup {
2844
+ _mode;
2845
+ _include;
2846
+ _exclude;
2847
+ _disableEnv;
2848
+ _registryInjected;
2849
+ _fromCliConfig;
2850
+ constructor(init) {
2851
+ this._mode = init.mode;
2852
+ this._include = init.include;
2853
+ this._exclude = init.exclude;
2854
+ this._disableEnv = init.disableEnv;
2855
+ this._registryInjected = init.registryInjected;
2856
+ this._fromCliConfig = init.fromCliConfig;
2857
+ }
2858
+ /**
2859
+ * Tier 1 constructor — config came from `createCli({ apcli })`.
2860
+ *
2861
+ * A non-auto mode from this tier wins over env var and yaml.
2862
+ */
2863
+ static fromCliConfig(config, opts) {
2864
+ return _ApcliGroup._build(
2865
+ config,
2866
+ opts,
2867
+ /*fromCliConfig*/
2868
+ true
2869
+ );
2870
+ }
2871
+ /**
2872
+ * Tier 3 constructor — config came from `apcore.yaml`.
2873
+ *
2874
+ * Env var (Tier 2) may override the yaml-supplied mode.
2875
+ */
2876
+ static fromYaml(config, opts) {
2877
+ return _ApcliGroup._build(
2878
+ config,
2879
+ opts,
2880
+ /*fromCliConfig*/
2881
+ false
2882
+ );
2883
+ }
2884
+ /**
2885
+ * Non-panicking Tier 3 factory (A-001 parity with Rust's `try_from_yaml`).
2886
+ * Returns `[instance, null]` on success or `[null, errorMessage]` on invalid input.
2887
+ * Use this in programmatic contexts where throwing/exiting is unwanted.
2888
+ */
2889
+ static tryFromYaml(config, opts) {
2890
+ if (config !== null && config !== void 0 && typeof config !== "boolean" && typeof config !== "object") {
2891
+ return [null, `apcore.yaml 'apcli:' must be a bool, object, or null; got ${typeof config}`];
2892
+ }
2893
+ if (config !== null && config !== void 0 && typeof config === "object" && !Array.isArray(config)) {
2894
+ const mode = config["mode"];
2895
+ if (mode !== void 0 && mode !== null) {
2896
+ const validModes = ["all", "none", "include", "exclude"];
2897
+ if (typeof mode !== "string" || !validModes.includes(mode)) {
2898
+ return [null, `Invalid apcli mode: '${mode}'. Must be one of: all, none, include, exclude.`];
2899
+ }
2900
+ }
2901
+ }
2902
+ return [_ApcliGroup.fromYaml(config, opts), null];
2903
+ }
2904
+ // -------------------------------------------------------------------------
2905
+ // Internal builder — shared by both factories
2906
+ // -------------------------------------------------------------------------
2907
+ static _build(config, opts, fromCliConfig) {
2908
+ if (config === true) {
2909
+ return new _ApcliGroup({
2910
+ mode: "all",
2911
+ include: [],
2912
+ exclude: [],
2913
+ disableEnv: false,
2914
+ registryInjected: opts.registryInjected,
2915
+ fromCliConfig
2916
+ });
2917
+ }
2918
+ if (config === false) {
2919
+ return new _ApcliGroup({
2920
+ mode: "none",
2921
+ include: [],
2922
+ exclude: [],
2923
+ disableEnv: false,
2924
+ registryInjected: opts.registryInjected,
2925
+ fromCliConfig
2926
+ });
2927
+ }
2928
+ if (config === void 0 || config === null) {
2929
+ return new _ApcliGroup({
2930
+ mode: "auto",
2931
+ include: [],
2932
+ exclude: [],
2933
+ disableEnv: false,
2934
+ registryInjected: opts.registryInjected,
2935
+ fromCliConfig
2936
+ });
2937
+ }
2938
+ if (typeof config !== "object" || Array.isArray(config)) {
2939
+ process.stderr.write(
2940
+ `Error: apcli config must be a boolean or object; got ${Array.isArray(config) ? "array" : typeof config}.
2941
+ `
2942
+ );
2943
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2944
+ }
2945
+ const cfg = config;
2946
+ let mode;
2947
+ if (cfg.mode === void 0 || cfg.mode === null) {
2948
+ mode = "auto";
2949
+ } else if (typeof cfg.mode !== "string") {
1834
2950
  process.stderr.write(
1835
- `Error: Unknown command '${commandName}'.
2951
+ `Error: apcli.mode must be a string; got ${typeof cfg.mode}. Expected one of all|none|include|exclude.
1836
2952
  `
1837
2953
  );
1838
2954
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2955
+ } else if (!VALID_USER_MODES.has(cfg.mode)) {
2956
+ process.stderr.write(
2957
+ `Error: apcli.mode '${cfg.mode}' is invalid. Expected one of all|none|include|exclude.
2958
+ `
2959
+ );
2960
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2961
+ } else {
2962
+ mode = cfg.mode;
2963
+ }
2964
+ const include = _ApcliGroup._normalizeList(cfg.include, "include");
2965
+ const exclude = _ApcliGroup._normalizeList(cfg.exclude, "exclude");
2966
+ const rawDisableEnv = cfg.disableEnv !== void 0 ? cfg.disableEnv : cfg["disable_env"];
2967
+ let disableEnv = false;
2968
+ if (rawDisableEnv !== void 0) {
2969
+ if (typeof rawDisableEnv === "boolean") {
2970
+ disableEnv = rawDisableEnv;
2971
+ } else {
2972
+ warn(
2973
+ `apcli.disable_env must be boolean; got ${typeof rawDisableEnv}. Treating as false.`
2974
+ );
2975
+ }
2976
+ }
2977
+ return new _ApcliGroup({
2978
+ mode,
2979
+ include,
2980
+ exclude,
2981
+ disableEnv,
2982
+ registryInjected: opts.registryInjected,
2983
+ fromCliConfig
2984
+ });
2985
+ }
2986
+ /**
2987
+ * Normalize an include/exclude list. Non-array → warn and return [].
2988
+ *
2989
+ * Unknown but well-formed entries emit a WARNING (spec §7 error table,
2990
+ * T-APCLI-25) but are retained in the returned list for forward-compat —
2991
+ * if apcore-cli later adds a subcommand named `foo`, existing configs
2992
+ * continue to work without a config change. At runtime, unknown names
2993
+ * simply never match any registered subcommand.
2994
+ */
2995
+ static _normalizeList(raw, label) {
2996
+ if (raw === void 0 || raw === null) return [];
2997
+ if (!Array.isArray(raw)) {
2998
+ warn(`apcli.${label} must be a list; got ${typeof raw}. Ignoring.`);
2999
+ return [];
3000
+ }
3001
+ const out = [];
3002
+ for (const entry of raw) {
3003
+ if (typeof entry === "string" && entry.length > 0) {
3004
+ if (!APCLI_SUBCOMMAND_NAMES.has(entry)) {
3005
+ warn(
3006
+ `Unknown apcli subcommand '${entry}' in ${label} list \u2014 ignoring.`
3007
+ );
3008
+ }
3009
+ out.push(entry);
3010
+ } else {
3011
+ warn(`apcli.${label} contains non-string entry; skipping.`);
3012
+ }
3013
+ }
3014
+ return out;
3015
+ }
3016
+ // -------------------------------------------------------------------------
3017
+ // Public API
3018
+ // -------------------------------------------------------------------------
3019
+ /**
3020
+ * Resolve effective visibility mode after applying tier precedence.
3021
+ *
3022
+ * Returns one of `"all" | "none" | "include" | "exclude"` — never `"auto"`.
3023
+ *
3024
+ * Tier order (spec §4.4):
3025
+ * 1. CliConfig non-auto wins outright.
3026
+ * 2. `APCORE_CLI_APCLI` env var (unless sealed by disableEnv).
3027
+ * 3. yaml non-auto.
3028
+ * 4. Auto-detect from registryInjected.
3029
+ */
3030
+ resolveVisibility() {
3031
+ if (this._fromCliConfig && this._mode !== "auto") {
3032
+ return this._mode;
3033
+ }
3034
+ if (!this._disableEnv) {
3035
+ const envMode = this._parseEnv(process.env.APCORE_CLI_APCLI);
3036
+ if (envMode !== null) {
3037
+ return envMode;
3038
+ }
3039
+ }
3040
+ if (this._mode !== "auto") {
3041
+ return this._mode;
3042
+ }
3043
+ return this._registryInjected ? "none" : "all";
3044
+ }
3045
+ /**
3046
+ * True iff `subcommand` passes the include/exclude filter.
3047
+ *
3048
+ * Callers MUST first check {@link resolveVisibility} — this method throws
3049
+ * under modes `"all"` or `"none"` (caller bug per spec §4.6).
3050
+ */
3051
+ isSubcommandIncluded(subcommand) {
3052
+ const mode = this.resolveVisibility();
3053
+ if (mode === "include") return this._include.includes(subcommand);
3054
+ if (mode === "exclude") return !this._exclude.includes(subcommand);
3055
+ throw new Error(
3056
+ `isSubcommandIncluded called under mode '${mode}'; caller should bypass.`
3057
+ );
3058
+ }
3059
+ /** True iff the `apcli` group itself should appear in root `--help`. */
3060
+ isGroupVisible() {
3061
+ return this.resolveVisibility() !== "none";
3062
+ }
3063
+ // -------------------------------------------------------------------------
3064
+ // Env parser (Tier 2) — co-located per spec §4.4
3065
+ // -------------------------------------------------------------------------
3066
+ /**
3067
+ * Parse APCORE_CLI_APCLI. Case-insensitive.
3068
+ *
3069
+ * - `show` / `1` / `true` → `"all"`
3070
+ * - `hide` / `0` / `false` → `"none"`
3071
+ * - Empty / unset → `null`
3072
+ * - Anything else → warn and return `null`
3073
+ */
3074
+ _parseEnv(raw) {
3075
+ if (raw === void 0 || raw === "") return null;
3076
+ const normalized = raw.toLowerCase();
3077
+ if (normalized === "show" || normalized === "1" || normalized === "true") {
3078
+ return "all";
3079
+ }
3080
+ if (normalized === "hide" || normalized === "0" || normalized === "false") {
3081
+ return "none";
1839
3082
  }
1840
- const resolved = cli.name() || progName;
1841
- const roff = generateManPage(commandName, cmd, resolved);
1842
- process.stdout.write(roff);
3083
+ warn(
3084
+ `Unknown APCORE_CLI_APCLI value '${raw}', ignoring. Expected: show, hide, 1, 0, true, false.`
3085
+ );
3086
+ return null;
3087
+ }
3088
+ };
3089
+
3090
+ // src/exposure.ts
3091
+ init_esm_shims();
3092
+ init_logger();
3093
+ function escapeRegex(str) {
3094
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3095
+ }
3096
+ function compilePattern(pattern) {
3097
+ const sentinel = "\0GLOB\0";
3098
+ const escaped = pattern.replaceAll("**", sentinel);
3099
+ const parts = escaped.split("*");
3100
+ const regexParts = parts.map((p) => {
3101
+ const restored = p.replaceAll(sentinel, "**");
3102
+ return escapeRegex(restored);
1843
3103
  });
1844
- cli.addCommand(manCmd);
3104
+ let regex = regexParts.join("[^.]*");
3105
+ regex = regex.replaceAll("\\*\\*", ".+");
3106
+ return new RegExp(`^${regex}$`);
3107
+ }
3108
+ var ExposureFilter = class _ExposureFilter {
3109
+ static VALID_MODES = ["all", "include", "exclude", "none"];
3110
+ _mode;
3111
+ _compiledInclude;
3112
+ _compiledExclude;
3113
+ constructor(mode = "all", include, exclude) {
3114
+ if (!_ExposureFilter.VALID_MODES.includes(mode)) {
3115
+ process.stderr.write(
3116
+ `Warning: Unknown ExposureFilter mode '${mode}' \u2014 defaulting to 'none'. Valid modes: ${_ExposureFilter.VALID_MODES.join(", ")}.
3117
+ `
3118
+ );
3119
+ mode = "none";
3120
+ }
3121
+ this._mode = mode;
3122
+ const dedup = (arr) => [...new Set(arr)];
3123
+ this._compiledInclude = dedup(include ?? []).map(compilePattern);
3124
+ this._compiledExclude = dedup(exclude ?? []).map(compilePattern);
3125
+ }
3126
+ /** Return true if the module should be exposed as a CLI command. */
3127
+ isExposed(moduleId) {
3128
+ if (this._mode === "all") return true;
3129
+ if (this._mode === "include") {
3130
+ return this._compiledInclude.some((rx) => rx.test(moduleId));
3131
+ }
3132
+ if (this._mode === "exclude") {
3133
+ return !this._compiledExclude.some((rx) => rx.test(moduleId));
3134
+ }
3135
+ return false;
3136
+ }
3137
+ /** Partition moduleIds into [exposed, hidden] lists. */
3138
+ filterModules(moduleIds) {
3139
+ const exposed = [];
3140
+ const hidden = [];
3141
+ for (const mid of moduleIds) {
3142
+ (this.isExposed(mid) ? exposed : hidden).push(mid);
3143
+ }
3144
+ return [exposed, hidden];
3145
+ }
3146
+ /**
3147
+ * Create an ExposureFilter from a parsed config dict.
3148
+ *
3149
+ * Expected: `{ expose: { mode: "include", include: ["admin.*"] } }`
3150
+ */
3151
+ static fromConfig(config) {
3152
+ const expose = config.expose ?? {};
3153
+ if (typeof expose !== "object" || expose === null || Array.isArray(expose)) {
3154
+ warn("Invalid 'expose' config (expected dict), using mode: all.");
3155
+ return new _ExposureFilter();
3156
+ }
3157
+ const exposeObj = expose;
3158
+ const mode = exposeObj.mode ?? "all";
3159
+ if (!["all", "include", "exclude"].includes(mode)) {
3160
+ throw new Error(
3161
+ `Invalid expose mode: '${mode}'. Must be one of: all, include, exclude.`
3162
+ );
3163
+ }
3164
+ let include = exposeObj.include ?? [];
3165
+ if (!Array.isArray(include)) {
3166
+ warn("Invalid 'expose.include' (expected list), ignoring.");
3167
+ include = [];
3168
+ }
3169
+ let exclude = exposeObj.exclude ?? [];
3170
+ if (!Array.isArray(exclude)) {
3171
+ warn("Invalid 'expose.exclude' (expected list), ignoring.");
3172
+ exclude = [];
3173
+ }
3174
+ const filterList = (arr, label) => {
3175
+ const result = [];
3176
+ for (const p of arr) {
3177
+ if (!p) {
3178
+ warn(`Empty pattern in expose.${label}, skipping.`);
3179
+ } else {
3180
+ result.push(String(p));
3181
+ }
3182
+ }
3183
+ return result;
3184
+ };
3185
+ return new _ExposureFilter(
3186
+ mode,
3187
+ filterList(include, "include"),
3188
+ filterList(exclude, "exclude")
3189
+ );
3190
+ }
3191
+ };
3192
+
3193
+ // src/main.ts
3194
+ init_audit();
3195
+
3196
+ // src/canonical-help.ts
3197
+ init_esm_shims();
3198
+ function resolveHelpText(cmd, section) {
3199
+ const bag = cmd._helpText;
3200
+ const v = bag?.[section];
3201
+ if (typeof v === "function") return v({ error: false, command: cmd });
3202
+ return v ?? "";
3203
+ }
3204
+ function uppercasePlaceholders(flags) {
3205
+ return flags.replace(/<([a-zA-Z0-9_-]+)>/g, (_, name) => `<${name.toUpperCase()}>`).replace(/\[([a-zA-Z0-9_-]+)\]/g, (_, name) => `[${name.toUpperCase()}]`);
3206
+ }
3207
+ function optionTerm(opt) {
3208
+ const flags = uppercasePlaceholders(opt.flags);
3209
+ if (!opt.short && flags.startsWith("--")) return " " + flags;
3210
+ return flags;
3211
+ }
3212
+ function optionDescription(opt) {
3213
+ let desc = opt.description;
3214
+ const d = opt.defaultValue;
3215
+ if (d !== void 0 && d !== false && d !== "" && d !== null) {
3216
+ desc = `${desc} [default: ${String(d)}]`;
3217
+ }
3218
+ return desc;
3219
+ }
3220
+ function reorderHelpVersionLast(opts) {
3221
+ const helpOpts = [];
3222
+ const versionOpts = [];
3223
+ const rest = [];
3224
+ for (const o of opts) {
3225
+ if (o.long === "--help") helpOpts.push(o);
3226
+ else if (o.long === "--version") versionOpts.push(o);
3227
+ else rest.push(o);
3228
+ }
3229
+ return [...rest, ...helpOpts, ...versionOpts];
3230
+ }
3231
+ function canonicalFormatHelp(cmd, helper) {
3232
+ const sections = [];
3233
+ const beforeAll = resolveHelpText(cmd, "beforeAll");
3234
+ if (beforeAll) sections.push(beforeAll);
3235
+ const desc = cmd.description();
3236
+ if (desc) sections.push(desc);
3237
+ const before = resolveHelpText(cmd, "before");
3238
+ if (before) sections.push(before);
3239
+ const visibleOpts = reorderHelpVersionLast(helper.visibleOptions(cmd));
3240
+ const visibleCmds = helper.visibleCommands(cmd);
3241
+ const args = cmd.registeredArguments ?? [];
3242
+ let usage = `Usage: ${cmd.name()}`;
3243
+ if (visibleOpts.length > 0) usage += " [OPTIONS]";
3244
+ for (const a of args) {
3245
+ const n = a.name().toUpperCase();
3246
+ usage += a.required ? ` <${n}>` : ` [${n}]`;
3247
+ }
3248
+ if (visibleCmds.length > 0) usage += " [COMMAND]";
3249
+ sections.push(usage);
3250
+ if (visibleCmds.length > 0) {
3251
+ const terms = visibleCmds.map((c) => c.name());
3252
+ const w = Math.max(...terms.map((t) => t.length));
3253
+ const lines = ["Commands:"];
3254
+ visibleCmds.forEach((sub, i) => {
3255
+ lines.push(` ${terms[i].padEnd(w)} ${sub.description()}`);
3256
+ });
3257
+ sections.push(lines.join("\n"));
3258
+ }
3259
+ if (visibleOpts.length > 0) {
3260
+ const terms = visibleOpts.map(optionTerm);
3261
+ const w = Math.max(...terms.map((t) => t.length));
3262
+ const lines = ["Options:"];
3263
+ visibleOpts.forEach((opt, i) => {
3264
+ lines.push(` ${terms[i].padEnd(w)} ${optionDescription(opt)}`);
3265
+ });
3266
+ sections.push(lines.join("\n"));
3267
+ }
3268
+ const after = resolveHelpText(cmd, "after");
3269
+ if (after) sections.push(after);
3270
+ const afterAll = resolveHelpText(cmd, "afterAll");
3271
+ if (afterAll) sections.push(afterAll);
3272
+ return sections.join("\n\n") + "\n";
1845
3273
  }
1846
3274
 
1847
3275
  // src/main.ts
1848
- var __dirname3 = path6.dirname(fileURLToPath3(import.meta.url));
3276
+ var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
1849
3277
  var verboseHelp = false;
1850
3278
  function setVerboseHelp(verbose) {
1851
3279
  verboseHelp = verbose;
@@ -1857,28 +3285,225 @@ function setDocsUrl(url) {
1857
3285
  function hasVerboseFlag() {
1858
3286
  return process.argv.includes("--verbose");
1859
3287
  }
3288
+ function resolveIntOption(cliValue, envValue, defaultValue) {
3289
+ if (cliValue !== void 0 && Number.isFinite(cliValue) && cliValue > 0) {
3290
+ return cliValue;
3291
+ }
3292
+ if (envValue !== void 0 && envValue !== "") {
3293
+ const parsed = parseInt(envValue, 10);
3294
+ if (Number.isFinite(parsed) && parsed > 0) {
3295
+ return parsed;
3296
+ }
3297
+ process.stderr.write(
3298
+ `Warning: invalid integer env value '${envValue}'; using default ${defaultValue}.
3299
+ `
3300
+ );
3301
+ }
3302
+ return defaultValue;
3303
+ }
3304
+ function resolveStringOption(cliValue, envValue) {
3305
+ if (typeof cliValue === "string" && cliValue !== "") {
3306
+ return cliValue;
3307
+ }
3308
+ if (envValue !== void 0 && envValue !== "") {
3309
+ return envValue;
3310
+ }
3311
+ return void 0;
3312
+ }
1860
3313
  var VERSION = "0.0.0";
1861
3314
  try {
1862
- const pkg = JSON.parse(readFileSync3(path6.resolve(__dirname3, "../package.json"), "utf-8"));
3315
+ const pkg = JSON.parse(readFileSync2(path4.resolve(__dirname2, "../package.json"), "utf-8"));
1863
3316
  VERSION = pkg.version;
1864
3317
  } catch {
1865
3318
  }
1866
- function createCli(extensionsDir, progName, verbose = false) {
3319
+ function emitErrorJson(e, exitCode) {
3320
+ const err = e instanceof Error ? e : new Error(String(e));
3321
+ const errRecord = err;
3322
+ const code = errRecord.code ?? "UNKNOWN";
3323
+ const payload = {
3324
+ error: true,
3325
+ code,
3326
+ message: err.message,
3327
+ exit_code: exitCode
3328
+ };
3329
+ for (const field of ["details", "suggestion", "ai_guidance", "retryable", "user_fixable"]) {
3330
+ const val = errRecord[field];
3331
+ if (val !== void 0 && val !== null) {
3332
+ payload[field] = val;
3333
+ }
3334
+ }
3335
+ process.stderr.write(JSON.stringify(payload) + "\n");
3336
+ }
3337
+ function emitErrorTty(e, exitCode) {
3338
+ const err = e instanceof Error ? e : new Error(String(e));
3339
+ const errRecord = err;
3340
+ const code = errRecord.code;
3341
+ const header = code ? `Error [${code}]: ${err.message}` : `Error: ${err.message}`;
3342
+ process.stderr.write(header + "\n");
3343
+ const details = errRecord.details;
3344
+ if (details && typeof details === "object" && !Array.isArray(details)) {
3345
+ process.stderr.write("\n Details:\n");
3346
+ for (const [k, v] of Object.entries(details)) {
3347
+ process.stderr.write(` ${k}: ${v}
3348
+ `);
3349
+ }
3350
+ }
3351
+ const suggestion = errRecord.suggestion;
3352
+ if (suggestion) {
3353
+ process.stderr.write(`
3354
+ Suggestion: ${suggestion}
3355
+ `);
3356
+ }
3357
+ const retryable = errRecord.retryable;
3358
+ if (retryable !== void 0 && retryable !== null) {
3359
+ const label = retryable ? "Yes" : "No (same input will fail again)";
3360
+ process.stderr.write(` Retryable: ${label}
3361
+ `);
3362
+ }
3363
+ process.stderr.write(`
3364
+ Exit code: ${exitCode}
3365
+ `);
3366
+ }
3367
+ function createCli(extensionsDirOrOpts, progName, verbose = false) {
3368
+ let extensionsDir;
3369
+ let registry;
3370
+ let executor;
3371
+ let extraCommands;
3372
+ let app;
3373
+ let expose;
3374
+ let apcliOption;
3375
+ if (typeof extensionsDirOrOpts === "object" && extensionsDirOrOpts !== null) {
3376
+ extensionsDir = extensionsDirOrOpts.extensionsDir;
3377
+ progName = extensionsDirOrOpts.progName ?? progName;
3378
+ verbose = extensionsDirOrOpts.verbose ?? verbose;
3379
+ app = extensionsDirOrOpts.app;
3380
+ registry = extensionsDirOrOpts.registry;
3381
+ executor = extensionsDirOrOpts.executor;
3382
+ extraCommands = extensionsDirOrOpts.extraCommands;
3383
+ expose = extensionsDirOrOpts.expose;
3384
+ apcliOption = extensionsDirOrOpts.apcli;
3385
+ } else {
3386
+ extensionsDir = extensionsDirOrOpts;
3387
+ }
1867
3388
  verboseHelp = verbose;
1868
3389
  registerConfigNamespace();
1869
- const resolvedProgName = progName ?? path6.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
3390
+ try {
3391
+ const auditLogger = new AuditLogger();
3392
+ setAuditLogger(auditLogger);
3393
+ } catch {
3394
+ }
3395
+ const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1870
3396
  const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
1871
3397
  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;
3398
+ if (app && (registry || executor)) {
3399
+ process.stderr.write("Error: app is mutually exclusive with registry/executor\n");
3400
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3401
+ }
3402
+ if (app) {
3403
+ registry = app.registry;
3404
+ executor = app.executor;
3405
+ }
3406
+ if (executor && !registry) {
3407
+ process.stderr.write("Error: executor requires registry \u2014 pass both or neither\n");
3408
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3409
+ }
3410
+ if (executor && typeof executor.setApprovalHandler === "function") {
3411
+ try {
3412
+ const handler = new CliApprovalHandler(
3413
+ /*autoApprove*/
3414
+ false
3415
+ );
3416
+ executor.setApprovalHandler(handler);
3417
+ } catch {
3418
+ }
3419
+ }
3420
+ const registryInjected = registry !== void 0;
3421
+ const program = new Command5(resolvedProgName).exitOverride().version(VERSION, "-V, --version", "Print version").helpOption("-h, --help", "Print help").addHelpCommand("help [command]", "Print this message or the help of the given subcommand(s)").description("apcore CLI \u2014 execute apcore modules from the command line").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING").option("--verbose", "Show all options in help output (including built-in apcore options)");
3422
+ program.configureHelp({ formatHelp: canonicalFormatHelp });
3423
+ if (!registryInjected) {
3424
+ program.option("--extensions-dir <path>", "Path to extensions directory");
3425
+ program.option("--commands-dir <path>", "Path to convention-based commands directory");
3426
+ program.option("--binding <path>", "Path to binding.yaml for display overlay");
3427
+ }
3428
+ let apcliCfg;
3429
+ if (apcliOption instanceof ApcliGroup) {
3430
+ apcliCfg = apcliOption;
3431
+ } else if (apcliOption !== void 0) {
3432
+ apcliCfg = ApcliGroup.fromCliConfig(apcliOption, { registryInjected });
3433
+ } else {
3434
+ let yamlVal = null;
3435
+ try {
3436
+ const resolver = new ConfigResolver();
3437
+ yamlVal = resolver.resolveObject("apcli");
3438
+ } catch {
3439
+ yamlVal = null;
3440
+ }
3441
+ apcliCfg = ApcliGroup.fromYaml(yamlVal, { registryInjected });
3442
+ }
3443
+ const apcliGroup = program.command("apcli", { hidden: !apcliCfg.isGroupVisible() }).description("apcore-cli built-in commands");
3444
+ if (registry) {
3445
+ program._registry = registry;
3446
+ if (executor) {
3447
+ program._executor = executor;
3448
+ }
3449
+ } else {
3450
+ const resolvedExtDir = extensionsDir ?? process.env.APCORE_EXTENSIONS_ROOT ?? "./extensions";
3451
+ void resolvedExtDir;
3452
+ }
3453
+ let exposureFilter;
3454
+ try {
3455
+ if (expose instanceof ExposureFilter) {
3456
+ exposureFilter = expose;
3457
+ } else if (typeof expose === "object" && expose !== null) {
3458
+ exposureFilter = ExposureFilter.fromConfig({ expose });
3459
+ } else {
3460
+ exposureFilter = new ExposureFilter();
3461
+ }
3462
+ } catch (err) {
3463
+ const msg = err instanceof Error ? err.message : String(err);
3464
+ process.stderr.write(`Error: invalid 'expose' option \u2014 ${msg}
3465
+ `);
3466
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3467
+ }
3468
+ _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter);
3469
+ _registerDeprecationShims(program, apcliGroup, registryInjected, resolvedProgName);
1875
3470
  program.addHelpText("after", [
1876
3471
  "",
1877
3472
  "Use --help --verbose to show all options (including built-in apcore options).",
1878
3473
  "Use --help --man to display a formatted man page."
1879
3474
  ].join("\n"));
1880
- registerInitCommand(program);
1881
3475
  configureManHelp(program, resolvedProgName, VERSION);
3476
+ if (extraCommands && extraCommands.length > 0) {
3477
+ for (const cmd of extraCommands) {
3478
+ const cmdName = cmd.name();
3479
+ if (RESERVED_GROUP_NAMES.has(cmdName)) {
3480
+ process.stderr.write(
3481
+ `Error: extraCommands name '${cmdName}' is reserved
3482
+ `
3483
+ );
3484
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3485
+ }
3486
+ const existing = program.commands.find((c) => c.name() === cmdName);
3487
+ if (existing) {
3488
+ const isShim = existing.__isDeprecationShim === true;
3489
+ if (isShim) {
3490
+ warn(
3491
+ `extraCommands '${cmdName}' overrides the deprecation shim for the same name. The shim will be removed.`
3492
+ );
3493
+ const cmds = program.commands;
3494
+ const idx = cmds.indexOf(existing);
3495
+ if (idx >= 0) cmds.splice(idx, 1);
3496
+ } else {
3497
+ process.stderr.write(
3498
+ `Error: extraCommands name '${cmdName}' collides with an existing command
3499
+ `
3500
+ );
3501
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
3502
+ }
3503
+ }
3504
+ program.addCommand(cmd);
3505
+ }
3506
+ }
1882
3507
  program.hook("preAction", async (thisCommand) => {
1883
3508
  const opts = thisCommand.opts();
1884
3509
  const commandsDir = opts.commandsDir;
@@ -1887,25 +3512,146 @@ function createCli(extensionsDir, progName, verbose = false) {
1887
3512
  });
1888
3513
  return program;
1889
3514
  }
3515
+ var _ALWAYS_REGISTERED = /* @__PURE__ */ new Set(["exec"]);
3516
+ function _registerApcliSubcommands(apcliGroup, apcliCfg, registry, executor, exposureFilter) {
3517
+ const emitUnwiredError = () => {
3518
+ process.stderr.write(
3519
+ "Error: no apcore-js registry wired. In standalone mode, pass --extensions-dir <path> to enable module discovery.\n"
3520
+ );
3521
+ process.exit(EXIT_CODES.CONFIG_INVALID);
3522
+ };
3523
+ const effectiveRegistry = registry ?? {
3524
+ listModules: () => emitUnwiredError(),
3525
+ getModule: () => emitUnwiredError()
3526
+ };
3527
+ const TABLE = [
3528
+ { name: "list", requiresExecutor: false, register: (g) => registerListCommand(g, effectiveRegistry, exposureFilter) },
3529
+ { name: "describe", requiresExecutor: false, register: (g) => registerDescribeCommand(g, effectiveRegistry) },
3530
+ { name: "exec", requiresExecutor: true, register: (g, _r, ex) => registerExecCommand(g, effectiveRegistry, ex) },
3531
+ { name: "validate", requiresExecutor: true, register: (g, _r, ex) => registerValidateCommand(g, effectiveRegistry, ex) },
3532
+ { name: "init", requiresExecutor: false, register: (g) => registerInitCommand(g) },
3533
+ { name: "health", requiresExecutor: true, register: (g, _r, ex) => registerHealthCommand(g, ex) },
3534
+ { name: "usage", requiresExecutor: true, register: (g, _r, ex) => registerUsageCommand(g, ex) },
3535
+ { name: "enable", requiresExecutor: true, register: (g, _r, ex) => registerEnableCommand(g, ex) },
3536
+ { name: "disable", requiresExecutor: true, register: (g, _r, ex) => registerDisableCommand(g, ex) },
3537
+ { name: "reload", requiresExecutor: true, register: (g, _r, ex) => registerReloadCommand(g, ex) },
3538
+ { name: "config", requiresExecutor: true, register: (g, _r, ex) => registerConfigCommand(g, ex) },
3539
+ { name: "completion", requiresExecutor: false, register: (g) => registerCompletionCommand(g) },
3540
+ { name: "describe-pipeline", requiresExecutor: true, register: (g, _r, ex) => registerPipelineCommand(g, ex) }
3541
+ ];
3542
+ const mode = apcliCfg.resolveVisibility();
3543
+ for (const entry of TABLE) {
3544
+ let shouldRegister;
3545
+ if (mode === "all" || mode === "none") {
3546
+ shouldRegister = true;
3547
+ } else {
3548
+ shouldRegister = _ALWAYS_REGISTERED.has(entry.name) || apcliCfg.isSubcommandIncluded(entry.name);
3549
+ }
3550
+ if (!shouldRegister) continue;
3551
+ if (entry.requiresExecutor && !executor) {
3552
+ if (_ALWAYS_REGISTERED.has(entry.name)) {
3553
+ warn(
3554
+ `apcli.${entry.name} is in _ALWAYS_REGISTERED but no executor is wired \u2014 subcommand unavailable. Pass executor to createCli() or avoid ${entry.name} invocations.`
3555
+ );
3556
+ }
3557
+ continue;
3558
+ }
3559
+ entry.register(apcliGroup, registry, executor);
3560
+ }
3561
+ }
3562
+ var _DEPRECATED_ROOT_COMMANDS = [
3563
+ "list",
3564
+ "describe",
3565
+ "exec",
3566
+ "init",
3567
+ "validate",
3568
+ "health",
3569
+ "usage",
3570
+ "enable",
3571
+ "disable",
3572
+ "reload",
3573
+ "config",
3574
+ "completion",
3575
+ "describe-pipeline"
3576
+ ];
3577
+ function _registerDeprecationShims(root, apcliGroup, registryInjected, cliName) {
3578
+ if (registryInjected) return;
3579
+ for (const name of _DEPRECATED_ROOT_COMMANDS) {
3580
+ const apcliSub = apcliGroup.commands.find((c) => c.name() === name);
3581
+ if (!apcliSub) continue;
3582
+ if (root.commands.some((c) => c.name() === name)) continue;
3583
+ const shim = root.command(name).description(`[DEPRECATED] Use '${cliName} apcli ${name}' instead.`).allowUnknownOption(true).allowExcessArguments(true).helpOption(false);
3584
+ shim.__isDeprecationShim = true;
3585
+ shim.action(async function() {
3586
+ process.stderr.write(
3587
+ `WARNING: '${name}' as a root-level command is deprecated. Use '${cliName} apcli ${name}' instead.
3588
+ Will be removed in v0.8. See: https://aiperceivable.github.io/apcore-cli/features/builtin-group/#11-migration
3589
+ `
3590
+ );
3591
+ const tail = _collectShimForwardArgs(this);
3592
+ await apcliSub.parseAsync(tail, { from: "user" });
3593
+ });
3594
+ }
3595
+ }
3596
+ function _collectShimForwardArgs(shim) {
3597
+ const shimArgs = (shim.args ?? []).slice();
3598
+ if (shimArgs.length > 0) return shimArgs;
3599
+ const shimName = shim.name();
3600
+ const idx = process.argv.indexOf(shimName);
3601
+ if (idx < 0) return [];
3602
+ return process.argv.slice(idx + 1);
3603
+ }
3604
+ var bindingDisplayMap = /* @__PURE__ */ new Map();
3605
+ function lookupBindingDisplay(moduleId) {
3606
+ return bindingDisplayMap.get(moduleId);
3607
+ }
1890
3608
  async function applyToolkitIntegration(commandsDir, bindingPath) {
1891
3609
  if (!commandsDir && !bindingPath) {
1892
3610
  return;
1893
3611
  }
3612
+ let toolkit;
1894
3613
  try {
1895
3614
  const toolkitModule = "apcore-toolkit";
1896
- const toolkit = await import(
3615
+ toolkit = await import(
1897
3616
  /* @vite-ignore */
1898
3617
  toolkitModule
1899
3618
  );
1900
- if (commandsDir) {
1901
- console.warn("Convention scanning not yet available in TypeScript toolkit");
3619
+ } catch {
3620
+ warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
3621
+ return;
3622
+ }
3623
+ if (commandsDir) {
3624
+ warn("Convention scanning not available in the TypeScript toolkit");
3625
+ }
3626
+ if (bindingPath) {
3627
+ try {
3628
+ await loadBindingDisplayOverlay(toolkit, bindingPath);
3629
+ } catch (err) {
3630
+ const msg = err instanceof Error ? err.message : String(err);
3631
+ warn(`apcore-toolkit: failed to load binding '${bindingPath}': ${msg}`);
1902
3632
  }
1903
- if (bindingPath) {
1904
- const resolver = new toolkit.DisplayResolver();
1905
- void resolver;
3633
+ }
3634
+ }
3635
+ async function loadBindingDisplayOverlay(toolkit, bindingPath) {
3636
+ const BindingLoaderCtor = toolkit.BindingLoader;
3637
+ const DisplayResolverCtor = toolkit.DisplayResolver;
3638
+ if (!BindingLoaderCtor || !DisplayResolverCtor) {
3639
+ return;
3640
+ }
3641
+ const loader = new BindingLoaderCtor();
3642
+ const scanned = loader.load(bindingPath);
3643
+ const resolver = new DisplayResolverCtor();
3644
+ const resolved = resolver.resolve(scanned, { bindingPath });
3645
+ for (const mod of resolved) {
3646
+ if (!mod || typeof mod !== "object") continue;
3647
+ const entry = mod;
3648
+ const id = typeof entry.moduleId === "string" ? entry.moduleId : null;
3649
+ if (!id) continue;
3650
+ const meta = entry.metadata ?? {};
3651
+ const display = meta.display;
3652
+ if (display && typeof display === "object" && !Array.isArray(display)) {
3653
+ bindingDisplayMap.set(id, display);
1906
3654
  }
1907
- } catch {
1908
- console.warn("apcore-toolkit not installed \u2014 toolkit features unavailable");
1909
3655
  }
1910
3656
  }
1911
3657
  function main(progName) {
@@ -1913,13 +3659,13 @@ function main(progName) {
1913
3659
  const program = createCli(void 0, progName, verboseHelp);
1914
3660
  try {
1915
3661
  program.parse(process.argv);
1916
- } catch (error2) {
1917
- if (error2 instanceof CommanderError) {
1918
- process.exit(error2.exitCode);
3662
+ } catch (error) {
3663
+ if (error instanceof CommanderError) {
3664
+ process.exit(error.exitCode);
1919
3665
  }
1920
- const code = exitCodeForError(error2);
1921
- if (error2 instanceof Error) {
1922
- process.stderr.write(`Error: ${error2.message}
3666
+ const code = exitCodeForError(error);
3667
+ if (error instanceof Error) {
3668
+ process.stderr.write(`Error: ${error.message}
1923
3669
  `);
1924
3670
  }
1925
3671
  process.exit(code);
@@ -1942,23 +3688,44 @@ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdNam
1942
3688
  }
1943
3689
  schemaOptions = schemaToCliOptions(resolvedSchema, helpTextMaxLength);
1944
3690
  }
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();
3691
+ const cmd = new Command5(effectiveCmdName).description(cmdHelp);
3692
+ const inputOpt = new Option4("--input <source>", "Read JSON input from a file path, or use '-' to read from stdin pipe");
3693
+ const yesOpt = new Option4("-y, --yes", "Skip interactive approval prompts (for scripts and CI)").default(false);
3694
+ const largeInputOpt = new Option4("--large-input", "Allow stdin input larger than 10MB (default limit protects against accidental pipes)").default(false);
3695
+ const formatOpt = new Option4("--format <format>", "Output format: json, table, csv, yaml, jsonl.").choices(["json", "table", "csv", "yaml", "jsonl"]);
3696
+ const fieldsOpt = new Option4("--fields <fields>", "Comma-separated dot-paths to select from the result (e.g., 'status,data.count').");
3697
+ const sandboxOpt = new Option4("--sandbox", "Run module in an isolated subprocess with restricted filesystem and env access").default(false).hideHelp();
3698
+ const dryRunOpt = new Option4("--dry-run", "Run preflight checks without executing the module. Shows validation results.").default(false);
3699
+ const traceOpt = new Option4("--trace", "Show execution pipeline trace with per-step timing after the result.").default(false);
3700
+ const streamOpt = new Option4("--stream", "Stream module output as JSONL (one JSON object per line, flushed immediately).").default(false);
3701
+ const strategyOpt = new Option4("--strategy <name>", "Execution pipeline strategy: standard (default), internal, testing, performance.").choices(["standard", "internal", "testing", "performance", "minimal"]);
3702
+ const approvalTimeoutOpt = new Option4("--approval-timeout <seconds>", "Override approval prompt timeout in seconds (default: 60).").argParser(parseInt);
3703
+ const approvalTokenOpt = new Option4("--approval-token <token>", "Resume a pending approval with the given token (for async approval flows).");
1951
3704
  if (!verbose) {
1952
3705
  inputOpt.hideHelp();
1953
3706
  yesOpt.hideHelp();
1954
3707
  largeInputOpt.hideHelp();
1955
3708
  formatOpt.hideHelp();
3709
+ fieldsOpt.hideHelp();
3710
+ dryRunOpt.hideHelp();
3711
+ traceOpt.hideHelp();
3712
+ streamOpt.hideHelp();
3713
+ strategyOpt.hideHelp();
3714
+ approvalTimeoutOpt.hideHelp();
3715
+ approvalTokenOpt.hideHelp();
1956
3716
  }
1957
3717
  cmd.addOption(inputOpt);
1958
3718
  cmd.addOption(yesOpt);
1959
3719
  cmd.addOption(largeInputOpt);
1960
3720
  cmd.addOption(formatOpt);
3721
+ cmd.addOption(fieldsOpt);
1961
3722
  cmd.addOption(sandboxOpt);
3723
+ cmd.addOption(dryRunOpt);
3724
+ cmd.addOption(traceOpt);
3725
+ cmd.addOption(streamOpt);
3726
+ cmd.addOption(strategyOpt);
3727
+ cmd.addOption(approvalTimeoutOpt);
3728
+ cmd.addOption(approvalTokenOpt);
1962
3729
  const footerParts = [];
1963
3730
  if (!verbose) {
1964
3731
  footerParts.push("Use --verbose to show all options (including built-in apcore options).");
@@ -1981,49 +3748,225 @@ function buildModuleCommand(moduleDef, executor, helpTextMaxLength = 1e3, cmdNam
1981
3748
  const autoApprove = options.yes;
1982
3749
  const largeInput = options.largeInput;
1983
3750
  const outputFormat = options.format;
3751
+ const outputFields = options.fields;
1984
3752
  const sandboxEnabled = options.sandbox;
3753
+ const dryRun = options.dryRun;
3754
+ const traceFlag = options.trace;
3755
+ const streamFlag = options.stream;
3756
+ const strategyName = resolveStringOption(options.strategy, process.env.APCORE_CLI_STRATEGY);
3757
+ const approvalTimeout = resolveIntOption(
3758
+ options.approvalTimeout,
3759
+ process.env.APCORE_CLI_APPROVAL_TIMEOUT,
3760
+ 60
3761
+ );
3762
+ const approvalToken = options.approvalToken;
1985
3763
  const schemaKwargs = {};
1986
- const builtinKeys = /* @__PURE__ */ new Set(["input", "yes", "largeInput", "format", "sandbox", "verbose"]);
3764
+ const builtinKeys = /* @__PURE__ */ new Set([
3765
+ "input",
3766
+ "yes",
3767
+ "largeInput",
3768
+ "format",
3769
+ "fields",
3770
+ "sandbox",
3771
+ "verbose",
3772
+ "dryRun",
3773
+ "trace",
3774
+ "stream",
3775
+ "strategy",
3776
+ "approvalTimeout",
3777
+ "approvalToken"
3778
+ ]);
1987
3779
  for (const [k, v] of Object.entries(options)) {
1988
3780
  if (!builtinKeys.has(k)) {
1989
3781
  schemaKwargs[k] = v;
1990
3782
  }
1991
3783
  }
3784
+ let merged = {};
3785
+ const startTime = performance.now();
1992
3786
  try {
1993
- const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
3787
+ merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
1994
3788
  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);
3789
+ merged = reconverted;
3790
+ if (dryRun) {
3791
+ if (!executor.validate) {
3792
+ process.stderr.write("Error: Executor does not support validate.\n");
3793
+ process.exit(EXIT_CODES.MODULE_EXECUTE_ERROR);
3794
+ }
3795
+ const preflight = await executor.validate(moduleId, merged);
3796
+ formatPreflightResult(preflight, outputFormat);
3797
+ if (traceFlag) {
3798
+ const pureSteps = /* @__PURE__ */ new Set([
3799
+ "context_creation",
3800
+ "call_chain_guard",
3801
+ "module_lookup",
3802
+ "acl_check",
3803
+ "input_validation"
3804
+ ]);
3805
+ const allSteps = [
3806
+ "context_creation",
3807
+ "call_chain_guard",
3808
+ "module_lookup",
3809
+ "acl_check",
3810
+ "approval_gate",
3811
+ "middleware_before",
3812
+ "input_validation",
3813
+ "execute",
3814
+ "output_validation",
3815
+ "middleware_after",
3816
+ "return_result"
3817
+ ];
3818
+ process.stderr.write("\nPipeline preview (dry-run):\n");
3819
+ for (const s of allSteps) {
3820
+ if (pureSteps.has(s)) {
3821
+ process.stderr.write(` \u2713 ${s.padEnd(24)} (pure \u2014 would execute)
3822
+ `);
3823
+ } else {
3824
+ process.stderr.write(` \u25CB ${s.padEnd(24)} (impure \u2014 skipped in dry-run)
3825
+ `);
3826
+ }
3827
+ }
3828
+ }
3829
+ process.exit(preflight.valid ? 0 : firstFailedExitCode(preflight));
3830
+ }
3831
+ if (approvalToken) {
3832
+ merged._approval_token = approvalToken;
3833
+ }
3834
+ await checkApproval(moduleDef, autoApprove, approvalTimeout);
3835
+ if (streamFlag) {
3836
+ if (resolveFormat(outputFormat) === "table") {
3837
+ process.stderr.write("Warning: Streaming mode always outputs JSONL; --format table is ignored.\n");
3838
+ }
3839
+ const annotations = moduleDef.annotations;
3840
+ const isStreaming = annotations?.streaming === true;
3841
+ if (!isStreaming) {
3842
+ process.stderr.write(
3843
+ `Warning: Module '${moduleId}' does not declare streaming support. Falling back to standard execution.
3844
+ `
3845
+ );
3846
+ }
3847
+ if (isStreaming && executor.stream) {
3848
+ let chunks = 0;
3849
+ for await (const chunk of executor.stream(moduleId, merged)) {
3850
+ chunks++;
3851
+ process.stdout.write(JSON.stringify(chunk) + "\n");
3852
+ if (process.stderr.isTTY) {
3853
+ process.stderr.write(`\rStreaming ${moduleId}... (${chunks} chunks)`);
3854
+ }
3855
+ }
3856
+ if (process.stderr.isTTY) {
3857
+ process.stderr.write("\n");
3858
+ }
3859
+ const durationMs2 = Math.round(performance.now() - startTime);
3860
+ const { getAuditLogger: getAuditLogger3 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
3861
+ const auditLogger2 = getAuditLogger3();
3862
+ if (auditLogger2) {
3863
+ auditLogger2.logExecution(moduleId, merged, "success", 0, durationMs2);
3864
+ }
3865
+ return;
3866
+ }
3867
+ }
3868
+ if (traceFlag && executor.callWithTrace) {
3869
+ const [result2, trace] = await executor.callWithTrace(
3870
+ moduleId,
3871
+ merged,
3872
+ strategyName ? { strategy: strategyName } : void 0
3873
+ );
3874
+ const durationMs2 = Math.round(performance.now() - startTime);
3875
+ const resolved = resolveFormat(outputFormat);
3876
+ if (resolved === "json" || !process.stdout.isTTY) {
3877
+ const traceData = {
3878
+ strategy: trace.strategyName,
3879
+ total_duration_ms: trace.totalDurationMs,
3880
+ success: trace.success,
3881
+ steps: trace.steps.map((s) => ({
3882
+ name: s.name,
3883
+ duration_ms: s.durationMs,
3884
+ skipped: s.skipped,
3885
+ ...s.skipped ? { skip_reason: s.skipReason ?? null } : {}
3886
+ }))
3887
+ };
3888
+ let output;
3889
+ if (typeof result2 === "object" && result2 !== null && !Array.isArray(result2)) {
3890
+ output = { ...result2, _trace: traceData };
3891
+ } else {
3892
+ output = { result: result2, _trace: traceData };
3893
+ }
3894
+ process.stdout.write(JSON.stringify(output, null, 2) + "\n");
3895
+ } else {
3896
+ formatExecResult(result2, outputFormat, outputFields);
3897
+ const stepCount = trace.steps.length;
3898
+ process.stderr.write(
3899
+ `
3900
+ Pipeline Trace (strategy: ${trace.strategyName}, ${stepCount} steps, ${trace.totalDurationMs.toFixed(1)}ms)
3901
+ `
3902
+ );
3903
+ for (const s of trace.steps) {
3904
+ if (s.skipped) {
3905
+ const reason = s.skipReason ?? "n/a";
3906
+ process.stderr.write(` \u25CB ${s.name.padEnd(24)} ${"\u2014".padStart(8)} skipped (${reason})
3907
+ `);
3908
+ } else {
3909
+ process.stderr.write(` \u2713 ${s.name.padEnd(24)} ${(s.durationMs.toFixed(1) + "ms").padStart(8)}
3910
+ `);
3911
+ }
3912
+ }
3913
+ }
3914
+ const { getAuditLogger: getAL2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
3915
+ const al2 = getAL2();
3916
+ if (al2) {
3917
+ al2.logExecution(moduleId, merged, "success", 0, durationMs2);
3918
+ }
3919
+ return;
3920
+ }
3921
+ let result;
3922
+ if (strategyName && executor.callWithTrace) {
3923
+ const [res] = await executor.callWithTrace(
3924
+ moduleId,
3925
+ merged,
3926
+ { strategy: strategyName }
3927
+ );
3928
+ result = res;
3929
+ if (strategyName !== "standard" && process.stderr.isTTY) {
3930
+ process.stderr.write(`Warning: Using '${strategyName}' strategy.
3931
+ `);
3932
+ }
3933
+ } else {
3934
+ const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
3935
+ const sandbox = new Sandbox2(sandboxEnabled);
3936
+ result = await sandbox.execute(moduleId, merged, executor);
3937
+ }
2000
3938
  const durationMs = Math.round(performance.now() - startTime);
3939
+ formatExecResult(result, outputFormat, outputFields);
2001
3940
  const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
2002
3941
  const auditLogger = getAuditLogger2();
2003
3942
  if (auditLogger) {
2004
- auditLogger.logExecution(moduleId, reconverted, "success", 0, durationMs);
3943
+ auditLogger.logExecution(moduleId, merged, "success", 0, durationMs);
2005
3944
  }
2006
- formatExecResult(result, outputFormat);
2007
3945
  } 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);
3946
+ const exitCode = exitCodeForError(err);
3947
+ const durationMs = Math.round(performance.now() - startTime);
3948
+ try {
3949
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
3950
+ const auditLogger = getAuditLogger2();
3951
+ if (auditLogger) {
3952
+ auditLogger.logExecution(moduleId, merged, "error", exitCode, durationMs);
3953
+ }
3954
+ } catch {
2013
3955
  }
2014
- if (err instanceof Error) {
2015
- process.stderr.write(`Error: ${err.message}
2016
- `);
3956
+ if (outputFormat === "json" || !process.stderr.isTTY) {
3957
+ emitErrorJson(err, exitCode);
3958
+ } else {
3959
+ emitErrorTty(err, exitCode);
2017
3960
  }
2018
- process.exit(code);
3961
+ process.exit(exitCode);
2019
3962
  }
2020
3963
  });
2021
3964
  return cmd;
2022
3965
  }
2023
3966
  function validateModuleId(moduleId) {
2024
- if (moduleId.length > 128) {
3967
+ if (moduleId.length > 192) {
2025
3968
  process.stderr.write(
2026
- `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
3969
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is 192 characters.
2027
3970
  `
2028
3971
  );
2029
3972
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
@@ -2046,45 +3989,57 @@ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
2046
3989
  if (!stdinFlag) {
2047
3990
  return cliKwargsNonNull;
2048
3991
  }
3992
+ let raw;
3993
+ let source;
2049
3994
  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);
2057
- }
2058
- if (!raw) {
2059
- return cliKwargsNonNull;
2060
- }
2061
- let stdinData;
3995
+ raw = await readStdin();
3996
+ source = "STDIN";
3997
+ } else {
3998
+ source = `file '${stdinFlag}'`;
2062
3999
  try {
2063
- stdinData = JSON.parse(raw);
2064
- } catch {
2065
- process.stderr.write(
2066
- "Error: STDIN does not contain valid JSON.\n"
2067
- );
4000
+ raw = readFileSync2(stdinFlag, "utf-8");
4001
+ } catch (err) {
4002
+ const msg = err instanceof Error ? err.message : String(err);
4003
+ process.stderr.write(`Error: Could not read input ${source}: ${msg}
4004
+ `);
2068
4005
  process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2069
4006
  }
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}.
4007
+ }
4008
+ const rawSize = Buffer.byteLength(raw, "utf-8");
4009
+ if (rawSize > 10485760 && !largeInput) {
4010
+ process.stderr.write(
4011
+ `Error: ${source} input exceeds 10MB limit. Use --large-input to override.
2073
4012
  `
2074
- );
2075
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2076
- }
2077
- return { ...stdinData, ...cliKwargsNonNull };
4013
+ );
4014
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
4015
+ }
4016
+ if (!raw) {
4017
+ return cliKwargsNonNull;
4018
+ }
4019
+ let parsed;
4020
+ try {
4021
+ parsed = JSON.parse(raw);
4022
+ } catch {
4023
+ process.stderr.write(`Error: ${source} does not contain valid JSON.
4024
+ `);
4025
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
4026
+ }
4027
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
4028
+ process.stderr.write(
4029
+ `Error: ${source} JSON must be an object, got ${Array.isArray(parsed) ? "array" : typeof parsed}.
4030
+ `
4031
+ );
4032
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
2078
4033
  }
2079
- return cliKwargsNonNull;
4034
+ return { ...parsed, ...cliKwargsNonNull };
2080
4035
  }
2081
4036
  function readStdin() {
2082
- return new Promise((resolve3, reject) => {
4037
+ return new Promise((resolve2, reject) => {
2083
4038
  const chunks = [];
2084
4039
  const onData = (chunk) => chunks.push(chunk);
2085
4040
  const onEnd = () => {
2086
4041
  cleanup();
2087
- resolve3(Buffer.concat(chunks).toString("utf-8"));
4042
+ resolve2(Buffer.concat(chunks).toString("utf-8"));
2088
4043
  };
2089
4044
  const onError = (err) => {
2090
4045
  cleanup();
@@ -2124,8 +4079,25 @@ function reconvertEnumValues(kwargs, options) {
2124
4079
 
2125
4080
  // src/cli.ts
2126
4081
  init_esm_shims();
2127
- import { Command as Command3 } from "commander";
2128
- var BUILTIN_COMMANDS = ["completion", "describe", "exec", "init", "list", "man"];
4082
+ import { Command as Command6 } from "commander";
4083
+ init_logger();
4084
+ init_errors();
4085
+ function assertNotReserved(kind, name, moduleId) {
4086
+ if (!RESERVED_GROUP_NAMES.has(name)) return;
4087
+ let msg;
4088
+ if (kind === "group") {
4089
+ msg = `Error: Module '${moduleId}': display.cli.group '${name}' is reserved. Use a different CLI alias or set display.cli.group to another value.
4090
+ `;
4091
+ } else if (kind === "auto-group") {
4092
+ msg = `Error: Module '${moduleId}': auto-group '${name}' is reserved. Rename the module id or set display.cli.group to another value.
4093
+ `;
4094
+ } else {
4095
+ msg = `Error: Module '${moduleId}': top-level CLI name '${name}' is reserved. Use a different CLI alias.
4096
+ `;
4097
+ }
4098
+ process.stderr.write(msg);
4099
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
4100
+ }
2129
4101
  var LazyModuleGroup = class {
2130
4102
  registry;
2131
4103
  executor;
@@ -2208,7 +4180,7 @@ var LazyGroup = class {
2208
4180
  this.members = members;
2209
4181
  this._executor = executor;
2210
4182
  this._helpTextMaxLength = helpTextMaxLength;
2211
- this.command = new Command3(name).description(`${name} commands`);
4183
+ this.command = new Command6(name).description(`${name} commands`);
2212
4184
  for (const [cmdName, [, descriptor]] of this.members) {
2213
4185
  const cmd = buildModuleCommand(
2214
4186
  descriptor,
@@ -2250,10 +4222,41 @@ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
2250
4222
  /** Cached LazyGroup instances */
2251
4223
  groupCache = /* @__PURE__ */ new Map();
2252
4224
  groupMapBuilt = false;
4225
+ /** Exposure filter (FE-12) — controls which modules appear as CLI commands */
4226
+ exposureFilter;
4227
+ /** Effective group depth (CLAUDE.md v0.6.0): constructor arg > APCORE_CLI_GROUP_DEPTH env > 1. */
4228
+ groupDepth;
4229
+ constructor(registry, executor, helpTextMaxLength = 1e3, exposureFilter, groupDepth) {
4230
+ super(registry, executor, helpTextMaxLength);
4231
+ this.exposureFilter = exposureFilter ?? new ExposureFilter();
4232
+ this.groupDepth = _GroupedModuleGroup.resolveGroupDepth(groupDepth);
4233
+ }
4234
+ /**
4235
+ * Resolve group depth from constructor arg > APCORE_CLI_GROUP_DEPTH env > default 1.
4236
+ * Invalid env values (non-integer, non-positive) fall through to the default.
4237
+ */
4238
+ static resolveGroupDepth(explicit) {
4239
+ if (explicit !== void 0 && Number.isFinite(explicit) && explicit > 0) {
4240
+ return Math.floor(explicit);
4241
+ }
4242
+ const raw = process.env.APCORE_CLI_GROUP_DEPTH;
4243
+ if (raw !== void 0 && raw !== "") {
4244
+ const parsed = parseInt(raw, 10);
4245
+ if (Number.isFinite(parsed) && parsed > 0) {
4246
+ return parsed;
4247
+ }
4248
+ }
4249
+ return 1;
4250
+ }
2253
4251
  /**
2254
4252
  * Determine (groupName | null, commandName) for a module from its display overlay.
4253
+ *
4254
+ * @param groupDepth Number of dotted segments to consume as the group prefix.
4255
+ * Defaults to 1 (e.g., "math.add" → group="math", cmd="add").
4256
+ * Set to 2 for multi-level grouping (e.g., "math.trig.sin" →
4257
+ * group="math.trig", cmd="sin").
2255
4258
  */
2256
- static resolveGroup(moduleId, descriptor) {
4259
+ static resolveGroup(moduleId, descriptor, groupDepth = 1) {
2257
4260
  if (!moduleId) {
2258
4261
  warn("Empty module_id encountered in resolveGroup");
2259
4262
  return [null, ""];
@@ -2269,65 +4272,76 @@ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
2269
4272
  }
2270
4273
  const cliName = cliDisplay.alias ?? moduleId;
2271
4274
  if (cliName.includes(".")) {
2272
- const dotIdx = cliName.indexOf(".");
2273
- const group = cliName.substring(0, dotIdx);
2274
- const cmd = cliName.substring(dotIdx + 1);
4275
+ const parts = cliName.split(".");
4276
+ const depth = Math.max(1, Math.min(groupDepth, parts.length - 1));
4277
+ const group = parts.slice(0, depth).join(".");
4278
+ const cmd = parts.slice(depth).join(".");
2275
4279
  return [group, cmd];
2276
4280
  }
2277
4281
  return [null, cliName];
2278
4282
  }
2279
4283
  /**
2280
4284
  * Build the group map from registry modules.
4285
+ *
4286
+ * FE-13: hard-fails with exit 2 when a module resolves to the reserved
4287
+ * `apcli` namespace in any of three ways — explicit `display.cli.group`,
4288
+ * auto-grouped dotted prefix, or top-level alias/id. See spec §4.10.
2281
4289
  */
2282
4290
  buildGroupMap() {
2283
4291
  if (this.groupMapBuilt) {
2284
4292
  return;
2285
4293
  }
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
- }
4294
+ this.buildAliasMap();
4295
+ for (const descriptor of this.registry.listModules()) {
4296
+ const moduleId = descriptor.id;
4297
+ const cached = this.descriptorCache.get(moduleId);
4298
+ if (!cached) {
4299
+ continue;
2308
4300
  }
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
- );
4301
+ if (!this.exposureFilter.isExposed(moduleId)) {
4302
+ continue;
4303
+ }
4304
+ const display = getDisplay(cached);
4305
+ const cliDisplay = display.cli && typeof display.cli === "object" && !Array.isArray(display.cli) ? display.cli : {};
4306
+ const explicitGroup = typeof cliDisplay.group === "string" && cliDisplay.group !== "" ? cliDisplay.group : void 0;
4307
+ if (explicitGroup !== void 0) {
4308
+ assertNotReserved("group", explicitGroup, moduleId);
4309
+ }
4310
+ const [group, cmd] = _GroupedModuleGroup.resolveGroup(moduleId, cached, this.groupDepth);
4311
+ if (group !== null && explicitGroup === void 0) {
4312
+ assertNotReserved("auto-group", group, moduleId);
4313
+ }
4314
+ if (group === null) {
4315
+ assertNotReserved("top-level", cmd, moduleId);
4316
+ this.topLevelModules.set(cmd, [moduleId, cached]);
4317
+ } else if (!/^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)*$/.test(group)) {
4318
+ warn(
4319
+ `Module '${moduleId}': group name '${group}' is not shell-safe \u2014 treating as top-level.`
4320
+ );
4321
+ this.topLevelModules.set(cmd, [moduleId, cached]);
4322
+ } else {
4323
+ if (!this.groupMap.has(group)) {
4324
+ this.groupMap.set(group, /* @__PURE__ */ new Map());
2314
4325
  }
4326
+ this.groupMap.get(group).set(cmd, [moduleId, cached]);
2315
4327
  }
2316
- this.groupMapBuilt = true;
2317
- } catch {
2318
- warn("Failed to build group map");
2319
4328
  }
4329
+ this.groupMapBuilt = true;
2320
4330
  }
2321
4331
  /**
2322
- * List all available command names: builtins + group names + top-level module names.
4332
+ * List all available command names: group names + top-level module names.
4333
+ *
4334
+ * FE-13: the built-in subcommand list is no longer folded in here — those
4335
+ * commands live under the `apcli` prefix and are registered directly by
4336
+ * `createCli`.
2323
4337
  */
2324
4338
  listCommands() {
2325
4339
  this.buildGroupMap();
2326
4340
  const groupNames = [...this.groupMap.keys()].filter(
2327
- (g) => !BUILTIN_COMMANDS.includes(g)
4341
+ (g) => !RESERVED_GROUP_NAMES.has(g)
2328
4342
  );
2329
4343
  const topNames = [...this.topLevelModules.keys()];
2330
- return [.../* @__PURE__ */ new Set([...BUILTIN_COMMANDS, ...groupNames, ...topNames])].sort();
4344
+ return [.../* @__PURE__ */ new Set([...groupNames, ...topNames])].sort();
2331
4345
  }
2332
4346
  /**
2333
4347
  * Get a command by name: check builtins -> group cache -> group map -> top-level modules.
@@ -2377,119 +4391,66 @@ var GroupedModuleGroup = class _GroupedModuleGroup extends LazyModuleGroup {
2377
4391
  }
2378
4392
  };
2379
4393
 
2380
- // src/discovery.ts
2381
- init_esm_shims();
2382
- init_errors();
2383
- import { Command as Command4 } from "commander";
2384
- var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
2385
- function validateTag(tag) {
2386
- if (!TAG_PATTERN.test(tag)) {
2387
- process.stderr.write(
2388
- `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
2389
- `
2390
- );
2391
- process.exit(EXIT_CODES.INVALID_CLI_INPUT);
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);
2401
- }
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);
2416
- });
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) {
2422
- process.stderr.write(
2423
- `Error: Module '${moduleId}' not found.
2424
- `
2425
- );
2426
- process.exit(EXIT_CODES.MODULE_NOT_FOUND);
2427
- }
2428
- const fmt = resolveFormat(opts.format);
2429
- formatModuleDetail(moduleDef, fmt);
2430
- });
2431
- cli.addCommand(describeCmd);
2432
- }
2433
-
2434
4394
  // src/index.ts
2435
4395
  init_errors();
4396
+ init_logger();
2436
4397
  init_security();
2437
4398
  export {
4399
+ ApcliGroup,
2438
4400
  ApprovalDeniedError,
2439
4401
  ApprovalTimeoutError,
2440
4402
  AuditLogger,
2441
4403
  AuthProvider,
2442
4404
  AuthenticationError,
2443
- BUILTIN_COMMANDS,
4405
+ CliApprovalHandler,
2444
4406
  ConfigDecryptionError,
2445
4407
  ConfigEncryptor,
2446
4408
  ConfigResolver,
2447
4409
  DEFAULTS,
2448
4410
  EXIT_CODES,
4411
+ ExposureFilter,
2449
4412
  GroupedModuleGroup,
2450
4413
  LazyGroup,
2451
4414
  LazyModuleGroup,
2452
4415
  ModuleExecutionError,
2453
4416
  ModuleNotFoundError,
4417
+ RESERVED_GROUP_NAMES,
2454
4418
  Sandbox,
2455
4419
  SchemaValidationError,
2456
4420
  applyToolkitIntegration,
2457
4421
  buildModuleCommand,
2458
- buildProgramManPage,
2459
4422
  checkApproval,
2460
4423
  collectInput,
2461
4424
  configureManHelp,
2462
4425
  createCli,
2463
- debug,
2464
- docsUrl,
2465
- error,
4426
+ emitErrorJson,
4427
+ emitErrorTty,
2466
4428
  exitCodeForError,
2467
- extractHelp,
2468
4429
  formatExecResult,
2469
- formatModuleDetail,
2470
- formatModuleList,
2471
4430
  getAuditLogger,
2472
- getCliDisplayFields,
2473
- getDisplay,
2474
4431
  getLogLevel,
2475
- info,
2476
4432
  main,
2477
- mapType,
2478
4433
  reconvertEnumValues,
4434
+ registerCompletionCommand,
4435
+ registerConfigCommand,
2479
4436
  registerConfigNamespace,
2480
- registerDiscoveryCommands,
4437
+ registerDescribeCommand,
4438
+ registerDisableCommand,
4439
+ registerEnableCommand,
4440
+ registerExecCommand,
4441
+ registerHealthCommand,
2481
4442
  registerInitCommand,
2482
- registerShellCommands,
2483
- resolveFormat,
4443
+ registerListCommand,
4444
+ registerPipelineCommand,
4445
+ registerReloadCommand,
4446
+ registerUsageCommand,
4447
+ registerValidateCommand,
2484
4448
  resolveRefs,
2485
4449
  schemaToCliOptions,
2486
4450
  setAuditLogger,
2487
4451
  setDocsUrl,
2488
4452
  setLogLevel,
2489
4453
  setVerboseHelp,
2490
- truncate,
2491
- validateModuleId,
2492
- verboseHelp,
2493
- warn
4454
+ validateModuleId
2494
4455
  };
2495
4456
  //# sourceMappingURL=index.js.map