@everfur/cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -17
- package/dist/chunk-CEWWBPPT.js +12726 -0
- package/dist/{chunk-67H43MI6.js → chunk-S6UN7D2O.js} +295 -510
- package/dist/chunk-TBFOL2D3.js +161 -0
- package/dist/chunk-VHZWTB6J.js +893 -0
- package/dist/chunk-WCH6AI6K.js +2512 -0
- package/dist/everfur-mcp-container.d.ts +2 -0
- package/dist/everfur-mcp-container.js +60 -0
- package/dist/everfur-mcp-hosted.d.ts +2 -0
- package/dist/everfur-mcp-hosted.js +28 -0
- package/dist/everfur-mcp-http.d.ts +2 -0
- package/dist/everfur-mcp-http.js +21 -0
- package/dist/everfur-mcp.js +11 -365
- package/dist/everfur.js +4 -3
- package/dist/index.d.ts +875 -41
- package/dist/index.js +2 -21
- package/package.json +5 -4
- package/schema/diagnosis.v1.schema.json +41 -0
- package/schema/plan.v1.schema.json +90 -4
- package/schema/verification.v1.schema.json +7 -3
- package/contract/contract-public.json +0 -1335
- package/dist/chunk-KQZ6ZXL7.js +0 -3994
|
@@ -1,61 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
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-CEWWBPPT.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
|
-
|
|
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 (
|
|
49
|
+
if (supported === null || supported.includes(token)) {
|
|
102
50
|
selected.add(token);
|
|
103
51
|
continue;
|
|
104
52
|
}
|
|
105
|
-
errors.push(`unknown capability "${token}"; supported: ${
|
|
53
|
+
errors.push(`unknown capability "${token}"; supported: ${supported.join(", ")}`);
|
|
106
54
|
}
|
|
107
|
-
if (errors.length > 0) return { value: null, errors };
|
|
108
|
-
|
|
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
|
|
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
|
|
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 =
|
|
79
|
+
const identity = parseIdentityMode2(identityRaw, space);
|
|
128
80
|
const backend = parseSessionMintLanguage(backendRaw, space);
|
|
129
|
-
const
|
|
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(
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
|
376
|
+
return lines;
|
|
713
377
|
}
|
|
714
|
-
function
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
const
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
if (
|
|
722
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
-
/**
|
|
1043
|
-
|
|
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
|
-
|
|
1047
|
-
|
|
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 (
|
|
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:
|
|
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
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
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 (
|
|
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
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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
|
-
"
|
|
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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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,
|
|
1607
|
+
export { COMMANDS, buildSandboxDocument, buildVerificationDocument, failureOutcome, formatOutcome, matchCommand, nextActionsFor, parseCapabilities, parseIdentityMode2 as parseIdentityMode, parseSessionMintLanguage, parseSuppliedFacts, projectSandboxCreate, renderDiagnosis, renderPlan, renderSandboxCreate, renderVerification, runCli };
|