apcore-cli 0.2.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.
@@ -0,0 +1,1755 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
12
+ import path from "path";
13
+ import { fileURLToPath } from "url";
14
+ var init_esm_shims = __esm({
15
+ "node_modules/.pnpm/tsup@8.5.1_postcss@8.5.8_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
16
+ "use strict";
17
+ }
18
+ });
19
+
20
+ // src/errors.ts
21
+ function exitCodeForError(error2) {
22
+ if (error2 instanceof ApprovalTimeoutError) {
23
+ return EXIT_CODES.APPROVAL_TIMEOUT;
24
+ }
25
+ if (error2 instanceof ApprovalDeniedError) {
26
+ return EXIT_CODES.APPROVAL_DENIED;
27
+ }
28
+ if (error2 instanceof AuthenticationError) {
29
+ return EXIT_CODES.ACL_DENIED;
30
+ }
31
+ if (error2 instanceof ConfigDecryptionError) {
32
+ return EXIT_CODES.CONFIG_INVALID;
33
+ }
34
+ if (error2 instanceof SchemaValidationError) {
35
+ return EXIT_CODES.SCHEMA_VALIDATION_ERROR;
36
+ }
37
+ if (error2 instanceof ModuleNotFoundError) {
38
+ return EXIT_CODES.MODULE_NOT_FOUND;
39
+ }
40
+ if (error2 instanceof ModuleExecutionError) {
41
+ return EXIT_CODES.MODULE_EXECUTE_ERROR;
42
+ }
43
+ if (error2 instanceof Error) {
44
+ const code = error2.code;
45
+ const codeMap = {
46
+ MODULE_NOT_FOUND: EXIT_CODES.MODULE_NOT_FOUND,
47
+ MODULE_LOAD_ERROR: EXIT_CODES.MODULE_LOAD_ERROR,
48
+ MODULE_DISABLED: EXIT_CODES.MODULE_DISABLED,
49
+ SCHEMA_VALIDATION_ERROR: EXIT_CODES.SCHEMA_VALIDATION_ERROR,
50
+ SCHEMA_CIRCULAR_REF: EXIT_CODES.SCHEMA_CIRCULAR_REF,
51
+ APPROVAL_DENIED: EXIT_CODES.APPROVAL_DENIED,
52
+ APPROVAL_TIMEOUT: EXIT_CODES.APPROVAL_TIMEOUT,
53
+ CONFIG_NOT_FOUND: EXIT_CODES.CONFIG_NOT_FOUND,
54
+ CONFIG_INVALID: EXIT_CODES.CONFIG_INVALID,
55
+ MODULE_EXECUTE_ERROR: EXIT_CODES.MODULE_EXECUTE_ERROR,
56
+ MODULE_TIMEOUT: EXIT_CODES.MODULE_TIMEOUT,
57
+ ACL_DENIED: EXIT_CODES.ACL_DENIED
58
+ };
59
+ if (code && code in codeMap) {
60
+ return codeMap[code];
61
+ }
62
+ }
63
+ return EXIT_CODES.MODULE_EXECUTE_ERROR;
64
+ }
65
+ var ApprovalTimeoutError, AuthenticationError, ConfigDecryptionError, ModuleExecutionError, ApprovalDeniedError, SchemaValidationError, ModuleNotFoundError, EXIT_CODES;
66
+ var init_errors = __esm({
67
+ "src/errors.ts"() {
68
+ "use strict";
69
+ init_esm_shims();
70
+ ApprovalTimeoutError = class extends Error {
71
+ constructor(message = "Approval timed out") {
72
+ super(message);
73
+ this.name = "ApprovalTimeoutError";
74
+ }
75
+ };
76
+ AuthenticationError = class extends Error {
77
+ constructor(message = "Authentication failed") {
78
+ super(message);
79
+ this.name = "AuthenticationError";
80
+ }
81
+ };
82
+ ConfigDecryptionError = class extends Error {
83
+ constructor(message = "Config decryption failed") {
84
+ super(message);
85
+ this.name = "ConfigDecryptionError";
86
+ }
87
+ };
88
+ ModuleExecutionError = class extends Error {
89
+ constructor(message = "Module execution failed") {
90
+ super(message);
91
+ this.name = "ModuleExecutionError";
92
+ }
93
+ };
94
+ ApprovalDeniedError = class extends Error {
95
+ constructor(message = "Approval denied") {
96
+ super(message);
97
+ this.name = "ApprovalDeniedError";
98
+ }
99
+ };
100
+ SchemaValidationError = class extends Error {
101
+ constructor(message = "Schema validation failed") {
102
+ super(message);
103
+ this.name = "SchemaValidationError";
104
+ }
105
+ };
106
+ ModuleNotFoundError = class extends Error {
107
+ constructor(message = "Module not found") {
108
+ super(message);
109
+ this.name = "ModuleNotFoundError";
110
+ }
111
+ };
112
+ EXIT_CODES = {
113
+ SUCCESS: 0,
114
+ MODULE_EXECUTE_ERROR: 1,
115
+ MODULE_TIMEOUT: 1,
116
+ INVALID_CLI_INPUT: 2,
117
+ MODULE_NOT_FOUND: 44,
118
+ MODULE_LOAD_ERROR: 44,
119
+ MODULE_DISABLED: 44,
120
+ SCHEMA_VALIDATION_ERROR: 45,
121
+ APPROVAL_DENIED: 46,
122
+ APPROVAL_TIMEOUT: 46,
123
+ CONFIG_NOT_FOUND: 47,
124
+ CONFIG_INVALID: 47,
125
+ SCHEMA_CIRCULAR_REF: 48,
126
+ ACL_DENIED: 77,
127
+ KEYBOARD_INTERRUPT: 130
128
+ };
129
+ }
130
+ });
131
+
132
+ // src/security/audit.ts
133
+ var audit_exports = {};
134
+ __export(audit_exports, {
135
+ AuditLogger: () => AuditLogger,
136
+ getAuditLogger: () => getAuditLogger,
137
+ setAuditLogger: () => setAuditLogger
138
+ });
139
+ import * as crypto from "crypto";
140
+ import * as fs from "fs";
141
+ import * as os from "os";
142
+ import * as path2 from "path";
143
+ function setAuditLogger(auditLogger) {
144
+ _auditLogger = auditLogger;
145
+ }
146
+ function getAuditLogger() {
147
+ return _auditLogger;
148
+ }
149
+ var _auditLogger, AuditLogger;
150
+ var init_audit = __esm({
151
+ "src/security/audit.ts"() {
152
+ "use strict";
153
+ init_esm_shims();
154
+ _auditLogger = null;
155
+ AuditLogger = class _AuditLogger {
156
+ static DEFAULT_PATH = path2.join(
157
+ os.homedir(),
158
+ ".apcore-cli",
159
+ "audit.jsonl"
160
+ );
161
+ logPath;
162
+ constructor(path6) {
163
+ this.logPath = path6 ?? _AuditLogger.DEFAULT_PATH;
164
+ this.ensureDirectory();
165
+ }
166
+ ensureDirectory() {
167
+ try {
168
+ fs.mkdirSync(path2.dirname(this.logPath), { recursive: true });
169
+ } catch {
170
+ }
171
+ }
172
+ logExecution(moduleId, inputData, status, exitCode, durationMs) {
173
+ const entry = {
174
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
175
+ user: this.getUser(),
176
+ module_id: moduleId,
177
+ input_hash: this.hashInput(inputData),
178
+ status,
179
+ exit_code: exitCode,
180
+ duration_ms: durationMs
181
+ };
182
+ try {
183
+ fs.appendFileSync(this.logPath, JSON.stringify(entry) + "\n");
184
+ } catch (err) {
185
+ console.warn(`Could not write audit log: ${err}`);
186
+ }
187
+ }
188
+ hashInput(inputData) {
189
+ const salt = crypto.randomBytes(16);
190
+ const sortedKeys = Object.keys(inputData).sort();
191
+ const payload = JSON.stringify(inputData, sortedKeys);
192
+ return crypto.createHash("sha256").update(Buffer.concat([salt, Buffer.from(payload, "utf-8")])).digest("hex");
193
+ }
194
+ getUser() {
195
+ try {
196
+ return os.userInfo().username;
197
+ } catch {
198
+ return process.env.USER ?? process.env.USERNAME ?? "unknown";
199
+ }
200
+ }
201
+ };
202
+ }
203
+ });
204
+
205
+ // src/security/config-encryptor.ts
206
+ import * as crypto2 from "crypto";
207
+ import * as os2 from "os";
208
+ async function getKeytar() {
209
+ if (keytarModule) return keytarModule;
210
+ try {
211
+ keytarModule = await import("keytar");
212
+ return keytarModule;
213
+ } catch {
214
+ return null;
215
+ }
216
+ }
217
+ var keytarModule, ConfigEncryptor;
218
+ var init_config_encryptor = __esm({
219
+ "src/security/config-encryptor.ts"() {
220
+ "use strict";
221
+ init_esm_shims();
222
+ init_errors();
223
+ keytarModule = null;
224
+ ConfigEncryptor = class _ConfigEncryptor {
225
+ static SERVICE_NAME = "apcore-cli";
226
+ /**
227
+ * Encrypt and store a configuration value.
228
+ */
229
+ async store(key, value) {
230
+ const keytar = await getKeytar();
231
+ if (keytar) {
232
+ try {
233
+ await keytar.setPassword(_ConfigEncryptor.SERVICE_NAME, key, value);
234
+ return `keyring:${key}`;
235
+ } catch {
236
+ }
237
+ }
238
+ console.warn("OS keyring unavailable. Using file-based encryption.");
239
+ const ciphertext = this.aesEncrypt(value);
240
+ return `enc:${Buffer.from(ciphertext).toString("base64")}`;
241
+ }
242
+ /**
243
+ * Retrieve and decrypt a configuration value.
244
+ */
245
+ async retrieve(configValue, key) {
246
+ if (configValue.startsWith("keyring:")) {
247
+ const keytar = await getKeytar();
248
+ if (!keytar) {
249
+ throw new ConfigDecryptionError(
250
+ `Keyring module not available to retrieve '${key}'.`
251
+ );
252
+ }
253
+ try {
254
+ const refKey = configValue.slice("keyring:".length);
255
+ const result = await keytar.getPassword(
256
+ _ConfigEncryptor.SERVICE_NAME,
257
+ refKey
258
+ );
259
+ if (result === null || result === void 0) {
260
+ throw new ConfigDecryptionError(
261
+ `Keyring entry not found for '${refKey}'.`
262
+ );
263
+ }
264
+ return result;
265
+ } catch (err) {
266
+ if (err instanceof ConfigDecryptionError) throw err;
267
+ throw new ConfigDecryptionError(
268
+ `Failed to retrieve from keyring: ${err}`
269
+ );
270
+ }
271
+ }
272
+ if (configValue.startsWith("enc:")) {
273
+ const ciphertext = Buffer.from(
274
+ configValue.slice("enc:".length),
275
+ "base64"
276
+ );
277
+ try {
278
+ return this.aesDecrypt(ciphertext);
279
+ } catch {
280
+ throw new ConfigDecryptionError(
281
+ `Failed to decrypt configuration value '${key}'. Re-configure with 'apcore-cli config set ${key}'.`
282
+ );
283
+ }
284
+ }
285
+ return configValue;
286
+ }
287
+ // NOTE: Best-effort fallback when OS keyring is unavailable.
288
+ // The key is derived from hostname + username (non-secret inputs).
289
+ // For production security, ensure the OS keyring is accessible.
290
+ deriveKey() {
291
+ const hostname2 = os2.hostname();
292
+ const username = process.env.USER ?? process.env.USERNAME ?? "unknown";
293
+ const salt = Buffer.from("apcore-cli-config-v1");
294
+ const material = `${hostname2}:${username}`;
295
+ return crypto2.pbkdf2Sync(material, salt, 1e5, 32, "sha256");
296
+ }
297
+ aesEncrypt(plaintext) {
298
+ const key = this.deriveKey();
299
+ const nonce = crypto2.randomBytes(12);
300
+ const cipher = crypto2.createCipheriv("aes-256-gcm", key, nonce);
301
+ const ct = Buffer.concat([
302
+ cipher.update(plaintext, "utf-8"),
303
+ cipher.final()
304
+ ]);
305
+ const tag = cipher.getAuthTag();
306
+ return Buffer.concat([nonce, tag, ct]);
307
+ }
308
+ aesDecrypt(data) {
309
+ const key = this.deriveKey();
310
+ const nonce = data.subarray(0, 12);
311
+ const tag = data.subarray(12, 28);
312
+ const ct = data.subarray(28);
313
+ const decipher = crypto2.createDecipheriv("aes-256-gcm", key, nonce);
314
+ decipher.setAuthTag(tag);
315
+ const plaintext = Buffer.concat([decipher.update(ct), decipher.final()]);
316
+ return plaintext.toString("utf-8");
317
+ }
318
+ };
319
+ }
320
+ });
321
+
322
+ // src/security/auth.ts
323
+ var AuthProvider;
324
+ var init_auth = __esm({
325
+ "src/security/auth.ts"() {
326
+ "use strict";
327
+ init_esm_shims();
328
+ init_errors();
329
+ init_config_encryptor();
330
+ AuthProvider = class {
331
+ config;
332
+ encryptor;
333
+ constructor(config, encryptor) {
334
+ this.config = config;
335
+ this.encryptor = encryptor ?? new ConfigEncryptor();
336
+ }
337
+ /**
338
+ * Retrieve the API key from the configured sources.
339
+ * Handles keyring: and enc: prefixes via ConfigEncryptor.
340
+ */
341
+ async getApiKey() {
342
+ const result = this.config.resolve(
343
+ "auth.api_key",
344
+ "--api-key",
345
+ "APCORE_AUTH_API_KEY"
346
+ );
347
+ if (result === null || result === void 0) {
348
+ return null;
349
+ }
350
+ const strResult = String(result);
351
+ if (strResult.startsWith("keyring:") || strResult.startsWith("enc:")) {
352
+ return this.encryptor.retrieve(strResult, "auth.api_key");
353
+ }
354
+ return strResult;
355
+ }
356
+ /**
357
+ * Add authentication headers to an outgoing request.
358
+ */
359
+ async authenticateRequest(headers) {
360
+ const key = await this.getApiKey();
361
+ if (!key) {
362
+ throw new AuthenticationError(
363
+ "Remote registry requires authentication. Set --api-key, APCORE_AUTH_API_KEY, or auth.api_key in config."
364
+ );
365
+ }
366
+ return { ...headers, Authorization: `Bearer ${key}` };
367
+ }
368
+ /**
369
+ * Handle an HTTP response status code for auth-related errors.
370
+ */
371
+ handleResponse(statusCode) {
372
+ if (statusCode === 401 || statusCode === 403) {
373
+ throw new AuthenticationError(
374
+ "Authentication failed. Verify your API key."
375
+ );
376
+ }
377
+ }
378
+ };
379
+ }
380
+ });
381
+
382
+ // src/security/sandbox.ts
383
+ import * as child_process from "child_process";
384
+ import * as fs2 from "fs";
385
+ import * as os3 from "os";
386
+ import * as path3 from "path";
387
+ var Sandbox;
388
+ var init_sandbox = __esm({
389
+ "src/security/sandbox.ts"() {
390
+ "use strict";
391
+ init_esm_shims();
392
+ init_errors();
393
+ Sandbox = class {
394
+ enabled;
395
+ constructor(enabled = false) {
396
+ this.enabled = enabled;
397
+ }
398
+ /**
399
+ * Execute a module, optionally inside a sandboxed subprocess.
400
+ */
401
+ async execute(moduleId, inputData, executor) {
402
+ if (!this.enabled) {
403
+ return executor.execute(moduleId, inputData);
404
+ }
405
+ return this.sandboxedExecute(moduleId, inputData);
406
+ }
407
+ sandboxedExecute(moduleId, inputData) {
408
+ const env = {};
409
+ for (const key of ["PATH", "NODE_PATH", "LANG", "LC_ALL"]) {
410
+ if (process.env[key]) {
411
+ env[key] = process.env[key];
412
+ }
413
+ }
414
+ for (const [key, value] of Object.entries(process.env)) {
415
+ if (key.startsWith("APCORE_") && value) {
416
+ env[key] = value;
417
+ }
418
+ }
419
+ const tmpDir = fs2.mkdtempSync(
420
+ path3.join(os3.tmpdir(), "apcore_sandbox_")
421
+ );
422
+ try {
423
+ env.HOME = tmpDir;
424
+ env.TMPDIR = tmpDir;
425
+ const script = [
426
+ "let d='';",
427
+ "process.stdin.setEncoding('utf-8');",
428
+ "process.stdin.on('data',c=>d+=c);",
429
+ "process.stdin.on('end',()=>{",
430
+ " const input=JSON.parse(d);",
431
+ ` process.stdout.write(JSON.stringify({error:"Sandbox runner not yet implemented for module: ${moduleId}"}));`,
432
+ "});"
433
+ ].join("");
434
+ const result = child_process.execFileSync(
435
+ process.execPath,
436
+ ["-e", script],
437
+ {
438
+ input: JSON.stringify(inputData),
439
+ env,
440
+ cwd: tmpDir,
441
+ timeout: 3e5,
442
+ maxBuffer: 10 * 1024 * 1024
443
+ }
444
+ );
445
+ return JSON.parse(result.toString("utf-8"));
446
+ } catch (err) {
447
+ if (err instanceof Error && "killed" in err && err.killed) {
448
+ throw new ModuleExecutionError(
449
+ `Error: Module '${moduleId}' timed out in sandbox.`
450
+ );
451
+ }
452
+ const stderr = err instanceof Error && "stderr" in err ? String(err.stderr) : String(err);
453
+ throw new ModuleExecutionError(
454
+ `Error: Module '${moduleId}' execution failed: ${stderr}`
455
+ );
456
+ } finally {
457
+ try {
458
+ fs2.rmSync(tmpDir, { recursive: true, force: true });
459
+ } catch {
460
+ }
461
+ }
462
+ }
463
+ };
464
+ }
465
+ });
466
+
467
+ // src/security/index.ts
468
+ var security_exports = {};
469
+ __export(security_exports, {
470
+ AuditLogger: () => AuditLogger,
471
+ AuthProvider: () => AuthProvider,
472
+ ConfigEncryptor: () => ConfigEncryptor,
473
+ Sandbox: () => Sandbox,
474
+ getAuditLogger: () => getAuditLogger,
475
+ setAuditLogger: () => setAuditLogger
476
+ });
477
+ var init_security = __esm({
478
+ "src/security/index.ts"() {
479
+ "use strict";
480
+ init_esm_shims();
481
+ init_audit();
482
+ init_auth();
483
+ init_config_encryptor();
484
+ init_sandbox();
485
+ }
486
+ });
487
+
488
+ // src/index.ts
489
+ init_esm_shims();
490
+
491
+ // src/main.ts
492
+ init_esm_shims();
493
+ init_errors();
494
+ import { readFileSync } from "fs";
495
+ import { fileURLToPath as fileURLToPath2 } from "url";
496
+ import * as path4 from "path";
497
+ import { Command, CommanderError } from "commander";
498
+
499
+ // src/ref-resolver.ts
500
+ init_esm_shims();
501
+ init_errors();
502
+ function resolveRefs(schema, maxDepth = 32, moduleId = "") {
503
+ const cloned = structuredClone(schema);
504
+ const defs = cloned.$defs ?? cloned.definitions ?? {};
505
+ const result = resolveNode(
506
+ cloned,
507
+ defs,
508
+ /* @__PURE__ */ new Set(),
509
+ 0,
510
+ maxDepth,
511
+ moduleId
512
+ );
513
+ delete result.$defs;
514
+ delete result.definitions;
515
+ return result;
516
+ }
517
+ function resolveNode(node, defs, visited, depth, maxDepth, moduleId) {
518
+ if (typeof node !== "object" || node === null || Array.isArray(node)) {
519
+ return node;
520
+ }
521
+ const obj = node;
522
+ if ("$ref" in obj) {
523
+ const refPath = obj.$ref;
524
+ if (depth >= maxDepth) {
525
+ process.stderr.write(
526
+ `Error: $ref resolution depth exceeded maximum of ${maxDepth} for module '${moduleId}'.
527
+ `
528
+ );
529
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
530
+ }
531
+ if (visited.has(refPath)) {
532
+ process.stderr.write(
533
+ `Error: Circular $ref detected in schema for module '${moduleId}' at path '${refPath}'.
534
+ `
535
+ );
536
+ process.exit(EXIT_CODES.SCHEMA_CIRCULAR_REF);
537
+ }
538
+ const parts = refPath.split("/");
539
+ const key = parts[parts.length - 1];
540
+ if (!(key in defs)) {
541
+ process.stderr.write(
542
+ `Error: Unresolvable $ref '${refPath}' in schema for module '${moduleId}'.
543
+ `
544
+ );
545
+ process.exit(EXIT_CODES.SCHEMA_VALIDATION_ERROR);
546
+ }
547
+ const newVisited = new Set(visited);
548
+ newVisited.add(refPath);
549
+ return resolveNode(defs[key], defs, newVisited, depth + 1, maxDepth, moduleId);
550
+ }
551
+ if ("allOf" in obj && Array.isArray(obj.allOf)) {
552
+ const merged = {
553
+ properties: {},
554
+ required: []
555
+ };
556
+ for (const subSchema of obj.allOf) {
557
+ const resolved = resolveNode(
558
+ subSchema,
559
+ defs,
560
+ visited,
561
+ depth + 1,
562
+ maxDepth,
563
+ moduleId
564
+ );
565
+ if (resolved.properties) {
566
+ Object.assign(
567
+ merged.properties,
568
+ resolved.properties
569
+ );
570
+ }
571
+ if (Array.isArray(resolved.required)) {
572
+ merged.required.push(...resolved.required);
573
+ }
574
+ }
575
+ merged.required = [...new Set(merged.required)];
576
+ for (const [k, v] of Object.entries(obj)) {
577
+ if (k !== "allOf" && !(k in merged)) {
578
+ merged[k] = v;
579
+ }
580
+ }
581
+ return merged;
582
+ }
583
+ for (const keyword of ["anyOf", "oneOf"]) {
584
+ if (keyword in obj && Array.isArray(obj[keyword])) {
585
+ const merged = {
586
+ properties: {},
587
+ required: []
588
+ };
589
+ const allRequiredSets = [];
590
+ for (const subSchema of obj[keyword]) {
591
+ const resolved = resolveNode(
592
+ subSchema,
593
+ defs,
594
+ visited,
595
+ depth + 1,
596
+ maxDepth,
597
+ moduleId
598
+ );
599
+ if (resolved.properties) {
600
+ Object.assign(
601
+ merged.properties,
602
+ resolved.properties
603
+ );
604
+ }
605
+ if (Array.isArray(resolved.required)) {
606
+ allRequiredSets.push(new Set(resolved.required));
607
+ }
608
+ }
609
+ if (allRequiredSets.length > 0) {
610
+ let intersection = allRequiredSets[0];
611
+ for (let i = 1; i < allRequiredSets.length; i++) {
612
+ intersection = new Set(
613
+ [...intersection].filter((x) => allRequiredSets[i].has(x))
614
+ );
615
+ }
616
+ merged.required = [...intersection];
617
+ } else {
618
+ merged.required = [];
619
+ }
620
+ for (const [k, v] of Object.entries(obj)) {
621
+ if (k !== keyword && !(k in merged)) {
622
+ merged[k] = v;
623
+ }
624
+ }
625
+ return merged;
626
+ }
627
+ }
628
+ if ("properties" in obj && typeof obj.properties === "object" && obj.properties !== null) {
629
+ const props = obj.properties;
630
+ for (const [propName, propSchema] of Object.entries(props)) {
631
+ props[propName] = resolveNode(
632
+ propSchema,
633
+ defs,
634
+ visited,
635
+ depth + 1,
636
+ maxDepth,
637
+ moduleId
638
+ );
639
+ }
640
+ }
641
+ return obj;
642
+ }
643
+
644
+ // src/schema-parser.ts
645
+ init_esm_shims();
646
+ init_errors();
647
+ var BOOLEAN_FLAG = /* @__PURE__ */ Symbol("BOOLEAN_FLAG");
648
+ function mapType(propName, propSchema) {
649
+ const schemaType = propSchema.type;
650
+ if (schemaType === "string" && (propName.endsWith("_file") || propSchema["x-cli-file"] === true)) {
651
+ return "file";
652
+ }
653
+ const typeMap = {
654
+ string: "string",
655
+ integer: "int",
656
+ number: "float",
657
+ boolean: BOOLEAN_FLAG,
658
+ object: "string",
659
+ array: "string"
660
+ };
661
+ if (!schemaType) {
662
+ return "string";
663
+ }
664
+ return typeMap[schemaType] ?? "string";
665
+ }
666
+ function extractHelp(propSchema) {
667
+ let text = propSchema["x-llm-description"];
668
+ if (!text) {
669
+ text = propSchema.description;
670
+ }
671
+ if (!text) {
672
+ return void 0;
673
+ }
674
+ if (text.length > 200) {
675
+ return text.slice(0, 197) + "...";
676
+ }
677
+ return text;
678
+ }
679
+ var RESERVED_NAMES = /* @__PURE__ */ new Set(["input", "yes", "large_input", "format", "sandbox"]);
680
+ function schemaToCliOptions(schema) {
681
+ const properties = schema.properties ?? {};
682
+ const requiredList = schema.required ?? [];
683
+ const options = [];
684
+ const flagNames = {};
685
+ for (const [propName, propSchema] of Object.entries(properties)) {
686
+ const flagName = "--" + propName.replace(/_/g, "-");
687
+ if (flagName in flagNames) {
688
+ process.stderr.write(
689
+ `Error: Flag name collision: properties '${propName}' and '${flagNames[flagName]}' both map to '${flagName}'.
690
+ `
691
+ );
692
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
693
+ }
694
+ flagNames[flagName] = propName;
695
+ if (RESERVED_NAMES.has(propName)) {
696
+ process.stderr.write(
697
+ `Error: Module schema property '${propName}' conflicts with a reserved CLI option name. Rename the property.
698
+ `
699
+ );
700
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
701
+ }
702
+ const typeResult = mapType(propName, propSchema);
703
+ const isRequired = requiredList.includes(propName);
704
+ const helpBase = extractHelp(propSchema);
705
+ const helpText = isRequired ? (helpBase ? helpBase + " " : "") + "[required]" : helpBase ?? "";
706
+ const defaultValue = propSchema.default;
707
+ if (typeResult === BOOLEAN_FLAG) {
708
+ const flagBase = propName.replace(/_/g, "-");
709
+ const defaultVal = propSchema.default ?? false;
710
+ options.push({
711
+ name: propName,
712
+ flags: `--${flagBase}, --no-${flagBase}`,
713
+ description: helpText,
714
+ defaultValue: defaultVal,
715
+ required: false,
716
+ isBooleanFlag: true
717
+ });
718
+ } else if ("enum" in propSchema && Array.isArray(propSchema.enum)) {
719
+ const enumValues = propSchema.enum;
720
+ if (enumValues.length === 0) {
721
+ options.push({
722
+ name: propName,
723
+ flags: `${flagName} <value>`,
724
+ description: helpText,
725
+ defaultValue,
726
+ required: false
727
+ });
728
+ } else {
729
+ const stringValues = enumValues.map(String);
730
+ const enumOriginalTypes = {};
731
+ for (const v of enumValues) {
732
+ if (typeof v === "number" && Number.isInteger(v)) {
733
+ enumOriginalTypes[String(v)] = "int";
734
+ } else if (typeof v === "number") {
735
+ enumOriginalTypes[String(v)] = "float";
736
+ } else if (typeof v === "boolean") {
737
+ enumOriginalTypes[String(v)] = "bool";
738
+ }
739
+ }
740
+ options.push({
741
+ name: propName,
742
+ flags: `${flagName} <value>`,
743
+ description: helpText,
744
+ defaultValue: defaultValue !== void 0 ? String(defaultValue) : void 0,
745
+ required: false,
746
+ choices: stringValues,
747
+ enumOriginalTypes: Object.keys(enumOriginalTypes).length > 0 ? enumOriginalTypes : void 0
748
+ });
749
+ }
750
+ } else {
751
+ let parseArg;
752
+ if (typeResult === "int") {
753
+ parseArg = (v) => {
754
+ const n = parseInt(v, 10);
755
+ if (isNaN(n)) throw new Error(`Invalid integer: ${v}`);
756
+ return n;
757
+ };
758
+ } else if (typeResult === "float") {
759
+ parseArg = (v) => {
760
+ const n = parseFloat(v);
761
+ if (isNaN(n)) throw new Error(`Invalid number: ${v}`);
762
+ return n;
763
+ };
764
+ }
765
+ options.push({
766
+ name: propName,
767
+ flags: `${flagName} <value>`,
768
+ description: helpText,
769
+ defaultValue,
770
+ required: false,
771
+ parseArg
772
+ });
773
+ }
774
+ }
775
+ return options;
776
+ }
777
+
778
+ // src/approval.ts
779
+ init_esm_shims();
780
+ init_errors();
781
+ import * as readline from "readline";
782
+ function getAnnotation(annotations, key, defaultValue = void 0) {
783
+ if (!annotations || typeof annotations !== "object") return defaultValue;
784
+ const ann = annotations;
785
+ return key in ann ? ann[key] : defaultValue;
786
+ }
787
+ async function checkApproval(moduleDef, autoApprove) {
788
+ const annotations = moduleDef.annotations;
789
+ let requiresApproval;
790
+ if (moduleDef.requiresApproval !== void 0) {
791
+ requiresApproval = moduleDef.requiresApproval;
792
+ } else if (annotations) {
793
+ requiresApproval = getAnnotation(annotations, "requires_approval", false) === true;
794
+ } else {
795
+ return;
796
+ }
797
+ if (!requiresApproval) {
798
+ return;
799
+ }
800
+ const moduleId = moduleDef.id;
801
+ if (autoApprove) {
802
+ return;
803
+ }
804
+ const envVal = process.env.APCORE_CLI_AUTO_APPROVE ?? "";
805
+ if (envVal === "1") {
806
+ return;
807
+ }
808
+ if (envVal !== "" && envVal !== "1") {
809
+ process.stderr.write(
810
+ `Warning: APCORE_CLI_AUTO_APPROVE is set to '${envVal}', expected '1'. Ignoring.
811
+ `
812
+ );
813
+ }
814
+ if (!process.stdin.isTTY) {
815
+ process.stderr.write(
816
+ `Error: Module '${moduleId}' requires approval but no interactive terminal is available. Use --yes or set APCORE_CLI_AUTO_APPROVE=1 to bypass.
817
+ `
818
+ );
819
+ process.exit(EXIT_CODES.APPROVAL_DENIED);
820
+ }
821
+ await promptWithTimeout(moduleDef, 60);
822
+ }
823
+ async function promptWithTimeout(moduleDef, timeout) {
824
+ timeout = Math.max(1, Math.min(timeout, 3600));
825
+ const moduleId = moduleDef.id;
826
+ const annotations = moduleDef.annotations;
827
+ const message = (annotations ? getAnnotation(annotations, "approval_message") : void 0) ?? `Module '${moduleId}' requires approval to execute.`;
828
+ process.stderr.write(message + "\n");
829
+ const rl = readline.createInterface({
830
+ input: process.stdin,
831
+ output: process.stderr
832
+ });
833
+ let timer;
834
+ try {
835
+ const answer = await Promise.race([
836
+ new Promise((resolve3) => {
837
+ rl.question("Proceed? [y/N] ", (ans) => resolve3(ans));
838
+ }),
839
+ new Promise((_, reject) => {
840
+ timer = setTimeout(() => {
841
+ reject(new ApprovalTimeoutError(
842
+ `Approval prompt timed out after ${timeout} seconds.`
843
+ ));
844
+ }, timeout * 1e3);
845
+ })
846
+ ]);
847
+ if (timer) clearTimeout(timer);
848
+ const normalized = answer.trim().toLowerCase();
849
+ if (normalized === "y" || normalized === "yes") {
850
+ return;
851
+ }
852
+ process.stderr.write("Error: Approval denied.\n");
853
+ process.exit(EXIT_CODES.APPROVAL_DENIED);
854
+ } catch (err) {
855
+ if (timer) clearTimeout(timer);
856
+ if (err instanceof ApprovalTimeoutError) {
857
+ process.stderr.write(
858
+ `Error: Approval prompt timed out after ${timeout} seconds.
859
+ `
860
+ );
861
+ process.exit(EXIT_CODES.APPROVAL_TIMEOUT);
862
+ }
863
+ throw err;
864
+ } finally {
865
+ rl.close();
866
+ }
867
+ }
868
+
869
+ // src/output.ts
870
+ init_esm_shims();
871
+ function resolveFormat(explicitFormat) {
872
+ if (explicitFormat !== void 0) {
873
+ return explicitFormat;
874
+ }
875
+ return process.stdout.isTTY ? "table" : "json";
876
+ }
877
+ function truncate(text, maxLength = 80) {
878
+ if (text.length <= maxLength) {
879
+ return text;
880
+ }
881
+ return text.slice(0, maxLength - 3) + "...";
882
+ }
883
+ function formatTable(headers, rows) {
884
+ const colWidths = headers.map(
885
+ (h, i) => Math.max(h.length, ...rows.map((r) => (r[i] ?? "").length))
886
+ );
887
+ const sep = colWidths.map((w) => "-".repeat(w)).join(" ");
888
+ const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(" ");
889
+ const dataLines = rows.map(
890
+ (row) => row.map((cell, i) => (cell ?? "").padEnd(colWidths[i])).join(" ")
891
+ );
892
+ return [headerLine, sep, ...dataLines].join("\n") + "\n";
893
+ }
894
+ function formatModuleList(modules, format, filterTags) {
895
+ if (format === "table") {
896
+ if (modules.length === 0 && filterTags && filterTags.length > 0) {
897
+ process.stdout.write(
898
+ `No modules found matching tags: ${filterTags.join(", ")}.
899
+ `
900
+ );
901
+ return;
902
+ }
903
+ if (modules.length === 0) {
904
+ process.stdout.write("No modules found.\n");
905
+ return;
906
+ }
907
+ const headers = ["ID", "Description", "Tags"];
908
+ const rows = modules.map((m) => [
909
+ m.id,
910
+ truncate(m.description, 80),
911
+ (m.tags ?? []).join(", ")
912
+ ]);
913
+ process.stdout.write(formatTable(headers, rows));
914
+ } else if (format === "json") {
915
+ const result = modules.map((m) => ({
916
+ id: m.id,
917
+ description: m.description,
918
+ tags: m.tags ?? []
919
+ }));
920
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
921
+ }
922
+ }
923
+ function annotationsToDict(annotations) {
924
+ if (!annotations) return null;
925
+ if (typeof annotations !== "object" || Array.isArray(annotations)) return null;
926
+ const result = {};
927
+ for (const [k, v] of Object.entries(annotations)) {
928
+ if (v !== null && v !== void 0 && v !== false && v !== 0 && !(Array.isArray(v) && v.length === 0)) {
929
+ result[k] = v;
930
+ }
931
+ }
932
+ return Object.keys(result).length > 0 ? result : null;
933
+ }
934
+ function formatModuleDetail(moduleDef, format) {
935
+ if (format === "table") {
936
+ process.stdout.write(`
937
+ Module: ${moduleDef.id}
938
+ `);
939
+ process.stdout.write(`
940
+ Description:
941
+ ${moduleDef.description}
942
+ `);
943
+ if (moduleDef.inputSchema && Object.keys(moduleDef.inputSchema).length > 0) {
944
+ process.stdout.write("\nInput Schema:\n");
945
+ process.stdout.write(JSON.stringify(moduleDef.inputSchema, null, 2) + "\n");
946
+ }
947
+ if (moduleDef.outputSchema && Object.keys(moduleDef.outputSchema).length > 0) {
948
+ process.stdout.write("\nOutput Schema:\n");
949
+ process.stdout.write(JSON.stringify(moduleDef.outputSchema, null, 2) + "\n");
950
+ }
951
+ const annDict = annotationsToDict(
952
+ moduleDef.annotations
953
+ );
954
+ if (annDict) {
955
+ process.stdout.write("\nAnnotations:\n");
956
+ for (const [k, v] of Object.entries(annDict)) {
957
+ process.stdout.write(` ${k}: ${v}
958
+ `);
959
+ }
960
+ }
961
+ const metadata = moduleDef.metadata;
962
+ if (metadata) {
963
+ const xFields = {};
964
+ for (const [k, v] of Object.entries(metadata)) {
965
+ if (k.startsWith("x-") || k.startsWith("x_")) {
966
+ xFields[k] = v;
967
+ }
968
+ }
969
+ if (Object.keys(xFields).length > 0) {
970
+ process.stdout.write("\nExtension Metadata:\n");
971
+ for (const [k, v] of Object.entries(xFields)) {
972
+ process.stdout.write(` ${k}: ${v}
973
+ `);
974
+ }
975
+ }
976
+ }
977
+ const tags = moduleDef.tags ?? [];
978
+ if (tags.length > 0) {
979
+ process.stdout.write(`
980
+ Tags: ${tags.join(", ")}
981
+ `);
982
+ }
983
+ } else if (format === "json") {
984
+ const result = {
985
+ id: moduleDef.id,
986
+ description: moduleDef.description
987
+ };
988
+ if (moduleDef.inputSchema) result.input_schema = moduleDef.inputSchema;
989
+ if (moduleDef.outputSchema) result.output_schema = moduleDef.outputSchema;
990
+ const annDict = annotationsToDict(
991
+ moduleDef.annotations
992
+ );
993
+ if (annDict) result.annotations = annDict;
994
+ const tags = moduleDef.tags ?? [];
995
+ if (tags.length > 0) result.tags = tags;
996
+ const metadata = moduleDef.metadata;
997
+ if (metadata) {
998
+ for (const [k, v] of Object.entries(metadata)) {
999
+ if (k.startsWith("x-") || k.startsWith("x_")) {
1000
+ result[k] = v;
1001
+ }
1002
+ }
1003
+ }
1004
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1005
+ }
1006
+ }
1007
+ function formatExecResult(result, format) {
1008
+ if (result === null || result === void 0) {
1009
+ return;
1010
+ }
1011
+ const effective = resolveFormat(format);
1012
+ if (effective === "table" && typeof result === "object" && !Array.isArray(result)) {
1013
+ const entries = Object.entries(result);
1014
+ const headers = ["Key", "Value"];
1015
+ const rows = entries.map(([k, v]) => [String(k), String(v)]);
1016
+ process.stdout.write(formatTable(headers, rows));
1017
+ } else if (typeof result === "object") {
1018
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
1019
+ } else if (typeof result === "string") {
1020
+ process.stdout.write(result + "\n");
1021
+ } else {
1022
+ process.stdout.write(String(result) + "\n");
1023
+ }
1024
+ }
1025
+
1026
+ // src/logger.ts
1027
+ init_esm_shims();
1028
+ var LEVELS = { DEBUG: 0, INFO: 1, WARNING: 2, ERROR: 3 };
1029
+ var currentLevel = "WARNING";
1030
+ function setLogLevel(level) {
1031
+ const upper = level.toUpperCase();
1032
+ if (upper in LEVELS) {
1033
+ currentLevel = upper;
1034
+ }
1035
+ }
1036
+ function getLogLevel() {
1037
+ return currentLevel;
1038
+ }
1039
+ function shouldLog(level) {
1040
+ return LEVELS[level] >= LEVELS[currentLevel];
1041
+ }
1042
+ function debug(message) {
1043
+ if (shouldLog("DEBUG")) process.stderr.write(`DEBUG: ${message}
1044
+ `);
1045
+ }
1046
+ function info(message) {
1047
+ if (shouldLog("INFO")) process.stderr.write(`INFO: ${message}
1048
+ `);
1049
+ }
1050
+ function warn(message) {
1051
+ if (shouldLog("WARNING")) process.stderr.write(`WARNING: ${message}
1052
+ `);
1053
+ }
1054
+ function error(message) {
1055
+ if (shouldLog("ERROR")) process.stderr.write(`ERROR: ${message}
1056
+ `);
1057
+ }
1058
+
1059
+ // src/main.ts
1060
+ var __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
1061
+ var pkg = JSON.parse(readFileSync(path4.resolve(__dirname2, "../package.json"), "utf-8"));
1062
+ var VERSION = pkg.version;
1063
+ function createCli(extensionsDir, progName) {
1064
+ const resolvedProgName = progName ?? path4.basename(process.argv[1] ?? "apcore-cli") ?? "apcore-cli";
1065
+ const cliLogLevel = process.env.APCORE_CLI_LOGGING_LEVEL ?? process.env.APCORE_LOGGING_LEVEL ?? "WARNING";
1066
+ setLogLevel(cliLogLevel);
1067
+ const program = new Command(resolvedProgName).exitOverride().version(VERSION, "--version", `Show ${resolvedProgName} version`).description("apcore CLI \u2014 execute apcore modules from the command line").option("--extensions-dir <path>", "Path to extensions directory").option("--log-level <level>", "Logging level (DEBUG|INFO|WARNING|ERROR)", "WARNING");
1068
+ void extensionsDir;
1069
+ return program;
1070
+ }
1071
+ function main(progName) {
1072
+ const program = createCli(void 0, progName);
1073
+ try {
1074
+ program.parse(process.argv);
1075
+ } catch (error2) {
1076
+ if (error2 instanceof CommanderError) {
1077
+ process.exit(error2.exitCode);
1078
+ }
1079
+ const code = exitCodeForError(error2);
1080
+ if (error2 instanceof Error) {
1081
+ process.stderr.write(`Error: ${error2.message}
1082
+ `);
1083
+ }
1084
+ process.exit(code);
1085
+ }
1086
+ }
1087
+ function buildModuleCommand(moduleDef, executor) {
1088
+ const moduleId = moduleDef.id;
1089
+ let resolvedSchema = {};
1090
+ let schemaOptions = [];
1091
+ const inputSchema = moduleDef.inputSchema;
1092
+ if (inputSchema && typeof inputSchema === "object" && inputSchema.properties) {
1093
+ try {
1094
+ resolvedSchema = resolveRefs(inputSchema, 32, moduleId);
1095
+ } catch {
1096
+ resolvedSchema = inputSchema;
1097
+ }
1098
+ schemaOptions = schemaToCliOptions(resolvedSchema);
1099
+ }
1100
+ const cmd = new Command(moduleId).description(moduleDef.description);
1101
+ cmd.option("--input <source>", "Read input from STDIN ('-')");
1102
+ cmd.option("-y, --yes", "Bypass approval prompts", false);
1103
+ cmd.option("--large-input", "Allow STDIN input larger than 10MB", false);
1104
+ cmd.option("--format <format>", "Output format (json|table)");
1105
+ cmd.option("--sandbox", "Run module in subprocess sandbox", false);
1106
+ for (const opt of schemaOptions) {
1107
+ if (opt.parseArg) {
1108
+ cmd.option(opt.flags, opt.description, opt.parseArg, opt.defaultValue);
1109
+ } else {
1110
+ cmd.option(opt.flags, opt.description, opt.defaultValue);
1111
+ }
1112
+ }
1113
+ cmd.action(async (options) => {
1114
+ const stdinFlag = options.input;
1115
+ const autoApprove = options.yes;
1116
+ const largeInput = options.largeInput;
1117
+ const outputFormat = options.format;
1118
+ const sandboxEnabled = options.sandbox;
1119
+ const schemaKwargs = {};
1120
+ const builtinKeys = /* @__PURE__ */ new Set(["input", "yes", "largeInput", "format", "sandbox"]);
1121
+ for (const [k, v] of Object.entries(options)) {
1122
+ if (!builtinKeys.has(k)) {
1123
+ schemaKwargs[k] = v;
1124
+ }
1125
+ }
1126
+ try {
1127
+ const merged = await collectInput(stdinFlag, schemaKwargs, largeInput);
1128
+ const reconverted = reconvertEnumValues(merged, schemaOptions);
1129
+ await checkApproval(moduleDef, autoApprove);
1130
+ const { Sandbox: Sandbox2 } = await Promise.resolve().then(() => (init_security(), security_exports));
1131
+ const sandbox = new Sandbox2(sandboxEnabled);
1132
+ const startTime = performance.now();
1133
+ const result = await sandbox.execute(moduleId, reconverted, executor);
1134
+ const durationMs = Math.round(performance.now() - startTime);
1135
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
1136
+ const auditLogger = getAuditLogger2();
1137
+ if (auditLogger) {
1138
+ auditLogger.logExecution(moduleId, reconverted, "success", 0, durationMs);
1139
+ }
1140
+ formatExecResult(result, outputFormat);
1141
+ } catch (err) {
1142
+ const { getAuditLogger: getAuditLogger2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
1143
+ const auditLogger = getAuditLogger2();
1144
+ const code = exitCodeForError(err);
1145
+ if (auditLogger) {
1146
+ auditLogger.logExecution(moduleId, {}, "error", code, 0);
1147
+ }
1148
+ if (err instanceof Error) {
1149
+ process.stderr.write(`Error: ${err.message}
1150
+ `);
1151
+ }
1152
+ process.exit(code);
1153
+ }
1154
+ });
1155
+ return cmd;
1156
+ }
1157
+ function validateModuleId(moduleId) {
1158
+ if (moduleId.length > 128) {
1159
+ process.stderr.write(
1160
+ `Error: Invalid module ID format: '${moduleId}'. Maximum length is 128 characters.
1161
+ `
1162
+ );
1163
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1164
+ }
1165
+ if (!/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/.test(moduleId)) {
1166
+ process.stderr.write(
1167
+ `Error: Invalid module ID format: '${moduleId}'.
1168
+ `
1169
+ );
1170
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1171
+ }
1172
+ }
1173
+ async function collectInput(stdinFlag, cliKwargs = {}, largeInput) {
1174
+ const cliKwargsNonNull = {};
1175
+ for (const [k, v] of Object.entries(cliKwargs)) {
1176
+ if (v !== null && v !== void 0) {
1177
+ cliKwargsNonNull[k] = v;
1178
+ }
1179
+ }
1180
+ if (!stdinFlag) {
1181
+ return cliKwargsNonNull;
1182
+ }
1183
+ if (stdinFlag === "-") {
1184
+ const raw = await readStdin();
1185
+ const rawSize = Buffer.byteLength(raw, "utf-8");
1186
+ if (rawSize > 10485760 && !largeInput) {
1187
+ process.stderr.write(
1188
+ "Error: STDIN input exceeds 10MB limit. Use --large-input to override.\n"
1189
+ );
1190
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1191
+ }
1192
+ if (!raw) {
1193
+ return cliKwargsNonNull;
1194
+ }
1195
+ let stdinData;
1196
+ try {
1197
+ stdinData = JSON.parse(raw);
1198
+ } catch {
1199
+ process.stderr.write(
1200
+ "Error: STDIN does not contain valid JSON.\n"
1201
+ );
1202
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1203
+ }
1204
+ if (typeof stdinData !== "object" || stdinData === null || Array.isArray(stdinData)) {
1205
+ process.stderr.write(
1206
+ `Error: STDIN JSON must be an object, got ${Array.isArray(stdinData) ? "array" : typeof stdinData}.
1207
+ `
1208
+ );
1209
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1210
+ }
1211
+ return { ...stdinData, ...cliKwargsNonNull };
1212
+ }
1213
+ return cliKwargsNonNull;
1214
+ }
1215
+ function readStdin() {
1216
+ return new Promise((resolve3, reject) => {
1217
+ const chunks = [];
1218
+ const onData = (chunk) => chunks.push(chunk);
1219
+ const onEnd = () => {
1220
+ cleanup();
1221
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
1222
+ };
1223
+ const onError = (err) => {
1224
+ cleanup();
1225
+ reject(err);
1226
+ };
1227
+ const cleanup = () => {
1228
+ process.stdin.removeListener("data", onData);
1229
+ process.stdin.removeListener("end", onEnd);
1230
+ process.stdin.removeListener("error", onError);
1231
+ };
1232
+ process.stdin.on("data", onData);
1233
+ process.stdin.on("end", onEnd);
1234
+ process.stdin.on("error", onError);
1235
+ process.stdin.resume();
1236
+ });
1237
+ }
1238
+ function reconvertEnumValues(kwargs, options) {
1239
+ const result = { ...kwargs };
1240
+ for (const opt of options) {
1241
+ if (!opt.enumOriginalTypes) continue;
1242
+ const paramName = opt.name;
1243
+ if (!(paramName in result) || result[paramName] === null || result[paramName] === void 0) {
1244
+ continue;
1245
+ }
1246
+ const strVal = String(result[paramName]);
1247
+ const origType = opt.enumOriginalTypes[strVal];
1248
+ if (origType === "int") {
1249
+ result[paramName] = parseInt(strVal, 10);
1250
+ } else if (origType === "float") {
1251
+ result[paramName] = parseFloat(strVal);
1252
+ } else if (origType === "bool") {
1253
+ result[paramName] = strVal.toLowerCase() === "true";
1254
+ }
1255
+ }
1256
+ return result;
1257
+ }
1258
+
1259
+ // src/cli.ts
1260
+ init_esm_shims();
1261
+ var LazyModuleGroup = class {
1262
+ registry;
1263
+ executor;
1264
+ commandCache = /* @__PURE__ */ new Map();
1265
+ constructor(registry, executor) {
1266
+ this.registry = registry;
1267
+ this.executor = executor;
1268
+ }
1269
+ /**
1270
+ * List all available command names from the Registry.
1271
+ *
1272
+ * TODO: Implement registry enumeration.
1273
+ */
1274
+ listCommands() {
1275
+ return this.registry.listModules().map((m) => m.id);
1276
+ }
1277
+ /**
1278
+ * Get or lazily build a Commander Command for the given module.
1279
+ *
1280
+ * TODO: Implement lazy command construction with schema-based options.
1281
+ */
1282
+ getCommand(cmdName) {
1283
+ if (this.commandCache.has(cmdName)) {
1284
+ return this.commandCache.get(cmdName);
1285
+ }
1286
+ const moduleDef = this.registry.getModule(cmdName);
1287
+ if (!moduleDef) {
1288
+ return null;
1289
+ }
1290
+ const cmd = buildModuleCommand(moduleDef, this.executor);
1291
+ this.commandCache.set(cmdName, cmd);
1292
+ return cmd;
1293
+ }
1294
+ };
1295
+
1296
+ // src/config.ts
1297
+ init_esm_shims();
1298
+ import * as fs3 from "fs";
1299
+ import yaml from "js-yaml";
1300
+ var DEFAULTS = {
1301
+ "extensions.root": "./extensions",
1302
+ "logging.level": "WARNING",
1303
+ "sandbox.enabled": false,
1304
+ "cli.stdin_buffer_limit": 10485760,
1305
+ "cli.auto_approve": false
1306
+ };
1307
+ var ConfigResolver = class {
1308
+ cliFlags;
1309
+ configPath;
1310
+ fileCache = null;
1311
+ fileCacheLoaded = false;
1312
+ constructor(cliFlags, configPath) {
1313
+ this.cliFlags = cliFlags ?? {};
1314
+ this.configPath = configPath ?? "apcore.yaml";
1315
+ }
1316
+ /**
1317
+ * Resolve a single configuration key across all four tiers.
1318
+ */
1319
+ resolve(key, cliFlag, envVar) {
1320
+ const flagKey = cliFlag ?? key;
1321
+ if (flagKey in this.cliFlags) {
1322
+ const value = this.cliFlags[flagKey];
1323
+ if (value !== null && value !== void 0) {
1324
+ return value;
1325
+ }
1326
+ }
1327
+ if (envVar) {
1328
+ const envValue = process.env[envVar];
1329
+ if (envValue !== void 0 && envValue !== "") {
1330
+ return envValue;
1331
+ }
1332
+ }
1333
+ const fileValue = this.resolveFromFile(key);
1334
+ if (fileValue !== void 0) {
1335
+ return fileValue;
1336
+ }
1337
+ return DEFAULTS[key];
1338
+ }
1339
+ /**
1340
+ * Load a value from the config file using a dot-separated key path.
1341
+ */
1342
+ resolveFromFile(key) {
1343
+ if (!this.fileCacheLoaded) {
1344
+ this.fileCache = this.loadConfigFile();
1345
+ this.fileCacheLoaded = true;
1346
+ }
1347
+ if (this.fileCache === null) {
1348
+ return void 0;
1349
+ }
1350
+ return this.fileCache[key];
1351
+ }
1352
+ /**
1353
+ * Load and flatten a YAML config file.
1354
+ */
1355
+ loadConfigFile() {
1356
+ let content;
1357
+ try {
1358
+ content = fs3.readFileSync(this.configPath, "utf-8");
1359
+ } catch (err) {
1360
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
1361
+ return null;
1362
+ }
1363
+ console.warn(
1364
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1365
+ );
1366
+ return null;
1367
+ }
1368
+ let parsed;
1369
+ try {
1370
+ parsed = yaml.load(content);
1371
+ } catch {
1372
+ console.warn(
1373
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1374
+ );
1375
+ return null;
1376
+ }
1377
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1378
+ console.warn(
1379
+ `Configuration file '${this.configPath}' is malformed, using defaults.`
1380
+ );
1381
+ return null;
1382
+ }
1383
+ return this.flattenDict(parsed);
1384
+ }
1385
+ /**
1386
+ * Flatten nested dict to dot-notation keys.
1387
+ */
1388
+ flattenDict(d, prefix = "") {
1389
+ const result = {};
1390
+ for (const [key, value] of Object.entries(d)) {
1391
+ const fullKey = prefix ? `${prefix}.${key}` : key;
1392
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
1393
+ Object.assign(
1394
+ result,
1395
+ this.flattenDict(value, fullKey)
1396
+ );
1397
+ } else {
1398
+ result[fullKey] = value;
1399
+ }
1400
+ }
1401
+ return result;
1402
+ }
1403
+ };
1404
+
1405
+ // src/discovery.ts
1406
+ init_esm_shims();
1407
+ init_errors();
1408
+ import { Command as Command2 } from "commander";
1409
+ var TAG_PATTERN = /^[a-z][a-z0-9_-]*$/;
1410
+ function validateTag(tag) {
1411
+ if (!TAG_PATTERN.test(tag)) {
1412
+ process.stderr.write(
1413
+ `Error: Invalid tag format: '${tag}'. Tags must match [a-z][a-z0-9_-]*.
1414
+ `
1415
+ );
1416
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1417
+ }
1418
+ }
1419
+ function collectTag(value, previous) {
1420
+ return previous.concat([value]);
1421
+ }
1422
+ function registerDiscoveryCommands(cli, registry) {
1423
+ const listCmd = new Command2("list").description("List available modules in the registry.").option("--tag <tag>", "Filter modules by tag (AND logic). Repeatable.", collectTag, []).option("--format <format>", "Output format.", void 0).action((opts) => {
1424
+ for (const t of opts.tag) {
1425
+ validateTag(t);
1426
+ }
1427
+ const modules = [];
1428
+ for (const m of registry.listModules()) {
1429
+ modules.push(m);
1430
+ }
1431
+ let filtered = modules;
1432
+ if (opts.tag.length > 0) {
1433
+ const filterTags = new Set(opts.tag);
1434
+ filtered = modules.filter((m) => {
1435
+ const mTags = m.tags ?? [];
1436
+ return [...filterTags].every((t) => mTags.includes(t));
1437
+ });
1438
+ }
1439
+ const fmt = resolveFormat(opts.format);
1440
+ formatModuleList(filtered, fmt, opts.tag.length > 0 ? opts.tag : void 0);
1441
+ });
1442
+ cli.addCommand(listCmd);
1443
+ const describeCmd = new Command2("describe").description("Show metadata, schema, and annotations for a module.").argument("<module-id>", "Module ID to describe").option("--format <format>", "Output format.", void 0).action((moduleId, opts) => {
1444
+ validateModuleId(moduleId);
1445
+ const moduleDef = registry.getModule(moduleId);
1446
+ if (!moduleDef) {
1447
+ process.stderr.write(
1448
+ `Error: Module '${moduleId}' not found.
1449
+ `
1450
+ );
1451
+ process.exit(EXIT_CODES.MODULE_NOT_FOUND);
1452
+ }
1453
+ const fmt = resolveFormat(opts.format);
1454
+ formatModuleDetail(moduleDef, fmt);
1455
+ });
1456
+ cli.addCommand(describeCmd);
1457
+ }
1458
+
1459
+ // src/shell.ts
1460
+ init_esm_shims();
1461
+ init_errors();
1462
+ import { readFileSync as readFileSync3 } from "fs";
1463
+ import { fileURLToPath as fileURLToPath3 } from "url";
1464
+ import * as path5 from "path";
1465
+ import { Command as Command3 } from "commander";
1466
+ var __dirname3 = path5.dirname(fileURLToPath3(import.meta.url));
1467
+ var pkg2 = JSON.parse(readFileSync3(path5.resolve(__dirname3, "../package.json"), "utf-8"));
1468
+ var SHELL_VERSION = pkg2.version;
1469
+ function makeFunctionName(progName) {
1470
+ return "_" + progName.replace(/[^a-zA-Z0-9]/g, "_");
1471
+ }
1472
+ function shellQuote(s) {
1473
+ return "'" + s.replace(/'/g, "'\\''") + "'";
1474
+ }
1475
+ function generateBashCompletion(progName) {
1476
+ const fn = makeFunctionName(progName);
1477
+ const quoted = shellQuote(progName);
1478
+ const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e "process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})" 2>/dev/null`;
1479
+ return `${fn}() {
1480
+ local cur prev opts
1481
+ COMPREPLY=()
1482
+ cur="\${COMP_WORDS[COMP_CWORD]}"
1483
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
1484
+
1485
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
1486
+ opts="list describe completion man"
1487
+ COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
1488
+ return 0
1489
+ fi
1490
+
1491
+ if [[ "\${COMP_WORDS[1]}" == "exec" && \${COMP_CWORD} -eq 2 ]]; then
1492
+ local modules=$(${moduleListCmd})
1493
+ COMPREPLY=( $(compgen -W "\${modules}" -- \${cur}) )
1494
+ return 0
1495
+ fi
1496
+ }
1497
+ complete -F ${fn} ${quoted}
1498
+ `;
1499
+ }
1500
+ function generateZshCompletion(progName) {
1501
+ const fn = makeFunctionName(progName);
1502
+ const quoted = shellQuote(progName);
1503
+ const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e "process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})" 2>/dev/null`;
1504
+ return `#compdef ${progName}
1505
+
1506
+ ${fn}() {
1507
+ local -a commands
1508
+ commands=(
1509
+ 'list:List available modules'
1510
+ 'describe:Show module metadata and schema'
1511
+ 'completion:Generate shell completion script'
1512
+ 'man:Generate man page'
1513
+ )
1514
+
1515
+ _arguments -C \\
1516
+ '1:command:->command' \\
1517
+ '*::arg:->args'
1518
+
1519
+ case "$state" in
1520
+ command)
1521
+ _describe -t commands '${progName} commands' commands
1522
+ ;;
1523
+ args)
1524
+ case "\${words[1]}" in
1525
+ exec)
1526
+ local modules
1527
+ modules=($(${moduleListCmd}))
1528
+ compadd -a modules
1529
+ ;;
1530
+ esac
1531
+ ;;
1532
+ esac
1533
+ }
1534
+
1535
+ compdef ${fn} ${quoted}
1536
+ `;
1537
+ }
1538
+ function generateFishCompletion(progName) {
1539
+ const quoted = shellQuote(progName);
1540
+ const moduleListCmd = `${quoted} list --format json 2>/dev/null | node -e \\"process.stdin.on('data',d=>{JSON.parse(d).forEach(m=>console.log(m.id))})\\" 2>/dev/null`;
1541
+ return `# Fish completions for ${progName}
1542
+ complete -c ${quoted} -n "__fish_use_subcommand" -a list -d "List available modules"
1543
+ complete -c ${quoted} -n "__fish_use_subcommand" -a describe -d "Show module metadata and schema"
1544
+ complete -c ${quoted} -n "__fish_use_subcommand" -a completion -d "Generate shell completion script"
1545
+ complete -c ${quoted} -n "__fish_use_subcommand" -a man -d "Generate man page"
1546
+
1547
+ complete -c ${quoted} -n "__fish_seen_subcommand_from exec" -a "(${moduleListCmd})"
1548
+ `;
1549
+ }
1550
+ function buildSynopsis(command, progName, commandName) {
1551
+ if (!command) {
1552
+ return `\\fB${progName} ${commandName}\\fR [OPTIONS]`;
1553
+ }
1554
+ const parts = [`\\fB${progName} ${commandName}\\fR`];
1555
+ for (const opt of command.options) {
1556
+ const flag = opt.long ?? opt.short ?? "";
1557
+ if (opt.isBoolean?.()) {
1558
+ parts.push(`[${flag}]`);
1559
+ } else if (opt.required) {
1560
+ const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
1561
+ parts.push(`${flag} \\fI${typeName}\\fR`);
1562
+ } else {
1563
+ const typeName = (opt.argChoices ? "CHOICE" : "VALUE").toUpperCase();
1564
+ parts.push(`[${flag} \\fI${typeName}\\fR]`);
1565
+ }
1566
+ }
1567
+ for (const arg of command.registeredArguments ?? []) {
1568
+ const meta = arg.name().toUpperCase();
1569
+ if (arg.required) {
1570
+ parts.push(`\\fI${meta}\\fR`);
1571
+ } else {
1572
+ parts.push(`[\\fI${meta}\\fR]`);
1573
+ }
1574
+ }
1575
+ return parts.join(" ");
1576
+ }
1577
+ function generateManPage(commandName, command, progName, version = SHELL_VERSION) {
1578
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1579
+ const title = `${progName}-${commandName}`.toUpperCase();
1580
+ const pkgLabel = `${progName} ${version}`;
1581
+ const manualLabel = `${progName} Manual`;
1582
+ const sections = [];
1583
+ sections.push(`.TH "${title}" "1" "${today}" "${pkgLabel}" "${manualLabel}"`);
1584
+ sections.push(".SH NAME");
1585
+ const desc = command?.description() ?? commandName;
1586
+ const nameDesc = desc.split("\n")[0].replace(/\.$/, "");
1587
+ sections.push(`${progName}-${commandName} \\- ${nameDesc}`);
1588
+ sections.push(".SH SYNOPSIS");
1589
+ sections.push(buildSynopsis(command, progName, commandName));
1590
+ if (command?.description()) {
1591
+ sections.push(".SH DESCRIPTION");
1592
+ sections.push(
1593
+ command.description().replace(/\\/g, "\\\\").replace(/-/g, "\\-")
1594
+ );
1595
+ }
1596
+ if (command && command.options.length > 0) {
1597
+ sections.push(".SH OPTIONS");
1598
+ for (const opt of command.options) {
1599
+ const flag = [opt.short, opt.long].filter(Boolean).join(", ");
1600
+ sections.push(".TP");
1601
+ if (opt.isBoolean?.()) {
1602
+ sections.push(`\\fB${flag}\\fR`);
1603
+ } else {
1604
+ sections.push(`\\fB${flag}\\fR \\fIVALUE\\fR`);
1605
+ }
1606
+ if (opt.description) {
1607
+ sections.push(opt.description);
1608
+ }
1609
+ if (opt.defaultValue !== void 0 && !opt.isBoolean?.()) {
1610
+ sections.push(`Default: ${opt.defaultValue}.`);
1611
+ }
1612
+ }
1613
+ }
1614
+ sections.push(".SH ENVIRONMENT");
1615
+ sections.push(".TP");
1616
+ sections.push("\\fBAPCORE_EXTENSIONS_ROOT\\fR");
1617
+ sections.push(
1618
+ "Path to the apcore extensions directory. Overrides the default \\fI./extensions\\fR."
1619
+ );
1620
+ sections.push(".TP");
1621
+ sections.push("\\fBAPCORE_CLI_AUTO_APPROVE\\fR");
1622
+ sections.push(
1623
+ "Set to \\fB1\\fR to bypass approval prompts for modules that require human-in-the-loop confirmation."
1624
+ );
1625
+ sections.push(".TP");
1626
+ sections.push("\\fBAPCORE_CLI_LOGGING_LEVEL\\fR");
1627
+ sections.push(
1628
+ "CLI-specific logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Takes priority over \\fBAPCORE_LOGGING_LEVEL\\fR. Default: WARNING."
1629
+ );
1630
+ sections.push(".TP");
1631
+ sections.push("\\fBAPCORE_LOGGING_LEVEL\\fR");
1632
+ sections.push(
1633
+ "Global apcore logging verbosity. One of: DEBUG, INFO, WARNING, ERROR. Used as fallback when \\fBAPCORE_CLI_LOGGING_LEVEL\\fR is not set. Default: WARNING."
1634
+ );
1635
+ sections.push(".SH EXIT CODES");
1636
+ const exitCodes = [
1637
+ ["0", "Success."],
1638
+ ["1", "Module execution error."],
1639
+ ["2", "Invalid CLI input or missing argument."],
1640
+ ["44", "Module not found, disabled, or failed to load."],
1641
+ ["45", "Input failed JSON Schema validation."],
1642
+ [
1643
+ "46",
1644
+ "Approval denied, timed out, or no interactive terminal available."
1645
+ ],
1646
+ [
1647
+ "47",
1648
+ "Configuration error (extensions directory not found or unreadable)."
1649
+ ],
1650
+ ["48", "Schema contains a circular \\fB$ref\\fR."],
1651
+ ["77", "ACL denied \u2014 insufficient permissions for this module."],
1652
+ ["130", "Execution cancelled by user (SIGINT / Ctrl\\-C)."]
1653
+ ];
1654
+ for (const [code, meaning] of exitCodes) {
1655
+ sections.push(`.TP
1656
+ \\fB${code}\\fR
1657
+ ${meaning}`);
1658
+ }
1659
+ sections.push(".SH SEE ALSO");
1660
+ sections.push(
1661
+ [
1662
+ `\\fB${progName}\\fR(1)`,
1663
+ `\\fB${progName}\\-list\\fR(1)`,
1664
+ `\\fB${progName}\\-describe\\fR(1)`,
1665
+ `\\fB${progName}\\-completion\\fR(1)`
1666
+ ].join(", ")
1667
+ );
1668
+ return sections.join("\n");
1669
+ }
1670
+ function registerShellCommands(cli, progName = "apcore-cli") {
1671
+ const completionCmd = new Command3("completion").description(
1672
+ "Generate a shell completion script and print it to stdout."
1673
+ ).argument("<shell>", "Shell type: bash, zsh, or fish").action((shell) => {
1674
+ const validShells = ["bash", "zsh", "fish"];
1675
+ if (!validShells.includes(shell)) {
1676
+ process.stderr.write(
1677
+ `Error: Unknown shell '${shell}'. Expected: bash, zsh, or fish.
1678
+ `
1679
+ );
1680
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1681
+ }
1682
+ const resolved = cli.name() || progName;
1683
+ const generators = {
1684
+ bash: () => generateBashCompletion(resolved),
1685
+ zsh: () => generateZshCompletion(resolved),
1686
+ fish: () => generateFishCompletion(resolved)
1687
+ };
1688
+ process.stdout.write(generators[shell]());
1689
+ });
1690
+ cli.addCommand(completionCmd);
1691
+ const manCmd = new Command3("man").description("Generate a roff man page for COMMAND and print it to stdout.").argument("<command>", "Command to generate man page for").action((commandName) => {
1692
+ const knownBuiltins = /* @__PURE__ */ new Set(["list", "describe", "completion", "man"]);
1693
+ const cmd = cli.commands.find((c) => c.name() === commandName) ?? null;
1694
+ if (!cmd && !knownBuiltins.has(commandName)) {
1695
+ process.stderr.write(
1696
+ `Error: Unknown command '${commandName}'.
1697
+ `
1698
+ );
1699
+ process.exit(EXIT_CODES.INVALID_CLI_INPUT);
1700
+ }
1701
+ const resolved = cli.name() || progName;
1702
+ const roff = generateManPage(commandName, cmd, resolved);
1703
+ process.stdout.write(roff);
1704
+ });
1705
+ cli.addCommand(manCmd);
1706
+ }
1707
+
1708
+ // src/index.ts
1709
+ init_errors();
1710
+ init_security();
1711
+ export {
1712
+ ApprovalDeniedError,
1713
+ ApprovalTimeoutError,
1714
+ AuditLogger,
1715
+ AuthProvider,
1716
+ AuthenticationError,
1717
+ ConfigDecryptionError,
1718
+ ConfigEncryptor,
1719
+ ConfigResolver,
1720
+ DEFAULTS,
1721
+ EXIT_CODES,
1722
+ LazyModuleGroup,
1723
+ ModuleExecutionError,
1724
+ ModuleNotFoundError,
1725
+ Sandbox,
1726
+ SchemaValidationError,
1727
+ buildModuleCommand,
1728
+ checkApproval,
1729
+ collectInput,
1730
+ createCli,
1731
+ debug,
1732
+ error,
1733
+ exitCodeForError,
1734
+ extractHelp,
1735
+ formatExecResult,
1736
+ formatModuleDetail,
1737
+ formatModuleList,
1738
+ getAuditLogger,
1739
+ getLogLevel,
1740
+ info,
1741
+ main,
1742
+ mapType,
1743
+ reconvertEnumValues,
1744
+ registerDiscoveryCommands,
1745
+ registerShellCommands,
1746
+ resolveFormat,
1747
+ resolveRefs,
1748
+ schemaToCliOptions,
1749
+ setAuditLogger,
1750
+ setLogLevel,
1751
+ truncate,
1752
+ validateModuleId,
1753
+ warn
1754
+ };
1755
+ //# sourceMappingURL=index.js.map