typed

typed CLI

The first-party coding agent CLI for typed.cloud. It runs the agent loop -- read, search, edit, run, verify -- in your terminal, against your own codebase. Three tiers: typed++ (the premium hosted tier, paid), typed (the economy hosted tier, the cheapest paid option, and the typed CLI's default with a key on a paid plan) and typed-local (free, runs on your own machine; the typed CLI's default without one). With no typed.cloud key the CLI runs typed-local (free, on your machine). With a key on a paid plan it runs typed (hosted economy, billed to your plan). Name typed++ when you want the strongest tier. Beside them, the typed-openrouter add-on runs a model you pick on your own key (see "Bring your own key" below).

The native protocol is the Anthropic Messages API. The hosted tiers talk to typed.cloud, typed-local talks to a model server on your own machine, and the CLI works against any Anthropic-API-compatible endpoint if you point ANTHROPIC_BASE_URL elsewhere. It honors the same configuration surfaces as the reference CLI (Claude Code) -- CLAUDE.md, settings.json permission rules, skills, hooks, MCP servers from .mcp.json and .claude.json -- so an existing setup carries over without rewriting anything.

The typed CLI is the default client for new installs (apps/api/src/routes/launcher-version.ts advertises rollout.default_client = 'typed-cli' and the installer's setup_typed_cli writes ~/.config/typed/client = typed-cli on a successful install). Claude Code against typed.cloud remains fully supported; you can switch between the two clients at any time (see Install below).

Install

One curl installs the typed launcher. The key argument is optional: typed-local, the free tier, needs none, and for the hosted tiers the dashboard at app.typed.cloud pre-fills the command with your key:

curl -fsSL https://app.typed.cloud/install.sh | bash -s <your-typed-key>

On Windows, from PowerShell (not cmd). It runs the same installer under Git Bash, so it needs Git for Windows, and offers to install it with scoop when it is missing:

$env:TYPED_INSTALL_KEY='<your-typed-key>'; irm https://app.typed.cloud/install.ps1 | iex

To install into WSL Ubuntu instead, run the curl line inside Ubuntu, or from PowerShell (irm | iex cannot pass a switch, hence the scriptblock):

$env:TYPED_INSTALL_KEY='<your-typed-key>'; & ([scriptblock]::Create((irm https://app.typed.cloud/install.ps1))) -Wsl

From PowerShell, typed and t run through .cmd shims, which mangle ^, empty arguments and &: quote such a prompt after --%, or type it in interactive mode.

The first-party CLI needs a JavaScript runtime: a recent oam (the launcher names the exact minimum whenever the installed oam is older, as does typed doctor once oam is on PATH; oam self-update reaches it), or Node.js 22 or later. The launcher prefers oam. An older oam is never used: with Node.js installed the launcher runs the CLI under node and says so in one line on each launch, and without it the launch stops and names both fixes. oam self-update brings oam to the current release, and typed doctor reports which oam it found. An oam older than 0.18.0 updates itself by running oam's installer, which now verifies the release signature and needs ssh-keygen 8.1 or later: macOS and most Linux distributions ship it and Windows has it in the OpenSSH client, but a minimal container, a Windows install without the OpenSSH client or a distribution that ships 8.0 needs a newer one first. The installer's error names the fix.

By default typed and t launch the first-party CLI. Switch back to the reference client (or print the current state) with:

typed cli on       # default -- the launcher starts the first-party CLI
typed cli off      # back to the reference client
typed cli          # print the current state

(typed tui on|off is an interchangeable alias for typed cli on|off and still works. typed beta on|off is a deprecated alias for the same thing, still working for now.)

The opt-in is a one-line file at ~/.config/typed/client; nothing else about your setup changes, and typed cli off reverts it completely.

Quickstart

typed login       # browser device-auth; writes ~/.config/typed/api-key
t                 # start a session in the current project directory

Once you are in a session:

  • Type a task and press Enter.
  • Press ? on an empty input line (or run /help) for the one-screen help overlay: built-in commands, your discovered skills, key bindings, and how the permission gates work.
  • Ctrl+C interrupts the current run (twice within 1.5s exits); Ctrl+D exits; Tab completes / commands; Up/Down walk input history, with a dim History N/M after the recalled prompt showing where you are (oldest = 1).

The status line at the bottom shows the model tier, the run state, and the live session token counts (<in> in / <out> out). No surface in the CLI prints spend in dollars; /status reports the session in tokens too.

The everyday surface

Tools

Read (text and images), Write, Edit, Grep, Glob, Bash, PowerShell (Windows), Job (reads, waits on and ends background shell jobs), WebFetch, WebSearch, Agent (subagents for self-contained delegated tasks), and a model-driven progress list rendered in the footer. MCP servers (stdio, Streamable HTTP, or SSE; see MCP servers below) register their tools as mcp__<server>__<tool>.

Background jobs

A shell command runs in the foreground by default, with a 2-minute default and a 10-minute maximum timeout. For anything longer -- a full test suite, a build, a release, a watcher, a dev server -- the model can start it as a background job: the call returns a job id (j1) at once and the command keeps running while the session carries on. The model reads a job's new output, waits for it to finish with a single bounded call instead of polling, or ends it, and is told at its next turn when a job exits.

A foreground command that is still running at its timeout is not killed: it becomes a background job in place, and the call returns still running after 2 m -- continuing as background job j1 (pid 1234); /jobs lists it, /jobs kill j1 stops it with the output captured so far (its tail, when it is long). From there it is an ordinary job -- the same limits, the same /jobs listing -- and its 2-hour lifetime counts from when the command started. Only where no job can take it (4 already running) is it killed as before, and the result says why.

A background command goes through exactly the same permission gate, rules and hooks as a foreground one -- backgrounding changes how long it may run, never whether it may run. The Job tool that reads jobs comes with the shell tools: a --tools list or an agent's tools: list that names Bash or PowerShell gets Job too.

Limit Value
Jobs running at once 4 per session (a subagent's jobs count)
Lifetime 2 hours by default, up to 8 hours when asked for; then the job is killed
Output kept per job the first 200,000 and the last 1,800,000 characters, with the gap marked

Jobs belong to the session and do not outlive it. Esc / Ctrl+C on a turn does not stop a job -- that is the point of backgrounding; use /jobs kill <id>. Leaving the session kills every job still running (/exit warns once first), as does /clear. A typed -p run ends at the model's final reply: a job still running then is killed and reported on stderr, and the model is told this when the job starts, so it waits for the job before answering. A job a subagent started is killed when that subagent finishes, and its result says so. Jobs are not resumed by --resume: a job from an earlier run is reported as ended with that session. A finished job's output is kept with the session's other tool outputs, so /show <id> prints it.

Attaching images

Two ways to put an image in a prompt. Either one shows up in the composer as an [Image #N] chip; backspace through the chip to drop it, and attach as many as you like.

Paste or drag a file path. An absolute path ending in .png, .jpg, .jpeg, .gif or .webp becomes a chip when pasted.

Paste the image itself -- a screenshot, a bitmap copied out of a browser. A terminal can only deliver text, so typed reads the image from the OS clipboard when you ask it to:

Trigger Works when
Alt+V the terminal passes the key through (most do; on macOS the terminal has to treat Option as Meta)
Ctrl+V the terminal does not keep Ctrl+V for its own paste (common on macOS and Linux)
your terminal's paste shortcut, with no text copied the terminal sends an empty bracketed paste, which typed reads as "look for an image"
/paste always. Over SSH this reads the clipboard of the REMOTE machine, the one typed runs on

The read runs in the background behind a [Image #N: reading clipboard...] placeholder, and the chip that replaces it says what was attached, e.g. [Image #1: 1920x1080, 412 KB]. Keep typing while it runs; Esc cancels it. Press Enter before it is done and the placeholder changes to reading clipboard... sends when done: the prompt goes out the moment the image lands, and any other key takes that Enter back. With no image on the clipboard you get one quiet line, nothing is inserted, and nothing is sent.

An attachment is limited to about 3.75 MB (5 MB once encoded). A larger clipboard image -- a 4K screenshot often is -- gets downscaled to fit, aspect ratio kept, never enlarged, and the chip shows both sizes so you know the model sees a reduced image: [Image #1: 3840x2160 -> 1214x683, 2.2 MB].

Platform How the clipboard is read Downscaling
Windows Windows PowerShell (built in) built in
WSL the Windows clipboard, through powershell.exe built in
macOS pngpaste if installed, else osascript (built in) sips (built in)
Linux wl-paste from wl-clipboard (Wayland) or xclip (X11) ImageMagick (magick or convert); without it an oversized image is refused with its size and the limit

Privacy: the clipboard is read only when you press one of the triggers above -- never on focus, on a timer, or during an ordinary text paste -- and only image data is requested. Clipboard text is never read by this feature. The pasted image is a temporary file beside the session transcript; it is deleted when you remove its chip and when the session ends. The transcript keeps the image you actually sent, so --resume does not need the file.

The free local tier does not accept images; there the trigger says so and reads nothing.

Permission gates

Tool calls that can mutate state are gated before they run. The engine is fully deterministic -- rules only, no model in the loop -- so a denial is reproducible and names the exact rule that produced it.

At a gate: y allows once, n denies, and a (when offered) allows always -- writing a CC-syntax allow rule to .claude/settings.local.json at the top of the repository (the launch directory when there is none), so the rule is read from every directory in the project. The gate says in words what the rule covers before you press the key:

The call a saves Which covers
one plain command: git status -sb Bash(git status*) commands starting git status
a chain: cd apps/cli && pnpm test x Bash(cd apps/cli*) and Bash(pnpm test*) each of those commands, alone or chained
a redirect, a $var, $(...), an env prefix the exact command that command string only
an auto-mode hard stop: git push --force ... the exact command that command string only, never git push*
Edit / Write Edit(<file>) that file -- or press d for every file under its directory
WebFetch WebFetch(<origin>*) any page on that origin

Mutating gates confirm with Enter. A key pressed in the first 300 ms after a prompt appears is ignored, and the gate says so, rather than being replaced by Enter's allow-once. Rules use the same allow / deny / ask arrays and Tool(prefix*) syntax as the reference CLI.

/permissions lists every saved rule with what it covers, the file it came from and what it outranks; /permissions revoke <n> removes one from its file and from the running session. They are worth listing: an allow rule is read in every mode but plan and outranks readonly mode; an exact one also switches an auto-mode hard stop off, while a prefix one never does (the list says which is which -- a prefix rule saved before this became the rule has simply lost that power).

Rules saved by earlier versions under the directory typed was launched from are still read; nothing is moved on disk.

When auto mode stops a call because it reaches outside the project, and the path sits in another repository, the gate also offers d -- always allow this folder (<that repository's root>). It adds the folder as an additional directory (the same thing /add-dir <path> does), saves it, and runs the call; every other hard stop still applies inside it. A rule you then save for a file in that folder is written to, and read back from, that repository's own .claude/settings.local.json. A folder's committed settings.json can add deny and ask rules to your session but never allow ones: adding a folder does not let a file you did not write grant anything.

Auto mode

For unattended or long-running sessions, auto mode skips the per-prompt gate for mutating tools and fires the same a path the interactive a key does -- deriving a CC-syntax allow rule from the call, evaluating it, and running the tool. Each auto-fired decision is committed to the scrollback as + auto: <ToolName> (<rule>) so the session log shows exactly which rules the model self-allowed against.

Auto mode runs the routine work of a coding agent without asking: reads and searches anywhere; the project's own build, test, lint and format commands; creating, editing and deleting files inside the project and the temp directory; local git (add, commit, switch, stash, merge, a push of a feature branch). Redirects (2>&1, > file inside the project), pipes, env prefixes, cd <dir> && ..., loops and $(...) do not change that -- a compound command is judged one command at a time.

It still asks, once and with the reason, before a short list of hard stops:

Stop Examples
Deleting or writing outside the project rm -rf ~/x, echo x > ~/.bashrc, the project root itself, .git
Git that discards work or rewrites the remote push --force, a push to main, reset --hard, clean -f, branch -D, filter-repo
Publishing, deploying, changing remote systems npm publish, pnpm release, gh pr merge, docker push, kubectl apply, terraform apply
Privilege and system changes sudo, global package installs, services, registry, firewall, scheduled tasks
Credentials reading .env, ~/.ssh, ~/.npmrc, a credential store, a *_TOKEN variable
Sending data off the machine curl -d, scp, rsync to a host, ssh, nc (a server on localhost is fine)
Running downloaded or unreadable code curl ... | sh, npx <package not installed>, eval "$X", node -e, python -c, xargs, find -exec
Switching off a safety check --no-verify, --admin, HUSKY=0, PATH=... / LD_PRELOAD=... prefixes
MCP tools their server declares destructive destructiveHint: true

Working in more than one folder. A session that also edits a sibling repository can name it as an additional directory; auto mode then treats it like the project for the first row of that table (writes, deletes and local git inside it stop asking). Every other row still applies inside it, and a push is judged against that repository's own checked-out branch.

Surface Shape
In-session /add-dir <path> adds (applies at once, saved to .claude/settings.local.json); /add-dir lists; /add-dir remove <path> removes
Startup flag typed --add-dir <path> (repeatable)
Settings "permissions": { "additionalDirectories": ["../other-repo"] } in .claude/settings.json, .claude/settings.local.json or the user-level settings.json; all three are merged, relative paths resolve against the session directory

When a stop is about a path inside another repository, the prompt names the command that clears it (/add-dir <that repository>). An entry must be an existing directory and cannot be a drive or filesystem root, your home directory, or a folder that contains it -- that would make nothing "outside". A refused entry is reported at startup with the reason and skipped.

An exact allow rule for the command switches a stop off -- what a saves on the stopped prompt. A prefix rule such as Bash(git push*), whoever wrote it and whenever, allows ordinary pushes and never a stop; a rule auto mode recorded for itself never does either -- in the session that recorded it or, saved by --auto-persist, in a later one. This reads command text: it is a guard against a mistaken or misled model, not a sandbox -- it does not see inside a script file the agent runs.

Opt in three ways:

Surface Shape Effect
Startup flag typed --auto session-scoped auto mode; rules live only in memory and are lost on exit
Startup flag typed --auto --auto-persist auto mode + save each rule it records to permissions.autoAllow in .claude/settings.local.json as it fires. Those rules are read in auto mode only and never run a hard stop; they are not allow rules
In-session /auto toggle auto mode on or off in the current session
In-session /auto persist turn the current session's auto mode into the persisting variant (sticky)

To start every session in a project in auto mode (or any other mode), set permissions.defaultMode in a settings file -- /permissions default auto writes it, /permissions default off removes it. The settings files are shared with Claude Code, so its spellings (acceptEdits, bypassPermissions) are read as typed's and are what /permissions default writes where one exists. A value typed does not have (dontAsk, say) never stops the launch: one ! line names it, the file and the valid modes, and the session starts in default.

The mode a session starts in is decided once, at launch, and may not widen what runs without a prompt unless the user can see it happen:

  • A --permission-mode flag outranks everything.
  • A committed .claude/settings.json is the repository's file, not yours: it can make sessions stricter (plan, readonly) but cannot start them in bypass or auto. Those are honored from .claude/settings.local.json and from the user-level settings.json only; a refused value is named at launch.
  • --resume / --continue brings a session back in the mode it was last in, except bypass, which is never restored from a transcript (pass --permission-mode bypass).
  • A headless run (typed -p, piped stdin) never starts wider than default without a flag, whatever the transcript or the settings say.
  • Whenever a session starts in anything but default, one launch line says which mode and where it came from ((mode auto, from <file>)); headless runs print it on stderr.

The status line shows an [auto] chip when auto is on, so it is visible at a glance. Auto mode does not bypass deny rules -- the engine still rejects any call that matches an explicit deny. Headless runs pick it up via --permission-mode auto; there is nobody to ask in a headless run, so a hard stop is refused rather than prompted. The headless default behavior (deny gated calls) is unchanged for default / accept-edits / readonly.

Built-in commands

Command What it does
/help (or ? on an empty line) one-screen help overlay
/undo restore every file the session has written, from per-session shadow snapshots taken before the first write to each file (works on dirty trees; no git required)
/compact summarize older history to free context (auto-compaction also triggers at a threshold)
/show <id> print the full output of a truncated tool result
/paste attach the image on the clipboard (also Alt+V; see Attaching images). Over SSH, the remote machine's clipboard
/jobs list background shell jobs the model started; /jobs kill <id> ends one (see Background jobs above)
/status session state: model, context window, input budget and auto-compaction trigger, token counts, config sources
/auto [on|off|persist] toggle auto mode (or set the persisting variant); see Permission gates above
/permissions [revoke <n>|default <mode>|default off] list saved permission rules (what each covers, its file, what it outranks); revoke one; set the mode sessions in this project start in
/mcp [verb] every typed mcp verb from inside the session -- list, add, remove (rm), login, logout (see MCP servers below). add and remove edit the config files, so they apply from the next launch rather than to the servers this session already connected; login opens your browser and stores the tokens, which the next connect picks up
/suggest [auto|tab|off] show or set when prompt suggestions appear. tab (the default): nothing is offered until you press Tab. auto: a prompt you have already sent 3 or more times is also offered unasked, but a model suggestion is still Tab-only, because it is a billed request. off: no suggestions and no requests, and Tab and Right are plain keys. The choice applies at once and is saved to ~/.config/typed/suggest, so it holds at the next launch unless a variable below outranks it
/exit leave the session

Resuming work

typed --continue        # resume the most recent session for this directory
typed --resume <id>     # resume a specific session (id prefix ok)
typed --resume          # list available sessions for this directory and pick one

Sessions are JSONL transcripts stored per project directory (see Env vars below). A resumed session comes back in the permission mode it was last in, unless the launch names one with --permission-mode or --auto.

A resumed session also comes back on the tier it last ran on -- a session you moved to typed++ with /typed++, or one that ran on typed-local, resumes there rather than on this launch's default. The launch line says so when that differs from what the launch would otherwise have run: (model typed-local, restored from the resumed session; this launch would otherwise run typed). Naming a tier now outranks it: --model, --effort and your own ANTHROPIC_MODEL export win, and a ! line then names which one chose the other tier. The Yaw Mode pane tier, the .typed pins and the account default are standing defaults, and the session's tier outranks them. What does NOT come back, each said out loud: a hosted tier when no typed key is on this machine (the launch runs typed-local; typed login, then /typed or /typed++), a hosted tier in an old Yaw pane that routes to your own server, a typed-local whose model server is not answering or whose local setup is not finished (the launch keeps its hosted tier; fix that, then /typed-local), and a tier that no longer exists. /status reports source: session for a restored tier that differs from what the launch would have run. Under the resume note the TUI shows what the session was about -- its first prompt, its last prompt and the last reply, one line each (TYPED_CLI_RESUME_EXCERPT sets the width; 0 hides it). The --resume picker's model column shows where each session last ran, which is what resuming it restores. The full tool outputs a session stored are saved beside its transcript, so the output ids that ReadOutput and /show take (o12) keep working after --resume or --continue. Saved outputs are removed 14 days after the session was last used, and past 256 MB per session and 2 GB across sessions (oldest first); an id whose output was removed says so. TYPED_CLI_OUTPUT_STORE_DISK=0 keeps them in memory only; outputs saved before you set it are still removed on that schedule.

Subcommands

typed login | logout        # device-auth key management
typed use <tier> [--global] # pin a model tier (.typed file, or ~/.config/typed/model)
typed status                # offline: resolved config + key source
typed doctor                # probe the backend, key, and config for the common traps
typed ultracode [on|off]    # multi-agent orchestration mode (Pro tier and up)
typed update                # self-update: re-runs the installer; ~/.config/typed is preserved
typed codebase <verb>       # hosted codebase indexing (Team tier); see below
typed byok [key|model|models] # the bring-your-own-key add-on; see below
typed mcp <verb>            # MCP servers: list, add, remove (rm), login, logout; see below
typed local [install]       # free local tier: the llama-server command sized to this machine
typed local [--ctx-size <tokens> | --max-window] [--cache-ram <MiB|auto>] [--slots <N|auto>]
                            # ... at another window, with a prompt cache or one slot per pane, checked against RAM

typed doctor is the first thing to run when something is off: it checks the key source, the model pin, the well-known env traps, and probes the service, printing [ok] / [warn] / [FAIL] lines with a one-line fix for each failure.

Bring your own key (the typed-openrouter add-on)

A monthly add-on, bought on typed.cloud/pricing beside a plan or instead of one, that runs any model you pick on your own key, straight to your own account: typed.cloud only confirms the add-on is active and never sees the requests, and model usage is billed by your own account. Your own account's settings decide data retention and training; the hosted tiers' zero-data-retention guarantee does not cover these requests.

typed byok key              # store your own key (read from input, never argv; 0600 file)
typed byok key --clear      # remove it
typed byok models [<text>]  # what your own account offers, optionally filtered
typed byok model <model>    # the standing model for a bare --model typed-openrouter
typed byok model --clear
typed byok                  # status: key, model, and whether the add-on is active
typed --model typed-openrouter:<model>   # run one model for this launch
typed use typed-openrouter[:<model>]     # pin the add-on (the model goes to byok-model)
  • The key comes from TYPED_BYOK_API_KEY, else ~/.config/typed/byok-key (TYPED_BYOK_KEY_FILE moves it). It is never read from ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY, and a typed tk_ key is refused there. Your typed key is still needed, to confirm the add-on.
  • The model is the part after the first : of typed-openrouter:<model>, else the standing one in ~/.config/typed/byok-model (TYPED_BYOK_MODEL_FILE). A bare typed-openrouter with neither is refused with both ways to name one; it is never defaulted.
  • Before the first request the CLI confirms the add-on with typed.cloud (a confirmation is reused for an hour; if typed.cloud cannot be reached, one from the last 24 hours keeps it running with a ! note on every launch, and past that it refuses), then reads the model's context window and output limit from your own account and refuses a model that does not support tool calls. Each failure is its own one-line refusal; nothing falls back to another tier.
  • In a session /typed-openrouter [<model>] (and /model typed-openrouter:<model>) moves onto the add-on, /typed, /typed+, /typed++ and /typed-local move off it, and --resume restores it like any tier. Subagents run on the session's model.
  • What it does not include: the hosted knowledge layer, --codebase (refused), ultracode, the quota and spend lines (not shown), and model prompt suggestions (off, because they would spend your own credit). It runs only in the typed CLI, not when the launcher starts another client.
  • Errors from your own account are shown in typed's words (out of credit, rate limited, key refused, model not offered), never as the upstream's own text. typed doctor checks the key, the model, the add-on and the model probe, each with a fix line.

Hosted codebase indexing

Team plans only (Team, Team Max, Team Ultra). typed indexes an uploaded snapshot of your repository and retrieves from it on every turn, so the model can cite files that were never in the conversation. On any other plan these commands return the API's own upgrade payload, including the link.

typed codebase push [path]            # pack, upload, index; defaults to the cwd
typed codebase list                   # every codebase in your org
typed codebase status <id>            # one codebase, with its failure reason
typed codebase rm <id> [--yes]        # delete it (indexed chunks and archive too)

push packs the tree with .gitignore (and .typedignore, same syntax) honored and .git excluded, then drives create -> upload -> finalize and polls until the index is ready or failed, printing each step. It refuses before uploading if the archive is over the server's size limit, naming the limit and the actual size. On success it prints the codebase id and the line that attaches it:

typed --codebase <id>

That flag is a session flag: every turn of the session carries the codebase, and a startup note names the one it is attached to. When the backend answers that a turn could not use the index -- still indexing, indexing failed, or retrieval briefly unavailable -- the CLI says so rather than letting the answer look codebase-informed. The flag has no effect on the free local tier, where inference never leaves your machine; the session says that at startup instead of sending a header nothing will read.

What is never uploaded

Your ignore files are advisory, and the archive is retained in storage after indexing -- so push refuses credential-shaped files outright, whether or not you remembered to ignore them:

  • Credential filenames, by exact name, family prefix, or suffix: .env and every .env.*, .npmrc / .yarnrc (registry auth), .netrc, .pgpass, credentials, credentials.json, service-account.json, secrets.*, id_rsa and siblings, and *.pem / *.key / *.p12 / *.jks / *.gpg. Public certificates (*.crt, *.pub) are not treated as secrets.
  • Dotfiles you have not opted in, allowlisted down to declarative config (.gitignore, .editorconfig, .nvmrc, .tool-versions, the .eslintrc / .prettierrc families, .typedignore). .env.example is not on that list.
  • Any file whose contents look secret-bearing regardless of its name -- an api key, an AWS access key id, a JWT, a PEM private-key block, or a password-style assignment with a key-like value. The whole file is skipped; nothing is redacted and re-packed.

push names every file it withheld and why, before it uploads. Paths are printed so you know what is missing from the index; contents never are.

This cannot be overridden. An ignore file only excludes, it cannot re-include, and the server applies the same rules again after unpacking. If you genuinely need one of these files indexed, take the credential out of it first.

rm is irreversible (it wipes the indexed chunks and the uploaded archive), so it asks you to type the id back unless you pass --yes.

The full endpoint contract, the status lifecycle, and the retention policy are documented for Team customers in the dashboard.

Headless mode

For scripts and CI:

typed -p "fix the failing test in src/foo.test.ts" --output-format json

--output-format text prints the final answer; json prints a single result envelope on stdout:

Field Meaning
type, subtype "result", "success" or "error"
is_error boolean
result final assistant text (or the error message)
num_turns turns used
duration_ms, duration_api_ms wall time and time spent in API calls
total_cost_usd real spend on typed backends; null when unknown
usage input_tokens, output_tokens, cache_read_input_tokens, cache_creation_input_tokens
api_error_status HTTP status when an API error ended the run (optional)
session_id id of the session transcript

Other flags: --model <tier> (one of typed++, typed, typed-local; no flag = the account default described under "Which tier a launch runs" below), --effort <word> (a compatibility alias for older shell setups; maps the word to a tier and yields to --model), --max-budget-usd (stop when estimated spend exceeds the budget), --max-turns <n> (model turns allowed per prompt, a whole number from 1 to 1000; by default there is no turn limit, except 30 on the free local tier typed-local -- without the flag a hosted run stops only when the model finishes, on an error, or at --max-budget-usd; a run that reaches a limit pauses with nothing lost, and "continue" resumes it with a fresh count; the flag is read at launch, so to raise it mid-session relaunch with --resume <id> --max-turns <n> (the pause line names the id), which resumes the same session; subagents inherit the same limit, or none; headless exits 1 with the reason on stderr), --append-system-prompt-file, --permission-mode default|accept-edits|readonly|bypass|auto (alias --auto; with no flag the mode is permissions.defaultMode from settings, else default; add --auto-persist to save the rules auto mode records, under permissions.autoAllow, as they fire), --tools <list>, --cwd <dir>, --add-dir <dir> (repeatable), --codebase <id> (attach a hosted codebase index -- Team tier; see Subcommands above).

Headless runs never prompt: gated tool calls are denied (the model is told why) unless you pass --permission-mode bypass. Hooks run headless only when the project already has a trust record granting them, or TYPED_CLI_TRUST_HOOKS=1 is set.

A headless run always ends when its last turn does. Any background job still running at that point is killed, and the run says so on stderr (! background job j1 killed at the end of the run after 42 s: <command>), so a script never hangs on, or silently leaves behind, a process the model started.

Configuration surfaces

The CLI reads the same project configuration the reference CLI does:

Surface Support
CLAUDE.md (user, project, .claude/, CLAUDE.local.md) full, including @import lines
AGENTS.md (the client-neutral convention: root and ancestor directories) full; loaded alongside CLAUDE.md, identical copies deduped. Files below the start directory are not read
settings.json permissions (allow / deny / ask, plus typed's own autoAllow and defaultMode) full, same rule syntax; the repository top level's settings.json + settings.local.json, the launch directory's pair when it differs, and user settings merge additively
.claude/skills/<name>/SKILL.md and .claude/commands/<name>.md full; discovered skills appear in / autocomplete and the ? overlay
.agents/skills/**/SKILL.md (project and ~/.agents/skills, the Agent Skills standard) full; grouping folders scanned 4 levels deep, linked skill dirs followed, spec frontmatter (license, compatibility, metadata, allowed-tools, disable-model-invocation) read, spec violations warned about and loaded anyway
.claude/agents/*.md full (name, description, tools, model frontmatter)
hooks (PreToolUse, PostToolUse, UserPromptSubmit, Stop, SessionStart, PreCompact, SubagentStop, SessionEnd, Notification) these nine events, with the same stdin-JSON and exit-code semantics; not every occurrence the reference CLI fires them on (SessionStart fires only at launch, never after /clear or a compaction; Notification only for a permission prompt). command hooks and http hooks (url, headers with allowedEnvVars interpolation, onFailure; the payload is POSTed as JSON and the response body is read as the same JSON output, a non-2xx answer, failed connection or timeout is a non-blocking error unless onFailure is "block", the settings-level allowedHttpHookUrls and httpHookAllowedEnvVars lists apply, and an http hook on SessionStart -- which runs command hooks only -- is skipped with a startup line; any other hook type, such as prompt, agent or mcp_tool, is skipped with a startup line too), with matcher, timeout and the per-hook if filter ("if": "Bash(git commit*)"), matched as the reference CLI documents it (an exact name or , / `
MCP servers: mcpServers in .mcp.json and .claude.json (stdio, http, sse) full, with the same local > project > user precedence; a project .mcp.json is trust-gated. See MCP servers below
~/.config/typed/mcp.json (typed's own user-scope MCP servers) typed-only; typed mcp add writes user-scope servers here, updates included. Outranks .claude.json user servers, loses to project and local ones
~/.config/typed/mcp-auth/<server>.json (OAuth tokens for a remote MCP server) typed-only; written by typed mcp login, removed by typed mcp logout. See MCP servers below
CLAUDE_CONFIG_DIR honored everywhere ~/.claude would be used

Skills the model can pick itself. Skills with a description are listed for the model at session start as an Agent Skills catalog (name, description clipped to 200 characters, and the absolute SKILL.md path), and the model loads one by reading the file when a task matches. The catalog has no size budget and is never trimmed (buildSkillCatalog in src/skill-catalog.ts): on the paid hosted tiers (typed++, typed) and any other hosted backend it lists every such skill, project skills first. On the free local tier it lists only the Yaw workflow skills in LOCAL_TIER_ALLOWLIST (yaw-review, yaw-address-all, yaw-coverage, yaw-ship-ready, yaw-implement) that are on disk, because every entry is prompt your own machine evaluates on each cold turn and a Yaw overlay alone carries 20+ skills; with none of them on disk no catalog is sent. On every tier the catalog is also left out when the model has no Read tool to load a skill with: when --tools excludes Read, or, on the free local tier, when the server did not pass the startup tool-call probe. And project skills are left out of it in typed -p and typed acp without an explicit trust grant (TYPED_CLI_TRUST_PROJECT=1 or a saved decision). Every skill left out is counted in one startup note naming the reason. In the interactive session the note adds that those skills are still slash commands; typed -p writes the notes to stderr and typed acp to its stderr log, and since neither has slash commands, their notes name what would list the skills instead. A skill the model or you activated is named again after /compact, with an instruction to re-read it. disable-model-invocation: true keeps a skill slash-only; a skill whose name shadows a built-in command stays in the catalog but loses its slash command.

First-run trust: the first time you run the CLI in a project that ships any of this config, it shows what it found and asks before using it. Hooks are opt-in -- they execute arbitrary commands, so they never run silently. Decisions are remembered per project.

MCP servers

This is documented for customers at https://typed.cloud/mcp.

The CLI loads MCP servers from the same mcpServers maps the reference CLI reads, plus one file of its own, over all three transports: stdio (command, args, env), Streamable HTTP ("type": "http", url, headers), and legacy SSE ("type": "sse", url, headers). Each server's tools register as mcp__<server>__<tool>, so a permission rule such as mcp__github__* covers one whole server.

Variables in an entry. ${VAR} and ${VAR:-default} expand in every string value: command, each args entry, each env value, url, and each header value.

  • ${VAR} takes the variable's value. A variable that is unset or empty with no default is an error: the server is skipped with a message naming the server, the field (args[1], env.API_KEY, headers.Authorization, url, command) and the variable. Nothing is ever sent half-blank.
  • ${VAR:-default} uses the default when the variable is unset or empty, so ${TOKEN:-} is how you say "an empty value is fine here."
  • $${ is a literal ${, the one escape: $${HOME} is the literal text ${HOME}. A lone $ is literal, and a backslash is not an escape (it is the Windows path separator).
  • Nesting is not supported. ${A_${B}}, an unterminated ${, and a ${A:-${B}} default are each a skip that says so.
  • Windows %VAR% is never expanded by typed. For a .cmd/.bat server, cmd.exe may still expand one itself when it starts the shim.
  • cwd in an entry is not supported and is ignored with a warning: a stdio server starts in the session's working directory. Wrap the command in a script that changes directory if it needs another.

typed mcp add also says, at write time, which of the variables an entry references are not set in your current environment.

Where servers are read from. When two sources define a server of the same name, the one later in this list wins. <config dir> is CLAUDE_CONFIG_DIR, or ~/.claude when that is unset.

  1. ~/.mcp.json
  2. <config dir>/.mcp.json
  3. top-level mcpServers in ~/.claude.json, then in <config dir>/.claude.json (the reference CLI's user scope)
  4. ~/.config/typed/mcp.json, typed's own user-scope file (strict JSON)
  5. <project>/.mcp.json (project scope)
  6. projects["<project>"].mcpServers in ~/.claude.json, then in <config dir>/.claude.json (the reference CLI's local scope: private to you, bound to this directory)

Local scope therefore beats project scope, and project scope beats user scope, as in the reference CLI. The projects key is matched with / and \ treated alike, and case-insensitively on Windows. A server entry the CLI cannot use (an unknown type, a missing command or url, a field of the wrong type) is skipped with a ! startup warning that names it, as is a config file that exists but cannot be read or parsed. Headless runs write those warnings to stderr.

Trust. A project .mcp.json ships with the repository and can start any command, so it is read only in a trusted project: in the TUI, once the project is trusted (a yes at the first-run prompt, or a decision saved earlier); in a headless run, only with a saved decision trusting the project or TYPED_CLI_TRUST_PROJECT=1. An untrusted project's file is not read at all, so its servers can neither start nor displace a same-named server of your own. Both .claude.json scopes and ~/.config/typed/mcp.json are your own files and are not trust-gated.

Yaw MCP. When no configured server already runs Yaw MCP, the CLI preloads it from whatever is installed and says so in a startup note: the yaw-mcp bin on PATH, or else, when the ~/.yaw-mcp state directory exists, npx -y @yawlabs/mcp@latest. A configured server counts as Yaw MCP when its command or arguments name the @yawlabs/mcp package or a yaw-mcp bin, or when the server is named mcp or yaw; a match on the name alone also suppresses the preload, with a note naming the server when a preload was otherwise available. TYPED_CLI_NO_YAW_MCP=1 turns the preload off.

When the CLI itself runs on oam 0.18.0 or later (the oldest oam Yaw MCP agrees to be hosted on), it starts the Yaw MCP broker on that same oam binary (<oam> run --no-check <entry>) instead of the JS host the launch names -- node, or Yaw Terminal's yaw.exe with ELECTRON_RUN_AS_NODE=1 -- which saves about 27 MB of private memory per session (measured against node). That holds for a configured server whose command runs the package's dist/index.js as its first argument and for the preloaded yaw-mcp bin, when the installed @yawlabs/mcp is 1.0.11 or later. An npx launch, an older broker, a CLI running on node, and TYPED_CLI_NO_YAW_MCP_OAM=1 all start the server exactly as configured. /mcp marks a moved broker (run on typed's oam).

The preload registers as server mcp, so its tools are mcp__mcp__<tool>, the names the mcp__mcp__* rule Yaw MCP's installer writes already covers. Changed: typed CLI 1.5.0 and earlier registered the preload as yaw. Allow, ask and deny rules written for mcp__yaw__... no longer match its tools -- a stale deny rule no longer blocks anything -- so rewrite them as mcp__mcp__....

A yaw-mcp bin on PATH older than 0.75.0, the first release that starts in gateway mode, is still preloaded, with a note, and the CLI runs npm i -g @yawlabs/mcp@latest in the background so the next session starts the updated bin; the current session keeps the old one. TYPED_NO_YAW_AUTO_UPDATE=1, or the broader TYPED_NO_UPDATE_CHECK=1, stops that background update. At 0.75.0 or later the CLI runs no update of its own: it leaves that to Yaw MCP's own background self-upgrade (YAW_MCP_AUTO_UPGRADE=0 or false turns it off). If yaw-mcp --version is behind the latest release, run yaw-mcp upgrade --run or npm i -g @yawlabs/mcp@latest.

Which of Yaw MCP's tools you see. Yaw MCP 1.0.11 and newer lists three of its eleven mcp_connect_* tools to a typed session -- mcp_connect_exec, mcp_connect_find_tool and mcp_connect_read_tool, its lite exposure, chosen from the typed-cli name the CLI sends in the MCP handshake -- and the full set to other clients. Between them the three reach every installed tool without the list changing: find_tool names it, read_tool describes it, exec calls it and loads its server on first use, after which that server's tools join the list (see the next paragraph). The CLI keeps that default: every listed tool is inlined into every request, and the three cost about 1,250 tokens per request against about 3,000 for the eleven (measured 2026-10-08 at 1.0.20, at 3.5 characters per token). A startup note says which set the broker listed. What the lite set leaves out is the manual unload (mcp_connect_deactivate; the broker still unloads idle servers on its own), natural-language routing and catalog browsing (mcp_connect_dispatch, mcp_connect_discover) and the diagnostics (health, suggest, bundles, secrets); the CLI registers only what a server lists, so in lite the model cannot call those (the broker still accepts them by name, which is how /mcp lists servers). YAW_MCP_TOOL_EXPOSURE=gateway in the environment lists all eleven: the broker reads it from its own environment, which every stdio server inherits from the CLI's, so one exported variable covers the preload and a .claude.json or mcp.json entry alike, and an entry's own env map can carry it for that entry only. The CLI never sets or overrides it. Under any exposure a per-server cap (TYPED_CLI_MAX_MCP_TOOLS, or the free local tier's default of 3) keeps mcp_connect_exec first, so a capped broker can still call a tool; and a server that should be Yaw MCP -- the preload, or an entry whose command names the package or bin -- but lists neither set gets a ! warning rather than a guess.

Setting up Yaw MCP yourself. yaw-mcp install typed (a Yaw MCP release newer than 1.0.1) writes Yaw MCP's entry, named mcp, into ~/.config/typed/mcp.json, where it beats the mcp entry Yaw Terminal keeps in ~/.claude.json. yaw-mcp install claude-code writes the entry into the reference CLI's own files instead, together with the mcp__mcp__* allow rule in its settings.json; the CLI reads both. A separate npx connect guard refuses to wire an npx-spawned server back into the Yaw MCP that spawned it, so the bin and the preloaded gateway never form a loop on a misconfigured entry.

Tool list changes. A server can change its tools mid-session and say so with notifications/tools/list_changed (a gateway such as Yaw MCP does when it loads tools). The CLI then re-lists that server before the next request, through the same --tools allowlist, per-server cap and name checks startup uses, so the model sees the change from its next turn; a real change is announced in one line, and a call to a tool the server has withdrawn is refused locally. In headless runs only ! notes reach stderr. TYPED_CLI_MCP_LIST_CHANGED=off keeps every server's startup tools for the session, which is the right choice for reproducible tests but otherwise a regression -- a server that adds tools in the middle of a long task is the norm, not the exception.

Free local tier and tool caps. The free local tier connects no MCP servers, and preloads no Yaw MCP, unless TYPED_CLI_LOCAL_MCP=1 is set; when servers are configured, a startup note says how many it skipped. With it set, each server is capped at 3 tools unless TYPED_CLI_MAX_MCP_TOOLS is set. TYPED_CLI_MAX_MCP_TOOLS=<n> caps the tools registered per server on any tier (0 means no cap, the hosted default), and a ! warning lists the tools a cap drops.

Managing entries. typed mcp list (also plain typed mcp) prints the servers the files above define, before the trust gate and without the Yaw MCP preload, one aligned row each with a short launch description and the source file (-v adds the full command line). Values print as written, with ${VAR} unexpanded, and each remote server's line ends with auth: oauth (logged in), auth: header or auth: none -- no credential, expanded or stored, is ever printed. typed mcp add writes a user-scope server to ~/.config/typed/mcp.json (an update included), or to <project>/.mcp.json with --project; typed mcp remove (alias rm) removes a name from the first of typed's user-scope files that holds it (~/.config/typed/mcp.json, then <config dir>/.mcp.json, then ~/.mcp.json), or from <project>/.mcp.json with --project. Either edit applies from the next session. The other two verbs, typed mcp login and typed mcp logout, manage a remote server's stored credentials rather than its entry and are covered just below.

Every one of these verbs also runs inside a session, as /mcp <verb>: the slash form tokenizes the rest of the line and hands it to the same subcommand unchanged, so /mcp login <name> runs the same browser flow and /mcp logout <name> deletes the same file. Quote any path or header value that contains a space.

add takes one of three shapes:

typed mcp add <name> -- <command> [args...]                         # stdio
typed mcp add <name> --transport <sse|http> <url> [--header "K: V"] # remote
typed mcp add --yaw <npm|lemonsqueezy|context7>                     # a Yaw server by name

--header is repeatable and needs a remote transport; --yaw takes the server name as its only argument, writes the npx -y <package> entry for it, and cannot be combined with --transport or a -- command. A combination whose arguments one shape would silently drop is refused by name rather than half-written and reported as a success, and an unknown --yaw name lists the ones that exist. --project works on all three.

Authorizing a remote server. An http or sse server can be reached two ways.

  • A static token, as a header: typed mcp add gh --transport http https://host/mcp --header "Authorization: Bearer ${TOKEN}". The header is stored with the ${TOKEN} template intact and expanded at load time from your environment.
  • OAuth, with typed mcp login <name>. It runs the MCP authorization flow (OAuth 2.1 authorization code with PKCE): it reads the server's 401 challenge for its protected-resource metadata (falling back to /.well-known/oauth-protected-resource), reads the authorization server's metadata, registers typed as a client when the server supports dynamic registration, opens your browser, and receives the redirect on a http://127.0.0.1:<random port>/callback listener with a state check. Pass --no-browser to only print the URL, and --client-id <id> when the server has no dynamic registration and its operator registered a client for you. typed mcp logout <name> deletes the stored tokens; the server itself stays configured.

Tokens live in ~/.config/typed/mcp-auth/<server>.json, one file per server, mode 0600 on macOS and Linux. The file is named after the server, never after a token. The CLI attaches the bearer on connect and refreshes it silently when it has expired, rewriting that file; a server that rejects a token it issued is retried once with a refreshed one.

Only typed mcp login ever opens a browser. A session (interactive, headless -p or ACP) that meets a 401 or 403 never starts a sign-in: it fails that one server with a line naming the server, the status, the challenge and the fix -- run typed mcp login <name>, or add the header form above. A configured Authorization header wins over a stored login, and typed mcp login refuses while one is configured.

A server that lives in .claude.json is managed with the reference CLI (claude mcp remove <name>). Servers an editor supplies over ACP live in no file and are not listed -- see below.

Editor integration (ACP)

typed acp speaks the Agent Client Protocol v1 on stdin/stdout: newline-delimited JSON-RPC on stdout, diagnostics on stderr (the agent log your editor shows). It is a flag on the main command, so the flags a client passes through -- --cwd, --permission-mode, --model, --tools -- work as they do everywhere else. --continue and --resume do not: the agent advertises loadSession: false, because replaying a transcript that ever auto-compacted would replay one summary paragraph.

MCP servers your editor supplies. The mcpServers array of session/new is honored: those servers are connected for that session alongside the ones typed loads from its own config, over all three ACP transports (the agent advertises mcpCapabilities http and sse; stdio is unconditional).

ACP entry Becomes
{name, command, args, env: [{name, value}]} a stdio server (env list becomes typed's env record)
{type: "http", name, url, headers: [{name, value}]} a Streamable HTTP server
{type: "sse", name, url, headers: [{name, value}]} a legacy SSE server

Every field above is required by the ACP schema, args, env and headers included -- send [] rather than omitting them. The rest follows the configured-server path exactly: the same connect guard (MCP_TIMEOUT), the same per-server tool cap, the same mcp__<server>__<tool> names and permission rules, the same tools/list_changed refresh, and teardown when the session ends.

  • Trust. These come from the editor you configured, so they are treated as user scope: they are not subject to the trust gate a project's .mcp.json goes through. That gate exists because a project file ships with a repository and can start any command; your editor's settings are your own.
  • Strict validation. An entry that is not one of the three shapes above fails session/new with an error naming the entry and listing the accepted shapes, and no session is created. Nothing is skipped silently: a dropped server would be a server your editor believes is live and typed never started.
  • Name collisions. When your editor supplies a server whose name a configured server already uses, the editor's entry wins for that session and the configured one is not started. The replacement is reported on the diagnostic channel (acp: MCP server "<name>" from the editor replaces the configured server of that name for this session), never silently.
  • No ${VAR} expansion. Values are passed exactly as sent -- unlike a config file, where ${VAR} and ${VAR:-default} expand. Your editor has already resolved its own settings, and expanding again here would substitute from typed's environment rather than the editor's.
  • Credentials. A remote entry uses the headers your editor sent. A stored typed mcp login is keyed by the server name, so an editor entry that reuses a configured name only picks it up when it points at the same server: the bearer is attached only if the resource it was issued for covers that entry's url, and otherwise the 401 says a login exists but was not sent. See Authorizing a remote server above.
  • Session-only. Nothing is written to any file: typed mcp list does not show them, and they are gone when the process exits. MCP servers are connected once per process, so a second session/new naming a different set is refused rather than quietly running the first set; restart the agent to change them.
  • Free local tier. The free local tier skips these for the same reason it skips configured servers, with the same note, unless TYPED_CLI_LOCAL_MCP=1 is set.

Which tier a launch runs

With no typed.cloud key the CLI runs typed-local (free, on your machine). With a key on a paid plan it runs typed (hosted economy, billed to your plan). Name typed++ when you want the strongest tier.

The launch resolves, highest first: --model > --effort > ANTHROPIC_MODEL

the Yaw Mode tier (only inside a Yaw pane that exports the post-rename schema) > the nearest .typed pin walking up from the directory > the global pin (~/.config/typed/model) > the account default. "Paid" is what the API answers at GET /v1/me/plan, cached in ~/.config/typed/plan (mode 0600; override the path with TYPED_PLAN_FILE) and written only by the CLI: typed login, typed doctor (or typed doctor --record-plan, one line), typed ultracode on, and the first launch with a key that has no record yet (a 3-second probe; if it cannot answer, the launch runs typed-local and says why). typed status, typed use and /status print which layer decided and, for the account default, the reason.

Since 2026-09-23 the bare id typed names the hosted economy tier; before that it named the free tier, now typed-local. A pin file holding a bare typed without the # tier-schema 2 marker line is read as typed-local, rewritten in place with the marker, and announced once; typed use writes the marker into every pin. A bare typed exported from your own shell prints a one-shot note (once per machine) and runs the economy tier.

Switching tiers in a session

/typed++, /typed+, /typed, /typed-local and /model <id> switch tiers inside the session, in both directions, with no restart. Moving between the hosted tiers changes only the id. Crossing between a hosted tier and typed-local moves the wire: the CLI probes the model server (or re-reads your key and verifies it with one free request), re-sizes the prompt for the new window, rebuilds the connection, and only then commits -- every failure prints its cause and remedy and leaves the session exactly as it was. A crossing is refused while a turn is running or a background task is in flight, and a move onto typed-local is refused while the history carries an image (that tier is text-only). /login runs the device flow inside the session; nothing switches on its own afterwards. /status prints a transport: line for every session. Documented limits: MCP servers connect only at launch (a switch says which stay connected or stay off), the SessionStart hook does not re-run, and the turn limit follows the tier unless you named a number with --max-turns: switching to typed-local puts its 30-turn default on, switching away takes it off.

Env vars

Variable Effect
ANTHROPIC_BASE_URL backend endpoint (default https://api.typed.cloud)
ANTHROPIC_AUTH_TOKEN / ANTHROPIC_API_KEY / TYPED_API_KEY API key; falls back to ~/.config/typed/api-key
ANTHROPIC_MODEL model / tier: typed++, typed or typed-local. Unset = the account default (typed with a key on a paid plan, else typed-local); inside a Yaw Mode pane the tier chosen in Yaw's Settings wins over pins. A bare typed exported before 2026-09-23 meant the free tier; the CLI says so once (see "Which tier a launch runs")
CLAUDE_CONFIG_DIR overrides ~/.claude for config discovery
MCP_TIMEOUT milliseconds one MCP server gets to start -- spawn, initialize and tools/list together. Unset, a server launched through npx gets 90000, because resolving the package spec alone can take that long, and every other server 30000; a value here replaces both. It is a startup budget only, the same scope the reference CLI gives the name: a mid-session tool call keeps its own floor, so lowering this to fail a hung server fast never shortens a slow call. A value that is not positive milliseconds is warned about by name and the default used
TYPED_VERIFY_CYCLE_CAP verification-cycle cap: how many times the loop allows the model to repeat an identical check (same command, same file read) without making a change before steering it onward. Default 2; 0 disables
TYPED_NO_UPDATE_CHECK=1 disable the background version check entirely; also stops the background update of an outdated yaw-mcp bin (see MCP servers)
TYPED_NO_YAW_AUTO_UPDATE=1 stop only the background update of an outdated yaw-mcp bin
TYPED_CLI_NO_YAW_MCP=1 do not preload Yaw MCP (see MCP servers)
TYPED_CLI_NO_YAW_MCP_OAM=1 start the Yaw MCP broker on the JS host its launch names, not on the oam the CLI runs on (see MCP servers)
YAW_MCP_TOOL_EXPOSURE Yaw MCP's own variable, not typed's: it tells the broker how many of its mcp_connect_* meta-tools to list. A typed session gets the broker's lite set of three by default, chosen by the broker from the client name typed sends; gateway lists all eleven; full lists the eleven plus a placeholder for every cached server's tools (the pre-gateway surface, far larger). The broker trims and lower-cases the value, which it takes from the environment typed was launched with (exporting it mid-session or from a tool command changes nothing until the next session); a value it does not recognise keeps the default (lite for typed) and the broker writes one warning to its stderr. The CLI never sets, pins or overrides it -- put it in the environment (every stdio server inherits it) or in one entry's own env map. See MCP servers
TYPED_CLI_LOCAL_MCP=1 free local tier: connect the configured MCP servers, which that tier skips by default
TYPED_CLI_LOCAL_PROGRESS=1 free local tier: offer the Progress (task list) tool, which that tier withholds by default because every list update costs a whole local turn
TYPED_CLI_MAX_MCP_TOOLS tools registered per MCP server; 0 = no cap (the hosted default; the free local tier defaults to 3 once TYPED_CLI_LOCAL_MCP=1 is set)
TYPED_CLI_STATUS_FD file descriptor number; when set, the CLI emits one JSON status event per line on that fd (session, turn, state, tool, usage, error, done) for host integrations such as terminal UIs
TYPED_CLI_SESSIONS_DIR session transcript root (default ~/.config/typed/sessions); transcripts live under a per-project subdirectory, with each session's saved tool outputs beside its transcript
TYPED_CLI_RESUME_LIST rows the bare --resume picker lists before its +N older footer (default 20; 0 lists every session). Older sessions stay reachable with --resume <id>
TYPED_CLI_RESUME_EXCERPT characters per line of the excerpt shown under the resume note (the first prompt, the last prompt, the last reply; default 240). 0 hides the excerpt; an unusable value falls back to the default
TYPED_CLI_PASTE_DIR where pasted clipboard images are kept until the session ends (default: beside the session transcript, or the system temp directory when that path contains a space). Must be an absolute path with no spaces or quotes, because the attachment travels through the prompt as a file path; an unusable value is refused by name rather than ignored
TYPED_CLI_OUTPUT_STORE_DISK 1 (default): save full tool outputs beside the session transcript so ReadOutput and /show ids survive --resume; 0: keep them in memory only (outputs saved earlier are still removed on schedule). Any other value refuses to start
TYPED_CLI_MEMORY_DIR persistent per-project memory root (default ~/.config/typed/memory; in a Yaw Mode pane the default is the memory dir Claude Code uses for the project, under $YAW_PROJECTS_DIR, and setting this variable overrides that too)
TYPED_CLI_TRUST_HOOKS=1 headless only: run hooks without an interactive trust grant
TYPED_CLI_TRUST_PROJECT=1 headless only: treat the project as trusted for project-scope MCP servers and the model's skill catalog
TYPED_CLI_SKILL_ALLOWED_TOOLS=1 opt-in: /name on a skill in a trusted project arms its allowed-tools frontmatter as session allow rules when the run starts (a queued invocation arms nothing until it runs; in an untrusted project a line says the rules were not armed) the permission engine consults last (after your deny, allow and ask rules; ignored in readonly and plan). Each armed rule is committed to the scrollback as + allow for this session (skill <name>): <rule>; an entry the engine cannot evaluate is refused with a ! line, never armed dead. Nothing is written to disk
TYPED_CLI_COMPACT_THRESHOLD auto-compaction threshold in estimated history tokens (default on typed++ and typed: 450000, a fixed point, though both serve a 1,000,000-token context window; default on the free local tier: 60% of the context window left after the system prompt and tools). On typed++ and typed it is checked when you send a prompt and again at every turn of a run: past it, older tool results are replaced by one-line stubs the model can re-fetch, and the conversation is summarized only if that is not enough. The prompt being answered and the tool results the model has not read yet are never counted, so it does not limit one large prompt or file, pasted or read by a tool: a single request may use the whole window. On the free local tier it is checked when you send a prompt, and within one run history is trimmed only as it nears the window. Every turn re-sends the retained history, so on the paid tiers a lower value (for example 300000, the default before 2026-10) keeps less history verbatim and draws less monthly usage per turn, and a higher one keeps more. /status shows the value in force
TYPED_CLI_MAX_TOKENS output tokens a reply may request, 1024-64000 (default 16384); every turn is still clamped to the room the context window has left. Lower it on a small local window to stop the per-run 'output limited' line, or on typed++ or typed to leave more of the context window for input. An invalid value refuses to start
TYPED_CLI_LOCAL_PROBE_TIMEOUT_MS free local tier: milliseconds typed waits for your local model server to answer /props at launch and on a switch to typed-local, 500-60000 (default 500). A server that misses it is treated as down, so the session starts without tools and on an assumed context window. Raise it for a server on another machine or one slow to answer. It never lowers the default. An invalid value refuses to start
TYPED_CLI_MCP_LIST_CHANGED on (default): when an MCP server sends notifications/tools/list_changed, re-list its tools and send the new set from the next turn; off: keep every server's startup tools for the session. Any other value warns and uses on
TYPED_CLI_SUGGEST prompt-suggestion mode for one run (auto / tab / off), outranking the mode /suggest saved in ~/.config/typed/suggest. An unknown value is reported with the valid names and that layer is skipped, never coerced to a neighbour
TYPED_CLI_NO_SUGGEST=1 the older kill switch: an alias for off that outranks TYPED_CLI_SUGGEST and the saved mode. Exactly 1; any other value leaves the setting alone
TYPED_CLI_SUGGEST_PACE how readily the unasked ghost auto paints is offered: eager, normal (the default) or calm, trading prefix length, quiet window and relevance bar against each other. It changes nothing in tab, which offers nothing unasked. An unknown value is named in a startup note with the valid ones and normal is used, never a failed launch over a cosmetic knob
TYPED_CLI_HOOK_DEDUPE 1 (default, every tier): a headed section of UserPromptSubmit hook context that an earlier user turn in the last 65,536 tokens of the conversation (an image counts as 1,600 tokens) already carries whole is sent as a one-line pointer instead of a second copy. That distance is a per-tier bound: the hosted tiers and the free local tier both start at 65,536 tokens, the window typed local prescribes, and nothing has measured how well a pointer is followed further back than that on a larger hosted window. A copy counts only where a section ends: at the end of that turn, before the next ## or ### heading, or before a one-paragraph prompt, so a section whose file has lost lines at its end is sent whole; 0: send every section every time. Any other value refuses to start
TYPED_CODEBASE_POLL_MS typed codebase push poll interval while the index builds (default 3000)
TYPED_CODEBASE_POLL_TIMEOUT_MS how long typed codebase push waits before handing back (default 900000); the indexer keeps going either way
TYPED_INSTALL_URL installer URL used by typed update
TYPED_CLIENT one-shot launcher override (typed-cli or the reference client) without touching the typed cli state

Set by typed for the commands it runs

typed sets these for the commands it starts, so a script or the model can tell which harness and tier it is running under. typed's values replace any inherited ones.

Variable Value
TYPED_CLI 1 in every Bash / PowerShell tool call, ! command, hook and custom statusLine command
TYPED_CLI_TIER Bash / PowerShell tool calls and ! commands only: the session's tier, always canonical: typed++, typed (hosted economy) or typed-local (free, on your machine). A legacy id such as typed++xhigh reads as typed++, and typed-- as typed. It follows /model. It is unset when the backend is not typed (Anthropic direct, a proxy, or ANTHROPIC_BASE_URL pointed straight at a model server)

Resilience details worth knowing

  • Stall watchdog: every streamed response runs under two timers -- a byte-idle timer and a content-progress timer (keepalive pings do not count as progress). A content-silent stall is aborted and retried once before surfacing an error.
  • Quota: when a request exceeds your monthly quota, the error rendered in the CLI includes the remediation path (one-click top-up, or enable auto-top-up in the dashboard) instead of a dead error string.
  • Verification cap: repeated identical verification commands within one edit cycle are short-circuited with guidance rather than executed again -- this is the measured fix for verify-loop runaway and is on by default (see TYPED_VERIFY_CYCLE_CAP).
  • Comments while a run is in flight: a comment typed while the model is streaming redirects it immediately -- the partial answer is kept, the comment is appended, and the same turn is re-sent (up to 10 redirects per model turn; after that the remaining comments start a fresh prompt). A comment typed during tool calls interrupts read-only and idempotent calls and subagents, lets a mutating call finish, skips the rest of that batch with a "not run" result, and lands right after the tool results -- the footer chip shows (after <tool>) while it waits. The chip counts only comments the model has not received yet. Every delivered comment gets a /rewind checkpoint. Ctrl+C parks the queue; the next submit merges the backlog in the order you typed it.
  • Hook-blocked prompts stay visible: a UserPromptSubmit hook that blocks a comment does NOT drop it. A ! prompt blocked by hook: <reason> line commits under the blocked item, the footer shows it as [blocked: <reason>] with red text, and the drain continues to the next pending item. Blocked items persist for the rest of the session -- remove them with /queue-cancel <n> or let them die with /exit. The queue cap is 100; the 101st submit is dropped with a ! queue full (N) -- submission dropped line. Use /queue to see the visible queue at any time.

Support

Run typed doctor first; it diagnoses the common setup traps with a fix line per failure. If you are still stuck, email support@typed.cloud with your account email, the doctor output, and the approximate UTC timestamp of the failed request.