@everfur/cli 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,61 +1,7 @@
1
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';
2
+ import { VERIFY_IDENTITY_MODES, SURFACES, ENVIRONMENTS, BASE_URL_ENV_VAR, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, ALL_CAPABILITIES, expandCapabilities, errorOutcome, describeFailure, failureDetails, countsAsNotOn, diagnoseVerdict, isJsonObject, succeed, CAPABILITY_QUESTION, CAPABILITIES_TREE, WEB_EMBEDDING_QUESTION, PLATFORM_TREE, loadDecisionSpace, fetchContract, contractUnavailableOutcome, loadDecisionSpaceFromContract, parseEnvironment, detectProject, planIntegration, fail, ExitCode, DOCS_KEY, resolveContractPath, validateContractFile, buildSupportBundle, renderSupportBundle, chooseWidgetAuth, DIAGNOSTICS_PATH, diagnoseFailure, parseDiagnostics, buildDiagnosisDocument, PARTNER_KEY, SESSION_TOKEN, resolveCredential, ADMIN_TOKEN, withPrefix, parseOrigin, parseLoaderUrl, parseSurfaces, parseIdentityMode, runVerification, PARTNER_SECRET_KEY, resolveOffline, offlineSuggestions, loadErrorRegistryFromContract, loadErrorRegistry, resolveAfterRegistryMiss, knownCodes, buildExplanationDocument, renderExplanation, resolveEndpoint, isCorrelatableRequestId, EMPTY_REGISTRY, createApiClient, buildSdkExplanationDocument, renderSdkExplanation, buildContractTableExplanationDocument, renderContractTableExplanation, IDENTITY_QUESTION, IDENTITY_TREE, BACKEND_LANGUAGE_QUESTION, SESSION_MINT_TREE, CREDENTIAL_SPECS } from './chunk-KQICUK54.js';
3
3
  import { isAbsolute, resolve } from 'path';
4
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
5
  // src/cli/format.ts
