@everfur/cli 0.1.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,1822 @@
1
+ #!/usr/bin/env node
2
+ import { failureDetails, describeFailure, isJsonObject, CAPABILITY_QUESTION, CAPABILITIES_TREE, loadDecisionSpace, loadBundledDecisionSpace, detectProject, planIntegration, resolveContractPath, validateContractFile, PARTNER_KEY, SESSION_TOKEN, resolveCredential, ADMIN_TOKEN, withPrefix, runVerification, PARTNER_SECRET_KEY, loadErrorRegistry, resolveEndpoint, isCorrelatableRequestId, EMPTY_REGISTRY, createApiClient, BASE_URL_ENV_VAR, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, IDENTITY_QUESTION, IDENTITY_TREE, BACKEND_LANGUAGE_QUESTION, SESSION_MINT_TREE, CREDENTIAL_SPECS } from './chunk-KQZ6ZXL7.js';
3
+ import { isAbsolute, resolve } from 'path';
4
+
5
+ // src/cli/exit-codes.ts
6
+ var ExitCode = Object.freeze({
7
+ /** The command produced a complete answer. */
8
+ OK: 0,
9
+ /** An unexpected internal failure. A bug in the CLI, or an unreadable filesystem. */
10
+ INTERNAL_ERROR: 1,
11
+ /** The invocation was malformed: unknown command, unknown flag, missing flag value. */
12
+ USAGE_ERROR: 2,
13
+ /** The CLI refused to guess. Required facts were missing; see `missingFacts`. */
14
+ UNDERSPECIFIED: 3,
15
+ /**
16
+ * A NAMED ARTIFACT was missing, unparseable, or failed shape validation against its JSON
17
+ * Schema. All three mean the same thing to a caller: a file this command was pointed at
18
+ * cannot be used, and a human has to look at that file.
19
+ *
20
+ * The wording was tightened from "failed shape validation" because `file_not_found` and
21
+ * `invalid_json` have always mapped here too, and a code whose documented meaning is
22
+ * narrower than its behaviour is a code CI branches on wrongly. What does NOT belong here is
23
+ * a bad INVOCATION: `--cwd /nope` is a usage error (2), because nothing was loaded.
24
+ */
25
+ INVALID_CONTRACT: 4,
26
+ /** A loaded artifact was well shaped but its digest did not match its content. */
27
+ DIGEST_MISMATCH: 5,
28
+ /**
29
+ * The API could not be reached, did not answer in time, or answered with a typed error.
30
+ * The invocation was fine and no local artifact is wrong: the far side is the problem, so a
31
+ * CI wrapper branching on this retries or pages rather than sending someone to edit a file.
32
+ */
33
+ REMOTE_FAILURE: 6,
34
+ /**
35
+ * `verify` completed and at least one step FAILED. Distinct from 6 because the wire worked:
36
+ * the CLI got answers and one of them was wrong, which is a report to read rather than an
37
+ * outage to retry. A run with no failures but a skipped step settles at 3, not here, because
38
+ * a skip means the operator did not supply enough to exercise that step.
39
+ */
40
+ VERIFICATION_FAILED: 7,
41
+ /**
42
+ * `sandbox create` provisioned a key, and the server offered no claim URL for its secret.
43
+ * The CLI will not print a raw secret, so it discarded the one it was handed unread. The key
44
+ * exists and nobody can use it; the human projection prints the revoke command.
45
+ */
46
+ SECRET_UNCLAIMABLE: 8,
47
+ /**
48
+ * `plan` was told everything it needed and NOTHING the caller asked for can be built: every
49
+ * capability named resolved to a BLOCKED terminal in the contract.
50
+ *
51
+ * Distinct from 3 because supplying another flag will not help, and distinct from 0 because
52
+ * no plan was produced. A CI wrapper branching on 3 prompts for more input, which would loop
53
+ * forever here; one branching on 9 reads the reason and the request path out of the document
54
+ * and escalates to a human, which is the only thing that resolves it.
55
+ */
56
+ BLOCKED: 9
57
+ });
58
+
59
+ // src/cli/format.ts
60
+ function withNewline(body) {
61
+ return body.length === 0 ? "" : `${body}
62
+ `;
63
+ }
64
+ function formatOutcome(outcome, options) {
65
+ if (options.json) {
66
+ return Object.freeze({
67
+ stdout: withNewline(JSON.stringify(outcome.json, null, 2)),
68
+ stderr: "",
69
+ exitCode: outcome.exitCode
70
+ });
71
+ }
72
+ const body = withNewline(outcome.text);
73
+ return Object.freeze({
74
+ stdout: outcome.stream === "stdout" ? body : "",
75
+ stderr: outcome.stream === "stderr" ? body : "",
76
+ exitCode: outcome.exitCode
77
+ });
78
+ }
79
+
80
+ // src/commands/plan/facts.ts
81
+ function known(space, treeId, questionId) {
82
+ if (space === null) return null;
83
+ const choices = space.choices(treeId, questionId);
84
+ return choices.length === 0 ? null : choices;
85
+ }
86
+ function parseCapabilities(raw, space) {
87
+ if (raw === null) return { value: null, errors: [] };
88
+ const allowed = known(space, CAPABILITIES_TREE, CAPABILITY_QUESTION);
89
+ const tokens = raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
90
+ if (tokens.length === 0) {
91
+ return {
92
+ value: null,
93
+ errors: [
94
+ allowed === null ? "--capabilities was empty; name at least one capability" : `--capabilities was empty; name at least one of: ${allowed.join(", ")}`
95
+ ]
96
+ };
97
+ }
98
+ const errors = [];
99
+ const selected = /* @__PURE__ */ new Set();
100
+ for (const token of tokens) {
101
+ if (allowed === null || allowed.includes(token)) {
102
+ selected.add(token);
103
+ continue;
104
+ }
105
+ errors.push(`unknown capability "${token}"; supported: ${allowed.join(", ")}`);
106
+ }
107
+ if (errors.length > 0) return { value: null, errors };
108
+ return { value: Object.freeze([...selected].sort()), errors: [] };
109
+ }
110
+ function parseChoice(raw, space, treeId, questionId, flag) {
111
+ if (raw === null) return { value: null, errors: [] };
112
+ const token = raw.trim();
113
+ const allowed = known(space, treeId, questionId);
114
+ if (allowed === null || allowed.includes(token)) return { value: token, errors: [] };
115
+ return { value: null, errors: [`unknown ${flag} "${raw}"; supported: ${allowed.join(", ")}`] };
116
+ }
117
+ var IDENTITY_ALIASES = Object.freeze({ personalized: "partner-user-ref" });
118
+ function parseIdentityMode(raw, space) {
119
+ const normalized = raw === null ? null : IDENTITY_ALIASES[raw.trim()] ?? raw;
120
+ return parseChoice(normalized, space, IDENTITY_TREE, IDENTITY_QUESTION, "identity mode");
121
+ }
122
+ function parseSessionMintLanguage(raw, space) {
123
+ return parseChoice(raw, space, SESSION_MINT_TREE, BACKEND_LANGUAGE_QUESTION, "backend language");
124
+ }
125
+ function parseSuppliedFacts(capabilitiesRaw, identityRaw, backendRaw, space) {
126
+ const capabilities = parseCapabilities(capabilitiesRaw, space);
127
+ const identity = parseIdentityMode(identityRaw, space);
128
+ const backend = parseSessionMintLanguage(backendRaw, space);
129
+ const errors = [...capabilities.errors, ...identity.errors, ...backend.errors];
130
+ return Object.freeze({
131
+ ok: errors.length === 0,
132
+ capabilities: capabilities.value,
133
+ identityMode: identity.value,
134
+ sessionMintLanguage: backend.value,
135
+ errors: Object.freeze(errors)
136
+ });
137
+ }
138
+
139
+ // src/commands/plan/render.ts
140
+ function indent(lines, prefix) {
141
+ return lines.map((line) => `${prefix}${line}`);
142
+ }
143
+ function renderDetected(document) {
144
+ const { detected } = document;
145
+ const lines = ["Detected", ` root: ${detected.root}`];
146
+ lines.push(` project type: ${detected.projectType}`);
147
+ lines.push(...indent(detected.projectTypeEvidence, " - "));
148
+ lines.push(` package manager: ${detected.packageManager}`);
149
+ lines.push(...indent(detected.packageManagerEvidence, " - "));
150
+ if (detected.dependencies.length === 0) {
151
+ lines.push(" dependencies: none of the relevant packages are declared");
152
+ return lines;
153
+ }
154
+ lines.push(" dependencies:");
155
+ for (const dependency of detected.dependencies) {
156
+ const declared = dependency.declared ?? "(none)";
157
+ const installed = dependency.installed === null ? "not installed" : `installed ${dependency.installed}`;
158
+ lines.push(` ${dependency.name} ${declared} (${installed}, ${dependency.scope}, ${dependency.source})`);
159
+ }
160
+ return lines;
161
+ }
162
+ function renderContract(document) {
163
+ const { decisionSpace } = document;
164
+ if (decisionSpace === null) return [];
165
+ return [
166
+ "Decision space",
167
+ ` source: ${decisionSpace.source}`,
168
+ ` digest: ${decisionSpace.digest}`,
169
+ ` trees: ${decisionSpace.trees.join(", ")}`
170
+ ];
171
+ }
172
+ function renderBlocked(document) {
173
+ const lines = [
174
+ `Blocked (${document.blocked.length})`,
175
+ " These cannot be built today. No steps below cover them."
176
+ ];
177
+ for (const blocked of document.blocked) {
178
+ lines.push("");
179
+ lines.push(` ${blocked.capability}`);
180
+ lines.push(` why: ${blocked.reason}`);
181
+ lines.push(` ask: ${blocked.unblockOwner}`);
182
+ lines.push(` at: ${blocked.requestPath}`);
183
+ if (blocked.capabilityKeys.length > 0) {
184
+ lines.push(` covers: ${blocked.capabilityKeys.join(", ")}`);
185
+ }
186
+ }
187
+ return lines;
188
+ }
189
+ function renderWarnings(document) {
190
+ const lines = ["Warnings"];
191
+ for (const warning of document.warnings) {
192
+ lines.push(` [${warning.code}] ${warning.message}`);
193
+ }
194
+ return lines;
195
+ }
196
+ function renderPrinciples(document) {
197
+ const lines = ["Always true"];
198
+ for (const principle of document.principles) {
199
+ lines.push("");
200
+ lines.push(` ${principle.title}`);
201
+ lines.push(` ${principle.statement}`);
202
+ if (principle.docPath !== null) lines.push(` see: ${principle.docPath}`);
203
+ }
204
+ return lines;
205
+ }
206
+ function renderRefusal(document) {
207
+ const lines = [
208
+ "everfur plan: refusing to guess.",
209
+ "",
210
+ `${document.missingFacts.length} required fact${document.missingFacts.length === 1 ? "" : "s"} missing:`
211
+ ];
212
+ for (const fact of document.missingFacts) {
213
+ lines.push("");
214
+ lines.push(` ${fact.fact}`);
215
+ lines.push(` why: ${fact.reason}`);
216
+ lines.push(` supply: ${fact.supplyWith}`);
217
+ }
218
+ lines.push("");
219
+ lines.push(...renderDetected(document));
220
+ if (document.blocked.length > 0) {
221
+ lines.push("");
222
+ lines.push(...renderBlocked(document));
223
+ }
224
+ if (document.warnings.length > 0) {
225
+ lines.push("");
226
+ lines.push(...renderWarnings(document));
227
+ }
228
+ lines.push("");
229
+ lines.push("No plan was produced. Supply the facts above and run again.");
230
+ return lines.join("\n");
231
+ }
232
+ function renderBlockedPlan(document) {
233
+ const lines = [
234
+ "everfur plan: nothing you asked for can be built today.",
235
+ "",
236
+ ...renderBlocked(document),
237
+ "",
238
+ ...renderDetected(document)
239
+ ];
240
+ if (document.warnings.length > 0) {
241
+ lines.push("");
242
+ lines.push(...renderWarnings(document));
243
+ }
244
+ const contract = renderContract(document);
245
+ if (contract.length > 0) {
246
+ lines.push("");
247
+ lines.push(...contract);
248
+ }
249
+ lines.push("");
250
+ lines.push("No steps were produced. An install plan for a surface that answers 404 is not a smaller plan, it is a wrong one.");
251
+ return lines.join("\n");
252
+ }
253
+ function renderReady(document) {
254
+ const capabilities = document.supplied.capabilities ?? [];
255
+ const lines = [
256
+ `everfur plan: ${document.steps.length} step${document.steps.length === 1 ? "" : "s"} for a ${document.detected.projectType} project.`,
257
+ "",
258
+ ...renderDetected(document),
259
+ "",
260
+ "Supplied",
261
+ ` capabilities: ${capabilities.join(", ")}`,
262
+ ` identity mode: ${document.supplied.identityMode ?? "(none)"}`
263
+ ];
264
+ if (document.supplied.sessionMintLanguage !== null) {
265
+ lines.push(` mint language: ${document.supplied.sessionMintLanguage}`);
266
+ }
267
+ lines.push("", "Plan");
268
+ document.steps.forEach((step, index) => {
269
+ lines.push("");
270
+ lines.push(` ${index + 1}. [${step.phase}] ${step.title}`);
271
+ lines.push(` ${step.detail}`);
272
+ for (const command of step.commands) lines.push(` $ ${command}`);
273
+ for (const file of step.files) lines.push(` file: ${file}`);
274
+ });
275
+ if (document.blocked.length > 0) {
276
+ lines.push("");
277
+ lines.push(...renderBlocked(document));
278
+ }
279
+ if (document.warnings.length > 0) {
280
+ lines.push("");
281
+ lines.push(...renderWarnings(document));
282
+ }
283
+ if (document.principles.length > 0) {
284
+ lines.push("");
285
+ lines.push(...renderPrinciples(document));
286
+ }
287
+ const contract = renderContract(document);
288
+ if (contract.length > 0) {
289
+ lines.push("");
290
+ lines.push(...contract);
291
+ }
292
+ return lines.join("\n");
293
+ }
294
+ function renderPlan(document) {
295
+ if (document.status === "underspecified") return renderRefusal(document);
296
+ if (document.status === "blocked") return renderBlockedPlan(document);
297
+ return renderReady(document);
298
+ }
299
+
300
+ // src/cli/outcome.ts
301
+ function succeed(json, text) {
302
+ return Object.freeze({ exitCode: 0, json, text, stream: "stdout" });
303
+ }
304
+ function fail(exitCode, json, text) {
305
+ return Object.freeze({ exitCode, json, text, stream: "stderr" });
306
+ }
307
+
308
+ // src/cli/errors.ts
309
+ var EXIT_FOR_CODE = Object.freeze({
310
+ usage_error: ExitCode.USAGE_ERROR,
311
+ unknown_command: ExitCode.USAGE_ERROR,
312
+ /**
313
+ * A credential the command needs is not exported. That is a bad INVOCATION of the same shape
314
+ * as a missing required flag, so it settles at 2 and the message names the variable. It is
315
+ * deliberately NOT 6: nothing was sent, so the far side is not implicated.
316
+ */
317
+ credential_missing: ExitCode.USAGE_ERROR,
318
+ /** `explain-error` was given a code the loaded registry does not list. A bad argument. */
319
+ unknown_error_code: ExitCode.USAGE_ERROR,
320
+ file_not_found: ExitCode.INVALID_CONTRACT,
321
+ invalid_json: ExitCode.INVALID_CONTRACT,
322
+ invalid_contract: ExitCode.INVALID_CONTRACT,
323
+ digest_mismatch: ExitCode.DIGEST_MISMATCH,
324
+ /**
325
+ * The three remote failures share exit 6 and stay distinct in the document, for the same
326
+ * reason `file_not_found` and `invalid_json` share 4: one branch for a wrapper, one action
327
+ * for an operator, and full detail for anything reading `error.code`.
328
+ */
329
+ remote_unreachable: ExitCode.REMOTE_FAILURE,
330
+ remote_error: ExitCode.REMOTE_FAILURE,
331
+ malformed_response: ExitCode.REMOTE_FAILURE,
332
+ internal_error: ExitCode.INTERNAL_ERROR
333
+ });
334
+ function exitCodeFor(code) {
335
+ return EXIT_FOR_CODE[code];
336
+ }
337
+ function errorOutcome(code, message, details = []) {
338
+ const document = {
339
+ schemaVersion: 1,
340
+ kind: "everfur.error",
341
+ error: { code, message, details }
342
+ };
343
+ const lines = [`error: ${message}`, ...details.map((detail) => ` ${detail}`)];
344
+ return fail(exitCodeFor(code), document, lines.join("\n"));
345
+ }
346
+
347
+ // src/http/outcome.ts
348
+ var CODE_FOR_KIND = Object.freeze({
349
+ network: "remote_unreachable",
350
+ timeout: "remote_unreachable",
351
+ http: "remote_error",
352
+ malformed: "malformed_response"
353
+ });
354
+ function failureOutcome(failure, extraDetails = []) {
355
+ return errorOutcome(CODE_FOR_KIND[failure.kind], describeFailure(failure), [
356
+ ...failureDetails(failure),
357
+ ...extraDetails
358
+ ]);
359
+ }
360
+
361
+ // src/commands/diagnose/document.ts
362
+ var DIAGNOSIS_SCHEMA_VERSION = 1;
363
+ var DIAGNOSIS_DOCUMENT_KIND = "everfur.diagnosis";
364
+ function countByReasonCode(diagnosis) {
365
+ const counts = /* @__PURE__ */ new Map();
366
+ for (const capability of diagnosis.capabilities) {
367
+ counts.set(capability.reasonCode, (counts.get(capability.reasonCode) ?? 0) + 1);
368
+ }
369
+ const sorted = [...counts.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
370
+ return Object.fromEntries(sorted);
371
+ }
372
+ function buildDiagnosisDocument(input) {
373
+ const { diagnosis } = input;
374
+ const available = diagnosis.capabilities.filter((capability) => capability.available).length;
375
+ const capabilities = diagnosis.capabilities.map((capability) => ({
376
+ key: capability.key,
377
+ available: capability.available,
378
+ reasonCode: capability.reasonCode,
379
+ reason: capability.reason,
380
+ entitlementReasonCode: capability.entitlementReasonCode,
381
+ decidedAtLevel: capability.decidedAtLevel,
382
+ decidedAtKind: capability.decidedAtKind,
383
+ surface: capability.surface,
384
+ nextAction: capability.nextAction
385
+ }));
386
+ return {
387
+ schemaVersion: DIAGNOSIS_SCHEMA_VERSION,
388
+ kind: DIAGNOSIS_DOCUMENT_KIND,
389
+ generatedBy: { name: "@everfur/cli", version: input.cliVersion },
390
+ endpoint: { baseUrl: input.baseUrl, path: input.path },
391
+ requestId: diagnosis.requestId,
392
+ evaluatedAt: diagnosis.evaluatedAt,
393
+ client: { id: diagnosis.clientId, slug: diagnosis.clientSlug },
394
+ auth: {
395
+ mode: diagnosis.auth.mode,
396
+ anonymous: diagnosis.auth.anonymous,
397
+ userRefPrefix: diagnosis.auth.userRefPrefix,
398
+ userRole: diagnosis.auth.userRole,
399
+ roleSource: diagnosis.auth.roleSource,
400
+ sessionId: diagnosis.auth.sessionId,
401
+ partnerKeyScopeResolved: diagnosis.auth.partnerKeyScopeResolved
402
+ },
403
+ scope: {
404
+ provisioned: diagnosis.scope.provisioned,
405
+ resolverStatus: diagnosis.scope.resolverStatus,
406
+ revision: diagnosis.scope.revision,
407
+ region: diagnosis.scope.region,
408
+ levels: diagnosis.scope.levels.map((level) => ({
409
+ level: level.level,
410
+ kind: level.kind,
411
+ present: level.present
412
+ }))
413
+ },
414
+ summary: {
415
+ total: diagnosis.capabilities.length,
416
+ available,
417
+ unavailable: diagnosis.capabilities.length - available,
418
+ byReasonCode: countByReasonCode(diagnosis)
419
+ },
420
+ capabilities
421
+ };
422
+ }
423
+
424
+ // src/commands/diagnose/reasons.ts
425
+ var REASON_CODES = Object.freeze([
426
+ "granted",
427
+ "scope_not_provisioned",
428
+ "capability_denied",
429
+ "outside_ceiling",
430
+ "not_in_grant",
431
+ "runtime_gate_off",
432
+ "gate_unknown",
433
+ "platform_invariant",
434
+ "resolver_unavailable"
435
+ ]);
436
+ function isKnownReasonCode(value) {
437
+ return REASON_CODES.includes(value);
438
+ }
439
+ var NEXT_ACTION = Object.freeze({
440
+ granted: "Nothing to fix here. If a call against it still fails, run `everfur verify` and quote the request id.",
441
+ scope_not_provisioned: "Ask Everfur support to provision an entitlement scope for this tenant, quoting the client id and request id. Nothing on your side can make this resolve.",
442
+ capability_denied: "A deny was set deliberately at the level printed beside the key. Ask your Everfur contact to lift it; re-integrating will not change the answer.",
443
+ outside_ceiling: "This key is outside your tenant's agreed subset of the platform. That is a commercial change rather than a configuration one: raise it with your Everfur contact.",
444
+ not_in_grant: "Your scope is healthy and this key is simply not in the platform grant it resolves against. Ask Everfur support whether it is meant to be in your bundle.",
445
+ runtime_gate_off: "The lattice grants this and the surface is switched off, so its routes answer 404 whatever the verdict says. Ask Everfur to enable the named surface; entitlement changes will not help.",
446
+ gate_unknown: "Report this. A key was granted to you that the capability contract does not describe, so its runtime posture could not be read. Quote the key and the request id.",
447
+ platform_invariant: "Nothing to do. A platform invariant is never resolved through the entitlement lattice, so it is not a capability you can be granted.",
448
+ resolver_unavailable: "The state is UNKNOWN rather than denied. Retry in a few seconds; if it persists, quote the request id to Everfur support."
449
+ });
450
+ function nextActionFor(code) {
451
+ if (isKnownReasonCode(code)) return NEXT_ACTION[code];
452
+ return `This cause is newer than this CLI. Upgrade @everfur/cli, and quote the code "${code}" and the request id to Everfur support.`;
453
+ }
454
+
455
+ // src/commands/diagnose/parse.ts
456
+ function str(record, key, fallback = "") {
457
+ const value = record[key];
458
+ return typeof value === "string" ? value : fallback;
459
+ }
460
+ function bool(record, key) {
461
+ return record[key] === true;
462
+ }
463
+ function num(record, key, fallback = 0) {
464
+ const value = record[key];
465
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
466
+ }
467
+ function optionalNumber(record, key) {
468
+ const value = record[key];
469
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
470
+ }
471
+ function optionalString(record, key) {
472
+ const value = record[key];
473
+ return typeof value === "string" && value.length > 0 ? value : null;
474
+ }
475
+ function parseLevels(raw) {
476
+ if (!Array.isArray(raw)) return [];
477
+ const levels = [];
478
+ for (const item of raw) {
479
+ if (!isJsonObject(item)) continue;
480
+ levels.push(
481
+ Object.freeze({ level: num(item, "level"), kind: str(item, "kind", "unknown"), present: bool(item, "present") })
482
+ );
483
+ }
484
+ return Object.freeze(levels);
485
+ }
486
+ function parseCapability(raw) {
487
+ if (!isJsonObject(raw)) return null;
488
+ const key = str(raw, "key");
489
+ if (key.length === 0) return null;
490
+ const reasonCode = str(raw, "reason_code", "unknown");
491
+ return Object.freeze({
492
+ key,
493
+ available: bool(raw, "available"),
494
+ reasonCode,
495
+ reason: str(raw, "reason"),
496
+ entitlementReasonCode: str(raw, "entitlement_reason_code", reasonCode),
497
+ decidedAtLevel: optionalNumber(raw, "decided_at_level"),
498
+ decidedAtKind: optionalString(raw, "decided_at_kind"),
499
+ surface: optionalString(raw, "surface"),
500
+ nextAction: nextActionFor(reasonCode)
501
+ });
502
+ }
503
+ function parseDiagnostics(body, headerRequestId) {
504
+ if (!isJsonObject(body)) return { ok: false, detail: "the body is not a JSON object" };
505
+ const rawCapabilities = body["capabilities"];
506
+ if (!Array.isArray(rawCapabilities)) {
507
+ return { ok: false, detail: "the body carries no `capabilities` array" };
508
+ }
509
+ const rawScope = body["scope"];
510
+ if (!isJsonObject(rawScope)) return { ok: false, detail: "the body carries no `scope` object" };
511
+ const rawAuth = body["auth"];
512
+ if (!isJsonObject(rawAuth)) return { ok: false, detail: "the body carries no `auth` object" };
513
+ const capabilities = rawCapabilities.map(parseCapability).filter((entry) => entry !== null);
514
+ return {
515
+ ok: true,
516
+ diagnosis: Object.freeze({
517
+ requestId: optionalString(body, "request_id") ?? headerRequestId,
518
+ evaluatedAt: str(body, "evaluated_at"),
519
+ clientId: str(body, "client_id"),
520
+ clientSlug: str(body, "client_slug"),
521
+ auth: Object.freeze({
522
+ mode: str(rawAuth, "mode", "unknown"),
523
+ anonymous: bool(rawAuth, "anonymous"),
524
+ userRefPrefix: str(rawAuth, "user_ref_prefix"),
525
+ userRole: str(rawAuth, "user_role"),
526
+ roleSource: str(rawAuth, "role_source"),
527
+ sessionId: str(rawAuth, "session_id"),
528
+ partnerKeyScopeResolved: bool(rawAuth, "partner_key_scope_resolved")
529
+ }),
530
+ scope: Object.freeze({
531
+ provisioned: bool(rawScope, "provisioned"),
532
+ resolverStatus: str(rawScope, "resolver_status", "unknown"),
533
+ revision: num(rawScope, "revision"),
534
+ region: str(rawScope, "region", "unknown"),
535
+ levels: parseLevels(rawScope["levels"])
536
+ }),
537
+ capabilities: Object.freeze(capabilities)
538
+ })
539
+ };
540
+ }
541
+
542
+ // src/commands/diagnose/render.ts
543
+ function renderLevels(diagnosis) {
544
+ if (diagnosis.scope.levels.length === 0) return "(none reported)";
545
+ return diagnosis.scope.levels.map((level) => `L${level.level} ${level.kind}${level.present ? "" : " (absent)"}`).join(", ");
546
+ }
547
+ function renderDiagnosis(input) {
548
+ const { diagnosis } = input;
549
+ const available = diagnosis.capabilities.filter((capability) => capability.available);
550
+ const unavailable = diagnosis.capabilities.filter((capability) => !capability.available);
551
+ const lines = [
552
+ `everfur diagnose: ${available.length} of ${diagnosis.capabilities.length} capabilities available.`,
553
+ "",
554
+ "Connection",
555
+ ` base url: ${input.baseUrl}`,
556
+ ` request id: ${diagnosis.requestId ?? "(the response carried none)"}`,
557
+ ` evaluated at: ${diagnosis.evaluatedAt}`,
558
+ "",
559
+ "Caller",
560
+ ` client: ${diagnosis.clientSlug} (${diagnosis.clientId})`,
561
+ ` auth mode: ${diagnosis.auth.mode}${diagnosis.auth.anonymous ? " (anonymous)" : ""}`,
562
+ ` user ref: ${diagnosis.auth.userRefPrefix}... as ${diagnosis.auth.userRole} (${diagnosis.auth.roleSource})`,
563
+ ` session id: ${diagnosis.auth.sessionId}`,
564
+ ` key scope: ${diagnosis.auth.partnerKeyScopeResolved ? "resolved" : "not resolved"}`,
565
+ "",
566
+ "Scope",
567
+ ` provisioned: ${diagnosis.scope.provisioned ? "yes" : "no"}`,
568
+ ` resolver: ${diagnosis.scope.resolverStatus}`,
569
+ ` revision: ${diagnosis.scope.revision}`,
570
+ ` region: ${diagnosis.scope.region}`,
571
+ ` levels: ${renderLevels(diagnosis)}`
572
+ ];
573
+ lines.push("", `Available (${available.length})`);
574
+ if (available.length === 0) {
575
+ lines.push(" none");
576
+ } else {
577
+ for (const capability of available) lines.push(` ${capability.key}`);
578
+ }
579
+ lines.push("", `Unavailable (${unavailable.length})`);
580
+ if (unavailable.length === 0) {
581
+ lines.push(" none");
582
+ return lines.join("\n");
583
+ }
584
+ const groups = /* @__PURE__ */ new Map();
585
+ for (const capability of unavailable) {
586
+ const existing = groups.get(capability.reasonCode);
587
+ if (existing === void 0) groups.set(capability.reasonCode, [capability]);
588
+ else existing.push(capability);
589
+ }
590
+ for (const [code, members] of groups) {
591
+ const first = members[0];
592
+ if (first === void 0) continue;
593
+ lines.push("");
594
+ lines.push(` [${code}] ${members.length} capabilit${members.length === 1 ? "y" : "ies"}`);
595
+ for (const member of members) {
596
+ const decided = member.decidedAtLevel === null ? "" : ` (decided at L${member.decidedAtLevel} ${member.decidedAtKind ?? "unknown"})`;
597
+ const surface = member.surface === null ? "" : ` (surface ${member.surface})`;
598
+ const masked = member.entitlementReasonCode === member.reasonCode ? "" : ` (entitlement says ${member.entitlementReasonCode})`;
599
+ lines.push(` ${member.key}${decided}${surface}${masked}`);
600
+ }
601
+ if (first.reason.length > 0) lines.push(` why: ${first.reason}`);
602
+ lines.push(` next: ${first.nextAction}`);
603
+ }
604
+ return lines.join("\n");
605
+ }
606
+
607
+ // src/commands/explain-error/explain.ts
608
+ var EXPLANATION_SCHEMA_VERSION = 1;
609
+ var EXPLANATION_DOCUMENT_KIND = "everfur.error-explanation";
610
+ function nextActionFor2(entry) {
611
+ if (entry.retryable) {
612
+ if (entry.httpStatus === 429) {
613
+ return "Back off and retry. This is a rate limit, so retrying faster makes it worse; honour Retry-After if the response carried one.";
614
+ }
615
+ return "Retry with backoff. If it survives a few attempts, quote the request id from the failing response to Everfur support.";
616
+ }
617
+ switch (entry.httpStatus) {
618
+ case 400:
619
+ case 413:
620
+ case 415:
621
+ case 422:
622
+ return "Fix the request before sending it again. Branch on the HTTP status as well as the code: this code is the default for 400, 413, 415 and 422 alike, so a payload that is too large looks exactly like a bad field.";
623
+ case 401:
624
+ return "The credential was refused or the session expired. Mint a fresh session and retry once; every credential failure returns the same opaque 401, so do not read the message as a hint about which one.";
625
+ case 402:
626
+ return "The tenant needs a plan change. This is a commercial step, not a configuration one.";
627
+ case 403:
628
+ return "Authenticated but not permitted. Run `everfur diagnose` to see which fence blocked it and what the next action for that fence is.";
629
+ case 404:
630
+ return "Either the resource does not exist for this caller, or its surface is gated off and answers 404 by design. `everfur diagnose` tells the two apart.";
631
+ case 409:
632
+ return "A conflict, not a transient fault. Do not repeat the same request; build a new one.";
633
+ case 501:
634
+ return "The route exists and the behaviour does not. Nothing on the client side will change the answer.";
635
+ default:
636
+ return entry.httpStatus >= 500 ? "A server fault. Retry once, then quote the request id from the failing response to Everfur support." : "Quote the code and the request id from the failing response to Everfur support.";
637
+ }
638
+ }
639
+ function buildExplanationDocument(input) {
640
+ const { entry } = input;
641
+ return {
642
+ schemaVersion: EXPLANATION_SCHEMA_VERSION,
643
+ kind: EXPLANATION_DOCUMENT_KIND,
644
+ generatedBy: { name: "@everfur/cli", version: input.cliVersion },
645
+ code: entry.code,
646
+ source: { path: input.contractPath, digest: input.digest },
647
+ envelope: entry.envelope,
648
+ httpStatus: entry.httpStatus,
649
+ namingFamily: entry.namingFamily,
650
+ retryable: entry.retryable,
651
+ confirmedOnWire: entry.confirmedOnWire,
652
+ surfaces: [...entry.surfaces],
653
+ cause: entry.note,
654
+ display: entry.display,
655
+ docUrl: entry.docUrl,
656
+ nextAction: nextActionFor2(entry)
657
+ };
658
+ }
659
+ function renderExplanation(input) {
660
+ const { entry } = input;
661
+ const lines = [
662
+ `${entry.code} (HTTP ${entry.httpStatus}, ${entry.envelope})`,
663
+ "",
664
+ "Cause",
665
+ ` ${entry.note.length > 0 ? entry.note : "(the registry carries no note for this code)"}`,
666
+ "",
667
+ "Retryable",
668
+ ` ${entry.retryable ? "yes" : "no"}`,
669
+ "",
670
+ "Next action",
671
+ ` ${nextActionFor2(entry)}`,
672
+ "",
673
+ "Detail",
674
+ ` naming family: ${entry.namingFamily}`,
675
+ ` surfaces: ${entry.surfaces.length === 0 ? "(none listed)" : entry.surfaces.join(", ")}`,
676
+ ` confirmed on wire: ${entry.confirmedOnWire ? "yes" : "no"}`,
677
+ ` docs: ${entry.docUrl}`,
678
+ ` safe copy: ${entry.display}`,
679
+ "",
680
+ "Source",
681
+ ` ${input.contractPath}`,
682
+ ` digest (sha256): ${input.digest}`
683
+ ];
684
+ if (!entry.confirmedOnWire) {
685
+ lines.push(
686
+ "",
687
+ "This code exists in source but the compiler could not trace a live route emitting it.",
688
+ "Treat it as documentation rather than as a contract to branch on."
689
+ );
690
+ }
691
+ return lines.join("\n");
692
+ }
693
+
694
+ // src/commands/explain-error/suggest.ts
695
+ var MAX_SUGGESTIONS = 5;
696
+ var MAX_DISTANCE = 3;
697
+ function editDistance(left, right) {
698
+ if (left === right) return 0;
699
+ if (left.length === 0) return right.length;
700
+ if (right.length === 0) return left.length;
701
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
702
+ for (let row = 1; row <= left.length; row += 1) {
703
+ const current = [row];
704
+ for (let column = 1; column <= right.length; column += 1) {
705
+ const substitution = (previous[column - 1] ?? 0) + (left[row - 1] === right[column - 1] ? 0 : 1);
706
+ const deletion = (previous[column] ?? 0) + 1;
707
+ const insertion = (current[column - 1] ?? 0) + 1;
708
+ current[column] = Math.min(substitution, deletion, insertion);
709
+ }
710
+ previous = current;
711
+ }
712
+ return previous[right.length] ?? Math.max(left.length, right.length);
713
+ }
714
+ function suggestCodes(query, codes) {
715
+ const needle = query.toLowerCase();
716
+ const exact = [];
717
+ const contains = [];
718
+ const near = [];
719
+ for (const code of codes) {
720
+ const lower = code.toLowerCase();
721
+ if (lower === needle) {
722
+ exact.push(code);
723
+ continue;
724
+ }
725
+ if (lower.includes(needle) || needle.includes(lower)) {
726
+ contains.push(code);
727
+ continue;
728
+ }
729
+ const distance = editDistance(needle, lower);
730
+ if (distance <= MAX_DISTANCE) near.push({ code, distance });
731
+ }
732
+ near.sort((left, right) => left.distance - right.distance || (left.code < right.code ? -1 : 1));
733
+ return Object.freeze([...exact, ...contains.sort(), ...near.map((entry) => entry.code)].slice(0, MAX_SUGGESTIONS));
734
+ }
735
+
736
+ // src/commands/sandbox/create.ts
737
+ function readString(record, key) {
738
+ const value = record[key];
739
+ return typeof value === "string" && value.length > 0 ? value : null;
740
+ }
741
+ function projectSandboxCreate(body) {
742
+ if (!isJsonObject(body)) return { ok: false, detail: "the body is not a JSON object" };
743
+ const keyId = readString(body, "key_id");
744
+ if (keyId === null) return { ok: false, detail: "the body carries no `key_id`" };
745
+ const claimUrl = readString(body, "claim_url");
746
+ return {
747
+ ok: true,
748
+ creation: Object.freeze({
749
+ status: claimUrl === null ? "claim_unavailable" : "claim_issued",
750
+ keyId,
751
+ clientId: readString(body, "client_id") ?? "",
752
+ label: readString(body, "label"),
753
+ createdAt: readString(body, "created_at"),
754
+ claimUrl,
755
+ claimExpiresAt: readString(body, "claim_expires_at"),
756
+ secretDiscarded: typeof body["secret_value"] === "string"
757
+ })
758
+ };
759
+ }
760
+ var SANDBOX_SCHEMA_VERSION = 1;
761
+ var SANDBOX_DOCUMENT_KIND = "everfur.sandbox-create";
762
+ function revokePathFor(creation) {
763
+ return `/admin/clients/${encodeURIComponent(creation.clientId)}/secret-keys/${encodeURIComponent(creation.keyId)}`;
764
+ }
765
+ function nextActionsFor(creation, revocation) {
766
+ if (creation.status === "claim_issued") {
767
+ return Object.freeze([
768
+ "Open the claim URL in a browser and copy the secret from there.",
769
+ "The claim URL is single use and expires; a lapsed claim needs a new key."
770
+ ]);
771
+ }
772
+ if (revocation === "revoke_failed") {
773
+ return Object.freeze([
774
+ "The server returned no claim URL, so the secret it minted was discarded unread and cannot be recovered.",
775
+ "This command tried to revoke the orphaned key and the revoke did not succeed, so a live credential nobody holds still exists.",
776
+ `Revoke it by hand: DELETE /api/v1${revokePathFor(creation)}`,
777
+ "Until the claim flow exists server side, issue partner secrets through the admin route directly."
778
+ ]);
779
+ }
780
+ return Object.freeze([
781
+ "The server returned no claim URL, so the secret it minted was discarded unread and cannot be recovered.",
782
+ "The key this command minted was revoked again immediately, so nothing live was left behind.",
783
+ "Until the claim flow exists server side, issue partner secrets through the admin route directly."
784
+ ]);
785
+ }
786
+ function buildSandboxDocument(input) {
787
+ const { creation, revocation } = input;
788
+ return {
789
+ schemaVersion: SANDBOX_SCHEMA_VERSION,
790
+ kind: SANDBOX_DOCUMENT_KIND,
791
+ generatedBy: { name: "@everfur/cli", version: input.cliVersion },
792
+ status: creation.status,
793
+ requestId: input.requestId,
794
+ clientId: creation.clientId,
795
+ keyId: creation.keyId,
796
+ label: creation.label,
797
+ createdAt: creation.createdAt,
798
+ claimUrl: creation.claimUrl,
799
+ claimExpiresAt: creation.claimExpiresAt,
800
+ /**
801
+ * A literal `false` pinned by the schema. A machine reading this document can assert the
802
+ * invariant rather than trusting the prose, and any change that ever made it true would
803
+ * fail the contract test before it shipped.
804
+ */
805
+ secretDisclosed: false,
806
+ secretDiscarded: creation.secretDiscarded,
807
+ /**
808
+ * What became of an unclaimable key. `revoke_failed` is the one value that means a live
809
+ * credential nobody holds still exists, so a wrapper can branch on it rather than parse
810
+ * the prose in `nextActions`.
811
+ */
812
+ orphanedKey: revocation,
813
+ nextActions: nextActionsFor(creation, revocation)
814
+ };
815
+ }
816
+ function renderSandboxCreate(creation, revocation, requestId) {
817
+ const lines = [
818
+ creation.status === "claim_issued" ? "everfur sandbox create: key provisioned, claim it in a browser." : "everfur sandbox create: key provisioned, but there is no way to claim its secret.",
819
+ "",
820
+ ` key id: ${creation.keyId}`,
821
+ ` client id: ${creation.clientId}`,
822
+ ` label: ${creation.label ?? "(none)"}`,
823
+ ` created at: ${creation.createdAt ?? "(not reported)"}`,
824
+ ` request id: ${requestId ?? "(the response carried none)"}`
825
+ ];
826
+ if (creation.status === "claim_issued") {
827
+ lines.push(` claim url: ${creation.claimUrl ?? ""}`);
828
+ lines.push(` claim until: ${creation.claimExpiresAt ?? "(not reported)"}`);
829
+ } else {
830
+ lines.push(" claim url: none was offered");
831
+ }
832
+ lines.push("");
833
+ lines.push("The secret itself is never printed by this CLI, in either output mode.");
834
+ if (creation.secretDiscarded) {
835
+ lines.push("The response carried a raw secret. It was discarded unread and is unrecoverable.");
836
+ }
837
+ if (revocation === "revoked") {
838
+ lines.push("The unclaimable key was revoked again immediately, so nothing live was left behind.");
839
+ } else if (revocation === "revoke_failed") {
840
+ lines.push("The revoke of the unclaimable key FAILED. A live credential nobody holds still exists.");
841
+ }
842
+ lines.push("");
843
+ lines.push("Next");
844
+ for (const action of nextActionsFor(creation, revocation)) lines.push(` - ${action}`);
845
+ return lines.join("\n");
846
+ }
847
+ var BASE_URL_FLAG = Object.freeze({
848
+ name: "base-url",
849
+ kind: "string",
850
+ summary: `API base URL. Defaults to ${BASE_URL_ENV_VAR}.`,
851
+ placeholder: "url"
852
+ });
853
+ var TIMEOUT_FLAG = Object.freeze({
854
+ name: "timeout",
855
+ kind: "string",
856
+ summary: `Per attempt timeout in milliseconds (${MIN_TIMEOUT_MS}-${MAX_TIMEOUT_MS}, default ${DEFAULT_TIMEOUT_MS}).`,
857
+ placeholder: "ms"
858
+ });
859
+ var REQUEST_ID_FLAG = Object.freeze({
860
+ name: "request-id",
861
+ kind: "string",
862
+ summary: "Correlation id to send. Must be a UUID or 32 hex characters, or the API replaces it.",
863
+ placeholder: "id"
864
+ });
865
+ var CONTRACT_FLAG = Object.freeze({
866
+ name: "contract",
867
+ kind: "string",
868
+ summary: "Path to a contract-public projection, used to explain any error code returned.",
869
+ placeholder: "file"
870
+ });
871
+ var CONTRACT_ENV_VAR = "EVERFUR_CONTRACT_PUBLIC";
872
+ var DEFAULT_CONTRACT_PATHS = Object.freeze([
873
+ "contract-public/contract-public.json",
874
+ "contract-public.json"
875
+ ]);
876
+ function resolveFromCwd(cwd, path) {
877
+ return isAbsolute(path) ? path : resolve(cwd, path);
878
+ }
879
+ function parseTimeout(raw) {
880
+ if (raw === null) return { value: DEFAULT_TIMEOUT_MS, errors: [] };
881
+ const parsed = Number(raw.trim());
882
+ if (!Number.isFinite(parsed) || parsed <= 0) {
883
+ return { value: DEFAULT_TIMEOUT_MS, errors: [`--timeout must be a positive number of milliseconds, not "${raw}"`] };
884
+ }
885
+ if (parsed < MIN_TIMEOUT_MS || parsed > MAX_TIMEOUT_MS) {
886
+ return {
887
+ value: DEFAULT_TIMEOUT_MS,
888
+ errors: [`--timeout must be between ${MIN_TIMEOUT_MS} and ${MAX_TIMEOUT_MS} milliseconds`]
889
+ };
890
+ }
891
+ return { value: parsed, errors: [] };
892
+ }
893
+ function findContractPath(context, flagValue) {
894
+ if (flagValue !== null) return resolveFromCwd(context.cwd, flagValue);
895
+ const fromEnv = (context.env[CONTRACT_ENV_VAR] ?? "").trim();
896
+ if (fromEnv.length > 0) return resolveFromCwd(context.cwd, fromEnv);
897
+ for (const candidate of DEFAULT_CONTRACT_PATHS) {
898
+ const full = resolveFromCwd(context.cwd, candidate);
899
+ if (context.fs.readFile(full) !== null) return full;
900
+ }
901
+ return null;
902
+ }
903
+ function setUpWire(context, flags, options = {}) {
904
+ const errors = [];
905
+ const endpointResolution = resolveEndpoint(flags.string("base-url"), context.env);
906
+ if (!endpointResolution.ok) errors.push(...endpointResolution.errors);
907
+ const timeout = parseTimeout(flags.string("timeout"));
908
+ errors.push(...timeout.errors);
909
+ const requestId = flags.string("request-id");
910
+ if (requestId !== null && !isCorrelatableRequestId(requestId.trim())) {
911
+ errors.push(
912
+ "--request-id must be a UUID or 32 hex characters; the API ignores any other shape and stamps its own"
913
+ );
914
+ }
915
+ if (errors.length > 0 || !endpointResolution.ok) {
916
+ return { ok: false, outcome: errorOutcome("usage_error", "the connection could not be configured", errors) };
917
+ }
918
+ const contractFlag = flags.string("contract");
919
+ const contractPath = findContractPath(context, contractFlag);
920
+ let registry = EMPTY_REGISTRY;
921
+ let registrySource = null;
922
+ if (contractPath !== null) {
923
+ const load = loadErrorRegistry(context.fs, contractPath);
924
+ if (load.kind === "loaded") {
925
+ registry = load.registry;
926
+ registrySource = load.path;
927
+ } else if (contractFlag !== null) {
928
+ return { ok: false, outcome: contractRejection(contractPath, load.kind) };
929
+ }
930
+ }
931
+ return {
932
+ ok: true,
933
+ client: createApiClient({
934
+ transport: context.transport,
935
+ clock: context.clock,
936
+ endpoint: endpointResolution.endpoint,
937
+ registry,
938
+ timeoutMs: timeout.value,
939
+ requestId: requestId === null ? null : requestId.trim(),
940
+ ...options.maxAttempts === void 0 ? {} : { maxAttempts: options.maxAttempts },
941
+ ...options.retryDelayMs === void 0 ? {} : { retryDelayMs: options.retryDelayMs }
942
+ }),
943
+ endpoint: endpointResolution.endpoint,
944
+ registry,
945
+ registrySource
946
+ };
947
+ }
948
+ function contractRejection(path, kind) {
949
+ return errorOutcome("invalid_contract", `the contract at ${path} could not be used (${kind})`, [
950
+ "run `everfur contract validate --path <file>` for the specific failure"
951
+ ]);
952
+ }
953
+
954
+ // src/commands/verify/index.ts
955
+ var DEFAULT_USER_REF = "everfur-cli-verify";
956
+ var DEFAULT_ATTEMPTS = 3;
957
+ var POLL_DELAY_MS = 500;
958
+ var MAX_ATTEMPTS = 20;
959
+ var VERIFICATION_SCHEMA_VERSION = 1;
960
+ var VERIFICATION_DOCUMENT_KIND = "everfur.verification";
961
+ var FLAGS = Object.freeze([
962
+ BASE_URL_FLAG,
963
+ {
964
+ name: "user-ref",
965
+ kind: "string",
966
+ summary: `End user reference to mint a session for. Defaults to ${DEFAULT_USER_REF}.`,
967
+ placeholder: "ref"
968
+ },
969
+ {
970
+ name: "attempts",
971
+ kind: "string",
972
+ summary: `How many times to read the verdict before calling it empty (default ${DEFAULT_ATTEMPTS}).`,
973
+ placeholder: "n"
974
+ },
975
+ CONTRACT_FLAG,
976
+ TIMEOUT_FLAG,
977
+ REQUEST_ID_FLAG
978
+ ]);
979
+ function buildVerificationDocument(verification, baseUrl, cliVersion) {
980
+ const counts = { passed: 0, failed: 0, skipped: 0 };
981
+ for (const entry of verification.steps) {
982
+ if (entry.result === "PASS") counts.passed += 1;
983
+ else if (entry.result === "FAIL") counts.failed += 1;
984
+ else counts.skipped += 1;
985
+ }
986
+ return {
987
+ schemaVersion: VERIFICATION_SCHEMA_VERSION,
988
+ kind: VERIFICATION_DOCUMENT_KIND,
989
+ generatedBy: { name: "@everfur/cli", version: cliVersion },
990
+ status: verification.status,
991
+ baseUrl,
992
+ summary: counts,
993
+ steps: verification.steps.map((entry) => ({
994
+ id: entry.id,
995
+ title: entry.title,
996
+ result: entry.result,
997
+ reason: entry.reason,
998
+ requestId: entry.requestId,
999
+ details: [...entry.details]
1000
+ }))
1001
+ };
1002
+ }
1003
+ function renderVerification(verification, baseUrl) {
1004
+ const lines = [
1005
+ `everfur verify: ${verification.status}`,
1006
+ "",
1007
+ ` base url: ${baseUrl}`,
1008
+ ""
1009
+ ];
1010
+ for (const entry of verification.steps) {
1011
+ lines.push(` ${entry.result.padEnd(4)} ${entry.id} ${entry.title}`);
1012
+ lines.push(` ${entry.reason}`);
1013
+ lines.push(` request id: ${entry.requestId ?? "(none; no call was made)"}`);
1014
+ for (const detail of entry.details) lines.push(` - ${detail}`);
1015
+ lines.push("");
1016
+ }
1017
+ if (verification.status === "failed") {
1018
+ lines.push("At least one step failed. The reasons above are the report; the exit code only summarises them.");
1019
+ } else if (verification.status === "incomplete") {
1020
+ lines.push("Nothing failed, but a step was skipped, so the wire was not fully exercised.");
1021
+ } else {
1022
+ lines.push("Every step passed. The session mint, the verdict read, and the verdict itself are all healthy.");
1023
+ }
1024
+ return lines.join("\n");
1025
+ }
1026
+ function parseAttempts(raw) {
1027
+ if (raw === null) return { value: DEFAULT_ATTEMPTS, errors: [] };
1028
+ const parsed = Number(raw.trim());
1029
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > MAX_ATTEMPTS) {
1030
+ return {
1031
+ value: DEFAULT_ATTEMPTS,
1032
+ errors: [`--attempts must be a whole number between 1 and ${MAX_ATTEMPTS}, not "${raw}"`]
1033
+ };
1034
+ }
1035
+ return { value: parsed, errors: [] };
1036
+ }
1037
+ var verifyCommand = {
1038
+ path: ["verify"],
1039
+ summary: "Exercise the wire: mint a session, read the verdict, assert it is not empty.",
1040
+ usage: "verify [--base-url <url>] [--user-ref <ref>] [--attempts <n>] [--json]",
1041
+ flags: FLAGS,
1042
+ /** Exactly the pair `mintSession` resolves; without both, step one reports SKIP rather than FAIL. */
1043
+ credentials: Object.freeze([PARTNER_KEY, PARTNER_SECRET_KEY]),
1044
+ async run(context, flags) {
1045
+ const attempts = parseAttempts(flags.string("attempts"));
1046
+ if (attempts.errors.length > 0) {
1047
+ return errorOutcome("usage_error", "one or more flag values were not understood", attempts.errors);
1048
+ }
1049
+ const wire = setUpWire(context, flags);
1050
+ if (!wire.ok) return wire.outcome;
1051
+ const userRefFlag = flags.string("user-ref");
1052
+ const userRef = userRefFlag === null || userRefFlag.trim().length === 0 ? DEFAULT_USER_REF : userRefFlag.trim();
1053
+ const verification = await runVerification({
1054
+ client: wire.client,
1055
+ clock: context.clock,
1056
+ env: context.env,
1057
+ userRef,
1058
+ attempts: attempts.value,
1059
+ pollDelayMs: POLL_DELAY_MS
1060
+ });
1061
+ const document = buildVerificationDocument(verification, wire.client.baseUrl, context.version);
1062
+ const text = renderVerification(verification, wire.client.baseUrl);
1063
+ switch (verification.status) {
1064
+ case "passed":
1065
+ return succeed(document, text);
1066
+ case "failed":
1067
+ return fail(ExitCode.VERIFICATION_FAILED, document, text);
1068
+ case "incomplete":
1069
+ return fail(ExitCode.UNDERSPECIFIED, document, text);
1070
+ }
1071
+ }
1072
+ };
1073
+ var FLAGS2 = Object.freeze([
1074
+ {
1075
+ name: "cwd",
1076
+ kind: "string",
1077
+ summary: "Project root to inspect. Defaults to the current working directory.",
1078
+ placeholder: "path"
1079
+ },
1080
+ {
1081
+ name: "capabilities",
1082
+ kind: "string",
1083
+ summary: "Comma separated capability ids. The valid ids come from the contract decision trees.",
1084
+ placeholder: "list"
1085
+ },
1086
+ {
1087
+ name: "identity",
1088
+ kind: "string",
1089
+ summary: "How end users are identified. The valid modes come from the contract decision trees.",
1090
+ placeholder: "mode"
1091
+ },
1092
+ {
1093
+ name: "backend",
1094
+ kind: "string",
1095
+ summary: "Language that mints sessions server side. Required only when the identity mode mints.",
1096
+ placeholder: "language"
1097
+ },
1098
+ CONTRACT_FLAG
1099
+ ]);
1100
+ function asJsonObject(document) {
1101
+ return JSON.parse(JSON.stringify(document));
1102
+ }
1103
+ function contractOutcome(outcome) {
1104
+ switch (outcome.kind) {
1105
+ case "file_not_found":
1106
+ return errorOutcome("file_not_found", `no readable file at ${outcome.path}`);
1107
+ case "invalid_json":
1108
+ return errorOutcome("invalid_json", `${outcome.path} is not valid JSON`, [outcome.detail]);
1109
+ case "invalid_contract":
1110
+ return errorOutcome(
1111
+ "invalid_contract",
1112
+ `${outcome.path} does not match the contract-public envelope schema`,
1113
+ outcome.violations.map((violation) => `${violation.path}: ${violation.message}`)
1114
+ );
1115
+ case "digest_mismatch":
1116
+ return errorOutcome("digest_mismatch", `${outcome.path} declares a digest that does not match its projection`, [
1117
+ `declared: ${outcome.digest.expected}`,
1118
+ `recomputed: ${outcome.digest.actual}`,
1119
+ "no plan was built: an edited projection is not a source of truth about what a partner can integrate"
1120
+ ]);
1121
+ /* c8 ignore next 2 -- `valid` never reaches here; the loader returns `loaded` for it. */
1122
+ case "valid":
1123
+ return errorOutcome("internal_error", "a valid contract was reported as rejected");
1124
+ }
1125
+ }
1126
+ var planCommand = {
1127
+ path: ["plan"],
1128
+ summary: "Produce an integration plan for the project in the current directory.",
1129
+ usage: "plan [--cwd <path>] [--capabilities <list>] [--identity <mode>] [--backend <language>] [--contract <file>] [--json]",
1130
+ flags: FLAGS2,
1131
+ async run(context, flags) {
1132
+ const contractFlag = flags.string("contract");
1133
+ const contractPath = findContractPath(context, contractFlag);
1134
+ let space = null;
1135
+ let usedBundledContract = false;
1136
+ if (contractPath !== null) {
1137
+ const load = loadDecisionSpace(context.fs, contractPath);
1138
+ if (load.kind === "loaded") {
1139
+ space = load.space;
1140
+ } else if (contractFlag !== null) {
1141
+ return load.kind === "contract_rejected" ? contractOutcome(load.outcome) : errorOutcome("invalid_contract", `${load.path} carries no decision trees`, [
1142
+ "the projection validated, but it has no `decision-trees.yaml` document with a `trees` list",
1143
+ "this is a projection built before the planner decision trees were published, or a partial one"
1144
+ ]);
1145
+ }
1146
+ }
1147
+ if (space === null && contractPath === null) {
1148
+ const bundled = loadBundledDecisionSpace();
1149
+ if (bundled.kind === "loaded") {
1150
+ space = bundled.space;
1151
+ usedBundledContract = true;
1152
+ }
1153
+ }
1154
+ const supplied = parseSuppliedFacts(
1155
+ flags.string("capabilities"),
1156
+ flags.string("identity"),
1157
+ flags.string("backend"),
1158
+ space
1159
+ );
1160
+ if (!supplied.ok) {
1161
+ return errorOutcome("usage_error", "one or more flag values were not understood", supplied.errors);
1162
+ }
1163
+ const cwdFlag = flags.string("cwd");
1164
+ const root = cwdFlag === null ? context.cwd : isAbsolute(cwdFlag) ? cwdFlag : resolve(context.cwd, cwdFlag);
1165
+ if (!context.fs.isDirectory(root)) {
1166
+ return errorOutcome("usage_error", `not a directory: ${root}`, [
1167
+ "pass --cwd with a path to the application root"
1168
+ ]);
1169
+ }
1170
+ const detection = detectProject(context.fs, root);
1171
+ const document = planIntegration({
1172
+ detection,
1173
+ capabilities: supplied.capabilities,
1174
+ identityMode: supplied.identityMode,
1175
+ sessionMintLanguage: supplied.sessionMintLanguage,
1176
+ space,
1177
+ cliVersion: context.version
1178
+ });
1179
+ const json = asJsonObject(document);
1180
+ const text = renderPlan(document);
1181
+ if (document.status === "underspecified") {
1182
+ const hint = contractPath === null && !usedBundledContract && document.missingFacts.some((fact) => fact.fact === "decisionTrees") ? `${text}
1183
+
1184
+ Looked for a projection at: ${DEFAULT_CONTRACT_PATHS.join(", ")} under ${root}, and in ${CONTRACT_ENV_VAR}.` : text;
1185
+ return fail(ExitCode.UNDERSPECIFIED, json, hint);
1186
+ }
1187
+ if (document.status === "blocked") return fail(ExitCode.BLOCKED, json, text);
1188
+ return succeed(json, text);
1189
+ }
1190
+ };
1191
+
1192
+ // src/commands/contract/index.ts
1193
+ var FLAGS3 = Object.freeze([
1194
+ {
1195
+ name: "path",
1196
+ kind: "string",
1197
+ summary: "Path to the contract-public projection JSON file.",
1198
+ placeholder: "file",
1199
+ required: true
1200
+ }
1201
+ ]);
1202
+ var contractValidateCommand = {
1203
+ path: ["contract", "validate"],
1204
+ summary: "Validate a contract-public projection: envelope shape, then digest.",
1205
+ usage: "contract validate --path <file> [--json]",
1206
+ flags: FLAGS3,
1207
+ async run(context, flags) {
1208
+ const rawPath = flags.string("path");
1209
+ if (rawPath === null) {
1210
+ return errorOutcome("usage_error", "flag --path is required");
1211
+ }
1212
+ const path = resolveContractPath(context.cwd, rawPath);
1213
+ const outcome = validateContractFile(context.fs, path);
1214
+ switch (outcome.kind) {
1215
+ case "file_not_found":
1216
+ return errorOutcome("file_not_found", `no readable file at ${outcome.path}`);
1217
+ case "invalid_json":
1218
+ return errorOutcome("invalid_json", `${outcome.path} is not valid JSON`, [outcome.detail]);
1219
+ case "invalid_contract":
1220
+ return errorOutcome(
1221
+ "invalid_contract",
1222
+ `${outcome.path} does not match the contract-public envelope schema`,
1223
+ outcome.violations.map((violation) => `${violation.path}: ${violation.message}`)
1224
+ );
1225
+ case "digest_mismatch":
1226
+ return errorOutcome(
1227
+ "digest_mismatch",
1228
+ `${outcome.path} declares a digest that does not match its projection`,
1229
+ [
1230
+ `declared: ${outcome.digest.expected}`,
1231
+ `recomputed: ${outcome.digest.actual}`,
1232
+ "The projection was edited after the digest was written, or the two came from different builds."
1233
+ ]
1234
+ );
1235
+ case "valid": {
1236
+ const json = {
1237
+ schemaVersion: 1,
1238
+ kind: "everfur.contract-validation",
1239
+ status: "valid",
1240
+ path: outcome.path,
1241
+ generatedAt: outcome.generatedAt,
1242
+ digest: { algorithm: "sha256", value: outcome.digest.actual },
1243
+ projectionKeys: outcome.projectionKeys
1244
+ };
1245
+ const text = [
1246
+ `contract validate: ok`,
1247
+ ` file: ${outcome.path}`,
1248
+ ` generated at: ${outcome.generatedAt}`,
1249
+ ` digest (sha256): ${outcome.digest.actual}`,
1250
+ ` projection keys: ${outcome.projectionKeys.join(", ")}`
1251
+ ].join("\n");
1252
+ return succeed(json, text);
1253
+ }
1254
+ }
1255
+ }
1256
+ };
1257
+
1258
+ // src/commands/shared/widget-auth.ts
1259
+ function chooseWidgetAuth(env) {
1260
+ const session = resolveCredential(env, SESSION_TOKEN);
1261
+ if (session.status === "present" && session.credential !== null) {
1262
+ return {
1263
+ ok: true,
1264
+ auth: { mode: "session_token", headers: Object.freeze({ authorization: withPrefix(session.credential, "Bearer ") }) }
1265
+ };
1266
+ }
1267
+ const partner = resolveCredential(env, PARTNER_KEY);
1268
+ if (partner.status === "present" && partner.credential !== null) {
1269
+ return {
1270
+ ok: true,
1271
+ auth: { mode: "partner_key", headers: Object.freeze({ "x-everfur-partner-key": partner.credential }) }
1272
+ };
1273
+ }
1274
+ if (partner.status === "malformed") {
1275
+ return {
1276
+ ok: false,
1277
+ outcome: errorOutcome("usage_error", "the partner key is not usable", [
1278
+ partner.reason ?? "the value has the wrong shape",
1279
+ "the value itself is never printed; check what you exported"
1280
+ ])
1281
+ };
1282
+ }
1283
+ return {
1284
+ ok: false,
1285
+ outcome: errorOutcome("credential_missing", "no widget credential is configured", [
1286
+ `export ${PARTNER_KEY.envVar} with your publishable key (${PARTNER_KEY.prefix}...)`,
1287
+ `or export ${SESSION_TOKEN.envVar} with an existing session bearer to diagnose the signed path`
1288
+ ])
1289
+ };
1290
+ }
1291
+
1292
+ // src/commands/diagnose/index.ts
1293
+ var DIAGNOSTICS_PATH = "/widget/v1/diagnostics";
1294
+ var FLAGS4 = Object.freeze([
1295
+ BASE_URL_FLAG,
1296
+ {
1297
+ name: "user-ref",
1298
+ kind: "string",
1299
+ summary: "End user reference to diagnose as. Omit to diagnose the anonymous subject.",
1300
+ placeholder: "ref"
1301
+ },
1302
+ CONTRACT_FLAG,
1303
+ TIMEOUT_FLAG,
1304
+ REQUEST_ID_FLAG
1305
+ ]);
1306
+ var diagnoseCommand = {
1307
+ path: ["diagnose"],
1308
+ summary: "Explain why each capability is or is not available to this caller.",
1309
+ usage: "diagnose [--base-url <url>] [--user-ref <ref>] [--contract <file>] [--json]",
1310
+ flags: FLAGS4,
1311
+ /** Exactly what `chooseWidgetAuth` resolves: the bearer wins, the publishable key is the fallback. */
1312
+ credentials: Object.freeze([PARTNER_KEY, SESSION_TOKEN]),
1313
+ async run(context, flags) {
1314
+ const wire = setUpWire(context, flags);
1315
+ if (!wire.ok) return wire.outcome;
1316
+ const auth = chooseWidgetAuth(context.env);
1317
+ if (!auth.ok) return auth.outcome;
1318
+ const userRef = flags.string("user-ref");
1319
+ const headers = {};
1320
+ if (userRef !== null && userRef.trim().length > 0) {
1321
+ headers["x-everfur-user-ref"] = userRef.trim();
1322
+ }
1323
+ const result = await wire.client.request({
1324
+ method: "GET",
1325
+ path: DIAGNOSTICS_PATH,
1326
+ headers,
1327
+ credentials: auth.auth.headers
1328
+ });
1329
+ if (!result.ok) {
1330
+ return failureOutcome(result, [
1331
+ `auth mode: ${auth.auth.mode}`,
1332
+ "a 401 here means the credential was refused; every credential failure returns the same opaque 401"
1333
+ ]);
1334
+ }
1335
+ const parsed = parseDiagnostics(result.value, result.requestId);
1336
+ if (!parsed.ok) {
1337
+ return errorOutcome(
1338
+ "malformed_response",
1339
+ `${DIAGNOSTICS_PATH} answered ${result.status} with a body this CLI does not recognise`,
1340
+ [
1341
+ parsed.detail,
1342
+ result.requestId === null ? "the response carried no request id" : `request id: ${result.requestId}`,
1343
+ "check that --base-url points at the Everfur API and not at a proxy or login page"
1344
+ ]
1345
+ );
1346
+ }
1347
+ const document = buildDiagnosisDocument({
1348
+ diagnosis: parsed.diagnosis,
1349
+ baseUrl: wire.client.baseUrl,
1350
+ path: DIAGNOSTICS_PATH,
1351
+ cliVersion: context.version
1352
+ });
1353
+ return succeed(document, renderDiagnosis({ diagnosis: parsed.diagnosis, baseUrl: wire.client.baseUrl }));
1354
+ }
1355
+ };
1356
+
1357
+ // src/commands/explain-error/index.ts
1358
+ var FLAGS5 = Object.freeze([CONTRACT_FLAG]);
1359
+ var POSITIONALS = Object.freeze([
1360
+ {
1361
+ name: "code",
1362
+ summary: "The wire error code, exactly as the response carried it. Case sensitive.",
1363
+ required: true
1364
+ }
1365
+ ]);
1366
+ var explainErrorCommand = {
1367
+ path: ["explain-error"],
1368
+ summary: "Explain an API error code: cause, retryability, and next action.",
1369
+ usage: "explain-error <code> [--contract <file>] [--json]",
1370
+ flags: FLAGS5,
1371
+ positionals: POSITIONALS,
1372
+ async run(context, flags, positionals) {
1373
+ const code = (positionals[0] ?? "").trim();
1374
+ if (code.length === 0) {
1375
+ return errorOutcome("usage_error", "no error code was given", ["usage: everfur explain-error <code>"]);
1376
+ }
1377
+ const contractFlag = flags.string("contract");
1378
+ const contractPath = findContractPath(context, contractFlag);
1379
+ if (contractPath === null) {
1380
+ return errorOutcome("file_not_found", "no contract-public projection was found", [
1381
+ `pass --contract <file>, or export ${CONTRACT_ENV_VAR}`,
1382
+ `looked for: ${DEFAULT_CONTRACT_PATHS.join(", ")} under ${context.cwd}`,
1383
+ "the error registry is compiled by the platform; this CLI does not carry a copy"
1384
+ ]);
1385
+ }
1386
+ const load = loadErrorRegistry(context.fs, contractPath);
1387
+ if (load.kind === "contract_rejected") return contractOutcome2(load.outcome);
1388
+ if (load.kind === "no_registry") {
1389
+ return errorOutcome("invalid_contract", `${load.path} carries no error registry`, [
1390
+ "the projection validated, but it has no `errors.yaml` document with an `errors` list",
1391
+ "this is a projection built before the error registry was published, or a partial one"
1392
+ ]);
1393
+ }
1394
+ const entry = load.registry.lookup(code);
1395
+ if (entry === null) {
1396
+ const suggestions = suggestCodes(code, load.registry.codes);
1397
+ return errorOutcome("unknown_error_code", `"${code}" is not in the error registry`, [
1398
+ ...suggestions.length > 0 ? [`did you mean: ${suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : ["no similar code exists in the registry"],
1399
+ `the registry loaded from ${load.path} lists ${load.registry.codes.length} codes`
1400
+ ]);
1401
+ }
1402
+ const input = {
1403
+ entry,
1404
+ contractPath: load.path,
1405
+ digest: load.digest,
1406
+ cliVersion: context.version
1407
+ };
1408
+ return succeed(buildExplanationDocument(input), renderExplanation(input));
1409
+ }
1410
+ };
1411
+ function contractOutcome2(outcome) {
1412
+ switch (outcome.kind) {
1413
+ case "file_not_found":
1414
+ return errorOutcome("file_not_found", `no readable file at ${outcome.path}`);
1415
+ case "invalid_json":
1416
+ return errorOutcome("invalid_json", `${outcome.path} is not valid JSON`, [outcome.detail]);
1417
+ case "invalid_contract":
1418
+ return errorOutcome(
1419
+ "invalid_contract",
1420
+ `${outcome.path} does not match the contract-public envelope schema`,
1421
+ outcome.violations.map((violation) => `${violation.path}: ${violation.message}`)
1422
+ );
1423
+ case "digest_mismatch":
1424
+ return errorOutcome("digest_mismatch", `${outcome.path} declares a digest that does not match its projection`, [
1425
+ `declared: ${outcome.digest.expected}`,
1426
+ `recomputed: ${outcome.digest.actual}`,
1427
+ "the registry was not read: an edited projection is not a source of truth about error codes"
1428
+ ]);
1429
+ /* c8 ignore next 2 -- `valid` never reaches here; the loader returns `loaded` for it. */
1430
+ case "valid":
1431
+ return errorOutcome("internal_error", "a valid contract was reported as rejected");
1432
+ }
1433
+ }
1434
+
1435
+ // src/commands/sandbox/index.ts
1436
+ var FLAGS6 = Object.freeze([
1437
+ {
1438
+ name: "client",
1439
+ kind: "string",
1440
+ summary: "Client UUID to provision the key for.",
1441
+ placeholder: "client-id",
1442
+ required: true
1443
+ },
1444
+ {
1445
+ name: "label",
1446
+ kind: "string",
1447
+ summary: 'Human readable label stored with the key, for example "sandbox laptop".',
1448
+ placeholder: "text"
1449
+ },
1450
+ BASE_URL_FLAG,
1451
+ CONTRACT_FLAG,
1452
+ TIMEOUT_FLAG,
1453
+ REQUEST_ID_FLAG
1454
+ ]);
1455
+ var sandboxCreateCommand = {
1456
+ path: ["sandbox", "create"],
1457
+ summary: "Provision a partner secret key and return a claim URL. Never prints the secret.",
1458
+ usage: "sandbox create --client <client-id> [--label <text>] [--base-url <url>] [--json]",
1459
+ flags: FLAGS6,
1460
+ /** The one credential this command resolves. It never reads a partner key of either kind. */
1461
+ credentials: Object.freeze([ADMIN_TOKEN]),
1462
+ async run(context, flags) {
1463
+ const clientId = (flags.string("client") ?? "").trim();
1464
+ if (clientId.length === 0) {
1465
+ return errorOutcome("usage_error", "flag --client is required", [
1466
+ "pass the client UUID the key should belong to"
1467
+ ]);
1468
+ }
1469
+ const wire = setUpWire(context, flags);
1470
+ if (!wire.ok) return wire.outcome;
1471
+ const admin = resolveCredential(context.env, ADMIN_TOKEN);
1472
+ if (admin.status !== "present" || admin.credential === null) {
1473
+ return errorOutcome("credential_missing", "no admin credential is configured", [
1474
+ admin.reason ?? `${ADMIN_TOKEN.envVar} is not set`,
1475
+ `export ${ADMIN_TOKEN.envVar} with an admin bearer token`
1476
+ ]);
1477
+ }
1478
+ const path = `/admin/clients/${encodeURIComponent(clientId)}/secret-keys`;
1479
+ const label = flags.string("label");
1480
+ const body = { label: label === null ? null : label.trim() };
1481
+ const result = await wire.client.request({
1482
+ method: "POST",
1483
+ path,
1484
+ credentials: { authorization: withPrefix(admin.credential, "Bearer ") },
1485
+ body
1486
+ });
1487
+ if (!result.ok) {
1488
+ return failureOutcome(result, [
1489
+ "no key was provisioned, so there is nothing to revoke",
1490
+ "a 400 here means the client exists but is not an active partner tenant"
1491
+ ]);
1492
+ }
1493
+ const projected = projectSandboxCreate(result.value);
1494
+ if (!projected.ok) {
1495
+ return errorOutcome("malformed_response", `${path} answered ${result.status} with an unreadable body`, [
1496
+ projected.detail,
1497
+ result.requestId === null ? "the response carried no request id" : `request id: ${result.requestId}`,
1498
+ "a key MAY have been created; list the client's keys before retrying"
1499
+ ]);
1500
+ }
1501
+ let revocation = "not_needed";
1502
+ if (projected.creation.status === "claim_unavailable") {
1503
+ const revoke = await wire.client.request({
1504
+ method: "DELETE",
1505
+ path: revokePathFor(projected.creation),
1506
+ credentials: { authorization: withPrefix(admin.credential, "Bearer ") },
1507
+ /** 404 means the key is already gone, which is the outcome this call wanted. */
1508
+ acceptStatuses: [404]
1509
+ });
1510
+ revocation = revoke.ok ? "revoked" : "revoke_failed";
1511
+ }
1512
+ const document = buildSandboxDocument({
1513
+ creation: projected.creation,
1514
+ revocation,
1515
+ requestId: result.requestId,
1516
+ cliVersion: context.version
1517
+ });
1518
+ const text = renderSandboxCreate(projected.creation, revocation, result.requestId);
1519
+ return projected.creation.status === "claim_issued" ? succeed(document, text) : fail(ExitCode.SECRET_UNCLAIMABLE, document, text);
1520
+ }
1521
+ };
1522
+
1523
+ // src/cli/registry.ts
1524
+ var COMMANDS = Object.freeze([
1525
+ planCommand,
1526
+ contractValidateCommand,
1527
+ diagnoseCommand,
1528
+ sandboxCreateCommand,
1529
+ verifyCommand,
1530
+ explainErrorCommand
1531
+ ]);
1532
+ function matchCommand(path, commands = COMMANDS) {
1533
+ const ordered = [...commands].sort((left, right) => right.path.length - left.path.length);
1534
+ for (const command of ordered) {
1535
+ if (command.path.length > path.length) continue;
1536
+ const matches = command.path.every((token, index) => path[index] === token);
1537
+ if (matches) return { command, consumed: command.path.length };
1538
+ }
1539
+ return null;
1540
+ }
1541
+
1542
+ // src/cli/argv.ts
1543
+ var VALUE_FLAGS = /* @__PURE__ */ new Set([
1544
+ "cwd",
1545
+ "capabilities",
1546
+ "identity",
1547
+ "backend",
1548
+ "path",
1549
+ "base-url",
1550
+ "contract",
1551
+ "client",
1552
+ "label",
1553
+ "user-ref",
1554
+ "timeout",
1555
+ "attempts",
1556
+ "request-id"
1557
+ ]);
1558
+ var ALIASES = Object.freeze({
1559
+ h: "help",
1560
+ v: "version",
1561
+ C: "cwd"
1562
+ });
1563
+ function isFlagToken(token) {
1564
+ return token.startsWith("--") || token.startsWith("-") && token.length > 1 && !token.startsWith("--");
1565
+ }
1566
+ function normaliseName(raw) {
1567
+ if (raw.startsWith("--")) return raw.slice(2);
1568
+ const short = raw.slice(1);
1569
+ return ALIASES[short] ?? short;
1570
+ }
1571
+ function parseArgv(argv) {
1572
+ const path = [];
1573
+ const flags = /* @__PURE__ */ new Map();
1574
+ const errors = [];
1575
+ let sawFlag = false;
1576
+ for (let index = 0; index < argv.length; index += 1) {
1577
+ const token = argv[index];
1578
+ if (token === void 0) continue;
1579
+ if (token === "--") break;
1580
+ if (!isFlagToken(token)) {
1581
+ if (sawFlag) {
1582
+ errors.push(`unexpected argument "${token}" after flags began`);
1583
+ } else {
1584
+ path.push(token);
1585
+ }
1586
+ continue;
1587
+ }
1588
+ sawFlag = true;
1589
+ const equalsAt = token.indexOf("=");
1590
+ const rawName = equalsAt === -1 ? token : token.slice(0, equalsAt);
1591
+ const inlineValue = equalsAt === -1 ? null : token.slice(equalsAt + 1);
1592
+ const name = normaliseName(rawName);
1593
+ if (name.length === 0) {
1594
+ errors.push(`malformed flag "${token}"`);
1595
+ continue;
1596
+ }
1597
+ const duplicate = flags.has(name);
1598
+ if (duplicate) errors.push(`flag --${name} was given more than once`);
1599
+ if (inlineValue !== null) {
1600
+ if (!duplicate) flags.set(name, inlineValue);
1601
+ continue;
1602
+ }
1603
+ if (!VALUE_FLAGS.has(name)) {
1604
+ if (!duplicate) flags.set(name, true);
1605
+ continue;
1606
+ }
1607
+ const next = argv[index + 1];
1608
+ if (next === void 0 || isFlagToken(next)) {
1609
+ if (!duplicate) errors.push(`flag --${name} requires a value`);
1610
+ continue;
1611
+ }
1612
+ index += 1;
1613
+ if (!duplicate) flags.set(name, next);
1614
+ }
1615
+ return Object.freeze({ path: Object.freeze(path), flags, errors: Object.freeze(errors) });
1616
+ }
1617
+
1618
+ // src/cli/command.ts
1619
+ function commandName(command) {
1620
+ return command.path.join(" ");
1621
+ }
1622
+
1623
+ // src/cli/flags.ts
1624
+ var GLOBAL_FLAGS = Object.freeze([
1625
+ { name: "json", kind: "boolean", summary: "Emit the machine document on stdout instead of human text." },
1626
+ { name: "help", kind: "boolean", summary: "Show usage for this command." }
1627
+ ]);
1628
+ var EMPTY_BOUND = Object.freeze({
1629
+ string: () => null,
1630
+ boolean: () => false
1631
+ });
1632
+ function bindFlags(specs, parsed) {
1633
+ const all = [...specs, ...GLOBAL_FLAGS];
1634
+ const byName = new Map(all.map((spec) => [spec.name, spec]));
1635
+ const errors = [...parsed.errors];
1636
+ const values = /* @__PURE__ */ new Map();
1637
+ for (const [name, value] of parsed.flags) {
1638
+ const spec = byName.get(name);
1639
+ if (spec === void 0) {
1640
+ errors.push(`unknown flag --${name}`);
1641
+ continue;
1642
+ }
1643
+ if (spec.kind === "boolean" && typeof value === "string") {
1644
+ errors.push(`flag --${name} does not take a value`);
1645
+ continue;
1646
+ }
1647
+ if (spec.kind === "string" && value === true) {
1648
+ errors.push(`flag --${name} requires a value`);
1649
+ continue;
1650
+ }
1651
+ values.set(name, value);
1652
+ }
1653
+ for (const spec of all) {
1654
+ if (spec.required === true && !values.has(spec.name)) {
1655
+ errors.push(`flag --${spec.name} is required`);
1656
+ }
1657
+ }
1658
+ if (errors.length > 0) {
1659
+ return Object.freeze({ ok: false, flags: EMPTY_BOUND, errors: Object.freeze(errors) });
1660
+ }
1661
+ const flags = Object.freeze({
1662
+ string: (name) => {
1663
+ const value = values.get(name);
1664
+ return typeof value === "string" ? value : null;
1665
+ },
1666
+ boolean: (name) => values.get(name) === true
1667
+ });
1668
+ return Object.freeze({ ok: true, flags, errors: Object.freeze([]) });
1669
+ }
1670
+
1671
+ // src/cli/help.ts
1672
+ var NOT_IN_THIS_RELEASE = Object.freeze(["login", "test message"]);
1673
+ var FLAG_COLUMN = 34;
1674
+ function renderFlag(spec) {
1675
+ const placeholder = spec.kind === "string" ? ` <${spec.placeholder ?? "value"}>` : "";
1676
+ const required = spec.required === true ? " (required)" : "";
1677
+ return ` --${spec.name}${placeholder}`.padEnd(FLAG_COLUMN) + `${spec.summary}${required}`;
1678
+ }
1679
+ function renderRootHelp(commands, version) {
1680
+ const lines = [
1681
+ `everfur ${version}`,
1682
+ "Integration planner and contract validator for the Everfur partner platform.",
1683
+ "",
1684
+ "Usage",
1685
+ " everfur <command> [flags]",
1686
+ "",
1687
+ "Commands"
1688
+ ];
1689
+ for (const command of commands) {
1690
+ lines.push(` ${commandName(command).padEnd(20)}${command.summary}`);
1691
+ }
1692
+ lines.push("", "Global flags");
1693
+ for (const spec of GLOBAL_FLAGS) lines.push(renderFlag(spec));
1694
+ lines.push(" --version".padEnd(FLAG_COLUMN) + "Print the CLI version.");
1695
+ lines.push(...environmentBlock(CREDENTIAL_SPECS));
1696
+ lines.push(
1697
+ "",
1698
+ "Not in this release",
1699
+ ` ${NOT_IN_THIS_RELEASE.join(", ")}`,
1700
+ " These are unbuilt. See ROADMAP.md. They are absent rather than stubbed on purpose."
1701
+ );
1702
+ return lines.join("\n");
1703
+ }
1704
+ function environmentBlock(specs) {
1705
+ const lines = ["", "Environment"];
1706
+ lines.push(` ${BASE_URL_ENV_VAR}`.padEnd(FLAG_COLUMN) + "API base URL. Overridden by --base-url.");
1707
+ for (const spec of specs) {
1708
+ lines.push(` ${spec.envVar}`.padEnd(FLAG_COLUMN) + spec.summary);
1709
+ }
1710
+ lines.push(
1711
+ " Credentials are read from these variables only. No .env file is read, and no value",
1712
+ " is ever printed, logged, or written into a --json document."
1713
+ );
1714
+ return lines;
1715
+ }
1716
+ function renderCommandHelp(command) {
1717
+ const lines = [
1718
+ commandName(command),
1719
+ ` ${command.summary}`,
1720
+ "",
1721
+ "Usage",
1722
+ ` everfur ${command.usage}`
1723
+ ];
1724
+ const positionals = command.positionals ?? [];
1725
+ if (positionals.length > 0) {
1726
+ lines.push("", "Arguments");
1727
+ for (const spec of positionals) {
1728
+ const required = spec.required ? "" : " (optional)";
1729
+ lines.push(` <${spec.name}>`.padEnd(FLAG_COLUMN) + `${spec.summary}${required}`);
1730
+ }
1731
+ }
1732
+ if (command.flags.length > 0) {
1733
+ lines.push("", "Flags");
1734
+ for (const spec of command.flags) lines.push(renderFlag(spec));
1735
+ }
1736
+ lines.push("", "Global flags");
1737
+ for (const spec of GLOBAL_FLAGS) lines.push(renderFlag(spec));
1738
+ const credentials = command.credentials ?? [];
1739
+ if (credentials.length > 0) lines.push(...environmentBlock(credentials));
1740
+ return lines.join("\n");
1741
+ }
1742
+
1743
+ // src/cli/run.ts
1744
+ async function runCli(options) {
1745
+ const commands = options.commands ?? COMMANDS;
1746
+ const parsed = parseArgv(options.argv);
1747
+ const wantsJson = parsed.flags.get("json") === true;
1748
+ const format = { json: wantsJson };
1749
+ if (parsed.path.length === 0) {
1750
+ if (parsed.flags.get("version") === true) {
1751
+ return formatOutcome(
1752
+ succeed({ schemaVersion: 1, kind: "everfur.version", version: options.context.version }, options.context.version),
1753
+ format
1754
+ );
1755
+ }
1756
+ const help = renderRootHelp(commands, options.context.version);
1757
+ if (parsed.flags.get("help") === true) {
1758
+ return formatOutcome(succeed({ schemaVersion: 1, kind: "everfur.help", text: help }, help), format);
1759
+ }
1760
+ return formatOutcome(
1761
+ errorOutcome("usage_error", "no command given", ["run `everfur --help` for the command list"]),
1762
+ format
1763
+ );
1764
+ }
1765
+ const match = matchCommand(parsed.path, commands);
1766
+ if (match === null) {
1767
+ return formatOutcome(
1768
+ errorOutcome("unknown_command", `unknown command "${parsed.path.join(" ")}"`, [
1769
+ `available: ${commands.map(commandName).join(", ")}`,
1770
+ "run `everfur --help` for the full list, including what is deliberately not in this release"
1771
+ ]),
1772
+ format
1773
+ );
1774
+ }
1775
+ const positionals = parsed.path.slice(match.consumed);
1776
+ const specs = match.command.positionals ?? [];
1777
+ if (positionals.length > specs.length) {
1778
+ const surplus = positionals[specs.length] ?? "";
1779
+ return formatOutcome(
1780
+ errorOutcome("usage_error", `unexpected argument "${surplus}" after "${commandName(match.command)}"`, [
1781
+ `usage: everfur ${match.command.usage}`
1782
+ ]),
1783
+ format
1784
+ );
1785
+ }
1786
+ const missingPositionals = specs.filter((spec, index) => spec.required && (positionals[index] ?? "").length === 0).map((spec) => `<${spec.name}> is required`);
1787
+ if (missingPositionals.length > 0 && parsed.flags.get("help") !== true) {
1788
+ return formatOutcome(
1789
+ errorOutcome("usage_error", `invalid invocation of "${commandName(match.command)}"`, [
1790
+ ...missingPositionals,
1791
+ `usage: everfur ${match.command.usage}`
1792
+ ]),
1793
+ format
1794
+ );
1795
+ }
1796
+ if (parsed.flags.get("help") === true) {
1797
+ const help = renderCommandHelp(match.command);
1798
+ return formatOutcome(succeed({ schemaVersion: 1, kind: "everfur.help", text: help }, help), format);
1799
+ }
1800
+ const bound = bindFlags(match.command.flags, parsed);
1801
+ if (!bound.ok) {
1802
+ return formatOutcome(
1803
+ errorOutcome("usage_error", `invalid invocation of "${commandName(match.command)}"`, [
1804
+ ...bound.errors,
1805
+ `usage: everfur ${match.command.usage}`
1806
+ ]),
1807
+ format
1808
+ );
1809
+ }
1810
+ try {
1811
+ const outcome = await match.command.run(options.context, bound.flags, positionals);
1812
+ return formatOutcome(outcome, format);
1813
+ } catch (cause) {
1814
+ const message = cause instanceof Error ? cause.message : String(cause);
1815
+ return formatOutcome(
1816
+ errorOutcome("internal_error", `"${commandName(match.command)}" failed unexpectedly`, [message]),
1817
+ format
1818
+ );
1819
+ }
1820
+ }
1821
+
1822
+ export { COMMANDS, ExitCode, REASON_CODES, buildDiagnosisDocument, buildExplanationDocument, buildSandboxDocument, buildVerificationDocument, editDistance, failureOutcome, formatOutcome, isKnownReasonCode, matchCommand, nextActionFor, nextActionFor2, nextActionsFor, parseCapabilities, parseDiagnostics, parseIdentityMode, parseSessionMintLanguage, parseSuppliedFacts, projectSandboxCreate, renderDiagnosis, renderExplanation, renderPlan, renderSandboxCreate, renderVerification, runCli, suggestCodes };