@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 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. `everfur verify --origin https://your.page`
18
- adds the checks a web integration needs: the embed allowlist for that origin, the CORS exposure a
19
- browser depends on, the tenant's session policy, and the loader of the key's environment.
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: every in-page chat mount passes `onFindVet={findVet}`, which opens a maps search until you replace
46
- it with your own vet finder. A frame takes no `onFindVet`, so there the pill appears only while Everfur has its vet
47
- visit entry on for your client. The SDK bundles no picker and no
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. Records is the app's screen flow: `<EverfurRecords>`
74
- opens on the pet's dashboard and owns its own stack (the hardware back key pops it), every screen is exported by
75
- name for your own navigator, the mount passes `petName={selectedPet.name}` for headings such as
76
- `{petName}'s lab trends` (and `onCopy={copyText}` on React Native, for the share sheet), and
77
- `initialScreen={{ name: 'request', requestId }}` opens a request directly, which is what the generated
78
- notification router's `record_request` handler says to do. Lab markers, share links, the record PDF link, fact
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
- **Every plan that writes code names its environment.** `--environment staging|production` (or
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`. Test mode is not a third deployment: it is your **sandbox tenant on the staging
86
- API** (`https://api-staging.everfur.com/api/v1`), issued an ordinary `pk_live_` key, whose entitlements
87
- report `livemode: false`. No deployed environment accepts a `pk_test_` key. `--environment test` is
88
- accepted and resolves to that same staging deployment. The web framework is detected from the project: the Next.js App Router gets a
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`. `everfur verify --surfaces chat,records,televet,theme` exercises each surface
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. Today it is
101
- reachable at one endpoint:
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-staging.everfur.com/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
- That is the staging deployment, and it is where your sandbox tenant lives while you build. **The production
108
- endpoint `https://mcp.everfur.com/mcp` is not available yet:** the hostname does not resolve, so a client
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-staging.everfur.com/mcp`
116
- - **Cursor:** `.cursor/mcp.json` with `{"mcpServers":{"everfur":{"url":"https://mcp-staging.everfur.com/mcp"}}}`
117
- - **VS Code:** `.vscode/mcp.json` with `{"servers":{"everfur":{"type":"http","url":"https://mcp-staging.everfur.com/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** and paste the URL into the dialog. Click by click,
120
- including what the sign-in asks and how to tell it worked:
121
- [docs/ADD_THE_EVERFUR_CONNECTOR_IN_CLAUDE.md](docs/ADD_THE_EVERFUR_CONNECTOR_IN_CLAUDE.md)
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. The routes behind them are specified in
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 staging API while you build. Your test tenant is a **sandbox tenant on staging**; there is no separate test deployment. Going live changes this to `https://api.everfur.com/api/v1`. |
192
- | `EVERFUR_PARTNER_KEY` | The sandbox tenant's publishable key. It is a `pk_live_` key (no deployed environment accepts `pk_test_`); what makes the tenant a sandbox is the tenant, and its entitlements report `livemode: false`. |
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. The Everfur contract (the error registry, capability packs, credential
202
- shapes and the decision trees the planner walks) is not included in this package: the tools fetch it from
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
- # No deployed environment accepts pk_test_: use the pk_live_ key of a tenant created as a sandbox.
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, and refuses a code
270
- it does not recognise instead of explaining it from the shape of the string.
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` | Provision a partner secret key (Everfur-internal; requires an admin token). |
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. A sandbox tenant is issued the same `pk_live_` form; no deployed environment accepts `pk_test_`. |
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://claude.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. |
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 keys, the launch
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, offlineSuggestions, loadErrorRegistryFromContract, loadErrorRegistry, resolveAfterRegistryMiss, knownCodes, buildExplanationDocument, renderExplanation, resolveEndpoint, isCorrelatableRequestId, EMPTY_REGISTRY, createApiClient, buildSdkExplanationDocument, renderSdkExplanation, buildContractTableExplanationDocument, renderContractTableExplanation, IDENTITY_QUESTION, IDENTITY_TREE, BACKEND_LANGUAGE_QUESTION, SESSION_MINT_TREE, CREDENTIAL_SPECS } from './chunk-KQICUK54.js';
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 integration: the page origin (https://shop.example.com) the surface is embedded on. Adds the embed fence, CORS exposure, session policy and loader checks.",
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 with no surface checks is unchanged.
681
- summary: gated === 0 ? counts : { ...counts, gated },
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
- CONTRACT_FLAG
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 document = planIntegration({
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([CONTRACT_FLAG]);
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
- 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`]
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
- `neither the SDK taxonomy nor the SDK contract table this CLI carries knows it either (${knownCodes(load.registry).length} codes searched in all)`
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
- return succeed(buildExplanationDocument(input), renderExplanation(input));
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
- return resolution.kind === "sdk_member" ? succeed(buildSdkExplanationDocument(resolution.code, cliVersion), renderSdkExplanation(resolution.code)) : succeed(buildContractTableExplanationDocument(resolution.row, cliVersion), renderContractTableExplanation(resolution.row));
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
- summary: "Provision a partner secret key and return a claim URL. Never prints the secret.",
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
- if (sawFlag) {
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
- lines.push(` ${BASE_URL_ENV_VAR}`.padEnd(FLAG_COLUMN) + "API base URL. Overridden by --base-url.");
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) lines.push(...environmentBlock(credentials));
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-XMIERMOW.js';
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';