60
6
  function withNewline(body) {
61
7
  return body.length === 0 ? "" : `${body}
@@ -84,28 +30,31 @@ function known(space, treeId, questionId) {
84
30
  return choices.length === 0 ? null : choices;
85
31
  }
86
32
  function parseCapabilities(raw, space) {
87
- if (raw === null) return { value: null, errors: [] };
33
+ if (raw === null) return { value: null, errors: [], all: false };
88
34
  const allowed = known(space, CAPABILITIES_TREE, CAPABILITY_QUESTION);
35
+ const supported = allowed === null ? null : [...allowed, ALL_CAPABILITIES];
89
36
  const tokens = raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
90
37
  if (tokens.length === 0) {
91
38
  return {
92
39
  value: null,
93
40
  errors: [
94
- allowed === null ? "--capabilities was empty; name at least one capability" : `--capabilities was empty; name at least one of: ${allowed.join(", ")}`
95
- ]
41
+ supported === null ? "--capabilities was empty; name at least one capability" : `--capabilities was empty; name at least one of: ${supported.join(", ")}`
42
+ ],
43
+ all: false
96
44
  };
97
45
  }
98
46
  const errors = [];
99
47
  const selected = /* @__PURE__ */ new Set();
100
48
  for (const token of tokens) {
101
- if (allowed === null || allowed.includes(token)) {
49
+ if (supported === null || supported.includes(token)) {
102
50
  selected.add(token);
103
51
  continue;
104
52
  }
105
- errors.push(`unknown capability "${token}"; supported: ${allowed.join(", ")}`);
53
+ errors.push(`unknown capability "${token}"; supported: ${supported.join(", ")}`);
106
54
  }
107
- if (errors.length > 0) return { value: null, errors };
108
- return { value: Object.freeze([...selected].sort()), errors: [] };
55
+ if (errors.length > 0) return { value: null, errors, all: false };
56
+ const expanded = expandCapabilities([...selected].sort(), space);
57
+ return { value: Object.freeze([...expanded.capabilities]), errors: [], all: expanded.expanded };
109
58
  }
110
59
  function parseChoice(raw, space, treeId, questionId, flag) {
111
60
  if (raw === null) return { value: null, errors: [] };
@@ -115,23 +64,29 @@ function parseChoice(raw, space, treeId, questionId, flag) {
115
64
  return { value: null, errors: [`unknown ${flag} "${raw}"; supported: ${allowed.join(", ")}`] };
116
65
  }
117
66
  var IDENTITY_ALIASES = Object.freeze({ personalized: "partner-user-ref" });
118
- function parseIdentityMode(raw, space) {
67
+ function parseIdentityMode2(raw, space) {
119
68
  const normalized = raw === null ? null : IDENTITY_ALIASES[raw.trim()] ?? raw;
120
69
  return parseChoice(normalized, space, IDENTITY_TREE, IDENTITY_QUESTION, "identity mode");
121
70
  }
122
71
  function parseSessionMintLanguage(raw, space) {
123
72
  return parseChoice(raw, space, SESSION_MINT_TREE, BACKEND_LANGUAGE_QUESTION, "backend language");
124
73
  }
125
- function parseSuppliedFacts(capabilitiesRaw, identityRaw, backendRaw, space) {
74
+ function parseEmbedding(raw, space) {
75
+ return parseChoice(raw, space, PLATFORM_TREE, WEB_EMBEDDING_QUESTION, "embedding");
76
+ }
77
+ function parseSuppliedFacts(capabilitiesRaw, identityRaw, backendRaw, space, embeddingRaw = null) {
126
78
  const capabilities = parseCapabilities(capabilitiesRaw, space);
127
- const identity = parseIdentityMode(identityRaw, space);
79
+ const identity = parseIdentityMode2(identityRaw, space);
128
80
  const backend = parseSessionMintLanguage(backendRaw, space);
129
- const errors = [...capabilities.errors, ...identity.errors, ...backend.errors];
81
+ const embedding = parseEmbedding(embeddingRaw, space);
82
+ const errors = [...capabilities.errors, ...identity.errors, ...backend.errors, ...embedding.errors];
130
83
  return Object.freeze({
131
84
  ok: errors.length === 0,
132
85
  capabilities: capabilities.value,
86
+ allCapabilities: capabilities.all,
133
87
  identityMode: identity.value,
134
88
  sessionMintLanguage: backend.value,
89
+ embedding: embedding.value,
135
90
  errors: Object.freeze(errors)
136
91
  });
137
92
  }
@@ -147,6 +102,23 @@ function renderDetected(document) {
147
102
  lines.push(...indent(detected.projectTypeEvidence, " - "));
148
103
  lines.push(` package manager: ${detected.packageManager}`);
149
104
  lines.push(...indent(detected.packageManagerEvidence, " - "));
105
+ const theme = detected.theme ?? null;
106
+ if (theme !== null) {
107
+ if (theme.confidence === "low") {
108
+ lines.push(` theme: not read (${theme.source === "none" ? "no theme source found" : `${theme.source}, low confidence`})`);
109
+ lines.push(...indent(theme.missing, " - "));
110
+ } else {
111
+ const fields = Object.entries(theme.theme).map(([key, value]) => `${key} ${String(value)}`);
112
+ lines.push(` theme: ${theme.source}, ${theme.confidence} confidence`);
113
+ lines.push(...indent(fields, " - "));
114
+ lines.push(
115
+ ...indent(
116
+ theme.evidence.filter((entry) => entry.token !== null).map((entry) => `${entry.file}:${entry.line} ${entry.key} -> ${entry.token}`),
117
+ " read: "
118
+ )
119
+ );
120
+ }
121
+ }
150
122
  if (detected.dependencies.length === 0) {
151
123
  lines.push(" dependencies: none of the relevant packages are declared");
152
124
  return lines;
@@ -247,7 +219,9 @@ function renderBlockedPlan(document) {
247
219
  lines.push(...contract);
248
220
  }
249
221
  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.");
222
+ lines.push(
223
+ document.platformBlock ? "No steps were produced. An install plan for a project shape the SDK has no entry point for is not a smaller plan, it is a wrong one." : "No steps were produced. An install plan for a surface that answers 404 is not a smaller plan, it is a wrong one."
224
+ );
251
225
  return lines.join("\n");
252
226
  }
253
227
  function renderReady(document) {
@@ -297,53 +271,6 @@ function renderPlan(document) {
297
271
  return renderReady(document);
298
272
  }
299
273
 
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
274
  // src/http/outcome.ts
348
275
  var CODE_FOR_KIND = Object.freeze({
349
276
  network: "remote_unreachable",
@@ -358,187 +285,6 @@ function failureOutcome(failure, extraDetails = []) {
358
285
  ]);
359
286
  }
360
287
 
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
288
  // src/commands/diagnose/render.ts
543
289
  function renderLevels(diagnosis) {
544
290
  if (diagnosis.scope.levels.length === 0) return "(none reported)";
@@ -570,6 +316,7 @@ function renderDiagnosis(input) {
570
316
  ` region: ${diagnosis.scope.region}`,
571
317
  ` levels: ${renderLevels(diagnosis)}`
572
318
  ];
319
+ lines.push(...renderCauses(diagnosis));
573
320
  lines.push("", `Available (${available.length})`);
574
321
  if (available.length === 0) {
575
322
  lines.push(" none");
@@ -579,6 +326,7 @@ function renderDiagnosis(input) {
579
326
  lines.push("", `Unavailable (${unavailable.length})`);
580
327
  if (unavailable.length === 0) {
581
328
  lines.push(" none");
329
+ lines.push(...renderSwitches(diagnosis));
582
330
  return lines.join("\n");
583
331
  }
584
332
  const groups = /* @__PURE__ */ new Map();
@@ -601,136 +349,43 @@ function renderDiagnosis(input) {
601
349
  if (first.reason.length > 0) lines.push(` why: ${first.reason}`);
602
350
  lines.push(` next: ${first.nextAction}`);
603
351
  }
352
+ lines.push(...renderSwitches(diagnosis));
604
353
  return lines.join("\n");
605
354
  }
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.";
355
+ function renderSwitches(diagnosis) {
356
+ const lines = [""];
357
+ if (diagnosis.switches === null) {
358
+ lines.push("Switches", " (the response carried none)");
359
+ return lines;
637
360
  }
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
- );
361
+ const off = diagnosis.switches.filter(countsAsNotOn);
362
+ lines.push(`Switches (${diagnosis.switches.length}, ${off.length} not on)`);
363
+ if (diagnosis.switches.length === 0) {
364
+ lines.push(" none");
365
+ return lines;
690
366
  }
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;
367
+ for (const entry of diagnosis.switches) {
368
+ const state = entry.enabled === true ? "on" : entry.enabled === false ? "off" : "unknown";
369
+ lines.push(` ${state.padEnd(7)} ${entry.name}${entry.kind === "posture" ? " (posture)" : ""}`);
370
+ if (entry.enabled === true && entry.kind !== "posture") continue;
371
+ if (entry.gates.length > 0) lines.push(` gates: ${entry.gates.join(", ")}`);
372
+ if (entry.tenantTerm !== null) lines.push(` tenant term: ${entry.tenantTerm}`);
373
+ if (entry.reason.length > 0) lines.push(` why: ${entry.reason}`);
374
+ lines.push(` next: ${entry.nextAction}`);
711
375
  }
712
- return previous[right.length] ?? Math.max(left.length, right.length);
376
+ return lines;
713
377
  }
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 });
378
+ function renderCauses(diagnosis) {
379
+ const causes = diagnoseVerdict(diagnosis);
380
+ if (causes.length === 0) return [];
381
+ const lines = ["", `What is wrong (${causes.length})`];
382
+ for (const entry of causes) {
383
+ lines.push(` [${entry.cause}] ${entry.summary}`);
384
+ if (entry.capabilities.length > 0) lines.push(` capabilities: ${entry.capabilities.join(", ")}`);
385
+ if (entry.switches.length > 0) lines.push(` switches: ${entry.switches.join(", ")}`);
386
+ if (entry.switches.length > 0 || entry.reasonCodes.length !== 1) lines.push(` next: ${entry.nextAction}`);
731
387
  }
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));
388
+ return lines;
734
389
  }
735
390
 
736
391
  // src/commands/sandbox/create.ts
@@ -903,7 +558,7 @@ function findContractPath(context, flagValue) {
903
558
  function setUpWire(context, flags, options = {}) {
904
559
  const errors = [];
905
560
  const endpointResolution = resolveEndpoint(flags.string("base-url"), context.env);
906
- if (!endpointResolution.ok) errors.push(...endpointResolution.errors);
561
+ if (endpointResolution.ok === false) errors.push(...endpointResolution.errors);
907
562
  const timeout = parseTimeout(flags.string("timeout"));
908
563
  errors.push(...timeout.errors);
909
564
  const requestId = flags.string("request-id");
@@ -972,15 +627,48 @@ var FLAGS = Object.freeze([
972
627
  summary: `How many times to read the verdict before calling it empty (default ${DEFAULT_ATTEMPTS}).`,
973
628
  placeholder: "n"
974
629
  },
630
+ {
631
+ name: "origin",
632
+ kind: "string",
633
+ summary: "For a web integration: the page origin (https://shop.example.com) the surface is embedded on. Adds the embed fence, CORS exposure, session policy and loader checks.",
634
+ placeholder: "origin"
635
+ },
636
+ {
637
+ name: "loader-url",
638
+ kind: "string",
639
+ summary: "With --origin: the loader to probe instead of the one derived from the API host. Without it a local or unrecognised API reports the loader check as not checked.",
640
+ placeholder: "url"
641
+ },
642
+ {
643
+ // The same flag name `plan` takes for the same fact (the argv parser knows it as a value flag).
644
+ name: "identity",
645
+ kind: "string",
646
+ summary: `The identity mode to verify with: ${VERIFY_IDENTITY_MODES.join(" or ")}. partner-user-ref (the default) uses a signed session; anonymous uses the publishable key and a device session id, and reports whether the tenant requires signed sessions.`,
647
+ placeholder: "mode"
648
+ },
649
+ {
650
+ name: "surfaces",
651
+ kind: "string",
652
+ summary: `Comma separated surfaces to exercise with the minted session: ${SURFACES.join(", ")}. Televet mints a handoff only on a sandbox tenant. With --identity anonymous, records and televet are skipped: both take a signed session only.`,
653
+ placeholder: "list"
654
+ },
655
+ {
656
+ name: "project-root",
657
+ kind: "string",
658
+ summary: "The application root. Adds offline project checks: no secret key in source or client build output, no secret behind a public prefix, the secret read only by server code, 'use client' on App Router files, and the env files' API host. With --surfaces theme, the branding is judged merged with the theme prop everfur plan writes from the project's own theme.",
659
+ placeholder: "path"
660
+ },
975
661
  CONTRACT_FLAG,
976
662
  TIMEOUT_FLAG,
977
663
  REQUEST_ID_FLAG
978
664
  ]);
979
665
  function buildVerificationDocument(verification, baseUrl, cliVersion) {
980
666
  const counts = { passed: 0, failed: 0, skipped: 0 };
667
+ let gated = 0;
981
668
  for (const entry of verification.steps) {
982
669
  if (entry.result === "PASS") counts.passed += 1;
983
670
  else if (entry.result === "FAIL") counts.failed += 1;
671
+ else if (entry.result === "GATED") gated += 1;
984
672
  else counts.skipped += 1;
985
673
  }
986
674
  return {
@@ -989,7 +677,8 @@ function buildVerificationDocument(verification, baseUrl, cliVersion) {
989
677
  generatedBy: { name: "@everfur/cli", version: cliVersion },
990
678
  status: verification.status,
991
679
  baseUrl,
992
- summary: counts,
680
+ // `gated` appears only when a step was GATED, so a document from a run with no surface checks is unchanged.
681
+ summary: gated === 0 ? counts : { ...counts, gated },
993
682
  steps: verification.steps.map((entry) => ({
994
683
  id: entry.id,
995
684
  title: entry.title,
@@ -1008,7 +697,7 @@ function renderVerification(verification, baseUrl) {
1008
697
  ""
1009
698
  ];
1010
699
  for (const entry of verification.steps) {
1011
- lines.push(` ${entry.result.padEnd(4)} ${entry.id} ${entry.title}`);
700
+ lines.push(` ${entry.result.padEnd(5)} ${entry.id} ${entry.title}`);
1012
701
  lines.push(` ${entry.reason}`);
1013
702
  lines.push(` request id: ${entry.requestId ?? "(none; no call was made)"}`);
1014
703
  for (const detail of entry.details) lines.push(` - ${detail}`);
@@ -1018,8 +707,12 @@ function renderVerification(verification, baseUrl) {
1018
707
  lines.push("At least one step failed. The reasons above are the report; the exit code only summarises them.");
1019
708
  } else if (verification.status === "incomplete") {
1020
709
  lines.push("Nothing failed, but a step was skipped, so the wire was not fully exercised.");
710
+ } else if (verification.steps.some((entry) => entry.result === "GATED")) {
711
+ lines.push("Nothing failed. A GATED step is a surface Everfur has not switched on for this tenant, not a fault in the integration.");
1021
712
  } else {
1022
- lines.push("Every step passed. The session mint, the verdict read, and the verdict itself are all healthy.");
713
+ lines.push(
714
+ verification.steps.some((entry) => entry.id === "anonymous-identity") ? "Every step passed. The anonymous identity, the verdict read, and the verdict itself are all healthy." : "Every step passed. The session mint, the verdict read, and the verdict itself are all healthy."
715
+ );
1023
716
  }
1024
717
  return lines.join("\n");
1025
718
  }
@@ -1037,17 +730,37 @@ function parseAttempts(raw) {
1037
730
  var verifyCommand = {
1038
731
  path: ["verify"],
1039
732
  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]",
733
+ usage: "verify [--base-url <url>] [--user-ref <ref>] [--attempts <n>] [--identity <mode>] [--origin <origin>] [--loader-url <url>] [--surfaces <list>] [--project-root <path>] [--json]",
1041
734
  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]),
735
+ /**
736
+ * Exactly what `mintSession` resolves: the session token first, else the key pair it mints with locally. Without
737
+ * either source, step one reports SKIP rather than FAIL.
738
+ */
739
+ credentials: Object.freeze([SESSION_TOKEN, PARTNER_KEY, PARTNER_SECRET_KEY]),
1044
740
  async run(context, flags) {
1045
741
  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);
742
+ const originFlag = flags.string("origin");
743
+ const origin = originFlag === null ? { origin: null, error: null } : parseOrigin(originFlag);
744
+ const loaderFlag = flags.string("loader-url");
745
+ const loader = loaderFlag === null ? { url: null, error: null } : parseLoaderUrl(loaderFlag);
746
+ const surfaces = parseSurfaces(flags.string("surfaces"));
747
+ const identityMode = parseIdentityMode(flags.string("identity"));
748
+ const projectRootFlag = flags.string("project-root");
749
+ const projectRoot = projectRootFlag === null ? null : resolveFromCwd(context.cwd, projectRootFlag.trim());
750
+ const projectErrors = projectRoot !== null && !context.fs.isDirectory(projectRoot) ? [`--project-root is not a directory: ${projectRoot}`] : [];
751
+ const flagErrors = [
752
+ ...attempts.errors,
753
+ ...origin.error === null ? [] : [origin.error],
754
+ ...loader.error === null ? [] : [loader.error],
755
+ ...surfaces.errors,
756
+ ...identityMode.errors,
757
+ ...projectErrors
758
+ ];
759
+ if (flagErrors.length > 0) {
760
+ return errorOutcome("usage_error", "one or more flag values were not understood", flagErrors);
1048
761
  }
1049
762
  const wire = setUpWire(context, flags);
1050
- if (!wire.ok) return wire.outcome;
763
+ if (wire.ok === false) return wire.outcome;
1051
764
  const userRefFlag = flags.string("user-ref");
1052
765
  const userRef = userRefFlag === null || userRefFlag.trim().length === 0 ? DEFAULT_USER_REF : userRefFlag.trim();
1053
766
  const verification = await runVerification({
@@ -1056,7 +769,13 @@ var verifyCommand = {
1056
769
  env: context.env,
1057
770
  userRef,
1058
771
  attempts: attempts.value,
1059
- pollDelayMs: POLL_DELAY_MS
772
+ pollDelayMs: POLL_DELAY_MS,
773
+ origin: origin.origin,
774
+ loaderUrl: loader.url,
775
+ transport: context.transport,
776
+ surfaces: surfaces.value ?? [],
777
+ project: projectRoot === null ? null : { fs: context.fs, root: projectRoot },
778
+ identityMode: identityMode.value
1060
779
  });
1061
780
  const document = buildVerificationDocument(verification, wire.client.baseUrl, context.version);
1062
781
  const text = renderVerification(verification, wire.client.baseUrl);
@@ -1080,7 +799,7 @@ var FLAGS2 = Object.freeze([
1080
799
  {
1081
800
  name: "capabilities",
1082
801
  kind: "string",
1083
- summary: "Comma separated capability ids. The valid ids come from the contract decision trees.",
802
+ summary: 'Comma separated capability ids, or "all" for every capability the contract can plan. The valid ids come from the contract decision trees; verify is the fail-closed check of what the tenant actually has.',
1084
803
  placeholder: "list"
1085
804
  },
1086
805
  {
@@ -1095,6 +814,18 @@ var FLAGS2 = Object.freeze([
1095
814
  summary: "Language that mints sessions server side. Required only when the identity mode mints.",
1096
815
  placeholder: "language"
1097
816
  },
817
+ {
818
+ name: "embedding",
819
+ kind: "string",
820
+ summary: "How a web project embeds the surface: in-page (the SDK's React components) or frame (the loader). Detected from react-dom; the flag overrides.",
821
+ placeholder: "mode"
822
+ },
823
+ {
824
+ name: "environment",
825
+ kind: "string",
826
+ summary: `The Everfur deployment the generated configuration targets: ${ENVIRONMENTS.join(", ")}. Required for a plan that writes configuration; never assumed.`,
827
+ placeholder: "name"
828
+ },
1098
829
  CONTRACT_FLAG
1099
830
  ]);
1100
831
  function asJsonObject(document) {
@@ -1126,13 +857,13 @@ function contractOutcome(outcome) {
1126
857
  var planCommand = {
1127
858
  path: ["plan"],
1128
859
  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]",
860
+ usage: "plan [--cwd <path>] [--capabilities <list>] [--identity <mode>] [--backend <language>] [--embedding <mode>] [--environment <name>] [--contract <file>] [--json]",
1130
861
  flags: FLAGS2,
862
+ credentials: Object.freeze([DOCS_KEY]),
1131
863
  async run(context, flags) {
1132
864
  const contractFlag = flags.string("contract");
1133
865
  const contractPath = findContractPath(context, contractFlag);
1134
866
  let space = null;
1135
- let usedBundledContract = false;
1136
867
  if (contractPath !== null) {
1137
868
  const load = loadDecisionSpace(context.fs, contractPath);
1138
869
  if (load.kind === "loaded") {
@@ -1144,46 +875,58 @@ var planCommand = {
1144
875
  ]);
1145
876
  }
1146
877
  }
878
+ const cwdFlag = flags.string("cwd");
879
+ const root = cwdFlag === null ? context.cwd : isAbsolute(cwdFlag) ? cwdFlag : resolve(context.cwd, cwdFlag);
880
+ if (!context.fs.isDirectory(root)) {
881
+ return errorOutcome("usage_error", `not a directory: ${root}`, [
882
+ "pass --cwd with a path to the application root"
883
+ ]);
884
+ }
1147
885
  if (space === null && contractPath === null) {
1148
- const bundled = loadBundledDecisionSpace();
1149
- if (bundled.kind === "loaded") {
1150
- space = bundled.space;
1151
- usedBundledContract = true;
886
+ const fetched = await fetchContract({
887
+ env: context.env,
888
+ transport: context.transport,
889
+ clock: context.clock,
890
+ ...context.contractCache === void 0 ? {} : { cache: context.contractCache }
891
+ });
892
+ if (fetched.kind === "unavailable") return contractUnavailableOutcome(fetched);
893
+ const load = loadDecisionSpaceFromContract(fetched.contract);
894
+ if (load.kind !== "loaded") {
895
+ return errorOutcome("invalid_contract", `${fetched.contract.source} carries no decision trees`, [
896
+ "the contract validated, but it has no `decision-trees.yaml` document with a `trees` list"
897
+ ]);
1152
898
  }
899
+ space = load.space;
1153
900
  }
1154
901
  const supplied = parseSuppliedFacts(
1155
902
  flags.string("capabilities"),
1156
903
  flags.string("identity"),
1157
904
  flags.string("backend"),
1158
- space
905
+ space,
906
+ flags.string("embedding")
1159
907
  );
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"
908
+ const environment = parseEnvironment(flags.string("environment"));
909
+ if (!supplied.ok || environment.errors.length > 0) {
910
+ return errorOutcome("usage_error", "one or more flag values were not understood", [
911
+ ...supplied.errors,
912
+ ...environment.errors
1168
913
  ]);
1169
914
  }
1170
915
  const detection = detectProject(context.fs, root);
1171
916
  const document = planIntegration({
1172
917
  detection,
1173
918
  capabilities: supplied.capabilities,
919
+ allCapabilities: supplied.allCapabilities,
1174
920
  identityMode: supplied.identityMode,
1175
921
  sessionMintLanguage: supplied.sessionMintLanguage,
922
+ embedding: supplied.embedding,
923
+ environment: environment.value,
1176
924
  space,
1177
925
  cliVersion: context.version
1178
926
  });
1179
927
  const json = asJsonObject(document);
1180
928
  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
- }
929
+ if (document.status === "underspecified") return fail(ExitCode.UNDERSPECIFIED, json, text);
1187
930
  if (document.status === "blocked") return fail(ExitCode.BLOCKED, json, text);
1188
931
  return succeed(json, text);
1189
932
  }
@@ -1255,42 +998,7 @@ var contractValidateCommand = {
1255
998
  }
1256
999
  };
1257
1000
 
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
1001
  // src/commands/diagnose/index.ts
1293
- var DIAGNOSTICS_PATH = "/widget/v1/diagnostics";
1294
1002
  var FLAGS4 = Object.freeze([
1295
1003
  BASE_URL_FLAG,
1296
1004
  {
@@ -1299,6 +1007,17 @@ var FLAGS4 = Object.freeze([
1299
1007
  summary: "End user reference to diagnose as. Omit to diagnose the anonymous subject.",
1300
1008
  placeholder: "ref"
1301
1009
  },
1010
+ {
1011
+ name: "bundle",
1012
+ kind: "boolean",
1013
+ summary: "Emit one support bundle instead: project, framework, SDK and CLI versions, contract digest, credential presence (never values), and the diagnosis with its request id. Produced whatever state the integration is in."
1014
+ },
1015
+ {
1016
+ name: "project-root",
1017
+ kind: "string",
1018
+ summary: "With --bundle: the application root to describe. Defaults to the current working directory.",
1019
+ placeholder: "path"
1020
+ },
1302
1021
  CONTRACT_FLAG,
1303
1022
  TIMEOUT_FLAG,
1304
1023
  REQUEST_ID_FLAG
@@ -1306,15 +1025,41 @@ var FLAGS4 = Object.freeze([
1306
1025
  var diagnoseCommand = {
1307
1026
  path: ["diagnose"],
1308
1027
  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]",
1028
+ usage: "diagnose [--base-url <url>] [--user-ref <ref>] [--contract <file>] [--bundle [--project-root <path>]] [--json]",
1310
1029
  flags: FLAGS4,
1311
1030
  /** Exactly what `chooseWidgetAuth` resolves: the bearer wins, the publishable key is the fallback. */
1312
1031
  credentials: Object.freeze([PARTNER_KEY, SESSION_TOKEN]),
1313
1032
  async run(context, flags) {
1314
1033
  const wire = setUpWire(context, flags);
1315
- if (!wire.ok) return wire.outcome;
1034
+ if (flags.boolean("bundle")) {
1035
+ const projectRootFlag = flags.string("project-root");
1036
+ const root = projectRootFlag === null ? context.cwd : resolveFromCwd(context.cwd, projectRootFlag.trim());
1037
+ const wireErrors = wire.ok ? [] : [...wire.outcome.json.error?.details ?? []];
1038
+ const bundle = await buildSupportBundle({
1039
+ fs: context.fs,
1040
+ root,
1041
+ env: context.env,
1042
+ cliVersion: context.version,
1043
+ client: wire.ok ? wire.client : null,
1044
+ wireErrors,
1045
+ contractSource: wire.ok ? wire.registrySource : null,
1046
+ contract: () => fetchContract({
1047
+ env: context.env,
1048
+ transport: context.transport,
1049
+ clock: context.clock,
1050
+ ...context.contractCache === void 0 ? {} : { cache: context.contractCache }
1051
+ })
1052
+ });
1053
+ return succeed(bundle, renderSupportBundle(bundle));
1054
+ }
1055
+ if (flags.string("project-root") !== null) {
1056
+ return errorOutcome("usage_error", "--project-root describes the project in a support bundle, so it needs --bundle", [
1057
+ "run `everfur diagnose --bundle --project-root <path>`"
1058
+ ]);
1059
+ }
1060
+ if (wire.ok === false) return wire.outcome;
1316
1061
  const auth = chooseWidgetAuth(context.env);
1317
- if (!auth.ok) return auth.outcome;
1062
+ if (auth.ok === false) return auth.outcome;
1318
1063
  const userRef = flags.string("user-ref");
1319
1064
  const headers = {};
1320
1065
  if (userRef !== null && userRef.trim().length > 0) {
@@ -1326,14 +1071,29 @@ var diagnoseCommand = {
1326
1071
  headers,
1327
1072
  credentials: auth.auth.headers
1328
1073
  });
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"
1074
+ if (result.ok === false) {
1075
+ const diagnosed = diagnoseFailure(result, auth.auth);
1076
+ const outcome = failureOutcome(result, [
1077
+ `cause: ${diagnosed.summary}`,
1078
+ ...diagnosed.evidence,
1079
+ `next action: ${diagnosed.nextAction}`
1333
1080
  ]);
1081
+ const document2 = outcome.json;
1082
+ return {
1083
+ ...outcome,
1084
+ json: {
1085
+ ...document2,
1086
+ error: {
1087
+ ...document2.error,
1088
+ cause: diagnosed.cause,
1089
+ evidence: [...diagnosed.evidence],
1090
+ nextAction: diagnosed.nextAction
1091
+ }
1092
+ }
1093
+ };
1334
1094
  }
1335
1095
  const parsed = parseDiagnostics(result.value, result.requestId);
1336
- if (!parsed.ok) {
1096
+ if (parsed.ok === false) {
1337
1097
  return errorOutcome(
1338
1098
  "malformed_response",
1339
1099
  `${DIAGNOSTICS_PATH} answered ${result.status} with a body this CLI does not recognise`,
@@ -1369,34 +1129,50 @@ var explainErrorCommand = {
1369
1129
  usage: "explain-error <code> [--contract <file>] [--json]",
1370
1130
  flags: FLAGS5,
1371
1131
  positionals: POSITIONALS,
1132
+ credentials: Object.freeze([DOCS_KEY]),
1372
1133
  async run(context, flags, positionals) {
1373
1134
  const code = (positionals[0] ?? "").trim();
1374
1135
  if (code.length === 0) {
1375
1136
  return errorOutcome("usage_error", "no error code was given", ["usage: everfur explain-error <code>"]);
1376
1137
  }
1138
+ const offline = resolveOffline(code);
1139
+ if (offline?.kind === "sdk_member") return offlineOutcome(offline, context.version);
1377
1140
  const contractFlag = flags.string("contract");
1378
1141
  const contractPath = findContractPath(context, contractFlag);
1142
+ let load;
1379
1143
  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
- ]);
1144
+ const fetched = await fetchContract({
1145
+ env: context.env,
1146
+ transport: context.transport,
1147
+ clock: context.clock,
1148
+ ...context.contractCache === void 0 ? {} : { cache: context.contractCache }
1149
+ });
1150
+ if (fetched.kind === "unavailable") {
1151
+ if (offline !== null) return offlineOutcome(offline, context.version);
1152
+ const suggestions = offlineSuggestions(code);
1153
+ return contractUnavailableOutcome(fetched, [
1154
+ ...suggestions.length > 0 ? [`did you mean: ${suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : [`no code similar to "${code}" exists in the SDK taxonomy or the SDK contract table this CLI carries`]
1155
+ ]);
1156
+ }
1157
+ load = loadErrorRegistryFromContract(fetched.contract);
1158
+ } else {
1159
+ load = loadErrorRegistry(context.fs, contractPath);
1385
1160
  }
