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+Cinterrupts the current run (twice within 1.5s exits);Ctrl+Dexits;Tabcompletes/commands;Up/Downwalk input history, with a dimHistory N/Mafter 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-modeflag outranks everything. - A committed
.claude/settings.jsonis the repository's file, not yours: it can make sessions stricter (plan,readonly) but cannot start them inbypassorauto. Those are honored from.claude/settings.local.jsonand from the user-levelsettings.jsononly; a refused value is named at launch. --resume/--continuebrings a session back in the mode it was last in, exceptbypass, which is never restored from a transcript (pass--permission-mode bypass).- A headless run (
typed -p, piped stdin) never starts wider thandefaultwithout 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_FILEmoves it). It is never read fromANTHROPIC_AUTH_TOKENorANTHROPIC_API_KEY, and a typedtk_key is refused there. Your typed key is still needed, to confirm the add-on. - The model is the part after the first
:oftyped-openrouter:<model>, else the standing one in~/.config/typed/byok-model(TYPED_BYOK_MODEL_FILE). A baretyped-openrouterwith 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-localmove off it, and--resumerestores 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 doctorchecks 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:
.envand every.env.*,.npmrc/.yarnrc(registry auth),.netrc,.pgpass,credentials,credentials.json,service-account.json,secrets.*,id_rsaand 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/.prettierrcfamilies,.typedignore)..env.exampleis 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/.batserver,cmd.exemay still expand one itself when it starts the shim. cwdin 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.
~/.mcp.json<config dir>/.mcp.json- top-level
mcpServersin~/.claude.json, then in<config dir>/.claude.json(the reference CLI's user scope) ~/.config/typed/mcp.json, typed's own user-scope file (strict JSON)<project>/.mcp.json(project scope)projects["<project>"].mcpServersin~/.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 ahttp://127.0.0.1:<random port>/callbacklistener with a state check. Pass--no-browserto 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.jsongoes 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/newwith 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 loginis 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 listdoes not show them, and they are gone when the process exits. MCP servers are connected once per process, so a secondsession/newnaming 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=1is 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
.typedpin walking up from the directory > the global pin (~/.config/typed/model) > the account default. "Paid" is what the API answers atGET /v1/me/plan, cached in~/.config/typed/plan(mode 0600; override the path withTYPED_PLAN_FILE) and written only by the CLI:typed login,typed doctor(ortyped 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 runstyped-localand says why).typed status,typed useand/statusprint 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/rewindcheckpoint.Ctrl+Cparks the queue; the next submit merges the backlog in the order you typed it. - Hook-blocked prompts stay visible: a
UserPromptSubmithook 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 droppedline. Use/queueto 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.