Reference
Editor configuration
The two Editor config files, which keys belong in each, what every key defaults to, and why a key in the wrong file does nothing at all.
The Editor reads configuration from two JSON files in your prototype, plus a standard .mcp.json for extra MCP servers. They have different names, different loaders, and no knowledge of each other. Putting a key in the wrong one is the single most common way to spend an afternoon wondering why a setting has no effect: nothing warns you, because each loader only looks for the keys it owns and ignores everything else in the file.
This page is the map. Read the first table before you edit either file.
Which file takes which key#
Both files live in the directory you pass to the CLI (the root of your prototype), with one exception noted below.
Note One exception, and it only bites monorepos. The boot-time blocks resolve against the path you passed the CLI. The per-turn blocks (
webFetch,webSearch,figmaand.mcp.json) resolve against the git root instead. When your prototype is the repository, those are the same directory and this does not matter. When your prototype is a package inside a larger repository, put those blocks at the repository root, not beside the prototype'spackage.json.
| Key | File |
|---|---|
version |
.desde/config.json |
project, projectSlug, projectId, platformBaseUrl |
.desde/config.json |
chat (including chat.detachedSessions) |
.desde/config.json |
editor (including editor.canvas) |
.desde/config.json |
conventions |
.desde/config.json |
retention |
.desde/config.json |
readRoots |
desde.config.json |
webFetch, webSearch |
desde.config.json |
figma |
desde.config.json |
designSystems |
desde.config.json |
lanes |
desde.config.json |
hosts |
desde.config.json |
mcpServers |
.mcp.json |
.mcp.json is the standard MCP server file, read fresh on every chat turn. Each entry under mcpServers is an id mapping to { command, args, env }: same shape and same ${VAR} interpolation as the figma block below, which is really just a named shortcut for one of these. The id editor is reserved and refused; a figma id declared in both files is taken from .mcp.json.
Warning A key in the wrong file fails silently.
readRootsinside.desde/config.jsonis an unknown key that the project-config parser ignores;chatinsidedesde.config.jsonis never looked for. In both cases the file parses, the Editor boots, and the setting simply does not exist.
Neither file is created for you with content. Both are optional: the Editor runs with neither. .desde/config.json is written by the Editor itself when you create or link a project, and that write preserves every other key you authored.
Note Both files are parsed with strict JSON. Comments and trailing commas are syntax errors. The annotated examples below are valid JSON; the explanations live in the tables beside them.
.desde/config.json#
Project identity, chat behaviour, and on-disk housekeeping. Read once, at boot. Changing it requires restarting the CLI.
{
"version": 2,
"project": {
"id": "b6b1c0d2-4a1e-4a55-9c4a-2b0e8f6b1e33",
"name": "Checkout redesign",
"slug": "checkout-redesign"
},
"projectId": "b6b1c0d2-4a1e-4a55-9c4a-2b0e8f6b1e33",
"projectSlug": "checkout-redesign",
"platformBaseUrl": "https://viewer.internal.example.com",
"chat": {
"costCeilingUsd": 40,
"detachedSessions": true
},
"conventions": {
"useRepoConventions": true,
"excludeFiles": ["docs/legacy-styleguide.md"]
},
"editor": {
"reloadBackstop": true,
"canvas": false
},
"retention": {
"backups": { "keepNewest": 200, "maxAgeDays": 14 },
"chatSessionTurns": { "maxTurns": 500 }
}
}Identity and project link#
| Key | Type | Default | Notes |
|---|---|---|---|
version |
number | none (required) | Must be 1 or 2. Any other value is rejected. |
project |
object | absent | Written by the Editor when you create a project. { id, name, slug }, plus an optional viewerUrl. Hand-editing is not expected. |
projectSlug |
string | absent | Lowercase letters, digits and internal hyphens only (my-app). Optional since version 2. |
projectId |
string | absent | The Viewer's project id. Letters, digits, hyphens and underscores. |
platformBaseUrl |
string | absent | Absolute http: or https: URL of the Viewer you sync with. |
The project block is the one exception to the blast-radius rule below: a malformed identity block degrades to "no identity" and leaves the rest of the file working.
chat#
| Key | Type | Default | Notes |
|---|---|---|---|
chat.detachedSessions |
boolean | true |
Tabbed chat sessions. Opt out by setting false. |
chat.costCeilingUsd |
number or null |
none (unlimited) | Cumulative spend ceiling per chat session. The agent refuses a new turn once the running estimate crosses it. Omitting the key, or setting it to null or 0, all mean the same thing: no ceiling. Set a positive number to turn one on. |
chat.maxModelCallsPerTurn |
number | N/A | Accepted and validated (must be a positive number), but see the note below. |
chat.maxToolCallsPerTurn |
number | N/A | Same. |
Note
maxModelCallsPerTurnandmaxToolCallsPerTurnare parsed and carried through to the chat handler, but nothing currently enforces them: onlycostCeilingUsdreaches the agent runtime. Setting them is harmless and does nothing.
Warning There is no default ceiling. If you omit
chat.costCeilingUsd, a chat session can spend without limit. A $20 default existed until 2026-08-14, when it was removed at Mo's request: the number was never shown anywhere in the product (no cost meter, no remaining-budget chip, no setting), so it only ever reached you as a refusal after a long session stopped working. A limit nobody is told about reads as a bug, not a budget. If you want a ceiling, you now have to ask for one by setting this key.
This ceiling covers foreground chat only. The small repair turn that runs when a deterministic edit cannot be applied has its own, much tighter budget that this block does not change.
conventions#
Controls whether the Editor's AI grounds itself in the instruction files your repository already has. Discovery covers five repo-root files (CLAUDE.md, AGENTS.md, .cursorrules, .windsurfrules and .github/copilot-instructions.md) plus .cursor/rules/*.md(c) and your docs/ tree.
| Key | Type | Default | Notes |
|---|---|---|---|
conventions.useRepoConventions |
boolean | true |
Set false to skip discovery entirely. |
conventions.excludeFiles |
string[] | [] |
Repository-relative paths, forward slashes, to drop from discovery. |
editor#
| Key | Type | Default | Notes |
|---|---|---|---|
editor.reloadBackstop |
boolean | true |
Whether the workspace reloads the preview after each successful edit, as a backstop for a hot-reload miss. Set false to keep panel state and scroll position across edits, at the cost of an occasional manual refresh. |
editor.canvas |
boolean | false |
The canvas and screenshot-plan surface. Off by default. See feature switches. |
editor.codeView |
boolean | false |
The in-app code view reached by "Open in editor". Off by default. See feature switches. |
editor.notes |
boolean | false |
The Notes surface, DOM-anchored annotations alongside comments. Off by default. See feature switches. |
editor.vscodeLink |
boolean | false |
The "Open in VS Code" item in the prototype's right-click menu. Off by default. See feature switches. |
retention#
Housekeeping for two things that would otherwise grow without limit: the per-edit backup journal that powers Undo, and the saved chat transcripts.
| Key | Type | Default | Notes |
|---|---|---|---|
retention.backups.keepNewest |
positive integer | 200 |
Keep at most this many backup directories. |
retention.backups.maxAgeDays |
positive number | 14 |
Delete backups older than this. |
retention.chatSessionTurns.maxTurns |
positive integer | 500 |
Cap on turns kept in a session file. Older turns move to an archive sidecar and are never deleted. |
The backups sweep runs at boot and after each Commit.
The blast-radius rule#
This file validates as one unit. The parser returns on the first problem it finds anywhere in the file, and the caller then treats the whole file as unusable, not just the offending block.
So a typo in retention.backups.keepNewest does not merely disable that sweep. It also drops your projectSlug, your projectId, your chat settings, your conventions settings and your editor settings, all at once. The Editor still boots, in degraded mode, having printed the specific validation message as a warning line in the terminal.
Warning If a setting in this file stopped working, do not debug that setting. Read the terminal output from boot: one line names the exact key that failed validation, and that one key is disabling everything else.
A missing file is not an error. It produces one informational line saying the repository has no project association.
desde.config.json#
External read access, web access, Figma, and design systems. This file is at your prototype's root, not inside .desde/.
{
"readRoots": {
"production": {
"path": "../billing-app",
"description": "The production app this prototype mirrors."
}
},
"webFetch": {
"allowedHosts": ["docs.example.com", "raw.githubusercontent.com"]
},
"webSearch": {
"enabled": false
},
"designSystems": [
{ "kind": "installed", "package": "@acme/ui" },
{ "kind": "npm", "spec": "@acme/icons@^2", "designSystem": "acme" },
{
"kind": "repo",
"url": "https://github.com/acme/design-system",
"ref": "main",
"subdir": "packages/ui",
"allowBuild": true
}
],
"figma": {
"enabled": true,
"mcpServer": {
"command": "npx",
"args": ["-y", "figma-developer-mcp", "--stdio"],
"env": { "FIGMA_API_KEY": "${FIGMA_API_KEY}" }
}
},
"lanes": {
"detach": true,
"swap": true
},
"hosts": {
"astro": true,
"next": false
}
}When each block is read#
The blocks in this file are loaded at different times by different code, which is also why they fail differently.
| Block | Read | A problem in it causes |
|---|---|---|
readRoots |
Boot, plus live when you use the in-editor dialog | A config mistake (bad name, missing path, pointing at your own prototype) refuses the boot. A path that no longer resolves (a moved folder, an unplugged drive) is a warning instead: that one root is skipped and the CLI still starts. See readRoots below. |
designSystems |
Boot, in the background | An error printed to the terminal; design-system setup is skipped, the session continues. |
lanes |
Boot | A warning line in the terminal; the lane stays dormant and the session continues. |
hosts |
Boot, before the dev server starts | A warning line in the terminal; the shipped defaults stand and the session continues. |
webFetch / webSearch |
Every chat turn | An error message in the chat stream; web tools stay disabled for that turn. |
figma |
Every chat turn | An error message in the chat stream; Figma stays unavailable for that turn. |
Two consequences worth internalising. First, a JSON syntax error anywhere in this file stops the Editor from starting at all, because the read-roots loader parses the whole file at boot and a broken file is worse to fail silently than loudly. Second, webFetch, webSearch, figma, and readRoots changes made through the settings dialog all take effect on your next chat message: you do not need to restart the CLI. This last one has one exception: if you hand-edit readRoots in the JSON file directly, instead of using the dialog, the CLI does not notice until you restart it. Only writes made through the dialog (or the wizard, at project creation) update the running session live.
readRoots#
Additional folders the chat agent is allowed to read. The typical use is pointing it at the production codebase your prototype imitates, so it can see how a component is really used. A read root does not have to be a git repository. Any folder works: an exported design kit, a docs dump, a directory you unzipped somewhere.
The value is an object whose keys are the names the agent refers to, each mapping to { "path": "...", "description": "..." }.
You do not have to hand-edit this block. Once a project is open, the settings menu (the ⋮ icon) has a Reference folders item that opens a dialog to add or remove them. The launcher's per-project settings page has its own Reference folders tab that does the same thing, without opening the editor.
The New Project wizard used to have an "Add reference folders" step. It was removed 2026-08-31, to keep project creation simple. Reference folders now live only in the two settings surfaces above.
Folders added or removed through either surface apply to your next chat message. You do not need to restart the CLI. Hand-editing the JSON file directly still works, but the CLI only picks that up on the next restart.
Two separate kinds of problem can happen with a declared root, and they are handled differently on purpose. A config mistake (something you typed wrong) stops the whole session from starting, because it is a mistake you need to see and fix. An environment problem (the folder moved, or a drive is unplugged) only skips that one folder. The rest of your session, and every other read root, works normally.
| Rule | Kind of problem | What happens |
|---|---|---|
| Name format | Config | Must match ^[a-z][a-z0-9-]{0,30}$. A bad name fails boot. |
| Reserved name | Config | worktree is taken: it is the implicit root for your prototype itself. Using it fails boot. |
path |
Config | Required. Absolute, or relative to your prototype root. Symlinks are resolved once at load. A missing path field fails boot. |
| Cannot be the prototype | Config | Pointing a root at your own prototype root fails boot; use the implicit worktree root instead. |
| Must resolve | Environment | A path that does not resolve (moved folder, unmounted drive) is a warning. That one root is skipped; the session still starts. Before 2026-08-13 this was a fatal error that blocked the whole CLI from opening. |
| Must be a directory | Config | A path that resolves to a file, not a directory, fails boot. |
| Duplicates | Environment | Two names for the same path are allowed, with a warning. |
Whether a root is a git repository is recorded, not required. Each root reports isGit: true or isGit: false, and that flag decides which agent tools work on it:
| Tool | Git root | Plain directory (isGit: false) |
|---|---|---|
| Read a file | Reads the file at a commit (default HEAD). |
Reads the file live from disk. The sha you pass is ignored, and the result says so. |
| Search files | git grep, scoped to tracked files at HEAD. |
A filesystem walk. It skips node_modules, .git, dist, build, and dot-directories, and skips binary files. |
| List commits | Works. | Refuses, naming the root. There is no commit history to list. |
| Diff a file between refs | Works. | Refuses, naming the root. There are no refs to diff between. |
Read roots are read-only. Nothing in this block grants write access.
webFetch and webSearch#
Both web tools are denied by default. The reason is exfiltration: the agent can see your prototype's page content and your read roots, so text injected into a page could otherwise talk it into fetching an attacker's URL with your data in the query string. Enabling web access should be a decision, not an inheritance.
| Key | Type | Default | Notes |
|---|---|---|---|
webFetch.allowedHosts |
string[] | [] |
Bare hostnames, matched exactly and case-insensitively. No scheme, no wildcards, no paths: an entry containing * or :// is rejected. An empty or absent list means WebFetch is denied outright. |
webSearch.enabled |
boolean | false |
Enables search. Lower risk than arbitrary fetch, but not zero: the query text does leave your machine. |
figma#
The Editor does not bundle a Figma integration. You supply any stdio MCP server you trust, and the Editor registers it for the agent so you can ask it to build a screen from a Figma frame.
| Key | Type | Required | Notes |
|---|---|---|---|
figma.enabled |
boolean | Yes, when the figma block exists |
false disables everything below without deleting it. |
figma.mcpServer.command |
string | Yes, when enabled | The executable name or path, on its own. Shell metacharacters (semicolon, pipe, ampersand, angle brackets, dollar, backtick, newline) and control characters are rejected. Pass everything else through args. |
figma.mcpServer.args |
string[] | No | Each entry supports ${VAR} interpolation. |
figma.mcpServer.env |
object | No | String-to-string. Values support ${VAR} interpolation. |
figma.mcpServer.type |
string | No | Must be "stdio" if present. It is the only supported transport. |
figma.mcpServer.alwaysLoad |
boolean | No, default false |
Loads the server on every turn. Not recommended: it can add up to five seconds of startup to each message, and you get a warning if you set it. |
figma.allowedToolPrefixes |
string[] | No | Overrides the read-only allowlist. An empty array is rejected; disable the block instead. |
Read-only is enforced at runtime, not by convention. A Figma tool call is denied unless its name starts with one of get_, list_, read_, search_, fetch_ or find_. Override that list with allowedToolPrefixes only if the server you chose names its read tools differently.
Do not put a token in this file. Write "${FIGMA_API_KEY}" and set the variable in your shell. A ${VAR} that is not set in the environment is an error, not an empty string, so a missing token fails loudly. A literal value is accepted but produces a warning, because this file is usually committed.
Warning This file can start a subprocess of your choosing. Treat it with the same care as
package.jsonscripts: the Editor validates its shape but does not sandbox what it spawns. Content inside Figma (layer names, text, comments) is untrusted input, not instructions.
designSystems#
An array of the design systems the Editor should extract component information from. Each entry is flat, and its shape depends on kind.
kind |
Fields | Meaning |
|---|---|---|
installed |
package (required) |
A package already in your prototype's node_modules. |
npm |
spec (required) |
A package to install, e.g. @acme/ds@^2. |
repo |
url (required), ref, subdir |
A git repository to clone. |
Optional on any entry: designSystem, a display label, and allowBuild, a boolean that permits running the repository's own build (relevant to repo entries).
Declaring a system does no work by itself. A background pass after boot resolves declarations into extracted component data, so a fresh clone starts immediately and becomes grounded shortly after. See design-system grounding.
You often need no entries at all: installed component libraries that ship TypeScript declarations are discovered automatically.
lanes#
Turns dormant edit lanes back on. A dormant lane is one the Editor has stopped offering: its controls are absent from the workspace and the edit API refuses that kind of edit. Nothing is deleted: the applicators, and their tests, are intact and unchanged.
| Key | Type | Default | What it restores |
|---|---|---|---|
lanes.detach |
boolean | false |
Detach component in the Inspector's ⋮ menu and in the Structure tree's right-click menu. |
lanes.swap |
boolean | false |
Swap component in the Inspector's ⋮ menu, the swap dialog, and the icon picker. |
{
"lanes": {
"detach": true,
"swap": true
}
}Why these two are dormant. Both applicators rewrite Vue single-file-component syntax and neither has a React equivalent, so on a React project the Editor was offering an edit that could never land. On Vue, where they work, they had gone unused. Withdrawing the offering was cheaper than building and maintaining two React applicators for a capability with no users. They are separate keys because they come back at different times: detach needs a React applicator, swap needs that and a better matching model. It currently scores candidates by prop-name overlap alone.
The icon picker is on lanes.swap deliberately. Picking an icon is a swap: it goes through the same applicator with an empty prop mapping, so it carries the same Vue-only limit and the same refusal. There is no key that separates them.
Both ends are gated. With a lane dormant, the controls are not rendered and POST /api/editor/edit and the AI-fallback endpoint refuse that kind with a message naming the key. A stale browser tab cannot reach a dormant lane.
A badly shaped lanes block is a warning, never a boot failure: an unknown lane id, or a value that is not a boolean, prints one line naming the key and is then ignored. The fallback is the dormant default, which is the shipped product, and refusing to start over a typo in an opt-in flag would be the more damaging failure. (A JSON syntax error is a different matter: it stops the boot, because readRoots parses this same file first. See when each block is read.)
hosts#
Which dev servers the Editor may start in its own process. A host that is on is booted for you by desde .; a host that is off falls back to attach mode, where you start the dev server and the Editor connects to it.
| Key | Default | What it means |
|---|---|---|
hosts.vite |
On | Vue 3 or React on a plain vite.config. |
hosts.nuxt |
On | Nuxt. |
hosts["react-router"] |
On | React Router v7 / v8. |
hosts.next |
On | Next.js, App Router on Turbopack. |
hosts.astro |
Off | Astro. Boots and stamps correctly; see the caveat below before turning it on. |
{
"hosts": {
"astro": true,
"next": false
}
}Nuxt, React Router and Next.js became default-on on 2026-08-11. Before that date every one of them required this block to say true, and the published instructions were to use attach mode. If you have {"hosts": {"nuxt": true}} in a config file, it is now redundant and harmless. You can delete it.
Why Astro is off. Not because it fails: its boot gate passes as completely as the other three. Markup written directly in a .astro page has no applicator, so those pages are inspect-only, while .jsx, .tsx and .vue islands on the same page edit normally. That split lives inside a single project and is invisible until an edit is refused. Turning it on gives you a working in-process Astro dev server with that limit.
Setting a host to false is the escape hatch. It is how you take back control of your own dev server for a framework the Editor would otherwise boot, for a container, a custom start script, or a remote machine. The framework then behaves exactly as it did before it was flipped on, including the refusal message naming your dev command and the --attach line to paste.
A badly shaped hosts block warns and is ignored, on the same reasoning as lanes: an unknown host id lists the ids that do exist, and a non-boolean value names the key. Neither stops the boot. Note that hosts.attach is not a valid key: attach mode is reached by passing --attach <url>, never by opting in here.
The old filename#
Repositories written before the tool was renamed may have desde-composer.config.json. That name is still read, indefinitely, and never written. If both files exist the current name wins outright and the legacy one is ignored: no merging.
Warning The fallback covers only
webFetch,webSearchandfigma. ThereadRootsanddesignSystemsloaders opendesde.config.jsonby name with no fallback, so a repository still on the old filename gets its web and Figma settings honoured while its read roots and design systems silently do not exist. Rename the file rather than relying on the fallback.
Feature switches#
Seven switches change what appears in the workspace, through a config key or an environment variable. They do not all live in the same file, and they do not all default the same way.
| Switch | Where | File | Default | Turn it the other way |
|---|---|---|---|---|
| Tabbed chat sessions | chat.detachedSessions |
.desde/config.json |
On | "chat": { "detachedSessions": false } |
| Canvas and screenshot plans | editor.canvas |
.desde/config.json |
Off | "editor": { "canvas": true }, or the EDITOR_CANVAS=1 environment variable (either one enables it) |
| In-app code view ("Open in editor") | editor.codeView |
.desde/config.json |
Off | "editor": { "codeView": true }, or the EDITOR_CODE_VIEW=1 environment variable (either one enables it) |
| Notes | editor.notes |
.desde/config.json |
Off | "editor": { "notes": true }, or the EDITOR_NOTES=1 environment variable (either one enables it) |
| "Open in VS Code" | editor.vscodeLink |
.desde/config.json |
Off | "editor": { "vscodeLink": true }, or the EDITOR_VSCODE_LINK=1 environment variable (either one enables it) |
| Detach component | lanes.detach |
desde.config.json |
Off | "lanes": { "detach": true } |
| Swap component and the icon picker | lanes.swap |
desde.config.json |
Off | "lanes": { "swap": true } |
All seven are read when the workspace page loads, so flipping any of them requires restarting the CLI and reloading the page.
There is an eighth switch, and it works differently from the seven above. EDITOR_REMEMBER_SCOPE_CHOICE gates a checkbox in two of the scope dialogs (iteration scope and style scope): the option to remember your choice for the rest of the session. It is hardcoded to false in the source. There is no config key for it and no environment variable. The only way to turn it on is to edit src/lib/editor-feature-flags.ts and change the constant by hand.
The canvas surface is off deliberately. It is present and intact (the components, the replay machinery, the agent's plan tools) but undertested, and turned off until it gets more investment. Expect rough edges if you enable it.
Code view, Notes and "Open in VS Code" are off for a third reason, separate from canvas and separate from the two lanes switches below. Each is a deliberate product decision, not an unfinished feature. Code view needs visual work before it ships. Notes and "Open in VS Code" were turned off outright, on 2026-08-14 and 2026-08-18.
Turning Notes off is also what makes the right rail's Comments tab honest. Comments and Notes used to render as one merged list, because the two behave identically at the UI layer. That is exactly why the tab ended up named after neither. With Notes off, that list holds only comments, so the tab can say what it holds.
Code view and Notes are gated at both ends. The client stops offering the surface, and the CLI's own API refuses it too while the switch is off (GET /api/editor/file for code view, /api/editor/notes/* for Notes), so a stale browser tab cannot reach either behind a dormant surface. "Open in VS Code" only has one end to gate: it calls no API. It sets the browser's location to a vscode:// URL built from the repo path the CLI already sends the page, so there is nothing left on the server to refuse.
The two lanes switches are off for a different reason: they work, but only on Vue, and they were unused there. lanes above has the detail. There is no environment variable for either.
Environment variables#
Most configuration is file-based. The environment variables below are the ones the Editor actually reads.
| Variable | Read by | Effect |
|---|---|---|
ANTHROPIC_API_KEY |
Model provider registry | When set, model-backed features outside chat use the API with this key. Takes precedence over a key stored through the settings gear, which is never allowed to overwrite it. |
EDITOR_USE_CLAUDE_SUBSCRIPTION |
Model provider registry | 1, true, yes or on routes those same features through the bundled claude binary's subscription instead. Required when no API key is set: otherwise they refuse with Missing ANTHROPIC_API_KEY. |
EDITOR_CANVAS |
Workspace bootstrap | Exactly 1 enables the canvas surface. Any other value, including true, does nothing. |
EDITOR_CODE_VIEW |
Workspace bootstrap, GET /api/editor/file |
Exactly 1 enables the in-app code view. Any other value, including true, does nothing. See feature switches. |
EDITOR_NOTES |
Workspace bootstrap, /api/editor/notes/* |
Exactly 1 enables the Notes surface. Any other value, including true, does nothing. See feature switches. |
EDITOR_VSCODE_LINK |
Workspace bootstrap | Exactly 1 enables "Open in VS Code". Any other value, including true, does nothing. See feature switches. |
EDITOR_REVIEW_SURFACE |
Agent review surface | bridge, off, 0 or false disables the isolated headless browser the agent uses to check its own work, falling back to driving your live preview. Any other value, and absence, leaves it on. |
EDITOR_PROTOTYPE_TSCONFIG |
Manifest extraction | Absolute path to a TypeScript config to use instead of the auto-detected tsconfig.app.json or tsconfig.json. |
EDITOR_STORYBOOK_URLS |
Manifest extraction | Comma-separated Storybook base URLs to pull component information from. Each entry may add a display label and an import path: https://sb.example.com|acme|@acme/ui. Loopback and private hosts are rejected. |
EDITOR_STORYBOOK_HOST_ALLOWLIST |
Manifest extraction | Comma-separated hostnames exempt from that loopback and private-address rejection. |
A key can also be stored from the editor's settings gear, under Anthropic API key. It lives in ~/.config/desde/llm-credentials.json (0600) and is injected into the CLI's environment at boot, so it reaches both the provider registry above and the claude binary chat spawns. An environment variable always wins; when one is set, the settings panel disables its controls rather than offering an action it cannot perform.
Note Chat itself does not use
ANTHROPIC_API_KEY. It runs on the bundledclaudebinary and inherits whatever that binary is already signed in with. The two lanes can disagree: chat working perfectly while another action fails withMissing ANTHROPIC_API_KEYis expected behaviour, not a bug. See the quickstart.
Ports and asset paths are command line flags rather than environment variables. See the CLI reference.