1386
- const load = loadErrorRegistry(context.fs, contractPath);
1387
1161
  if (load.kind === "contract_rejected") return contractOutcome2(load.outcome);
1388
1162
  if (load.kind === "no_registry") {
1389
1163
  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",
1164
+ "no `errors.yaml` document with an `errors` list was found in the projection",
1391
1165
  "this is a projection built before the error registry was published, or a partial one"
1392
1166
  ]);
1393
1167
  }
1394
1168
  const entry = load.registry.lookup(code);
1395
1169
  if (entry === null) {
1396
- const suggestions = suggestCodes(code, load.registry.codes);
1170
+ const fallback = resolveAfterRegistryMiss(code, load.registry);
1171
+ if (fallback.kind !== "unknown") return offlineOutcome(fallback, context.version);
1397
1172
  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`
1173
+ ...fallback.suggestions.length > 0 ? [`did you mean: ${fallback.suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : ["no similar code exists in the registry"],
1174
+ `the registry loaded from ${load.path} lists ${load.registry.codes.length} codes`,
1175
+ `neither the SDK taxonomy nor the SDK contract table this CLI carries knows it either (${knownCodes(load.registry).length} codes searched in all)`
1400
1176
  ]);
1401
1177
  }
1402
1178
  const input = {
@@ -1408,6 +1184,9 @@ var explainErrorCommand = {
1408
1184
  return succeed(buildExplanationDocument(input), renderExplanation(input));
1409
1185
  }
1410
1186
  };
