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