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