1187
+ function offlineOutcome(resolution, cliVersion) {
1188
+ return resolution.kind === "sdk_member" ? succeed(buildSdkExplanationDocument(resolution.code, cliVersion), renderSdkExplanation(resolution.code)) : succeed(buildContractTableExplanationDocument(resolution.row, cliVersion), renderContractTableExplanation(resolution.row));
1189
+ }
1411
1190
  function contractOutcome2(outcome) {
1412
1191
  switch (outcome.kind) {
1413
1192
  case "file_not_found":
@@ -1467,7 +1246,7 @@ var sandboxCreateCommand = {
1467
1246
  ]);
1468
1247
  }
1469
1248
  const wire = setUpWire(context, flags);
1470
- if (!wire.ok) return wire.outcome;
1249
+ if (wire.ok === false) return wire.outcome;
1471
1250
  const admin = resolveCredential(context.env, ADMIN_TOKEN);
1472
1251
  if (admin.status !== "present" || admin.credential === null) {
1473
1252
  return errorOutcome("credential_missing", "no admin credential is configured", [
@@ -1484,14 +1263,14 @@ var sandboxCreateCommand = {
1484
1263
  credentials: { authorization: withPrefix(admin.credential, "Bearer ") },
1485
1264
  body
1486
1265
  });
1487
- if (!result.ok) {
1266
+ if (result.ok === false) {
1488
1267
  return failureOutcome(result, [
1489
1268
  "no key was provisioned, so there is nothing to revoke",
1490
1269
  "a 400 here means the client exists but is not an active partner tenant"
1491
1270
  ]);
1492
1271
  }
1493
1272
  const projected = projectSandboxCreate(result.value);
1494
- if (!projected.ok) {
1273
+ if (projected.ok === false) {
1495
1274
  return errorOutcome("malformed_response", `${path} answered ${result.status} with an unreadable body`, [
1496
1275
  projected.detail,
1497
1276
  result.requestId === null ? "the response carried no request id" : `request id: ${result.requestId}`,
@@ -1545,6 +1324,9 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
1545
1324
  "capabilities",
1546
1325
  "identity",
1547
1326
  "backend",
1327
+ "embedding",
1328
+ "origin",
1329
+ "loader-url",
1548
1330
  "path",
1549
1331
  "base-url",
1550
1332
  "contract",
@@ -1553,7 +1335,10 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
1553
1335
  "user-ref",
1554
1336
  "timeout",
1555
1337
  "attempts",
1556
- "request-id"
1338
+ "request-id",
1339
+ "environment",
1340
+ "surfaces",
1341
+ "project-root"
1557
1342
  ]);
1558
1343
  var ALIASES = Object.freeze({
1559
1344
  h: "help",
@@ -1798,7 +1583,7 @@ async function runCli(options) {
1798
1583
  return formatOutcome(succeed({ schemaVersion: 1, kind: "everfur.help", text: help }, help), format);
1799
1584
  }
1800
1585
  const bound = bindFlags(match.command.flags, parsed);
1801
- if (!bound.ok) {
1586
+ if (bound.ok === false) {
1802
1587
  return formatOutcome(
1803
1588
  errorOutcome("usage_error", `invalid invocation of "${commandName(match.command)}"`, [
1804
1589
  ...bound.errors,
@@ -1819,4 +1604,4 @@ async function runCli(options) {
1819
1604
  }
1820
1605
  }
1821
1606
 
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 };
1607
+ export { COMMANDS, buildSandboxDocument, buildVerificationDocument, failureOutcome, formatOutcome, matchCommand, nextActionsFor, parseCapabilities, parseIdentityMode2 as parseIdentityMode, parseSessionMintLanguage, parseSuppliedFacts, projectSandboxCreate, renderDiagnosis, renderPlan, renderSandboxCreate, renderVerification, runCli };