@everfur/cli 0.2.1 → 0.4.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.
- package/README.md +104 -50
- package/dist/{chunk-4UB26D67.js → chunk-2MTX6YWO.js} +74 -30
- package/dist/{chunk-FC23PLWG.js → chunk-A2QEBO5U.js} +1 -1
- package/dist/{chunk-KQICUK54.js → chunk-EBVP6FAD.js} +5625 -1103
- package/dist/{chunk-XMIERMOW.js → chunk-QCXZJW5C.js} +663 -227
- package/dist/{chunk-SJUEHHU6.js → chunk-YRGSY4R2.js} +81 -14
- package/dist/everfur-mcp-hosted.js +4 -4
- package/dist/everfur-mcp-http.js +3 -3
- package/dist/everfur-mcp.js +2 -2
- package/dist/everfur.js +2 -2
- package/dist/index.d.ts +420 -113
- package/dist/index.js +2 -2
- package/package.json +4 -3
- package/schema/error-explanation.v1.schema.json +26 -4
- package/schema/plan.v1.schema.json +49 -4
- package/schema/verification.v1.schema.json +5 -4
- package/dist/everfur-mcp-container.d.ts +0 -2
- package/dist/everfur-mcp-container.js +0 -60
package/README.md
CHANGED
|
@@ -14,9 +14,14 @@ template) includes a 2 KB loader and gets the chat in an iframe on Everfur's ori
|
|
|
14
14
|
a Shopify theme or app gets the Everfur app's `Everfur chat` block, with Shopify vouching for the
|
|
15
15
|
signed-in customer so no backend mints. `--embedding in-page|frame` overrides the detected embedding.
|
|
16
16
|
A capability the web cannot render (photo in the page, anything but chat in the frame) is reported as
|
|
17
|
-
blocked by the platform, beside the steps for the rest.
|
|
18
|
-
adds the checks a
|
|
19
|
-
browser depends on, the tenant's session policy, and the loader
|
|
17
|
+
blocked by the platform, beside the steps for the rest. For a page that includes the loader,
|
|
18
|
+
`everfur verify --origin https://your.page --embedding frame` adds the checks a framed page needs: the embed
|
|
19
|
+
allowlist for that origin, the CORS exposure a browser depends on, the tenant's session policy, and the loader
|
|
20
|
+
of the key's environment. An in-page integration calls the API from its own origin, so it passes `--origin` too, with
|
|
21
|
+
`--embedding in-page`: the preflight is of a widget request with the headers the SDK sends, the exposure and the
|
|
22
|
+
session policy are checked, and the embed fence and the loader, which it never uses, are reported `N/A` rather than
|
|
23
|
+
failed. Only a named `--embedding in-page` does that: with none named every check runs, and a project whose page
|
|
24
|
+
includes the Everfur loader is checked as the frame it embeds.
|
|
20
25
|
|
|
21
26
|
**The generated surface matches your own theme when the plan can read it, and says so when it cannot.** The
|
|
22
27
|
plan reads, statically and never by executing anything, a Tailwind v4 `@theme` block, a `tailwind.config.{js,ts}`
|
|
@@ -37,14 +42,22 @@ config that runs code when it loads) is low: no theme is written, and the step n
|
|
|
37
42
|
offers the manual `theme={{ ... }}` snippet. Over a remote MCP pass the config or CSS text as `theme_source`. The
|
|
38
43
|
tenant's console branding is merged with the emitted theme per field (a locked field keeps the console's value;
|
|
39
44
|
the console fills what the theme leaves unset); the plan holds no branding credential and never changes it.
|
|
45
|
+
Branding v2 (SDK 0.9.0): a success, warning or info colour and a heading stack the theme declares by name
|
|
46
|
+
(`--success`, `--color-warning`, `--font-heading`, `--font-display`, Tailwind v3 `colors.info` or
|
|
47
|
+
`fontFamily.heading`) are written too, as `successColor`, `warningColor`, `infoColor` (with their dark twins) and
|
|
48
|
+
`headingFontFamily`; a theme that declares none gets none. The logo and the chat's assistant avatar are uploaded in
|
|
49
|
+
the partner console, the only images the SDK shows, so a logo read from the project is named as the image to upload
|
|
50
|
+
rather than written. Custom fonts apply in-page only: the hosted frame keeps Everfur's fonts.
|
|
40
51
|
|
|
41
52
|
**The chat and records mounts match the Everfur app, and the host seams are generated with them.** Chat has the
|
|
42
53
|
header pet switcher (it appears by itself once two or more pets are registered), quick prompts, citations, thumbs
|
|
43
54
|
feedback, the urgency banner and the history drawer built in. On an emergency or schedule-soon reply the banner's
|
|
44
55
|
Find a vet pill opens the Everfur vet visit entry when Everfur has it on for your client, else calls `onFindVet`, else
|
|
45
|
-
does not render
|
|
46
|
-
|
|
47
|
-
|
|
56
|
+
does not render. No generated mount passes `onFindVet`, so the plan writes no vet finder of its own that sends the
|
|
57
|
+
member to a page outside your app; the commented `onFindVet` line beside each in-page chat mount is for an app with
|
|
58
|
+
its own in-app vet finder (on React Native, passing it also replaces the Everfur vet entry on the check-in card). A
|
|
59
|
+
frame takes no `onFindVet`,
|
|
60
|
+
so there the pill appears only while Everfur has its vet visit entry on for your client. The SDK bundles no picker and no
|
|
48
61
|
clipboard module, so an EXPO plan mounts `<EverfurChat attachments={{ pick: pickChatPhoto }}
|
|
49
62
|
onCopy={copyText} />` and writes both helpers into `everfur/EverfurRoot.tsx`, the module the app mounts from its
|
|
50
63
|
own entry (`app/_layout.tsx` in an Expo Router app, `App.tsx` otherwise): in an Expo app `pickChatPhoto` opens the system
|
|
@@ -70,24 +83,52 @@ because it asked for one) and always carries the size when the picker reports it
|
|
|
70
83
|
records upload, so no image picker is wired there. The system document UI is not the media library and needs no
|
|
71
84
|
permission on either platform, so unlike the chat photo picker there is nothing to request before it opens. On the web the
|
|
72
85
|
browser is the picker and the speech engine: photo attachments and voice dictation are on by default, and
|
|
73
|
-
`attachments={false}` and `dictation={false}` turn them off.
|
|
74
|
-
opens on the pet's dashboard and owns its own stack (the hardware back key pops it),
|
|
75
|
-
name
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
`attachments={false}` and `dictation={false}` turn them off. On React Native records is the app's screen flow:
|
|
87
|
+
`<EverfurRecords>` opens on the pet's dashboard and owns its own stack (the hardware back key pops it), and
|
|
88
|
+
`initialScreen={{ name: 'request', requestId }}` opens a request directly, which is what the generated notification
|
|
89
|
+
router's `record_request` handler says to do. On the web records is a page, the shape of Everfur's own web records
|
|
90
|
+
page: the generated `EverfurRecordsSurface` renders `EverfurRecordsPage` and opens what it links to (the full record,
|
|
91
|
+
the timeline, an upload) in the records flow in the same place, with no route added to your app; the page holds no clinic
|
|
92
|
+
request of its own, so `records-clinic` mounts beside it. Every screen is exported by name for your own navigator,
|
|
93
|
+
and the plan's `mount-records-screens` step names them per platform. The mount passes `petName={selectedPet.name}` for
|
|
94
|
+
headings such as `{petName}'s lab trends` (and `onCopy={copyText}` on React Native, for the share sheet). An Expo
|
|
95
|
+
records mount also passes `onOpenUrl={openRecordUrl}`, which handles the share sheet the way the Everfur app does: a
|
|
96
|
+
share link goes to the OS share sheet, and the record PDF is downloaded into the app's cache (`expo-file-system`),
|
|
97
|
+
shared as a file (`expo-sharing`) and deleted, so neither opens a browser. A bare React Native mount passes none, and
|
|
98
|
+
the SDK's default opens both in the system browser until you pass your own. Lab markers, share links, the record PDF link, fact
|
|
79
99
|
edits and the signature gallery need the platform flag `partner.records_full_depth_enabled`; while it is off the
|
|
80
100
|
SDK hides them.
|
|
81
101
|
|
|
82
|
-
**
|
|
102
|
+
**The vet visit runs inside your app.** A plan that mounts `televet` installs `@everfur/sdk@^0.9.0`, a release
|
|
103
|
+
whose `VetVisitButton`, `useVetVisit().open()` and `openVisit()`, and every vet entry inside chat open the visit in
|
|
104
|
+
the app by default, and writes the two things that default needs: `registerEverfurDaily(...)` once at load, from
|
|
105
|
+
`@everfur/sdk/televet/call` over `@daily-co/react-native-daily-js` (React Native) or from
|
|
106
|
+
`@everfur/sdk/web/televet/call` over `import('@daily-co/daily-js')` (the web), and one `<EverfurTelevetHost />`
|
|
107
|
+
inside the provider, where every visit opens: a full-screen modal on React Native, a dialog in the SDK's own DOM
|
|
108
|
+
subtree on the web. The button passes no `returnTo` and no `prefer`, the file registers no presenter and reads no
|
|
109
|
+
return URL, and the chat mount gets the member's state as `vetUsState`. On React Native the plan installs the Daily
|
|
110
|
+
modules the consumer app runs and the native setup (the Expo config plugin and a clean prebuild, or the bare
|
|
111
|
+
manifest and Info.plist edits), and the notifications file opens a tapped visit in the app through the same hook.
|
|
112
|
+
The booking lists vets only for the member's US state and the SDK never reads a device location, so the file's
|
|
113
|
+
`YOUR_USER_US_STATE` reaches the button, the chat and the notifications bridge, and `everfur verify --project-root`
|
|
114
|
+
fails while it is still `null` as shipped. The hosted visit in the system browser or a new tab is the SDK's explicit
|
|
115
|
+
`prefer="redirect"`, which no generated file passes. A plan without `televet` installs the same `@everfur/sdk@^0.9.0`
|
|
116
|
+
and none of the call.
|
|
117
|
+
|
|
118
|
+
<!-- Owner-approved 2026-10-01 (production sandbox plan, PC-1/PC-2): what `test` and the key prefixes mean. -->
|
|
119
|
+
**Every plan that writes code names its environment.** `--environment test|staging|production` (or
|
|
83
120
|
`environment` over MCP) is required and never assumed; generated files read the publishable key and base
|
|
84
121
|
URL from the framework's own variables (`NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`) and the plan writes the
|
|
85
|
-
names into `.env.example`.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
122
|
+
names into `.env.example`. `test` is your **sandbox tenant on the production API**
|
|
123
|
+
(`https://api.everfur.com/api/v1`), issued a `pk_test_` key, whose entitlements report `livemode: false`;
|
|
124
|
+
`staging` is your sandbox tenant on the staging API (`https://api-staging.everfur.com/api/v1`), for early
|
|
125
|
+
access; `production` is your live tenant and its `pk_live_` key. The key prefix names the tenant mode: a
|
|
126
|
+
sandbox created before `pk_test_` keys were issued may still carry `pk_live_`. The web framework is detected from the project: the Next.js App Router gets a
|
|
89
127
|
`'use client'` surface and a Route Handler, the Pages Router a component and an API route, Vite with React
|
|
90
|
-
reads `import.meta.env
|
|
128
|
+
reads `import.meta.env` and gets `server/dev-server.ts`, a development server for its session route (a Vite build has
|
|
129
|
+
no server, so the plan also says where the route goes in production). A signed-in React Native app gets the same server
|
|
130
|
+
on its own route, with the URL that reaches it from the iOS simulator, the Android emulator and a device, and its
|
|
131
|
+
server-only names in `server/.env.example`, never in the app's env file. `everfur verify --surfaces chat,records,televet,theme` exercises each surface
|
|
91
132
|
with the minted session (Televet mints a handoff only on a sandbox tenant, and a surface Everfur has not
|
|
92
133
|
switched on is reported `GATED`, not failed); `--project-root <path>` adds offline checks for a secret key
|
|
93
134
|
in source or client build output and for `'use client'`. `everfur diagnose --bundle` prints one support
|
|
@@ -97,28 +138,33 @@ document with no credential value in it.
|
|
|
97
138
|
|
|
98
139
|
### Hosted MCP: nothing to install
|
|
99
140
|
|
|
100
|
-
Everfur runs the MCP server for you, so there is no key, token, variable or file to set.
|
|
101
|
-
|
|
141
|
+
Everfur runs the MCP server for you, so there is no key, token, variable or file to set. It is reachable at
|
|
142
|
+
two endpoints, one per deployment:
|
|
102
143
|
|
|
103
144
|
```
|
|
104
|
-
https://mcp
|
|
145
|
+
https://mcp.everfur.com/mcp production: your sandbox (test) tenant and your live tenant
|
|
146
|
+
https://mcp-staging.everfur.com/mcp staging: an early-access sandbox tenant on the staging API
|
|
105
147
|
```
|
|
106
148
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
pointed at it fails to connect rather than falling back to anything. Use the staging URL until Everfur tells
|
|
110
|
-
you production is live.
|
|
149
|
+
Use the one for the deployment your tenant is on. A client pointed at the other one signs in to a different
|
|
150
|
+
deployment and does not fall back to anything.
|
|
111
151
|
|
|
112
152
|
Add the URL to your editor as a remote (Streamable HTTP) MCP server. You paste the URL yourself; there is
|
|
113
153
|
nothing to search for in a connector directory. Adding it is the whole configuration step:
|
|
114
154
|
|
|
115
|
-
- **Claude Code:** `claude mcp add --transport http everfur https://mcp
|
|
116
|
-
- **Cursor:** `.cursor/mcp.json` with `{"mcpServers":{"everfur":{"url":"https://mcp
|
|
117
|
-
- **VS Code:** `.vscode/mcp.json` with `{"servers":{"everfur":{"type":"http","url":"https://mcp
|
|
155
|
+
- **Claude Code:** `claude mcp add --transport http everfur https://mcp.everfur.com/mcp`
|
|
156
|
+
- **Cursor:** `.cursor/mcp.json` with `{"mcpServers":{"everfur":{"url":"https://mcp.everfur.com/mcp"}}}`
|
|
157
|
+
- **VS Code:** `.vscode/mcp.json` with `{"servers":{"everfur":{"type":"http","url":"https://mcp.everfur.com/mcp"}}}`
|
|
118
158
|
- **Codex:** a streamable HTTP server entry with that URL in `~/.codex/config.toml` (`[mcp_servers.everfur]`, `url`)
|
|
119
|
-
- **claude.ai:** add it as a **custom connector**
|
|
120
|
-
|
|
121
|
-
|
|
159
|
+
- **claude.ai:** add it as a **custom connector** at <https://claude.ai/customize/connectors> (**+**, then
|
|
160
|
+
**Add custom connector**): name it `Everfur` and paste the URL exactly, all lowercase, `https://`, `/mcp` on
|
|
161
|
+
the end, no trailing slash. Leave **Request headers** empty: Everfur never wants a key there. Click
|
|
162
|
+
**Connect** and sign in to the partner console. The approval page names Claude as the application asking
|
|
163
|
+
and shows the address Claude will be sent back to, `https://claude.ai/api/mcp/auth_callback` or
|
|
164
|
+
`https://claude.com/api/mcp/auth_callback`; anything else is not Everfur's consent page. Connecting does not
|
|
165
|
+
switch it on for every conversation: in a chat, **+**, then **Connectors**, then toggle **Everfur**. To check
|
|
166
|
+
it worked, ask Claude to explain the Everfur error code `authExpired`: an answer through
|
|
167
|
+
`everfur_explain_error` means the connection itself is good.
|
|
122
168
|
|
|
123
169
|
There is no key to type because the server authenticates you instead. On first connect it answers with a
|
|
124
170
|
`401` naming its OAuth resource metadata, your client registers itself automatically (dynamic client
|
|
@@ -149,8 +195,7 @@ from where the server runs:
|
|
|
149
195
|
`everfur_list_webhook_deliveries`, `everfur_get_webhook_delivery`, `everfur_list_events`,
|
|
150
196
|
`everfur_get_usage_summary`, `everfur_get_records_status_summary` and `everfur_search_docs` (the error
|
|
151
197
|
registry, capabilities and guide of your contract). None returns a signing secret, a key value or a
|
|
152
|
-
member's content.
|
|
153
|
-
[docs/HOSTED_TENANT_API.md](docs/HOSTED_TENANT_API.md).
|
|
198
|
+
member's content.
|
|
154
199
|
|
|
155
200
|
Everything below is for running the server yourself, which is what you want when the agent runs beside the
|
|
156
201
|
project (so `project_root` works) or when your organisation keeps its own MCP process.
|
|
@@ -188,8 +233,8 @@ The four values, and where they come from:
|
|
|
188
233
|
|
|
189
234
|
| Variable | What it is |
|
|
190
235
|
|---|---|
|
|
191
|
-
| `EVERFUR_API_BASE_URL` | The
|
|
192
|
-
| `EVERFUR_PARTNER_KEY` | The sandbox tenant's publishable key
|
|
236
|
+
| `EVERFUR_API_BASE_URL` | The API of your sandbox tenant while you build: `https://api.everfur.com/api/v1` for a test (production sandbox) tenant, the staging API for an early-access one. Going live uses `https://api.everfur.com/api/v1`. |
|
|
237
|
+
| `EVERFUR_PARTNER_KEY` | The sandbox tenant's publishable key, `pk_test_...` (a sandbox created before `pk_test_` keys may still carry `pk_live_`); its entitlements report `livemode: false`. |
|
|
193
238
|
| `EVERFUR_DOCS_KEY` | A read-only docs key (`dk_partner_...`) that opens `GET /partners/contract` and nothing else. |
|
|
194
239
|
| `EVERFUR_SESSION_TOKEN` | A short-lived session your backend minted for a test user. Only `everfur_verify_integration` reads it. |
|
|
195
240
|
|
|
@@ -198,8 +243,10 @@ live changes every value in the block: the production base URL, and the producti
|
|
|
198
243
|
key and docs key (its secret key changes on your backend at the same time). Production keys are issued only
|
|
199
244
|
after `everfur_verify_integration` passes on the sandbox tenant.
|
|
200
245
|
|
|
201
|
-
`EVERFUR_DOCS_KEY` is required
|
|
202
|
-
|
|
246
|
+
`EVERFUR_DOCS_KEY` is required here, for the server you run yourself. The Everfur-hosted connector above
|
|
247
|
+
needs no docs key: it signs in with OAuth and is served the contract as your tenant. The Everfur contract
|
|
248
|
+
(the error registry, capability packs, credential shapes and the decision trees the planner walks) is not
|
|
249
|
+
included in this package: the tools fetch it from
|
|
203
250
|
`GET /partners/contract` on `EVERFUR_API_BASE_URL`, presenting a partner docs key (`dk_partner_...`), and
|
|
204
251
|
cache it on disk (see [Credentials](#credentials)). A partner admin mints a docs key with
|
|
205
252
|
`POST /api/v1/partners/docs-keys` (list with `GET`, revoke with `DELETE /api/v1/partners/docs-keys/{key_id}`);
|
|
@@ -232,7 +279,12 @@ answer, naming each one:
|
|
|
232
279
|
Node backend gets the files; any other backend gets the recipe.
|
|
233
280
|
|
|
234
281
|
That is three questions for an anonymous integration and four for `partner-user-ref`. Once answered, the
|
|
235
|
-
plan's steps include the complete integration file, ready to write.
|
|
282
|
+
plan's steps include the complete integration file, ready to write. Over MCP the tool first returns a small
|
|
283
|
+
summary with a `plan_id` and a number of `pages` (the goal, the detected facts, each step's id, title, one-line
|
|
284
|
+
intent and page, and the checklist); the same call with `plan_id` and `page` returns that page's steps in full,
|
|
285
|
+
commands and file bodies included, and page 1 also the detection, warnings and checklist in full. `steps`
|
|
286
|
+
(step ids) in place of `page` still returns those steps, a page at a time. `everfur plan` prints the whole plan at
|
|
287
|
+
once.
|
|
236
288
|
|
|
237
289
|
The explicit `everfur-mcp` command is intentional: `@everfur/mcp` also ships `everfur-mcp-http` for a
|
|
238
290
|
server deployment, so bare `npx @everfur/mcp` can no longer safely guess which executable you mean.
|
|
@@ -243,7 +295,7 @@ For a partner-operated cloud/server deployment, run the guarded Streamable HTTP
|
|
|
243
295
|
|
|
244
296
|
```bash
|
|
245
297
|
export EVERFUR_API_BASE_URL="https://api-staging.everfur.com/api/v1"
|
|
246
|
-
#
|
|
298
|
+
# The publishable key of a tenant created as a sandbox (pk_test_..., or pk_live_... on an older staging sandbox).
|
|
247
299
|
export EVERFUR_PARTNER_KEY="pk_live_..."
|
|
248
300
|
export EVERFUR_DOCS_KEY="dk_partner_..."
|
|
249
301
|
# For verification only: a short-lived session your backend minted. The server never reads the secret key.
|
|
@@ -260,14 +312,14 @@ put either secret in an agent prompt, a browser, or a client bundle. The HTTP pr
|
|
|
260
312
|
scale behind a load balancer. It does **not** replace a platform OAuth service: use this mode when one
|
|
261
313
|
partner owns the deployment and its credentials.
|
|
262
314
|
|
|
263
|
-
For release gates, reverse-proxy requirements, negative tests, and the staged production rollout, use the
|
|
264
|
-
[remote MCP staging and production runbook](docs/REMOTE_MCP_STAGING_PRODUCTION_RUNBOOK.md).
|
|
265
|
-
|
|
266
315
|
Five tools are served: `everfur_plan_integration`, `everfur_verify_integration`, `everfur_diagnose`,
|
|
267
316
|
`everfur_explain_error`, and `everfur_send_test_message`. Each is a body of the CLI, so the two surfaces
|
|
268
317
|
answer the same question the same way. `everfur_explain_error` is the one to reach for when a response
|
|
269
|
-
carries an error code: it needs no project, reads the registry from the fetched contract,
|
|
270
|
-
|
|
318
|
+
carries an error code: it needs no project, reads the registry from the fetched contract, answers offline the SDK's
|
|
319
|
+
own codes (`EverfurError` and its closed sub-enums such as `AttachmentErrorCode` and `VetVisitErrorReason`), the
|
|
320
|
+
diagnostics reason codes, the CLI's own error codes and the `error.code` these tools return (`VERIFICATION_FAILED`,
|
|
321
|
+
`PLAN_CHANGED` and the rest), and refuses a code it does not recognise instead of explaining it from the shape of the
|
|
322
|
+
string. `everfur explain-error` gives the same answers.
|
|
271
323
|
|
|
272
324
|
Every tool declares an `outputSchema` and returns `structuredContent`. A failed call carries one envelope
|
|
273
325
|
whatever the tool: `error.code`, `error.retryable`, `error.next_action` (a closed vocabulary),
|
|
@@ -303,7 +355,7 @@ the package and the word after it names the binary.
|
|
|
303
355
|
| `everfur diagnose` | Explain why each capability is or is not available to this caller. |
|
|
304
356
|
| `everfur explain-error` | Explain an API error code: cause, retryability, and next action. Reads the registry from `--contract <file>` when given, otherwise from the contract fetched with `EVERFUR_DOCS_KEY`. |
|
|
305
357
|
| `everfur contract validate` | Validate a contract-public projection: envelope shape, then digest. |
|
|
306
|
-
| `everfur sandbox create` |
|
|
358
|
+
| `everfur sandbox create` | Everfur-internal (requires an admin token): mints a partner secret key and, because no claim flow exists to hand it over safely, revokes it again and exits `8`. |
|
|
307
359
|
|
|
308
360
|
`--json` on any command emits a machine-readable document on stdout, which is what you want in CI.
|
|
309
361
|
|
|
@@ -315,7 +367,7 @@ CI is invited to branch on these. An existing code never changes meaning.
|
|
|
315
367
|
|---|---|---|
|
|
316
368
|
| `0` | OK | The command produced a complete answer. |
|
|
317
369
|
| `1` | INTERNAL_ERROR | A bug in the CLI, or an unreadable filesystem. |
|
|
318
|
-
| `2` | USAGE_ERROR | Malformed invocation: unknown command or flag, missing flag value. |
|
|
370
|
+
| `2` | USAGE_ERROR | Malformed invocation: unknown command or flag, missing flag value, or an argument value the command does not recognise (for example `explain-error <unknown code>`). |
|
|
319
371
|
| `3` | UNDERSPECIFIED | The CLI refused to guess. Required facts were missing; supply them and retry. |
|
|
320
372
|
| `4` | INVALID_CONTRACT | A named artifact was missing, unparseable, or failed schema validation. |
|
|
321
373
|
| `5` | DIGEST_MISMATCH | A loaded artifact was well shaped but its digest did not match its content. |
|
|
@@ -323,6 +375,7 @@ CI is invited to branch on these. An existing code never changes meaning.
|
|
|
323
375
|
| `7` | VERIFICATION_FAILED | `verify` ran and at least one hard assertion failed. |
|
|
324
376
|
| `8` | SECRET_UNCLAIMABLE | `sandbox create` minted a key but no claim flow exists to hand it over safely. |
|
|
325
377
|
| `9` | BLOCKED | Nothing you asked for can be built today: the contract blocks every capability you named, or the SDK ships no entry point for this project shape. |
|
|
378
|
+
| `10` | CONTRACT_CREDENTIAL_MISSING | The command needs the Everfur contract and has none: no `--contract` file, no `EVERFUR_CONTRACT_PUBLIC`, and no `EVERFUR_DOCS_KEY` to fetch it with. The error says where a docs key comes from, and that the Everfur-hosted MCP connector (OAuth) needs none. |
|
|
326
379
|
|
|
327
380
|
---
|
|
328
381
|
|
|
@@ -354,7 +407,7 @@ with no network call and no credential.
|
|
|
354
407
|
| Variable | Used for |
|
|
355
408
|
|---|---|
|
|
356
409
|
| `EVERFUR_API_BASE_URL` | Everything that touches the network. No default host. |
|
|
357
|
-
| `EVERFUR_PARTNER_KEY` | Your publishable key (`pk_live_...`). Identifies your tenant.
|
|
410
|
+
| `EVERFUR_PARTNER_KEY` | Your publishable key (`pk_test_...` or `pk_live_...`). Identifies your tenant. The prefix names the tenant mode: `pk_test_` for a sandbox tenant, `pk_live_` for a live one; a sandbox created before `pk_test_` keys may still carry `pk_live_`. |
|
|
358
411
|
| `EVERFUR_PARTNER_SECRET_KEY` | Session minting, server side only. Never ship it in an app, and never put it in an MCP server's environment. `everfur verify` mints with it locally only when `EVERFUR_SESSION_TOKEN` is not set. |
|
|
359
412
|
| `EVERFUR_SESSION_TOKEN` | A short-lived session your backend minted. `everfur verify` and `everfur_verify_integration` use it instead of minting (the MCP tool verifies only this way); `everfur diagnose` uses it for the signed path. |
|
|
360
413
|
| `EVERFUR_DOCS_KEY` | Your partner docs key (`dk_partner_...`). Fetches the contract; accepted by no other route. Minted with `POST /api/v1/partners/docs-keys`. |
|
|
@@ -367,8 +420,9 @@ with no network call and no credential.
|
|
|
367
420
|
| `EVERFUR_MCP_HTTP_ALLOWED_ORIGINS` | Optional browser-origin allowlist. Origins are rejected unless explicitly listed. |
|
|
368
421
|
| `MCP_PUBLIC_URL` | Everfur-internal: the FULL endpoint URL of the hosted service (`everfur-mcp-hosted`), path included (`https://mcp-staging.everfur.com/mcp` on the staging deployment), exactly as a partner pastes it into Claude. It is the OAuth resource every token names and the base of every 401's metadata pointer; a bare origin is refused at start. With `EVERFUR_API_BASE_URL`, `EVERFUR_MCP_HTTP_PORT` and `MCP_TRUSTED_PROXY_HOPS` it is the hosted task's whole environment. Partners do not set this. |
|
|
369
422
|
| `MCP_ALLOWED_HOSTS` | Everfur-internal, optional: extra `Host` values the hosted service accepts beside its own. `MCP_PORT` is read only when `EVERFUR_MCP_HTTP_PORT` is unset. Partners do not set these. |
|
|
370
|
-
| `MCP_ALLOWED_ORIGINS` | Everfur-internal, optional: extra `Origin` values the hosted service's MCP endpoint accepts beside `https://claude.ai` and `https://
|
|
423
|
+
| `MCP_ALLOWED_ORIGINS` | Everfur-internal, optional: extra `Origin` values the hosted service's MCP endpoint accepts beside `https://claude.ai`, `https://claude.com` and `https://chatgpt.com`; any other `Origin` is refused with `403`. A browser page at an accepted origin gets its CORS preflight answered and can read the `401` challenge. Partners do not set this. |
|
|
371
424
|
| `MCP_TRUSTED_PROXY_HOPS` | Everfur-internal, optional: how many proxies appended to `X-Forwarded-For` (`1` behind the load balancer). Keys the hosted service's per-client budget of unauthenticated failures; `0` (default) uses the socket address. Partners do not set this. |
|
|
425
|
+
| `OPENAI_APPS_CHALLENGE_TOKEN` | Everfur-internal, optional: the domain verification token OpenAI's plugin portal generates. Set, the hosted service serves it as the whole plain-text body of `/.well-known/openai-apps-challenge` on its own host; unset or blank, that path is a `404`. Never logged. Partners do not set this. |
|
|
372
426
|
|
|
373
427
|
**Getting keys:** Everfur keys are currently provisioned by the Everfur team during partner onboarding;
|
|
374
428
|
there is no self-serve signup yet. Contact [support@everfur.com](mailto:support@everfur.com) to get set
|
|
@@ -377,8 +431,8 @@ up. The publishable key is designed to ship inside your app; the secret key is n
|
|
|
377
431
|
## Documentation
|
|
378
432
|
|
|
379
433
|
The integration guide, API reference, error catalog and testing guide are at
|
|
380
|
-
[everfur.com/docs](https://everfur.com/docs): adoption paths, the HTTP contract, API
|
|
381
|
-
checklist and troubleshooting.
|
|
434
|
+
[sdk.everfur.com/v1/docs](https://sdk.everfur.com/v1/docs/index.html): adoption paths, the HTTP contract, API
|
|
435
|
+
keys, the launch checklist and troubleshooting.
|
|
382
436
|
|
|
383
437
|
This page used to link to `github.com/Everfur-Health/everfur-sdk-rn`, which is a PRIVATE repository: the
|
|
384
438
|
link returned 404 for every partner who clicked it, since a partner is outside Everfur by definition. If
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
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,
|
|
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, checkTenantMode, 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, resolveWithoutRegistry, knownCodes, loadErrorRegistryFromContract, loadErrorRegistry, resolveAfterRegistryMiss, registryAlsoIn, buildExplanationDocument, renderExplanation, alsoInLines, resolveEndpoint, isCorrelatableRequestId, EMPTY_REGISTRY, createApiClient, offlineDocument, renderOffline, IDENTITY_QUESTION, IDENTITY_TREE, BACKEND_LANGUAGE_QUESTION, SESSION_MINT_TREE, CREDENTIAL_SPECS } from './chunk-EBVP6FAD.js';
|
|
3
3
|
import { isAbsolute, resolve } from 'path';
|
|
4
4
|
|
|
5
5
|
// src/cli/format.ts
|
|
@@ -186,6 +186,9 @@ function renderRefusal(document) {
|
|
|
186
186
|
lines.push(` ${fact.fact}`);
|
|
187
187
|
lines.push(` why: ${fact.reason}`);
|
|
188
188
|
lines.push(` supply: ${fact.supplyWith}`);
|
|
189
|
+
if (fact.requiredWhen !== void 0) {
|
|
190
|
+
lines.push(` only when: ${fact.requiredWhen.fact} is ${fact.requiredWhen.oneOf.join(" or ")}; answer it with that fact`);
|
|
191
|
+
}
|
|
189
192
|
}
|
|
190
193
|
lines.push("");
|
|
191
194
|
lines.push(...renderDetected(document));
|
|
@@ -523,6 +526,14 @@ var CONTRACT_FLAG = Object.freeze({
|
|
|
523
526
|
summary: "Path to a contract-public projection, used to explain any error code returned.",
|
|
524
527
|
placeholder: "file"
|
|
525
528
|
});
|
|
529
|
+
var PLAN_CONTRACT_FLAG = Object.freeze({
|
|
530
|
+
...CONTRACT_FLAG,
|
|
531
|
+
summary: "Path to a contract-public projection to plan from (its decision trees), read instead of the contract fetched with EVERFUR_DOCS_KEY."
|
|
532
|
+
});
|
|
533
|
+
var EXPLAIN_CONTRACT_FLAG = Object.freeze({
|
|
534
|
+
...CONTRACT_FLAG,
|
|
535
|
+
summary: "Path to a contract-public projection whose error registry answers the code, read instead of the contract fetched with EVERFUR_DOCS_KEY."
|
|
536
|
+
});
|
|
526
537
|
var CONTRACT_ENV_VAR = "EVERFUR_CONTRACT_PUBLIC";
|
|
527
538
|
var DEFAULT_CONTRACT_PATHS = Object.freeze([
|
|
528
539
|
"contract-public/contract-public.json",
|
|
@@ -613,6 +624,7 @@ var POLL_DELAY_MS = 500;
|
|
|
613
624
|
var MAX_ATTEMPTS = 20;
|
|
614
625
|
var VERIFICATION_SCHEMA_VERSION = 1;
|
|
615
626
|
var VERIFICATION_DOCUMENT_KIND = "everfur.verification";
|
|
627
|
+
var WEB_EMBEDDINGS = Object.freeze(["in-page", "frame"]);
|
|
616
628
|
var FLAGS = Object.freeze([
|
|
617
629
|
BASE_URL_FLAG,
|
|
618
630
|
{
|
|
@@ -630,9 +642,16 @@ var FLAGS = Object.freeze([
|
|
|
630
642
|
{
|
|
631
643
|
name: "origin",
|
|
632
644
|
kind: "string",
|
|
633
|
-
summary: "For a web
|
|
645
|
+
summary: "For a framed web page (the loader and an iframe): the page origin (https://shop.example.com) the frame renders on. Adds the CORS preflight, embed fence, CORS exposure, session policy and loader checks. An in-page integration needs no --origin.",
|
|
634
646
|
placeholder: "origin"
|
|
635
647
|
},
|
|
648
|
+
{
|
|
649
|
+
// The same flag name `plan` takes for the same fact (the argv parser knows it as a value flag).
|
|
650
|
+
name: "embedding",
|
|
651
|
+
kind: "string",
|
|
652
|
+
summary: `With --origin: how the page embeds the surface, ${WEB_EMBEDDINGS.join(" or ")}. frame (the default without --project-root) runs every check; in-page (the SDK's React components in the page) reports the embed fence and the loader N/A, since only the frame uses them. Read from --project-root when not given.`,
|
|
653
|
+
placeholder: "mode"
|
|
654
|
+
},
|
|
636
655
|
{
|
|
637
656
|
name: "loader-url",
|
|
638
657
|
kind: "string",
|
|
@@ -665,10 +684,12 @@ var FLAGS = Object.freeze([
|
|
|
665
684
|
function buildVerificationDocument(verification, baseUrl, cliVersion) {
|
|
666
685
|
const counts = { passed: 0, failed: 0, skipped: 0 };
|
|
667
686
|
let gated = 0;
|
|
687
|
+
let notApplicable = 0;
|
|
668
688
|
for (const entry of verification.steps) {
|
|
669
689
|
if (entry.result === "PASS") counts.passed += 1;
|
|
670
690
|
else if (entry.result === "FAIL") counts.failed += 1;
|
|
671
691
|
else if (entry.result === "GATED") gated += 1;
|
|
692
|
+
else if (entry.result === "N/A") notApplicable += 1;
|
|
672
693
|
else counts.skipped += 1;
|
|
673
694
|
}
|
|
674
695
|
return {
|
|
@@ -677,8 +698,9 @@ function buildVerificationDocument(verification, baseUrl, cliVersion) {
|
|
|
677
698
|
generatedBy: { name: "@everfur/cli", version: cliVersion },
|
|
678
699
|
status: verification.status,
|
|
679
700
|
baseUrl,
|
|
680
|
-
// `gated` appears only when a step was GATED, so a document from a run
|
|
681
|
-
|
|
701
|
+
// `gated` appears only when a step was GATED, and `notApplicable` only when one was N/A, so a document from a run
|
|
702
|
+
// with neither is unchanged.
|
|
703
|
+
summary: { ...counts, ...gated === 0 ? {} : { gated }, ...notApplicable === 0 ? {} : { notApplicable } },
|
|
682
704
|
steps: verification.steps.map((entry) => ({
|
|
683
705
|
id: entry.id,
|
|
684
706
|
title: entry.title,
|
|
@@ -709,6 +731,8 @@ function renderVerification(verification, baseUrl) {
|
|
|
709
731
|
lines.push("Nothing failed, but a step was skipped, so the wire was not fully exercised.");
|
|
710
732
|
} else if (verification.steps.some((entry) => entry.result === "GATED")) {
|
|
711
733
|
lines.push("Nothing failed. A GATED step is a surface Everfur has not switched on for this tenant, not a fault in the integration.");
|
|
734
|
+
} else if (verification.steps.some((entry) => entry.result === "N/A")) {
|
|
735
|
+
lines.push("Nothing failed. An N/A step is a check that does not apply to this integration, and it was not run.");
|
|
712
736
|
} else {
|
|
713
737
|
lines.push(
|
|
714
738
|
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."
|
|
@@ -716,6 +740,12 @@ function renderVerification(verification, baseUrl) {
|
|
|
716
740
|
}
|
|
717
741
|
return lines.join("\n");
|
|
718
742
|
}
|
|
743
|
+
function parseEmbedding2(raw) {
|
|
744
|
+
if (raw === null) return { value: null, errors: [] };
|
|
745
|
+
const value = raw.trim();
|
|
746
|
+
const known2 = WEB_EMBEDDINGS.find((embedding) => embedding === value);
|
|
747
|
+
return known2 === void 0 ? { value: null, errors: [`--embedding must be ${WEB_EMBEDDINGS.join(" or ")}, not "${raw}"`] } : { value: known2, errors: [] };
|
|
748
|
+
}
|
|
719
749
|
function parseAttempts(raw) {
|
|
720
750
|
if (raw === null) return { value: DEFAULT_ATTEMPTS, errors: [] };
|
|
721
751
|
const parsed = Number(raw.trim());
|
|
@@ -730,7 +760,7 @@ function parseAttempts(raw) {
|
|
|
730
760
|
var verifyCommand = {
|
|
731
761
|
path: ["verify"],
|
|
732
762
|
summary: "Exercise the wire: mint a session, read the verdict, assert it is not empty.",
|
|
733
|
-
usage: "verify [--base-url <url>] [--user-ref <ref>] [--attempts <n>] [--identity <mode>] [--origin <origin>] [--loader-url <url>] [--surfaces <list>] [--project-root <path>] [--json]",
|
|
763
|
+
usage: "verify [--base-url <url>] [--user-ref <ref>] [--attempts <n>] [--identity <mode>] [--origin <origin>] [--embedding <mode>] [--loader-url <url>] [--surfaces <list>] [--project-root <path>] [--json]",
|
|
734
764
|
flags: FLAGS,
|
|
735
765
|
/**
|
|
736
766
|
* Exactly what `mintSession` resolves: the session token first, else the key pair it mints with locally. Without
|
|
@@ -741,6 +771,7 @@ var verifyCommand = {
|
|
|
741
771
|
const attempts = parseAttempts(flags.string("attempts"));
|
|
742
772
|
const originFlag = flags.string("origin");
|
|
743
773
|
const origin = originFlag === null ? { origin: null, error: null } : parseOrigin(originFlag);
|
|
774
|
+
const embedding = parseEmbedding2(flags.string("embedding"));
|
|
744
775
|
const loaderFlag = flags.string("loader-url");
|
|
745
776
|
const loader = loaderFlag === null ? { url: null, error: null } : parseLoaderUrl(loaderFlag);
|
|
746
777
|
const surfaces = parseSurfaces(flags.string("surfaces"));
|
|
@@ -751,6 +782,7 @@ var verifyCommand = {
|
|
|
751
782
|
const flagErrors = [
|
|
752
783
|
...attempts.errors,
|
|
753
784
|
...origin.error === null ? [] : [origin.error],
|
|
785
|
+
...embedding.errors,
|
|
754
786
|
...loader.error === null ? [] : [loader.error],
|
|
755
787
|
...surfaces.errors,
|
|
756
788
|
...identityMode.errors,
|
|
@@ -771,6 +803,7 @@ var verifyCommand = {
|
|
|
771
803
|
attempts: attempts.value,
|
|
772
804
|
pollDelayMs: POLL_DELAY_MS,
|
|
773
805
|
origin: origin.origin,
|
|
806
|
+
embedding: embedding.value,
|
|
774
807
|
loaderUrl: loader.url,
|
|
775
808
|
transport: context.transport,
|
|
776
809
|
surfaces: surfaces.value ?? [],
|
|
@@ -826,7 +859,7 @@ var FLAGS2 = Object.freeze([
|
|
|
826
859
|
summary: `The Everfur deployment the generated configuration targets: ${ENVIRONMENTS.join(", ")}. Required for a plan that writes configuration; never assumed.`,
|
|
827
860
|
placeholder: "name"
|
|
828
861
|
},
|
|
829
|
-
|
|
862
|
+
PLAN_CONTRACT_FLAG
|
|
830
863
|
]);
|
|
831
864
|
function asJsonObject(document) {
|
|
832
865
|
return JSON.parse(JSON.stringify(document));
|
|
@@ -912,8 +945,10 @@ var planCommand = {
|
|
|
912
945
|
...environment.errors
|
|
913
946
|
]);
|
|
914
947
|
}
|
|
948
|
+
const mode = await checkTenantMode({ env: context.env, environment: environment.value, transport: context.transport, clock: context.clock });
|
|
949
|
+
if (mode.kind === "mismatch") return errorOutcome("usage_error", mode.message, mode.details);
|
|
915
950
|
const detection = detectProject(context.fs, root);
|
|
916
|
-
const
|
|
951
|
+
const planned = planIntegration({
|
|
917
952
|
detection,
|
|
918
953
|
capabilities: supplied.capabilities,
|
|
919
954
|
allCapabilities: supplied.allCapabilities,
|
|
@@ -924,6 +959,7 @@ var planCommand = {
|
|
|
924
959
|
space,
|
|
925
960
|
cliVersion: context.version
|
|
926
961
|
});
|
|
962
|
+
const document = mode.kind === "not_checked" && planned.status === "ready" ? Object.freeze({ ...planned, warnings: Object.freeze([...planned.warnings, mode.warning]) }) : planned;
|
|
927
963
|
const json = asJsonObject(document);
|
|
928
964
|
const text = renderPlan(document);
|
|
929
965
|
if (document.status === "underspecified") return fail(ExitCode.UNDERSPECIFIED, json, text);
|
|
@@ -1115,7 +1151,7 @@ var diagnoseCommand = {
|
|
|
1115
1151
|
};
|
|
1116
1152
|
|
|
1117
1153
|
// src/commands/explain-error/index.ts
|
|
1118
|
-
var FLAGS5 = Object.freeze([
|
|
1154
|
+
var FLAGS5 = Object.freeze([EXPLAIN_CONTRACT_FLAG]);
|
|
1119
1155
|
var POSITIONALS = Object.freeze([
|
|
1120
1156
|
{
|
|
1121
1157
|
name: "code",
|
|
@@ -1136,7 +1172,7 @@ var explainErrorCommand = {
|
|
|
1136
1172
|
return errorOutcome("usage_error", "no error code was given", ["usage: everfur explain-error <code>"]);
|
|
1137
1173
|
}
|
|
1138
1174
|
const offline = resolveOffline(code);
|
|
1139
|
-
if (offline?.kind === "sdk_member") return offlineOutcome(offline, context.version);
|
|
1175
|
+
if (offline?.kind === "sdk_member") return offlineOutcome(offline, context.version, "no_registry");
|
|
1140
1176
|
const contractFlag = flags.string("contract");
|
|
1141
1177
|
const contractPath = findContractPath(context, contractFlag);
|
|
1142
1178
|
let load;
|
|
@@ -1148,10 +1184,13 @@ var explainErrorCommand = {
|
|
|
1148
1184
|
...context.contractCache === void 0 ? {} : { cache: context.contractCache }
|
|
1149
1185
|
});
|
|
1150
1186
|
if (fetched.kind === "unavailable") {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return
|
|
1154
|
-
...suggestions.length > 0 ? [`did you mean: ${suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : [`no
|
|
1187
|
+
const resolved = resolveWithoutRegistry(code);
|
|
1188
|
+
if (resolved.kind !== "unknown") return offlineOutcome(resolved, context.version, "no_registry");
|
|
1189
|
+
return errorOutcome("unknown_error_code", `"${code}" is not a code this CLI knows`, [
|
|
1190
|
+
...resolved.suggestions.length > 0 ? [`did you mean: ${resolved.suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : [`no similar code exists in the ${knownCodes(null).length} codes this CLI carries`],
|
|
1191
|
+
"searched: the SDK taxonomy, the SDK contract table, the MCP tools' codes, the diagnostics reason codes, this CLI's error codes and the SDK's sub-enums",
|
|
1192
|
+
`the live error registry was not read (${fetched.message}); it can list a code these tables predate:`,
|
|
1193
|
+
...fetched.details.filter((detail) => detail.startsWith("export "))
|
|
1155
1194
|
]);
|
|
1156
1195
|
}
|
|
1157
1196
|
load = loadErrorRegistryFromContract(fetched.contract);
|
|
@@ -1168,11 +1207,11 @@ var explainErrorCommand = {
|
|
|
1168
1207
|
const entry = load.registry.lookup(code);
|
|
1169
1208
|
if (entry === null) {
|
|
1170
1209
|
const fallback = resolveAfterRegistryMiss(code, load.registry);
|
|
1171
|
-
if (fallback.kind !== "unknown") return offlineOutcome(fallback, context.version);
|
|
1210
|
+
if (fallback.kind !== "unknown") return offlineOutcome(fallback, context.version, "registry_miss");
|
|
1172
1211
|
return errorOutcome("unknown_error_code", `"${code}" is not in the error registry`, [
|
|
1173
1212
|
...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
1213
|
`the registry loaded from ${load.path} lists ${load.registry.codes.length} codes`,
|
|
1175
|
-
`
|
|
1214
|
+
`no other table this CLI carries knows it either: the SDK taxonomy, the SDK contract table, the MCP tools' codes, the diagnostics reason codes, this CLI's error codes and the SDK's sub-enums (${knownCodes(load.registry).length} codes searched in all)`
|
|
1176
1215
|
]);
|
|
1177
1216
|
}
|
|
1178
1217
|
const input = {
|
|
@@ -1181,11 +1220,13 @@ var explainErrorCommand = {
|
|
|
1181
1220
|
digest: load.digest,
|
|
1182
1221
|
cliVersion: context.version
|
|
1183
1222
|
};
|
|
1184
|
-
|
|
1223
|
+
const document = { ...buildExplanationDocument(input), ...registryAlsoIn(code) };
|
|
1224
|
+
return succeed(document, [renderExplanation(input), ...alsoInLines(document)].join("\n"));
|
|
1185
1225
|
}
|
|
1186
1226
|
};
|
|
1187
|
-
function offlineOutcome(resolution, cliVersion) {
|
|
1188
|
-
|
|
1227
|
+
function offlineOutcome(resolution, cliVersion, context) {
|
|
1228
|
+
const document = offlineDocument(resolution, cliVersion, context);
|
|
1229
|
+
return succeed(document, renderOffline(resolution, document, context));
|
|
1189
1230
|
}
|
|
1190
1231
|
function contractOutcome2(outcome) {
|
|
1191
1232
|
switch (outcome.kind) {
|
|
@@ -1233,7 +1274,11 @@ var FLAGS6 = Object.freeze([
|
|
|
1233
1274
|
]);
|
|
1234
1275
|
var sandboxCreateCommand = {
|
|
1235
1276
|
path: ["sandbox", "create"],
|
|
1236
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* What the command does TODAY (ROADMAP.md), not the designed claim flow: the live route returns the secret inline and
|
|
1279
|
+
* no claim URL, so the key is revoked unread and the command exits 8 (SECRET_UNCLAIMABLE).
|
|
1280
|
+
*/
|
|
1281
|
+
summary: "Everfur-internal. Mints a partner secret key and, because no claim flow exists to hand it over safely, revokes it again and exits 8. Never prints the secret.",
|
|
1237
1282
|
usage: "sandbox create --client <client-id> [--label <text>] [--base-url <url>] [--json]",
|
|
1238
1283
|
flags: FLAGS6,
|
|
1239
1284
|
/** The one credential this command resolves. It never reads a partner key of either kind. */
|
|
@@ -1357,20 +1402,14 @@ function parseArgv(argv) {
|
|
|
1357
1402
|
const path = [];
|
|
1358
1403
|
const flags = /* @__PURE__ */ new Map();
|
|
1359
1404
|
const errors = [];
|
|
1360
|
-
let sawFlag = false;
|
|
1361
1405
|
for (let index = 0; index < argv.length; index += 1) {
|
|
1362
1406
|
const token = argv[index];
|
|
1363
1407
|
if (token === void 0) continue;
|
|
1364
1408
|
if (token === "--") break;
|
|
1365
1409
|
if (!isFlagToken(token)) {
|
|
1366
|
-
|
|
1367
|
-
errors.push(`unexpected argument "${token}" after flags began`);
|
|
1368
|
-
} else {
|
|
1369
|
-
path.push(token);
|
|
1370
|
-
}
|
|
1410
|
+
path.push(token);
|
|
1371
1411
|
continue;
|
|
1372
1412
|
}
|
|
1373
|
-
sawFlag = true;
|
|
1374
1413
|
const equalsAt = token.indexOf("=");
|
|
1375
1414
|
const rawName = equalsAt === -1 ? token : token.slice(0, equalsAt);
|
|
1376
1415
|
const inlineValue = equalsAt === -1 ? null : token.slice(equalsAt + 1);
|
|
@@ -1477,7 +1516,7 @@ function renderRootHelp(commands, version) {
|
|
|
1477
1516
|
lines.push("", "Global flags");
|
|
1478
1517
|
for (const spec of GLOBAL_FLAGS) lines.push(renderFlag(spec));
|
|
1479
1518
|
lines.push(" --version".padEnd(FLAG_COLUMN) + "Print the CLI version.");
|
|
1480
|
-
lines.push(...environmentBlock(CREDENTIAL_SPECS));
|
|
1519
|
+
lines.push(...environmentBlock(CREDENTIAL_SPECS, "all"));
|
|
1481
1520
|
lines.push(
|
|
1482
1521
|
"",
|
|
1483
1522
|
"Not in this release",
|
|
@@ -1486,9 +1525,10 @@ function renderRootHelp(commands, version) {
|
|
|
1486
1525
|
);
|
|
1487
1526
|
return lines.join("\n");
|
|
1488
1527
|
}
|
|
1489
|
-
function environmentBlock(specs) {
|
|
1528
|
+
function environmentBlock(specs, baseUrlFlag) {
|
|
1490
1529
|
const lines = ["", "Environment"];
|
|
1491
|
-
|
|
1530
|
+
const override = baseUrlFlag === "all" ? " Overridden by --base-url on the commands that take it." : baseUrlFlag ? " Overridden by --base-url." : "";
|
|
1531
|
+
lines.push(` ${BASE_URL_ENV_VAR}`.padEnd(FLAG_COLUMN) + `API base URL.${override}`);
|
|
1492
1532
|
for (const spec of specs) {
|
|
1493
1533
|
lines.push(` ${spec.envVar}`.padEnd(FLAG_COLUMN) + spec.summary);
|
|
1494
1534
|
}
|
|
@@ -1521,7 +1561,9 @@ function renderCommandHelp(command) {
|
|
|
1521
1561
|
lines.push("", "Global flags");
|
|
1522
1562
|
for (const spec of GLOBAL_FLAGS) lines.push(renderFlag(spec));
|
|
1523
1563
|
const credentials = command.credentials ?? [];
|
|
1524
|
-
if (credentials.length > 0)
|
|
1564
|
+
if (credentials.length > 0) {
|
|
1565
|
+
lines.push(...environmentBlock(credentials, command.flags.some((spec) => spec.name === "base-url")));
|
|
1566
|
+
}
|
|
1525
1567
|
return lines.join("\n");
|
|
1526
1568
|
}
|
|
1527
1569
|
|
|
@@ -1561,8 +1603,10 @@ async function runCli(options) {
|
|
|
1561
1603
|
const specs = match.command.positionals ?? [];
|
|
1562
1604
|
if (positionals.length > specs.length) {
|
|
1563
1605
|
const surplus = positionals[specs.length] ?? "";
|
|
1606
|
+
const flagErrors = bindFlags(match.command.flags, parsed);
|
|
1564
1607
|
return formatOutcome(
|
|
1565
1608
|
errorOutcome("usage_error", `unexpected argument "${surplus}" after "${commandName(match.command)}"`, [
|
|
1609
|
+
...flagErrors.ok ? [] : flagErrors.errors,
|
|
1566
1610
|
`usage: everfur ${match.command.usage}`
|
|
1567
1611
|
]),
|
|
1568
1612
|
format
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { buildServer } from './chunk-
|
|
2
|
+
import { buildServer } from './chunk-QCXZJW5C.js';
|
|
3
3
|
import { createServer } from 'http';
|
|
4
4
|
import { timingSafeEqual } from 'crypto';
|
|
5
5
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|