jorgex-stack 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,496 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/lib/paths.ts
4
+ import os from "os";
5
+ import path from "path";
6
+ import { existsSync } from "fs";
7
+ import { fileURLToPath } from "url";
8
+ var HOME = os.homedir();
9
+ function stackRoot() {
10
+ let dir = path.dirname(fileURLToPath(import.meta.url));
11
+ for (let i = 0; i < 6; i++) {
12
+ const candidate = path.join(dir, "stack");
13
+ if (existsSync(path.join(candidate, "system-prompt", "AGENTS.md"))) return candidate;
14
+ dir = path.dirname(dir);
15
+ }
16
+ throw new Error("No se encontr\xF3 stack/ relativa al CLI \u2014 instalaci\xF3n rota.");
17
+ }
18
+ function dataDir() {
19
+ return path.join(HOME, ".jorgex-stack");
20
+ }
21
+ function samePath(a, b) {
22
+ const resolvedA = path.resolve(a);
23
+ const resolvedB = path.resolve(b);
24
+ if (process.platform === "win32") return resolvedA.toLowerCase() === resolvedB.toLowerCase();
25
+ return resolvedA === resolvedB;
26
+ }
27
+
28
+ // src/lib/quality-policy.ts
29
+ var QUALITY_PROFILES = ["routine", "elevated", "high", "release"];
30
+ function hasText(value) {
31
+ return typeof value === "string" && value.trim() !== "";
32
+ }
33
+ function isQualityProfile(value) {
34
+ return typeof value === "string" && QUALITY_PROFILES.includes(value);
35
+ }
36
+ function mergeStatus(current, next) {
37
+ if (current === "fail" || next === "fail") return "fail";
38
+ if (current === "incomplete" || next === "incomplete") return "incomplete";
39
+ return "pass";
40
+ }
41
+ function resultStatus(result) {
42
+ switch (result.status) {
43
+ case "fail":
44
+ return "fail";
45
+ case "incomplete":
46
+ return "incomplete";
47
+ case "not-applicable":
48
+ return "pass";
49
+ case "pass":
50
+ return hasText(result.evidence) ? "pass" : "incomplete";
51
+ default:
52
+ return "incomplete";
53
+ }
54
+ }
55
+ function evaluateQualityPolicy(input) {
56
+ const requestedProfile = input.profile === void 0 ? "routine" : input.profile;
57
+ if (!isQualityProfile(requestedProfile)) {
58
+ throw new Error(`Unknown quality profile: ${String(requestedProfile)}`);
59
+ }
60
+ const controls = /* @__PURE__ */ new Map();
61
+ let status = "pass";
62
+ let hasRequired = false;
63
+ for (const control of input.controls) {
64
+ if (!hasText(control.id) || control.requirement !== "required" && control.requirement !== "optional" || controls.has(control.id)) {
65
+ status = mergeStatus(status, "incomplete");
66
+ continue;
67
+ }
68
+ controls.set(control.id, control);
69
+ if (control.requirement === "required") hasRequired = true;
70
+ }
71
+ const results = /* @__PURE__ */ new Map();
72
+ for (const result of input.results) {
73
+ const control = controls.get(result.controlId);
74
+ if (!control || results.has(result.controlId)) {
75
+ status = mergeStatus(status, "incomplete");
76
+ }
77
+ if (control && !results.has(result.controlId)) {
78
+ results.set(result.controlId, result);
79
+ }
80
+ if (control && result.status === "not-applicable") {
81
+ const validException = control.requirement === "optional" && control.notApplicable === true && hasText(result.reason);
82
+ status = mergeStatus(status, validException ? "pass" : "incomplete");
83
+ continue;
84
+ }
85
+ if (control) status = mergeStatus(status, resultStatus(result));
86
+ }
87
+ if (!hasRequired) status = mergeStatus(status, "incomplete");
88
+ for (const control of controls.values()) {
89
+ if (control.requirement !== "required") continue;
90
+ const result = results.get(control.id);
91
+ if (!result) {
92
+ status = mergeStatus(status, "incomplete");
93
+ continue;
94
+ }
95
+ if (result.status === "not-applicable") {
96
+ status = mergeStatus(status, "incomplete");
97
+ } else {
98
+ status = mergeStatus(status, resultStatus(result));
99
+ }
100
+ }
101
+ return { profile: requestedProfile, status };
102
+ }
103
+
104
+ // src/lib/quality-receipt.ts
105
+ import { createHash } from "crypto";
106
+ var QUALITY_RECEIPT_NAMESPACE = "jorgex.quality.receipt";
107
+ var QUALITY_RECEIPT_VERSION = 1;
108
+ var COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
109
+ var SHA256_PATTERN = /^[0-9a-f]{64}$/i;
110
+ var MAX_EXCERPT_LENGTH = 512;
111
+ var REDACTED = "[REDACTED]";
112
+ var QUALITY_RESULT_STATUSES = [
113
+ "pass",
114
+ "fail",
115
+ "incomplete",
116
+ "not-applicable"
117
+ ];
118
+ var SENSITIVE_FLAG_PATTERN = /^(?:-{1,2})(?:(?:[a-z][a-z0-9_.-]*[-_]?)?(?:access[-_]?token|api[-_]?key|auth(?:orization)?|client[-_]?secret|credential|pass(?:word|wd)?|refresh[-_]?token|secret|token))$/i;
119
+ var SENSITIVE_ASSIGNMENT_PATTERN = /^(?:-{0,2})(?:(?:[a-z][a-z0-9_.-]*[-_]?)?(?:access[-_]?token|api[-_]?key|auth(?:orization)?|client[-_]?secret|credential|pass(?:word|wd)?|refresh[-_]?token|secret|token))\s*[=:]\s*.+$/i;
120
+ var SENSITIVE_OUTPUT_PATTERN = /((?:authorization\s*:\s*bearer\s+|bearer\s+))[^\s,;]+/gi;
121
+ var SENSITIVE_KEY_VALUE_PATTERN = /((?:^|(?<=[^\w.-]))(?:-{0,2})(?:(?:[a-z][a-z0-9_.-]*[-_]?)?(?:access[-_]?token|api[-_]?key|auth(?:orization)?|client[-_]?secret|credential|pass(?:word|wd)?|refresh[-_]?token|secret|token))\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\r\n,;]+)/gi;
122
+ var SENSITIVE_SEPARATE_FLAG_PATTERN = /((?:^|(?<=[^\w.-]))-{1,2}(?:(?:[a-z][a-z0-9_.-]*[-_]?)?(?:access[-_]?token|api[-_]?key|auth(?:orization)?|client[-_]?secret|credential|pass(?:word|wd)?|refresh[-_]?token|secret|token))[ \t]+)(?:"[^"]*"|'[^']*'|[^\r\n,;]+)/gi;
123
+ var SENSITIVE_STRUCTURED_VALUE_PATTERN = /((?:"|')?(?:token|password|api[-_]?key|access[-_]?token|_?auth(?:orization)?(?:[-_.]?token)?|aws[-_]?secret[-_]?access[-_]?key|private[-_]?key)(?:"|')?\s*:\s*)("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')/gi;
124
+ function isRecord(value) {
125
+ return value !== null && typeof value === "object" && !Array.isArray(value);
126
+ }
127
+ function hasOwn(value, key) {
128
+ return Object.prototype.hasOwnProperty.call(value, key);
129
+ }
130
+ function isDenseStringArray(value) {
131
+ if (!Array.isArray(value)) return false;
132
+ for (let index = 0; index < value.length; index += 1) {
133
+ if (!hasOwn(value, String(index)) || typeof value[index] !== "string") return false;
134
+ }
135
+ return true;
136
+ }
137
+ function assertExactKeys(value, allowed, label) {
138
+ const allowedKeys = new Set(allowed);
139
+ const unexpected = Object.keys(value).find((key) => !allowedKeys.has(key));
140
+ if (unexpected !== void 0) {
141
+ throw new Error(`Unexpected ${label} field: ${unexpected}`);
142
+ }
143
+ }
144
+ function requireRecord(value, label) {
145
+ if (!isRecord(value)) throw new Error(`Invalid ${label}`);
146
+ return value;
147
+ }
148
+ function requireText(value, label) {
149
+ if (typeof value !== "string" || value.trim() === "") {
150
+ throw new Error(`Invalid ${label}`);
151
+ }
152
+ return value;
153
+ }
154
+ function isQualityProfile2(value) {
155
+ return QUALITY_PROFILES.includes(value);
156
+ }
157
+ function isQualityReceiptResultStatus(value) {
158
+ return QUALITY_RESULT_STATUSES.includes(value);
159
+ }
160
+ function requireSha(value, label, pattern) {
161
+ const text = requireText(value, label);
162
+ if (!pattern.test(text)) throw new Error(`Invalid ${label}`);
163
+ return text;
164
+ }
165
+ function redactText(value) {
166
+ return value.replace(SENSITIVE_STRUCTURED_VALUE_PATTERN, (_match, prefix, quotedValue) => {
167
+ const quote = quotedValue[0];
168
+ return `${prefix}${quote}${REDACTED}${quote}`;
169
+ }).replace(SENSITIVE_OUTPUT_PATTERN, (_match, prefix) => {
170
+ const normalizedPrefix = prefix.toLowerCase().startsWith("authorization") ? prefix.slice(0, prefix.toLowerCase().indexOf("bearer") + "bearer".length) : "Bearer ";
171
+ return `${normalizedPrefix}${REDACTED}`;
172
+ }).replace(SENSITIVE_KEY_VALUE_PATTERN, `$1${REDACTED}`).replace(SENSITIVE_SEPARATE_FLAG_PATTERN, `$1${REDACTED}`);
173
+ }
174
+ function redactAssignment(value) {
175
+ const separator = value.indexOf("=") >= 0 ? "=" : ":";
176
+ const name = value.slice(0, value.indexOf(separator));
177
+ return `${name}${separator}${REDACTED}`;
178
+ }
179
+ function redactArgv(argv) {
180
+ if (!isDenseStringArray(argv)) throw new Error("Invalid argv: sparse or non-string array");
181
+ let redactNext = false;
182
+ return argv.map((argument) => {
183
+ if (redactNext) {
184
+ redactNext = false;
185
+ return REDACTED;
186
+ }
187
+ if (SENSITIVE_FLAG_PATTERN.test(argument)) {
188
+ redactNext = true;
189
+ return argument;
190
+ }
191
+ if (SENSITIVE_ASSIGNMENT_PATTERN.test(argument)) {
192
+ return redactAssignment(argument);
193
+ }
194
+ return redactText(argument);
195
+ });
196
+ }
197
+ function excerptFor(output) {
198
+ const stdout = redactText(output.stdout);
199
+ const stderr = redactText(output.stderr);
200
+ const combined = [stdout, stderr].filter((part) => part !== "").join("\n");
201
+ return Array.from(combined).slice(0, MAX_EXCERPT_LENGTH).join("");
202
+ }
203
+ function outputDigestFor(output) {
204
+ const sanitized = {
205
+ stdout: redactText(output.stdout),
206
+ stderr: redactText(output.stderr)
207
+ };
208
+ return sha256(canonicalJson(sanitized));
209
+ }
210
+ function normalizeCommand(command) {
211
+ return {
212
+ commandId: command.commandId,
213
+ executable: command.executable,
214
+ argv: redactArgv(command.argv),
215
+ exitCode: command.exitCode,
216
+ durationMs: command.durationMs,
217
+ excerpt: excerptFor(command.output),
218
+ outputDigest: outputDigestFor(command.output)
219
+ };
220
+ }
221
+ function normalizeResult(result) {
222
+ if (result.status === "pass") {
223
+ return {
224
+ controlId: result.controlId,
225
+ status: "pass",
226
+ evidence: result.evidence,
227
+ ...result.reason === void 0 ? {} : { reason: result.reason }
228
+ };
229
+ }
230
+ return {
231
+ controlId: result.controlId,
232
+ status: result.status,
233
+ ...result.evidence === void 0 ? {} : { evidence: result.evidence },
234
+ ...result.reason === void 0 ? {} : { reason: result.reason }
235
+ };
236
+ }
237
+ function normalizeProvenance(provenance) {
238
+ return {
239
+ issuer: provenance.issuer,
240
+ executionId: provenance.executionId,
241
+ evidenceLocator: provenance.evidenceLocator,
242
+ evidenceDigest: provenance.evidenceDigest
243
+ };
244
+ }
245
+ function canonicalValue(value) {
246
+ if (value === null) return "null";
247
+ switch (typeof value) {
248
+ case "string": {
249
+ const result = JSON.stringify(value);
250
+ if (result === void 0) throw new Error("Unable to canonicalize string");
251
+ return result;
252
+ }
253
+ case "boolean":
254
+ return value ? "true" : "false";
255
+ case "number": {
256
+ if (!Number.isFinite(value)) throw new Error("Cannot canonicalize non-finite number");
257
+ const result = JSON.stringify(value);
258
+ if (result === void 0) throw new Error("Unable to canonicalize number");
259
+ return result;
260
+ }
261
+ case "object": {
262
+ if (Array.isArray(value)) {
263
+ for (let index = 0; index < value.length; index += 1) {
264
+ if (!hasOwn(value, String(index))) {
265
+ throw new Error("Cannot canonicalize sparse array");
266
+ }
267
+ }
268
+ return `[${value.map((item) => canonicalValue(item)).join(",")}]`;
269
+ }
270
+ const prototype = Object.getPrototypeOf(value);
271
+ if (prototype !== Object.prototype && prototype !== null) {
272
+ throw new Error("Cannot canonicalize non-plain object");
273
+ }
274
+ const object = value;
275
+ return `{${Object.keys(object).sort().map((key) => {
276
+ return `${JSON.stringify(key)}:${canonicalValue(object[key])}`;
277
+ }).join(",")}}`;
278
+ }
279
+ default:
280
+ throw new Error(`Cannot canonicalize ${typeof value}`);
281
+ }
282
+ }
283
+ function canonicalJson(value) {
284
+ return canonicalValue(value);
285
+ }
286
+ function sha256(value) {
287
+ return createHash("sha256").update(value, "utf8").digest("hex");
288
+ }
289
+ function validateIdentity(value, expected) {
290
+ const identity = requireRecord(value, "identity");
291
+ assertExactKeys(identity, ["profile", "baseSha", "headSha", "policyDigest"], "identity");
292
+ const profile = requireText(identity.profile, "identity.profile");
293
+ if (!isQualityProfile2(profile)) throw new Error(`Invalid identity.profile: ${profile}`);
294
+ const baseSha = requireSha(identity.baseSha, "identity.baseSha", COMMIT_SHA_PATTERN);
295
+ const headSha = requireSha(identity.headSha, "identity.headSha", COMMIT_SHA_PATTERN);
296
+ const policyDigest = requireSha(identity.policyDigest, "identity.policyDigest", SHA256_PATTERN);
297
+ const actual = { profile, baseSha, headSha, policyDigest };
298
+ if (expected !== void 0) {
299
+ for (const field of ["profile", "baseSha", "headSha", "policyDigest"]) {
300
+ if (actual[field] !== expected[field]) {
301
+ throw new Error(`Quality receipt identity mismatch: ${field}`);
302
+ }
303
+ }
304
+ }
305
+ return actual;
306
+ }
307
+ function validateProvenance(value) {
308
+ const provenance = requireRecord(value, "provenance");
309
+ assertExactKeys(provenance, ["issuer", "executionId", "evidenceLocator", "evidenceDigest"], "provenance");
310
+ const issuer = requireText(provenance.issuer, "provenance.issuer");
311
+ const executionId = requireText(provenance.executionId, "provenance.executionId");
312
+ const evidenceLocator = requireText(provenance.evidenceLocator, "provenance.evidenceLocator");
313
+ if (!/^https?:\/\/\S+$/.test(evidenceLocator)) {
314
+ throw new Error("Invalid provenance.evidenceLocator");
315
+ }
316
+ try {
317
+ const parsed = new URL(evidenceLocator);
318
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
319
+ throw new Error("unsupported locator protocol");
320
+ }
321
+ } catch {
322
+ throw new Error("Invalid provenance.evidenceLocator");
323
+ }
324
+ const evidenceDigest = requireSha(provenance.evidenceDigest, "provenance.evidenceDigest", SHA256_PATTERN);
325
+ return { issuer, executionId, evidenceLocator, evidenceDigest };
326
+ }
327
+ function validateCommand(value, index) {
328
+ const command = requireRecord(value, `commands[${index}]`);
329
+ assertExactKeys(
330
+ command,
331
+ ["commandId", "executable", "argv", "exitCode", "durationMs", "excerpt", "outputDigest"],
332
+ `commands[${index}]`
333
+ );
334
+ const commandId = requireText(command.commandId, `commands[${index}].commandId`);
335
+ const executable = requireText(command.executable, `commands[${index}].executable`);
336
+ if (!isDenseStringArray(command.argv)) {
337
+ throw new Error(`Invalid commands[${index}].argv`);
338
+ }
339
+ if (typeof command.exitCode !== "number" || !Number.isInteger(command.exitCode)) {
340
+ throw new Error(`Invalid commands[${index}].exitCode`);
341
+ }
342
+ if (typeof command.durationMs !== "number" || !Number.isFinite(command.durationMs) || command.durationMs < 0) {
343
+ throw new Error(`Invalid commands[${index}].durationMs`);
344
+ }
345
+ if (!hasOwn(command, "excerpt") || typeof command.excerpt !== "string") {
346
+ throw new Error(`Invalid commands[${index}].excerpt`);
347
+ }
348
+ const excerpt = command.excerpt;
349
+ if (Array.from(excerpt).length > MAX_EXCERPT_LENGTH) throw new Error(`Invalid commands[${index}].excerpt`);
350
+ if (redactText(excerpt) !== excerpt) {
351
+ throw new Error(`Invalid commands[${index}].excerpt: contains an unredacted secret`);
352
+ }
353
+ const argv = [...command.argv];
354
+ const sanitizedArgv = redactArgv(argv);
355
+ if (sanitizedArgv.some((argument, argumentIndex) => argument !== argv[argumentIndex])) {
356
+ throw new Error(`Invalid commands[${index}].argv: contains an unredacted secret`);
357
+ }
358
+ const outputDigest = requireSha(command.outputDigest, `commands[${index}].outputDigest`, SHA256_PATTERN);
359
+ return {
360
+ commandId,
361
+ executable,
362
+ argv,
363
+ exitCode: command.exitCode,
364
+ durationMs: command.durationMs,
365
+ excerpt,
366
+ outputDigest
367
+ };
368
+ }
369
+ function validateResult(value, index) {
370
+ const result = requireRecord(value, `results[${index}]`);
371
+ assertExactKeys(result, ["controlId", "status", "evidence", "reason"], `results[${index}]`);
372
+ const controlId = requireText(result.controlId, `results[${index}].controlId`);
373
+ const status = result.status;
374
+ if (typeof status !== "string" || !isQualityReceiptResultStatus(status)) {
375
+ throw new Error(`Invalid results[${index}].status`);
376
+ }
377
+ let evidence;
378
+ if (hasOwn(result, "evidence")) {
379
+ if (typeof result.evidence !== "string") {
380
+ throw new Error(`Invalid results[${index}].evidence`);
381
+ }
382
+ evidence = result.evidence;
383
+ }
384
+ let reason;
385
+ if (hasOwn(result, "reason")) {
386
+ if (typeof result.reason !== "string") {
387
+ throw new Error(`Invalid results[${index}].reason`);
388
+ }
389
+ reason = result.reason;
390
+ }
391
+ if (status === "pass") {
392
+ if (evidence === void 0 || evidence.trim() === "") {
393
+ throw new Error(`Invalid results[${index}].evidence: pass requires evidence`);
394
+ }
395
+ return {
396
+ controlId,
397
+ status,
398
+ evidence,
399
+ ...reason === void 0 ? {} : { reason }
400
+ };
401
+ }
402
+ return {
403
+ controlId,
404
+ status,
405
+ ...evidence === void 0 ? {} : { evidence },
406
+ ...reason === void 0 ? {} : { reason }
407
+ };
408
+ }
409
+ function validateQualityReceipt(value, expectedIdentity) {
410
+ const receipt = requireRecord(value, "quality receipt");
411
+ if (receipt.namespace !== QUALITY_RECEIPT_NAMESPACE) {
412
+ throw new Error(`Invalid quality receipt namespace: ${String(receipt.namespace)}`);
413
+ }
414
+ if (receipt.version !== QUALITY_RECEIPT_VERSION) {
415
+ throw new Error(`Unsupported quality receipt version: ${String(receipt.version)}`);
416
+ }
417
+ assertExactKeys(receipt, ["namespace", "version", "authority", "identity", "commands", "results", "provenance"], "receipt");
418
+ if (receipt.authority !== "local" && receipt.authority !== "enforced") {
419
+ throw new Error(`Invalid quality receipt authority: ${String(receipt.authority)}`);
420
+ }
421
+ const identity = validateIdentity(receipt.identity, expectedIdentity);
422
+ if (!Array.isArray(receipt.commands)) throw new Error("Invalid quality receipt commands");
423
+ if (!Array.isArray(receipt.results)) throw new Error("Invalid quality receipt results");
424
+ for (let index = 0; index < receipt.commands.length; index += 1) {
425
+ validateCommand(receipt.commands[index], index);
426
+ }
427
+ for (let index = 0; index < receipt.results.length; index += 1) {
428
+ validateResult(receipt.results[index], index);
429
+ }
430
+ const hasProvenance = hasOwn(receipt, "provenance");
431
+ if (receipt.authority === "enforced" && (!hasProvenance || receipt.provenance === void 0)) {
432
+ throw new Error("Enforced quality receipts require provenance");
433
+ }
434
+ if (hasProvenance) {
435
+ if (receipt.provenance === void 0) throw new Error("Invalid quality receipt provenance");
436
+ validateProvenance(receipt.provenance);
437
+ }
438
+ void identity;
439
+ }
440
+ function createQualityReceipt(input) {
441
+ const authority = input.authority;
442
+ if (authority !== "local" && authority !== "enforced") {
443
+ throw new Error(`Invalid quality receipt authority: ${String(authority)}`);
444
+ }
445
+ if (input.authority === "enforced" && input.provenance === void 0) {
446
+ throw new Error("Enforced quality receipts require provenance");
447
+ }
448
+ const base = {
449
+ namespace: QUALITY_RECEIPT_NAMESPACE,
450
+ version: QUALITY_RECEIPT_VERSION,
451
+ identity: {
452
+ profile: input.identity.profile,
453
+ baseSha: input.identity.baseSha,
454
+ headSha: input.identity.headSha,
455
+ policyDigest: input.identity.policyDigest
456
+ },
457
+ commands: input.commands.map(normalizeCommand),
458
+ results: input.results.map(normalizeResult)
459
+ };
460
+ let receipt;
461
+ if (input.authority === "enforced") {
462
+ const provenance = input.provenance;
463
+ if (provenance === void 0) throw new Error("Enforced quality receipts require provenance");
464
+ receipt = {
465
+ ...base,
466
+ authority: "enforced",
467
+ provenance: normalizeProvenance(provenance)
468
+ };
469
+ } else {
470
+ receipt = {
471
+ ...base,
472
+ authority: "local",
473
+ ...input.provenance === void 0 ? {} : { provenance: normalizeProvenance(input.provenance) }
474
+ };
475
+ }
476
+ validateQualityReceipt(receipt);
477
+ return receipt;
478
+ }
479
+ function serializeQualityReceipt(receipt) {
480
+ validateQualityReceipt(receipt);
481
+ return canonicalJson(receipt);
482
+ }
483
+
484
+ export {
485
+ HOME,
486
+ stackRoot,
487
+ dataDir,
488
+ samePath,
489
+ QUALITY_PROFILES,
490
+ evaluateQualityPolicy,
491
+ canonicalJson,
492
+ sha256,
493
+ validateQualityReceipt,
494
+ createQualityReceipt,
495
+ serializeQualityReceipt
496
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Contrato central del proyecto (PRD §5): cada runtime implementa un Adapter
3
+ * que declara DÓNDE va cada cosa y CÓMO se escribe. Los componentes
4
+ * (src/components/) iteran (componente × adapter) sin switches por runtime.
5
+ */
6
+
7
+ type RuntimeId = "claude-code" | "codex" | "opencode";
8
+ type SelectableRuntimeId = RuntimeId | "pi";
9
+
10
+ declare const COMMANDS: readonly ["install", "sync", "models", "update", "doctor", "restore", "uninstall", "quality"];
11
+ type Command = (typeof COMMANDS)[number];
12
+ interface Flags {
13
+ agents: SelectableRuntimeId[];
14
+ targetDir?: string;
15
+ dryRun: boolean;
16
+ yes: boolean;
17
+ mode?: string;
18
+ subagentConcurrency?: string;
19
+ help: boolean;
20
+ version: boolean;
21
+ list: boolean;
22
+ check: boolean;
23
+ removeEngram: boolean;
24
+ playwright: boolean;
25
+ removePlaywright: boolean;
26
+ devtools: boolean;
27
+ noDevtools: boolean;
28
+ receipt?: string;
29
+ positional: string[];
30
+ unknownFlags: string[];
31
+ }
32
+ interface ParsedCli {
33
+ action: "run" | "help" | "version" | "unknown" | "unknown-flags";
34
+ command: Command;
35
+ flags: Flags;
36
+ unknownCommand?: string;
37
+ }
38
+ declare function parseFlags(args: string[], allowReceipt?: boolean): Flags;
39
+ declare function parseCliArgs(argv: string[]): ParsedCli;
40
+
41
+ export { type Command, type Flags, type ParsedCli, parseCliArgs, parseFlags };