# Introduction
URL: https://www.termio.sh/docs
Markdown: https://www.termio.sh/docs/index.md
Termio is a native Mac app that gives your AI coding agents a home. Instead of
juggling agents across scattered terminal tabs, you run them side by side —
each in a real terminal — and switch between them instantly. Everything runs on
hardware you own: this Mac, or a box you can `ssh` to.
## What Termio gives you
- **A real terminal per agent.** Claude Code, Codex, Amp, Droid, Cursor, Copilot,
Antigravity, Crush, Cline and the rest each run in a genuine PTY — not a
re-rendered chat log. What you’d see in iTerm is exactly what you see here.
- **A sidebar of sessions.** Every agent and shell is one click away. Statuses
update live, so you can see which agent is working, which is idle, and which
needs your attention.
- **Split panes.** Put an agent, a dev server, and a shell in the same window,
arranged the way you think.
- **Any machine you own.** A workspace can live on a VPS or the Mac mini across
the room. Termio copies one binary there over SSH and sets up the agents; the
sessions run on that box and keep running whether or not you’re attached.
- **A built-in inspector.** Browse the working directory, edit files with syntax
highlighting, read the git diff, and search across the project — all without
leaving the window.
- **An orchestration API.** The `termio sessions` CLI lets one agent — or your
scripts — spawn siblings, answer their prompts, and supervise the fleet over
a local socket, with pinned JSON shapes and honest exit codes.
- **Your phone as a remote.** Pair the iPhone app once and check in on a running
session from anywhere, over your own network or a tunnel you control.
## Local-first, always
Termio has no account and no sign-in. Your terminals, code and agent
conversations never pass through servers of ours — there are none in the
product’s data path. Reaching another machine goes through the system `ssh` and
your own `~/.ssh/config`; Termio provisions nothing and relays nothing. The
agents you run talk to their own providers under your own accounts, exactly as
they would from a plain terminal.
Termio is **free to use** — no account, no license keys, no payment backend.
## Pick your path
Most people arrive here for one of three reasons. Start where you actually are:
Install, open a project, start Claude Code or Codex in a real terminal, and
split the window. Ten minutes, no concepts required.
The part that pays off: live status per session, worktrees to keep two
agents out of each other's files, and the sidebar as a fleet dashboard.
A VPS, a Mac mini, a devbox. Termio copies one binary there over your own
SSH config and the sessions run on that machine, attached or not.
## Or let an agent read the docs for you
You already have a coding agent. These docs are published in a form it can read
directly, so you can hand it the setup instead of doing it yourself:
```text
Read https://termio.sh/llms.txt and then help me set up Termio for my project.
```
Every page also answers with plain Markdown at its own URL, and
[llms-full.txt](https://termio.sh/llms-full.txt) is the whole set in one file —
so an agent can answer from the docs rather than improvising.
## Start here
Download the Mac app and get it updating itself. No account required.
Open a project, launch an agent in a real terminal, and split the window.
Keep a fleet side by side and read each one’s live status at a glance.
Put a workspace on another box and set up the session host there.
Close, quit, update, reboot — exactly what happens to a running agent.
Let agents spawn, drive, and supervise each other with `termio sessions`.
Pair your phone and check in on a running agent from anywhere.
---
# Installation
URL: https://www.termio.sh/docs/installation
Markdown: https://www.termio.sh/docs/installation.md
Termio runs on Apple silicon and Intel Macs from the same download. Installation is a normal drag-to-Applications
— there’s no account to create and no license key to enter.
Termio needs macOS 14 or later, on Apple silicon or Intel. It hosts the agent
CLIs you already have installed — it doesn’t bundle them.
## What you need
| | |
| --- | --- |
| **macOS** | 14 (Sonoma) or later, Apple silicon or Intel — one universal download |
| **An account** | None. There is no sign-in, no license key, and no payment backend |
| **Agent CLIs** | Whichever you already use — `claude`, `codex`, and the rest. Termio hosts them, it doesn’t ship them |
| **For another machine** | Anything you can already `ssh` to. Termio reads your `~/.ssh/config` and installs nothing as root |
## Download and install
1. Download the latest build from
[downloads.termio.sh](https://downloads.termio.sh/termio.dmg). The link
always serves the newest notarized release.
2. Open the `.dmg` and drag **Termio** into your **Applications** folder.
3. Launch it from Applications. Because the app is notarized by Apple, it opens
without a Gatekeeper warning.
## Check it worked
Launch Termio and you should land on the welcome screen with **Open Project** as
the primary action. That’s the whole install — there is no setup wizard, no
account step, and nothing to configure before the first session.
On first launch Termio asks once whether to let your agents coordinate — that’s
**Session control**, which installs a small skill teaching them the
`termio sessions` commands. **Live agent status**, the hooks behind the sidebar’s
status dots, is a separate switch on the same tab; the sidebar offers it when it
sees an agent running. Both are optional and reversible; see [Status hooks &
session control](/docs/session-control).
If you already use [Ghostty](https://ghostty.org), there’s nothing to pick: Termio
reads your `~/.config/ghostty/config` on first launch and starts with the font and
theme you chose there.
## Install with Homebrew
Prefer the command line? Termio is a [Homebrew](https://brew.sh) cask:
```sh
brew install --cask termio-sh/tap/termio
```
This installs the same notarized build. Termio still updates itself through
Sparkle, so `brew upgrade` leaves it alone unless you pass `--greedy`.
## Staying up to date
Termio updates itself through Sparkle. It periodically checks the download
server for a newer version and offers to install it in place. The check carries
no account or device identifier — the server sees only an IP address and the
version being asked about.
You can trigger a check any time from **Termio ▸ Check for Updates**.
## Bring your own agents
Termio doesn’t bundle the coding agents — it hosts the ones you already have on
your Mac. Install whichever CLIs you use (for example `claude`, `codex`, or
`amp`) and make sure they’re on your `PATH`. Termio launches each one in a
login shell, so anything your shell profile sets up will be available.
Once an agent is installed, it shows up as a launchable option when you start a
session. The same is true of the agents on another machine — Termio looks for
them there and installs its hooks on that box. See [Devices](/docs/devices).
## The `termio` command (optional)
Termio ships a small command-line tool. Open **Settings ▸ Server**
and turn on **Command-line tool** to symlink `termio` onto your `PATH`. From then on
`termio` in any directory opens it as a project, and `termio sessions …` lets your
agents coordinate with each other. It’s entirely optional — see
[Session control & the CLI](/docs/session-control).
## Where Termio keeps things
Nothing is hidden, and nothing lives on a server of ours:
| Path | What’s in it |
| --- | --- |
| `~/.termio/` | Your config: `keybindings.json`, custom agent manifests under `config/agents/`, and the `chats/` scratch directory |
| `~/Library/Application Support/termio/` | App state and your `Themes/` folder |
| `~/.local/bin/` | The session host on machines you set up over SSH — `termiod`, plus `termio` on Linux. On this Mac it ships inside the app bundle and is never installed here |
| Each agent’s own config | Termio’s status hooks and its `termio` skill, written per agent |
## Uninstalling
Quit Termio and drag it to the Trash. To remove what it wrote outside the bundle,
turn off **Live agent status** and **Session control** under **Settings ▸ Agents**
first — both toggles remove the hooks and the skill they installed on **this
Mac**, leaving anything you wrote yourself alone. Then delete `~/.termio` and
`~/Library/Application Support/termio` if you want the state gone too.
Every other machine keeps what was installed on it until you clean it up there.
Stop the server first, because removing the service alone leaves a daemon that
something else started still running:
```bash
termiod stop
termiod service uninstall
rm -f ~/.local/bin/termiod ~/.local/bin/termio
```
## Next steps
Open a project and launch your first agent in a real terminal.
See which agents Termio recognizes out of the box.
---
# Your first session
URL: https://www.termio.sh/docs/first-session
Markdown: https://www.termio.sh/docs/first-session.md
A *session* in Termio is a real terminal running one thing — an agent, a dev
server, or a plain shell. Here’s the path from a fresh launch to a working setup.
## Open a project
From the welcome screen, choose **Open Project** and pick a folder. Termio adds
it to the sidebar and remembers it under **Recent** for next time. A project is
just a working directory — the agents you launch inside it start there.
The project is filed under the workspace you’re in, and opens on the machine
that workspace is on. With one machine there is nothing to choose; see
[Workspaces](/docs/workspaces) and [Devices](/docs/devices) once there are two.
## Launch an agent
With a project selected, start a new session and pick an agent — Claude Code,
Codex, Antigravity, or any other CLI you have installed. Termio opens it in a real
terminal at the project root. You type into it exactly as you would in any
terminal; the agent’s TUI renders natively.
The sidebar shows the session’s live status — **working**, **idle**, **done**, or
**needs you**. [Concepts](/docs/concepts#status) explains all four; the one to
watch for is **needs you**, which means the agent is blocked on a prompt or a
permission and can’t continue without you.
Kick off a long run in one session, switch to another, and let the sidebar tell
you the moment the first agent needs you back.
## Split the window
Most real work wants more than one pane. Split the current pane to the right with
`⌘D` or downward with `⇧⌘D`, then drop another session into the new pane — an
agent in one, your dev server in another, a shell in a third. Panes tile as a
tree, so you can nest splits into any layout.
To rearrange a layout, hover a pane’s header and drag the grab handle that
appears. A highlight previews where the pane will land: drop on another pane’s
left, right, top, or bottom half to place it on that side, or on the center to
swap the two panes.
Move focus between panes with `⌥⌘` + an arrow key, and temporarily zoom the
focused pane to fill the window with `⇧⌘↩`. `⌘W` **closes** the focused session,
the way it closes a browser tab, and its pane collapses with it. Every shortcut is
rebindable — see [Keyboard shortcuts](/docs/keyboard).
## A note on session lifetime
Sessions are live processes, not saved transcripts — and the process doesn’t
belong to the window. Every session runs inside `termiod`, the session host on
the machine that owns it, and Termio attaches to it. Close the window and the
agent keeps working; reopening from the Dock finds it where you left it.
Long runs survive a relaunch, too. The daemon keeps the process; the app
reattaches to the session it left rather than starting a new one — same process,
same output.
Ending a session is a deliberate act: `⌘W` closes the focused one. Everything
else — closing the window, quitting Termio, losing an SSH connection,
relaunching the app — detaches rather than kills. [What
survives](/docs/persistence) lays out every case, including the ones that do
cost you a process.
## Next steps
Read live status across a fleet and switch without losing your place.
Give each line of parallel agent work its own isolated checkout.
Browse files, read the diff, and search the project beside the terminal.
Check in on a running session from your phone.
---
# Concepts
URL: https://www.termio.sh/docs/concepts
Markdown: https://www.termio.sh/docs/concepts.md
Termio is a terminal for running several coding agents at once. Everything in the
app is built from six nouns and one status model. Learn these and the rest of the
docs read quickly.
## Device
A device is a machine you can reach: this Mac, the Mac mini on the same desk, a VPS.
Termio reads `~/.ssh/config` to find them and shells out to the system `ssh` to
get there — it never provisions a machine and never routes your traffic.
This Mac is a machine like any other, so the level stays invisible until you add
a second one. See [Devices](/docs/devices).
## Workspace
A workspace is a named scope in the sidebar, holding projects and loose
terminals. Switching to one swaps what the sidebar lists, and each workspace
remembers the session you left it on.
Every workspace belongs to exactly one machine, which is how the machine
question gets answered once, at the top, instead of again for every project.
`⌘1` through `⌘9` switch between them. See [Workspaces](/docs/workspaces).
## Project
A project is a folder on a machine — usually a repository. It’s the unit the
sidebar groups by, and the working directory every session inside it starts in.
Opening a project doesn’t copy or index anything. Termio remembers the path and
reads git for the rest.
## Session
A session is one real terminal running one thing: an agent, a dev server, or a
plain shell. Each session owns a PTY — the same kind of terminal `ssh` or
Terminal.app gives a process — and Termio renders it with
[libghostty](https://ghostty.org), Ghostty’s terminal core.
Three properties matter:
- **A session keeps running when you look away.** Switch to another session, hide
the window, or use another app; the agent keeps working. Its output is buffered
and repainted when you come back.
- **A session lives on its machine, not in the app.** Every session — including
the ones on this Mac — runs inside `termiod`, the session host, and the app is
a viewer that attaches to it. Closing the window leaves the agent working, and
a dropped SSH connection detaches you rather than killing anything. Only
**Close Session** ends one on purpose. [What survives](/docs/persistence) is
the full table.
- **A session is addressable.** Every session has a stable URL,
`termio://session/`, which is what the menu-bar tray, a notification, and
the `termio` CLI all use to bring one to the front.
## Pane
A pane is a session’s slot on screen. One session normally fills the window, and
splitting *groups* another session beside it — an agent on the left, a dev server
and a shell on the right.
Panes are a view concern, not a second kind of session: the thing in a pane is a
full session with its own sidebar row and its own status. That’s why the verbs are
**Group with** and **Ungroup** rather than “split” and “close pane” — grouping
changes how sessions are arranged, not what they are.
See [Keyboard shortcuts](/docs/keyboard#panes) for the bindings.
## Worktree
A git worktree is a second checkout of the same repository on its own branch. When
two agents work one repo at once, worktrees are what keep them out of each other’s
files.
Termio reads them from git (`git worktree list`) rather than tracking its own copy,
and shows each one as a nested folder under the project. Create one in the app or
with `git worktree add` on the command line — either way both agree, because git
is the source of truth. See [Git worktrees](/docs/worktrees).
A repository checked out on a machine is a *project* here, and a second
checkout of it is a *worktree*. Workspace is reserved for the named scope in
the sidebar, even though some other tools use it for the folder.
## Status
Every session reports what it’s doing right now. There are four states, and the
distinction between the last two is the point of the whole model:
| Status | Meaning | How it looks |
| --- | --- | --- |
| `idle` | Nothing pending, or you’re already looking at it. | No mark |
| `working` | The agent is processing a turn. | The comet replaces the session’s icon |
| `done` | The agent finished while you were elsewhere. | A green dot — *ready for you* |
| `needs-you` | The agent is blocked on you: a permission prompt, a question. | An orange ring — *waiting on you* |
A finished turn is `done`, never `needs-you`. Conflating the two is what makes a
fleet of agents feel like a pile of alarms: if everything demands attention,
nothing does. Termio keeps “ready” calm and reserves the loud state for an agent
that genuinely cannot continue without you.
These statuses roll up: a project’s row summarizes its sessions, and the menu-bar
tray summarizes everything, so you can watch a fleet from another app.
### Where the signal comes from
Termio doesn’t guess from pixels where it doesn’t have to. It reads, in order of
authority:
1. **The agent’s own hooks.** On first run Termio writes its status hooks into the
config of each agent that supports them, so the agent reports its own turns.
Nothing for you to configure. See [Session control](/docs/session-control).
2. **In-band terminal signals.** Progress and title sequences an agent already
emits (`OSC 9;4`, `OSC 777`) are read straight off the stream — that’s how Grok
reports busy and idle without hooks.
3. **The screen, as a last resort.** For an agent with neither, Termio watches the
pane for the shape of a prompt waiting on input, and promotes a status only
after the reading holds.
Open a project, start an agent, split the window.
The sidebar as a control surface for a fleet.
The scope that decides which machine a project opens on.
Put a workspace on another box and set up the session host there.
---
# The sidebar
URL: https://www.termio.sh/docs/sidebar
Markdown: https://www.termio.sh/docs/sidebar.md
Everything you run in Termio lives in the sidebar on the left. It’s one scrollable
list, grouped into sections, where every row is a live session reporting what it’s
doing right now. Click a row to bring it forward; the rest keep running in the
background.
## One workspace at a time
The sidebar is scoped to a workspace: the switcher in its toolbar names the one
you’re in, and the list below holds only what’s filed under it. Switch with `⌘1`
through `⌘9`, from the switcher, or from `File ▸ Workspace`. Each workspace comes
back to the session you left it on, with its panes and its inspector tab.
A workspace belongs to one machine, so switching workspaces is also how you move
between machines — the switcher names the machine beside any workspace whose own
name doesn’t already say it. See [Workspaces](/docs/workspaces) and
[Devices](/docs/devices).
## Sections
The sidebar groups your sessions by what they are:
- **Terminals** — standalone shells and agents you start with `⌘T`, not tied to a
project. Your scratchpad.
- **Chats** — throwaway agent sessions for quick questions (see below).
- **Projects** — the folders you’ve opened. Each project expands to the sessions
running inside it, and any git worktrees nest one level deeper. The tree is
three levels: project → worktree → session. See [Git
worktrees](/docs/worktrees).
Empty sections stay hidden, so the list only shows what you’re actually using.
Sessions the machine is hosting that Termio didn’t start — one you began over
plain `ssh`, or launched with `termiod` on the box — appear in the list
automatically as ordinary sessions, filed into the project whose folder they run
in, or under **Terminals** otherwise. There is no separate section to learn: a
session is a session, wherever it came from. If a machine can’t be reached, the
device switcher says so in `ssh`’s own words rather than going quiet.
## Status
Every session row carries a status dot that updates live. There are four states:
| Status | Meaning |
| --- | --- |
| **working** | The agent is actively producing output. |
| **idle** | Nothing pending, or you’re already looking at it. |
| **done** | Finished a task while you were on another session — a calm “ready” cue, so you notice a completed run without it shouting for attention. |
| **needs you** | Blocked on a question or a permission prompt. Go back. |
The distinction between **needs you** and **done** is the point: *needs you* means
an agent is stuck and can’t proceed without you; *done* means it finished on its
own and is just letting you know. [Concepts](/docs/concepts#status) covers the
model and where each signal comes from.
Start a long run, switch away, and let the status dots do the watching. When a
row flips to **needs you** you know to jump back; when it flips to **done** the
work landed while you were elsewhere.
## Chats
The **Chats** section holds scratch agent sessions — for when you want to ask an
agent something without opening a project. Press `⌘N` for a **New Chat** and it
starts in a shared scratch directory (`~/.termio/chats`), never in whatever folder
you happen to be in. Close them individually, or clear the lot from the section’s
menu. Treat them as disposable: quick questions, throwaway experiments, nothing
you need to keep.
## Switching without losing your place
Because each session is a real process running on its machine, an agent doesn’t
pause when it’s off-screen — or when you switch workspaces, or close the window.
Click between rows freely, or — once you have more than a handful — jump straight
to any session by name with `⇧⌘O`. See [Navigation](/docs/navigation).
Rows move by drag. Dropping one in the middle of another groups the two sessions
into one split; dropping on a row’s top or bottom edge reorders it into that gap.
A line means *between these*, a lifted row means *into this one*. Dragging a row
onto a pane groups it in there instead.
---
# Workspaces
URL: https://www.termio.sh/docs/workspaces
Markdown: https://www.termio.sh/docs/workspaces.md
A workspace is a named scope in the sidebar. It holds projects and loose
terminals, and switching to it swaps what the sidebar lists — the projects filed
under it, its worktrees, its sessions, and nothing else.
It’s the level between a [device](/docs/devices) and a project: every workspace
belongs to one machine, and everything filed under the workspace inherits it.
## Switching
Switch with `⌘1` through `⌘9`, or from the switcher in the sidebar’s toolbar.
`File ▸ Workspace` carries the same rows.
The digits are positional: they follow the order of the list, so what `⌘2`
reaches changes when the list does. That’s also why they aren’t rebindable — a
binding whose target moves isn’t one you can keep. The switcher draws each digit
beside its name so you read the number rather than memorize it.
A workspace remembers the session you left it on, with its panes, its split
group, and its inspector tab. Coming back to a scope puts your own work back on
screen rather than whatever sorts first.
Where a workspace’s name doesn’t already say which machine it’s on, the switcher
names the machine beside it — so switching workspaces is also how you move
between machines.
## Managing them
**Settings ▸ Workspaces** lists every workspace with the machine it’s on and how
many sessions it holds. Renaming and removing live there, beside the workspace
they act on, rather than in the switcher — a menu that shows the list one
checked row at a time can only ever offer those verbs for the workspace you’re
already in.
Removing a workspace closes its sessions. The folders on disk are left alone.
New workspaces come from `File ▸ Workspace ▸ New Workspace…` or the same row in
the switcher. With more than one machine known, that row asks which machine the
new workspace is on.
A workspace can’t span two machines. A checkout already lives on exactly one
box, so a workspace covering both was never more than a visual grouping over
leaves that disagreed. Work that touches a repo here and a repo on the VPS is
two workspaces.
Add a machine, set up the session host, and see what reaches across.
What a workspace scopes: projects, worktrees, and every session’s status.
---
# Command palette & Open Quickly
URL: https://www.termio.sh/docs/navigation
Markdown: https://www.termio.sh/docs/navigation.md
Termio has two overlay panels that keep your hands on the keyboard. One runs
*actions*, the other jumps to *things*. Both are fuzzy-matched, so you type a few
characters and pick from the narrowed list.
## Command Palette — `⇧⌘P`
Press `⇧⌘P` to open the Command Palette: a searchable list of every action in the
app, each showing its own keyboard shortcut. Type to filter, arrow to a command,
and press Return to run it — splitting a pane, opening Settings, toggling the
inspector, starting an agent, whatever the app can do.
Hold `⌘` while pressing Return (`⌘↩`) to run a command **and keep the palette
open**, so you can fire several actions in a row without reopening it.
It doubles as a way to *learn* the shortcuts: every command lists its key
equivalent right there, so the palette you reach for today teaches you the
binding you’ll reach for tomorrow.
## Open Quickly — `⇧⌘O`
Press `⇧⌘O` to jump somewhere. Open Quickly searches across three kinds of target
at once, grouped under headers:
- **Sessions** — every running session, by title. The fastest way to switch when
the sidebar is full.
- **Recent** — projects and folders you’ve opened before, to reopen in a keystroke.
- **Files** — once you start typing, the current project’s files (from `git
ls-files`, so ignored paths stay out), opened straight into the editor.
It’s the “jump to anything” companion to the Command Palette’s “do anything.” For
searching *inside* files rather than by name, use the inspector’s [Search
tab](/docs/inspector#search).
## Everything is rebindable
`⇧⌘P` and `⇧⌘O` are the defaults, but like every shortcut in Termio they can be
changed under **Settings ▸ Keyboard**. See [Keyboard
shortcuts](/docs/keyboard).
---
# The inspector
URL: https://www.termio.sh/docs/inspector
Markdown: https://www.termio.sh/docs/inspector.md
The inspector is Termio’s side panel for everything *around* the terminal:
files, changes, and search. It sits beside your sessions so you can glance at
code without switching apps.
Every pane reads the project on the machine the selected session runs on. Point
at a session on a VPS and the tree, the search and the diff are that box’s —
read over the session host, not copied down.
## Files
The file tree mirrors the working directory. Single-click a file to open it in a
built-in editor with syntax highlighting, soft-wrap, and a live line/column
readout. Edits auto-save — there’s no save button, and a file read from another
machine is written back to that machine. Images, PDFs, and HTML open in a Quick
Look preview instead.
### Editing
The editor is a real one, not a viewer:
- **Find and replace** with the Mac find keys — `⌘F`, `⌘G`, `⇧⌘G`, `⌘E`, and
replace or replace all.
- **Comment lines**, and **move** or **copy** a line or a selection up and down.
- **Tab** indents a block and **Return** keeps the indent you were on, with
indentation guides drawn behind the code.
- Other occurrences of the word under the caret get a quiet wash, so you can see
where a name is used without searching for it.
## Changes
The git pane has three read-only tabs:
- **Changes** — the current diff, rendered as a clean unified view.
- **Compare** — this branch against the branch it would merge into: the files and
commits a pull request from it would carry, diffed from the merge base so the
list matches what the forge will show.
- **History** — recent commits with author avatars, expandable file lists, and
per-commit diffs.
It’s deliberately read-only: writing to git (commit, push, PR) stays in the
terminal, where the agent and your own commands already live.
## Search
The inspector’s **Search** tab runs a content search across the project (backed
by `git grep`) and jumps you straight to the matching line in the editor — it
searches inside files, not just filenames. This is the “find in files” you know
from an editor.
Results read as excerpts, painted where the matcher hit, so you can tell which
match you want before opening anything. Search follows the session too: a
checkout on another machine is searched there, and a `cd` in that session moves
the search with it.
Looking for a file *by name* instead? That’s [Open Quickly](/docs/navigation)
(`⇧⌘O`), which also jumps between sessions and recent projects.
## Opening files from the terminal
Cmd-click a file path in terminal output — even inside an agent’s TUI — and
Termio opens it in the editor. Cmd-click a URL to open it in your browser, or a
`file://` link for a read-only preview overlay.
---
# Appearance
URL: https://www.termio.sh/docs/appearance
Markdown: https://www.termio.sh/docs/appearance.md
Open **Settings** with `⌘,`. It’s a standard macOS settings window with a sidebar
of tabs; **Appearance** is where the terminal’s look lives.
## Theme
Termio uses Ghostty-format themes, so anything from the wider Ghostty theme
ecosystem works. Pick a light and a dark theme from **Settings ▸ Appearance** and
Termio follows the system between them.
The two slots list the themes you have — Termio’s own canvas plus whatever is in
your Themes folder — not a catalog of hundreds you never chose.
If you already use Ghostty, there’s nothing to pick on a fresh install: Termio
reads your `~/.config/ghostty/config` on first launch and starts with the font and
theme you chose there.
## Getting more themes
**Browse Themes…** opens a store of 69 well-known schemes — Dracula, Catppuccin,
Gruvbox, Rose Pine, Solarized, and the rest — each one picked to stay readable
through a working day, and no two unrelated ones close enough to arrive twice
under different names. Flavors of one family are the exception: all four
Catppuccins are there, because the flavor name is exactly what its users pick
between. **Install** writes that theme into your Themes folder and selects it in
the light or dark slot it belongs to. Nothing is downloaded: the schemes ship
inside Termio.
If a file of the same name is already in your Themes folder, Install stops and
asks before replacing it — a theme you dropped in yourself is yours. **Remove**
deletes the file, and asks first if you have edited it.
To switch between the themes you have with your terminals as the preview, open
the Command Palette (`⇧⌘P`) and pick **Change Theme…** — arrowing through the
list restyles your open terminals live, Return keeps the theme, Escape puts it
back.
To add a theme the store doesn’t carry, drop a Ghostty-format theme file into:
```
~/Library/Application Support/termio/Themes/
```
The **Open Themes Folder** button takes you straight there, and **Reload** picks
up new files without a restart.
## Font
Choose any monospaced font installed on your Mac, set its size, and toggle
**Thicken glyphs** if you like a heavier weight. Font size also has live shortcuts
while you work — `⌘=` to grow, `⌘-` to shrink, `⌘0` to reset.
## Cursor & window
The same tab tunes the cursor (block, bar, or underline; steady or blinking) and
the window itself — padding around the terminal, and background opacity and blur
for a translucent window over your desktop. The app’s own chrome lives here too:
the sidebar’s font and density.
## The rest of Settings
The other tabs cover the rest of the app:
- **General** — language, the GitHub integration behind the inspector’s Issues
pane, and a Privacy switch for anonymous daily usage statistics.
- **Terminal** — scrollback history and text selection.
- **Server** — this Mac’s own session host, and the switches for the `termio`
[command-line tool](/docs/cli). See [The Termio server](/docs/server).
- **Remote Hosts** — every machine you reach from this Mac, one row each,
drilling into how that machine is reached and what Termio has installed on it.
See [Devices](/docs/devices).
- **Workspaces** — every workspace with the machine it’s on, and the place to
rename or remove one. See [Workspaces](/docs/workspaces).
- **Keyboard** — rebind any shortcut; see [Keyboard shortcuts](/docs/keyboard).
- **Agents** — add agents, enable them, turn on [session
control](/docs/session-control), and set task-completion notifications. Values
that differ per machine — where a CLI lives, whether it’s there — appear as one
row per machine on the agent’s own page, never behind a picker.
- **Usage** — token usage for agents you sign in to (Claude Code, Codex). Termio
reuses each CLI’s own credentials to show you where you stand against your
limits; it stores nothing of its own.
- **Mobile** — pair your iPhone with a machine, and publish an address it can
reach. See the [iOS app](/docs/iphone).
- **Community** — where to find the project: GitHub, Discord, and the WeChat group.
Machines are rows, never a picker. A setting that differs per machine lists one
row per machine where it belongs — the agent’s own page for a command path — and
a machine’s whole story is reached by navigation: **Server** for this Mac, a row
under **Remote Hosts** for any other.
---
# Keyboard shortcuts
URL: https://www.termio.sh/docs/keyboard
Markdown: https://www.termio.sh/docs/keyboard.md
Termio is built to run from the keyboard. The tables below are generated from the
app’s command catalog, so they list exactly what this version ships. Every command
is rebindable — see [Customizing](#customizing).
## Sessions and projects
New Terminal opens the shell where you already are — the working directory of the
focused session — and puts the new session beside it, in the same project. New
Terminal at Home always starts at `~`; it ships unbound, so bind it under
[Customizing](#customizing) if that is the one you reach for.
Cycling follows the sidebar’s visual order, so `⇧⌘]` moves to the next session as
you see it listed, not in the order the sessions were created.
## Workspaces
`⌘1` through `⌘9` switch to the first nine workspaces, in the order the switcher
lists them. They aren’t in the table below and they aren’t rebindable, because
they’re positional: what `⌘2` reaches changes when the list does, and a binding
whose target moves isn’t one you can keep. The switcher draws the digit beside
each name so you read the number instead of remembering it. See
[Workspaces](/docs/workspaces).
## Finding things
Both panels are fuzzy-matched. The Command Palette runs *actions*; Open Quickly
jumps to *things* — sessions, projects, and files. See
[Command palette & Open Quickly](/docs/navigation).
## Panes
Split Left and Split Up ship unbound, the way Ghostty ships them: the two
directions people reach for constantly get the keys, and the mirrored pair stays a
menu verb until you bind it yourself. Both are always available from the Command
Palette and the pane’s context menu.
`⌘W` is **Close Session**, the way `⌘W` closes a tab in a browser: it ends the
focused session, and its pane collapses with it. Once no session is left, `⌘W`
closes the window — the app keeps running with every other session alive.
**Ungroup** is the other half, and it ships unbound: it takes the focused pane out
of the group and leaves the session running on its own. It’s in the View menu, the
Command Palette, and the pane’s context menu, and you can give it a key in
Settings ▸ Keyboard.
Panes can also be rearranged with the mouse: hover a pane’s header, grab the
handle that appears, and drop the pane on a neighbour. A highlight previews the
drop — an edge half places the pane on that side, the center swaps the two.
## Branches and worktrees
Termio takes these two bindings from GitHub Desktop’s Branch menu verbatim, where
New Branch is `⇧⌘N`. Termio’s branch-creation verb is the worktree.
## View
The system shortcuts you’d expect work as usual — `⌘C` / `⌘V` to copy and paste,
`⌘A` to select all, `⌘,` for Settings, `⌘Q` to quit.
The Mac find keys belong to the editor: `⌘F` opens the find bar, `⌘G` and `⇧⌘G`
step through matches, and `⌘E` makes the selection the query. They’re fixed
rather than rebindable — they only mean anything while a file is open, and the
terminal never sees them.
## Customizing
Open **Settings ▸ Keyboard** to rebind any command. The editor lists every action
with its current binding and flags conflicts as you assign them. Your overrides
are saved to:
```
~/.termio/keybindings.json
```
Only the shortcuts you change are written there — the defaults ship with the app,
so the file stays small and easy to read.
A binding must include `⌘`. Without it, the chord would collide with what the
terminal itself needs: an agent’s TUI expects `⌃C`, `⌥←`, and the rest to reach it
untouched.
Not sure what a command is called? Open the [Command
Palette](/docs/navigation) (`⇧⌘P`) — it lists every action alongside its current
shortcut, so you can find and run things before you commit them to muscle memory.
---
# Running multiple agents
URL: https://www.termio.sh/docs/agents
Markdown: https://www.termio.sh/docs/agents.md
Termio is built for running more than one agent at once. The sidebar is your
control surface: every session is a row, and every row reports what its agent is
doing right now. Any terminal will let you open several agents at once; what
this buys you is not having to watch them.
## Built-in agents
Termio recognizes the common coding agents out of the box:
| Agent | CLI |
| --- | --- |
| Claude Code | `claude` |
| Codex | `codex` |
| OpenCode | `opencode` |
| OpenCode 2 | `opencode2` |
| Pi | `pi` |
| Amp | `amp` |
| Droid | `droid` |
| Cursor | `cursor-agent` |
| Copilot | `copilot` |
| Kimi | `kimi` |
| Qwen Code | `qwen` |
| Antigravity | `agy` |
| Crush | `crush` |
| Cline | `cline` |
| Hermes | `hermes` |
| Grok | `grok` |
Any of these that’s installed on your `PATH` appears as a launch option. A plain
shell is always available too. Running something else? Any CLI can become a
first-class agent — see [Custom agents](/docs/custom-agents).
`PATH` means the machine’s own `PATH`: Termio looks for these on whichever
machine the workspace is on, so a VPS offers what is installed there. **Settings
▸ Agents** says so in the row — *Not installed on `devbox`* — rather than hiding
it behind a machine picker, and the machine’s own page under [Remote
hosts](/docs/remote-hosts) is where you fix it.
Termio launches each agent through your real login shell, so everything your
shell profile sets up — version managers, `PATH` additions, credentials — is
there exactly as it would be in iTerm. If an agent is installed but Termio says
it isn’t found, that is almost always a `PATH` question; the Agents tab says so
and takes an absolute path in the command field.
## What each agent reports
Status isn’t guessed the same way for every agent, and the difference is worth
knowing when a dot doesn’t move. Each agent declares its own channels in its
[manifest](/docs/atp): **hooks** are the agent reporting its own turns and are
authoritative wherever they exist; **title** is an in-band signal read off the
terminal title; **progress** is the `OSC 9;4` progress sequence. An agent that
declares none of these is read from the pane itself — from whether its output is
still moving, not from matching words on the screen, which only happens for an
agent whose manifest spells the patterns out.
| Agent | Status signal | Resumes its conversation |
| --- | --- | --- |
| Claude Code | Hooks + title | Yes |
| Codex | Hooks + title | Yes |
| Grok | Hooks + title + progress | Yes |
| Qwen Code | Hooks + title | Yes |
| Pi | Hooks | Yes |
| OpenCode | Hooks (plugin) | Yes |
| OpenCode 2 | Hooks (plugin) | — |
| Amp | Hooks | — |
| Cursor | Hooks | — |
| Copilot | Hooks | — |
| Kimi | Hooks | — |
| Cline | Hooks | — |
| Droid | Hooks | — |
| Crush | Screen rules + progress | — |
| Antigravity | Output activity | — |
| Hermes | Output activity | — |
“Resumes its conversation” means reopening the session puts the agent back in the
*same* conversation rather than a blank one — see [What
survives](/docs/persistence). Agents without it come back as a fresh run in the
same directory.
Hooks are installed for you, per machine, when **Live agent status** is on. An
agent on a box you set up before turning that on has none, which is the usual
reason a remote session’s dot never moves — see [Status hooks & session
control](/docs/session-control).
## Reading status at a glance
Every session row carries a live status dot — **working**, **idle**, **done**, or
**needs you**. The [sidebar guide](/docs/sidebar#status) explains what each state
means, and [Concepts](/docs/concepts#status) covers where the signal comes from.
The distinction that matters is **done** versus **needs you**. A finished turn is
*done* — a calm cue that work landed while you were elsewhere. *Needs you* is
reserved for an agent that genuinely cannot continue: a permission prompt, a
question, a choice. Conflating the two is what turns a fleet into a pile of
alarms, so Termio keeps “ready” quiet and saves the loud state for the one case
where walking away costs you time.
Statuses roll up. A project’s row summarizes the sessions inside it, and the
menu-bar tray summarizes everything — so you can be in another app entirely and
still know the moment something needs you.
## Skipping permission prompts
Many agents ship a flag that stops them asking before each tool call. Where one
exists, Termio wires it to a toggle rather than making you remember the spelling:
turn it on for an agent in **Settings ▸ Agents** and Termio appends that agent’s
own flag when it launches.
It is a standing decision about that agent, so unlike its command path it is
**not** per machine — the flag goes on every launch of that agent, on every
machine. Read the flag before you turn the switch on: these are the vendors’ own
escape hatches and some of them give up more than the prompt. Codex’s is
`--dangerously-bypass-approvals-and-sandbox`, which drops its sandbox along with
the approvals. Termio appends what the vendor documents; it adds no sandbox of
its own.
An agent running unattended is exactly the one you want a real status channel
on. Turn on hooks before you turn off prompts, or you lose the signal that
would have told you it stopped.
## Switching without losing your place
Clicking a session brings it forward instantly; the others keep running in the
background. Because each session is a real process hosted on its machine, an
agent doesn’t pause when it’s off-screen — or when you switch workspaces, or
close the window. Kick off a long run in one, switch to another, and come back
when the first raises its hand.
When you have more than a handful of sessions, `⇧⌘O` jumps straight to any of
them by name — see [Navigation](/docs/navigation).
## Keeping two agents out of each other’s files
Two agents in one checkout will collide: same working tree, same index, same
branch. The fix isn’t coordination, it’s separation — give each line of work its
own git worktree and point an agent at each. Termio reads worktrees from git
itself and nests them under the project, so this is a right-click rather than a
setup step. See [Git worktrees](/docs/worktrees).
## Letting agents drive each other
Once a fleet is normal, the next thing you want is for one agent to run the
fleet. `termio sessions` is that API: an agent can list its siblings, spawn new
ones on a prompt, answer a session that’s blocked, and block until a turn
settles — all over a local socket, scoped to its own project.
```bash
termio sessions list
termio sessions spawn "update the changelog for 0.54" --agent codex
termio sessions watch
```
It’s opt-in, and turning it on also installs a small skill that teaches your
agents the commands exist. See [Command-line tool](/docs/cli) for the full verb
reference and [Session control](/docs/session-control) for the switch.
Teach Termio any CLI with a small JSON manifest.
What happens to a running agent when you close, quit, or reboot.
The `termio sessions` orchestration API, verb by verb.
---
# Custom agents
URL: https://www.termio.sh/docs/custom-agents
Markdown: https://www.termio.sh/docs/custom-agents.md
Termio ships with the common coding agents built in, but the catalog is open: any
command-line tool can become a first-class agent. You describe it in a small JSON
manifest, and from then on it appears in the new-session menu, the Settings ▸
Agents list, and the sidebar with its own name, icon, and live status — exactly
like Claude Code or Codex.
You don’t *need* a manifest to run an arbitrary CLI — you can always launch a
plain shell and run it there. A manifest is what promotes it to a real agent
with an icon and status detection.
## Add an agent
Drop a JSON file into your config folder, named after the agent’s `id`:
```
~/.termio/config/agents/.json
```
A minimal manifest needs only an `id`, a `name`, and the `command` to launch:
```json
{
"id": "myagent",
"name": "My Agent",
"command": "myagent --fancy"
}
```
Termio reads the folder at launch and again whenever you bring the app forward
with Agents settings open, so a new manifest usually appears without a restart.
The agent then shows up wherever you start a session; enable or reorder it from
**Settings ▸ Agents** like any other.
Where its CLI lives is a fact about a machine, so the command path is one row per
machine: open the agent under **Settings ▸ Agents** and its **Launch** section
lists every machine you have, each with its own path field. See [Remote
hosts](/docs/remote-hosts) for the same rows read from a machine’s side.
Use a built-in’s `id` (for example `claudeCode`) as your filename to **override**
it — handy for launching Claude Code through a wrapper script while keeping its
icon and hooks.
## Manifest fields
| Field | Required | Meaning |
| --- | --- | --- |
| `id` | ✓ | Stable slug and persistence key (letters, digits, `-`, `_`, `.`). |
| `name` | ✓ | Display name in menus and the sidebar. |
| `command` | | The program and arguments to launch. Omit it to open your login shell. The binary must be on your `PATH`. |
| `icon` | | An icon — see below. Defaults to a generic glyph. |
| `status` | | Screen-scrape rules for live status — see below. |
| `resume` | | How a reopened session continues its exact conversation — an object of launch-argument templates and a session-store descriptor. See the [Agent Terminal Protocol](/docs/atp#resume). Omit it and every launch starts fresh. |
| `permissionBypassFlag` | | A flag appended when you toggle “skip permission prompts” for this agent. |
| `tint` | | A hex color (e.g. `"#14B8A6"`) used to tint the “working” spinner. |
### Icon
Point at an SF Symbol, or an image dropped alongside the manifest:
```json
"icon": { "symbol": "sparkles" }
```
```json
"icon": { "path": "myagent.png" }
```
For `path`, put the image file next to the `.json` in the same folder.
### Live status
If your agent doesn’t ship a hook system, Termio can still read its status by
matching regular expressions against the terminal screen. List patterns that mean
“busy” under `working` and patterns that mean “waiting on you” under `attention`:
```json
"status": {
"working": ["thinking", "Running"],
"attention": ["approve\\?", "Continue\\? \\(y/n\\)"]
}
```
A line matching `attention` flips the session to **needs you** (the key keeps the
protocol’s name; it wins over `working`, since a prompt can sit under a
still-spinning header); a line matching `working` marks it **working**. See the
[sidebar status reference](/docs/sidebar#status) for what each state means.
Agents that ship their own hook system (like the built-ins) get more precise
status through hooks instead of screen-scraping. That path is covered under
[Session control](/docs/session-control) — for a custom agent, the `status`
regex above is the simplest route.
---
# Status hooks & session control
URL: https://www.termio.sh/docs/session-control
Markdown: https://www.termio.sh/docs/session-control.md
Two related, opt-in switches sit behind the live statuses you see in the sidebar:
**status hooks** that let agents report what they’re doing, and the **session
control** skill that teaches them to drive sibling sessions. Neither is required
to use Termio; both make a fleet of agents easier to run. The commands they
expose are documented in [Command-line tool](/docs/cli).
## Live agent status
Termio can install small **status hooks** into your agents’ own config files
(Claude Code, Codex, Cursor, and the plugin-based agents). When an agent starts
working, finishes, or stops to ask a question, the hook reports that to Termio, so
the sidebar’s [status dots](/docs/sidebar#status) reflect exactly what’s happening
instead of being guessed from output.
Turn it on from **Settings ▸ Agents ▸ Live agent status** (Termio also offers it
once on first launch). Toggling it off removes the hooks again, leaving any hooks
you added yourself untouched.
The switch says whether you want the feature; each machine installs it for its own
agents. Open a machine under **Settings ▸ Remote Hosts** and press **Set Up** — or
**Reinstall Hooks and Skill** on its **Agents** page — to put the hooks on that box. That is what gives
an agent running on a VPS the same status dots as one running here. See
[Devices](/docs/devices).
## Session control
**Settings ▸ Agents ▸ Session control** turns on the `termio sessions`
orchestration API below, and teaches your agents it exists by installing a
`termio` **agent skill** into each agent’s skills folder (`~/.claude/skills`,
`~/.codex/skills`). The skill loads on demand — an agent carries only its
one-line description until a task actually involves driving sibling sessions —
and Termio re-asserts it on every launch, so app updates propagate and
hand-edits heal automatically. Toggling it off removes the skill again.
Running an agent Termio doesn’t auto-configure? The same skill is published at
[termio.sh/skill.md](https://termio.sh/skill.md) and installable straight from
the repo:
```bash
npx skills add termio-sh/termio --skill termio
```
Both toggles only write files on your Mac — hook entries in agent configs and
the skill file. Nothing leaves your machine: status reports and session
commands travel over a local socket to the Termio app, and nowhere else.
---
# Git worktrees
URL: https://www.termio.sh/docs/worktrees
Markdown: https://www.termio.sh/docs/worktrees.md
When you have several agents working a repository at once, you don’t want them
stepping on each other’s files. Git worktrees give each line of work its own
checkout on its own branch — and Termio surfaces them natively.
## How Termio sees worktrees
Termio derives worktrees directly from git (`git worktree list`), so any
worktree you create — from the command line or elsewhere — shows up in the
sidebar automatically. Each one appears as a nested folder under its project,
labeled with its branch, forming a three-level tree: project → worktree →
sessions.
The list stays in sync: Termio reconciles it when a project loads, when the app
regains focus, and when the git directory changes.
## Working in a worktree
Open a session inside a worktree and it starts in that worktree’s directory on
that worktree’s branch. Point one agent at your `main` checkout and another at a
`feat/…` worktree, and their edits never collide — each has its own working tree,
its own index, its own branch.
An empty worktree still appears in the tree. Open its context menu and choose
**New Terminal** or **New Agent Session** to start there.
## Reviewing the work
The inspector’s git pane is read-only by design: three tabs — **Changes**,
**Compare**, and **History** — let you read the diff, see what the branch would
merge, and browse commits per worktree. Committing, pushing, and opening pull
requests happen in the terminal, where you (or the agent) already are — see the
[inspector guide](/docs/inspector).
---
# Devices
URL: https://www.termio.sh/docs/devices
Markdown: https://www.termio.sh/docs/devices.md
Termio runs your agents on machines you already own: this Mac, the Mac mini on
the same desk, a VPS you pay for. There is no Termio cloud to provision from and
nothing to sign into — a machine is a machine Termio can `ssh` to.
Everything in the app files under one hierarchy:
```
Device a machine you can reach, this Mac included
└ Workspace a named scope in the sidebar
└ Project a checkout on that machine
└ Session one real terminal
```
Each level belongs to exactly one level above it. That is what makes the machine
question answerable once, at the top, instead of being asked again for every
project and every pane. The level that answers it is the
[workspace](/docs/workspaces).
## Adding a machine
This Mac is a device like any other, and it’s the one you already have. Until
you add a second, the level stays out of your way — `New Terminal` and `Open
Project…` are plain verbs, because there is only one place they could mean.
Termio reads `~/.ssh/config` and never rewrites it behind your back. Anything
you can already `ssh` to is a machine Termio can reach:
- **File ▸ Connect to…** lists the aliases in your config that Termio hasn’t
worked on yet. Connecting opens a terminal there.
- **Add Host…** appends a `Host` block for a box that isn’t in your config yet.
Lead with the address; the name is what you’ll type after `ssh`. A key is the
credential that works everywhere, including for the sessions running on the
box. A password is saved to your Keychain, never to `ssh` config, and read
only when `ssh` asks for it.
## Setting up a machine
Open **Settings ▸ Remote Hosts**, pick the machine, and press **Set Up**. Termio
copies the session host — `termiod`, plus the `termio` CLI on Linux — into
`~/.local/bin` over SSH.
No root, no package manager, nothing to add to your shell profile. It then looks
for the agent CLIs installed there and writes Termio’s hooks and its agent skill
for each one, so an agent on a VPS reports **working**, **needs you**, and
**done** exactly the way a local one does.
The same button is the update, and the machine’s page reduces the whole chain to
one line — *Ready*, *Set up this host*, or *Update ready*. [Remote
hosts](/docs/remote-hosts) is the operating manual: testing the route, why a
password isn’t enough, what each rung installs, and how to repair one.
Termio never provisions a machine, never runs a control plane, and never
routes your traffic. It shells out to the system `ssh`, with your
`~/.ssh/config` and your keychain as the only authorities. Your code stays on
hardware you control — which is the only trust story that survives giving an
agent a shell on a private repo.
## Sessions live on the machine
Every session runs inside `termiod` on the machine that owns it — including the
sessions on this Mac, which run in the local daemon. The app is a viewer that
attaches and detaches.
That’s why closing the window leaves everything running, and why a remote
session isn’t tied to the SSH connection that opened it: dropping the link
detaches you, it doesn’t kill the agent. Reattaching restores the screen.
Only **Close Session** (`⌘W`) ends a session on purpose. Quitting Termio detaches
instead, and the next launch reattaches.
## What reaches across
Once a machine is set up, the panes that read a project read it on the machine
the project is on:
- **Files** — browse the checkout, open a file, and save it back to the machine
it was read from.
- **Search** — search a checkout on the machine, follow its `cd`, and open what
the search found.
- **Changes, Compare, History** — the git pane reads that machine’s repository,
and stays as read-only there as it is here.
- **Agents** — the same manifests, hooks, and skill, installed on the box.
Where an agent’s CLI lives is a fact about one machine, so it’s answered on
that machine’s own page under **Remote Hosts** rather than on a page about
agents.
- **Paste an image** into an agent running on the machine and Termio carries the
file across.
Sessions started outside Termio show up by themselves — `ssh` in, launch
something with `termiod`, and the sidebar files it as an ordinary session. The
machine’s own roster is the source, so nothing running on the box goes missing.
Adding a host, testing the route, and reading the one line that says ready.
The scope that decides which machine a project opens on.
The hooks that make status honest, on this Mac and on every machine.
Pair the phone with a machine and reach its sessions from anywhere.
---
# Remote hosts
URL: https://www.termio.sh/docs/remote-hosts
Markdown: https://www.termio.sh/docs/remote-hosts.md
[Devices](/docs/devices) is the model: a machine is a machine you can `ssh` to,
and everything files under it. This page is the operating manual for the pane
that does the work — **Settings ▸ Remote Hosts**, one row per machine, each
opening onto that machine’s own page.
The shape is worth knowing before the details. A machine is two things at once:
a **route** (how you reach it) and an **identity** (what’s installed on it). You
can’t install anything on a box you can’t reach, so the pane reads in that
order, and so does everything below.
## Adding a host
Termio reads `~/.ssh/config` and never rewrites it behind your back. Anything you
can already `ssh` to is a machine Termio can reach — there is nothing to import.
- **File ▸ Connect to…** lists the aliases in your config Termio hasn’t worked on
yet. Picking one opens a terminal there.
- **Add Host…** — at the end of **File ▸ New SSH Connection**, and on the Remote
Hosts pane — appends a `Host` block for a box that isn’t in your config yet. Lead with the
address; the name is what you’ll type after `ssh`. If your other hosts already
share a user and a key, that pair is offered rather than retyped.
A host you add this way is an ordinary `~/.ssh/config` entry afterwards. The
machine’s page carries a **Host block ▸ Edit** row that opens the entry it was
defined in, so the config stays the one place the answer lives.
## Testing the route
The **Reached by** section names the destination and says how `ssh` will sign in
— either the key your config pins, or “the keys `ssh` offers on its own”. Beside
it is a **Test** button. It reports one of four outcomes, the last of which
carries `ssh`’s own reason rather than a fixed label:
| Outcome | What it means |
| --- | --- |
| Reachable | `ssh` connected and authenticated. Nothing to fix. |
| Wants a password | The host offered password auth. The one failure with a one-click fix — see below. |
| Auth failed | It answered, and refused the credentials. A key problem, not a network one. |
| The reason `ssh` gave | Everything else — refused, timed out, unknown host — reported in `ssh`’s words. |
Test with the machine’s own alias, the same way the rest of the app reaches it.
If `ssh mybox` fails in your terminal, this will fail too — Termio carries no
credentials of its own.
## Why a password isn’t enough
A host that takes a password will let you open a plain terminal and nothing else.
The connections that carry sessions, the file tree, search, and the git panes all run non-interactively
(`BatchMode=yes`), which means they can never answer a prompt. A password works
exactly where a human is sitting there to type it, and fails everywhere else.
So the pane says that plainly and offers the fix in place: **Set Up Key…** runs
`ssh-copy-id` in a terminal with the key Termio would use. The host asks for your
password once, there, and from then on every path can reach it. If `~/.ssh` has
no key `ssh` would offer on its own, the row says so and points at `ssh-keygen`
first.
The key is yours and the command is the standard one. Termio never stores a
password, never embeds an SSH implementation, and never writes a credential
anywhere but where `ssh` and your keychain already keep them.
## Setting it up
Getting a box ready is a real dependency chain — put the server there, find out
which agent CLIs it has, write Termio’s hooks and skill into them. Showing that
as four independent states would turn picking a machine into infrastructure
triage, so the pane promises **one outcome** and keeps the rungs underneath it:
| The line says | What it means |
| --- | --- |
| **Set up this host** | Nothing asked yet, or a rung is blocking. Press **Set Up**. |
| **Checking…** | It’s running, and names the rung it’s on. |
| **Ready** | Agents on that machine can run — and, with the integration switches on, report their status back here. |
| **Update ready** | A newer server is staged, and the running one is holding it off because it has work in progress. |
Pressing **Set Up** walks the ladder: copy `termiod` into `~/.local/bin` over
SSH, probe for your agent CLIs, then bring the hooks and the skill into line with
what the switches on **Settings ▸ Agents** ask for — installing each half, or
removing it, per switch. With both off it deliberately installs neither, and the
line then promises only that agents there can run. No root, no package manager,
nothing added to your shell profile.
When a rung fails, the line names **that rung** rather than “setup failed” — and
only the first one. A machine with no `termiod` also has no hooks, and naming
both invites you to fix the consequence instead of the cause.
An agent CLI that isn’t installed there doesn’t block **Ready**. It’s a fact
about that agent, not a fault in the machine — the machine is ready to run the
agents it actually has.
## Agents on that box
A remote machine’s page carries an **Agents** link — “what does *this box*
have” — which is the same question as **Settings ▸ Agents** asked from the other
axis. Opening it gives you:
- **Command paths.** One row per agent, each with the machine’s own answer
underneath: *Installed*, *Not installed*, or *Can’t check*. Leave a path empty
to launch the agent the way that machine’s login shell would; fill it in
(`/opt/homebrew/bin/codex`) when the binary lives somewhere the profile doesn’t
add. Which agents appear at all is still **Settings ▸ Agents**.
- **Installed by Termio.** What Termio wrote into each agent’s config on that
machine — **Hooks** (so agents report their status) and the **Skill**. One
**Reinstall Hooks and Skill** button repairs both; reach for it after
hand-editing one, or when a status dot stops moving.
The skill a box gets is not the one this Mac gets. Agents here learn
`termio sessions`, scoped to their own project; agents on a machine learn
`termiod list`, `create`, `send` and `kill`, scoped to that whole machine —
because that is the command that exists there.
*Can’t check* is a third answer on purpose, and it’s never a defect in the agent:
it means Termio couldn’t reach the machine to ask. Reporting that as “not
installed” would send you to reinstall a CLI that’s already there; reporting it
as “installed” would promise a launch that can’t happen.
Both switches themselves — **Live agent status** and **Session control** — live
on **Settings ▸ Agents**, because wanting the feature is a preference. Where it
gets installed is a machine operation, which is why it happens here, per
machine. See [Status hooks & session control](/docs/session-control).
## Keeping a host current
The same **Set Up** button is the update. It works from any state — nothing
installed, an older build, a build the running daemon hasn’t picked up yet — and
it puts the old binary back if the new one doesn’t answer. The machine’s page
shows the version it’s actually running.
An update that would interrupt work in progress doesn’t take it. The line reads
**Update ready**, names the session holding it up, and offers **Update Anyway**
beside it. Left alone, the new build takes over the next time the daemon stops on
its own. Your idle terminals never hold an update back; a command still running
or an agent mid-task does.
From the command line the same reconcile is one verb:
```bash
termiod deploy --host mybox
```
It ends in the same state whatever state it started in, so running it against a
box that looks wrong is safe. See [The Termio server](/docs/server).
## When a host stops answering
Work from the outside in — `ssh` first, then the daemon, then Termio:
```bash
ssh mybox true # does SSH itself work?
termiod list --host mybox # is the server answering there?
termiod deploy --host mybox # reinstall and verify it
```
A dropped connection detaches you; it doesn’t kill anything. The sessions are on
that box, in its daemon, and reattaching restores the screen — see [What
survives](/docs/persistence). If they really did go away after a reboot, the box
is missing a service: `termiod service install`.
The device switcher reports an unreachable machine in `ssh`’s own words rather
than going quiet, so the failure you read is the failure `ssh` had. More symptoms
and their checks are in [Troubleshooting](/docs/troubleshooting).
The model: what a machine is, and what reaches across to one.
`termiod` itself — installing it, keeping it alive, looking at it.
The two switches whose files land on every machine you set up.
---
# The Termio server
URL: https://www.termio.sh/docs/server
Markdown: https://www.termio.sh/docs/server.md
Every session runs inside `termiod`, the Termio server, on the machine that owns
it — including the ones on this Mac. The Mac app, the iOS app and the CLI
are all *viewers*: they attach to a session and detach from it. Nothing about a
session belongs to the window you were looking at.
That is the whole reason quitting Termio does not kill your agents.
## What it is
`termiod` is a session host, not a window manager. It owns the PTY, keeps the
scrollback, and hands a viewer a snapshot when one attaches. Locally a viewer
reaches it over a Unix socket; remotely the app opens an SSH pipe to the same
binary on the far box. One protocol, both ways — which is why a session on a VPS
behaves like a session here.
Panes, splits and layout are the app's business and never the server's.
## Getting it onto a machine
Adding a box under **Settings ▸ Remote Hosts** and pressing **Set Up** copies one
binary into `~/.local/bin` over SSH and starts it. There is no package to
install and no root involved.
```bash
termiod deploy # install or update it here, then verify
termiod deploy --host mybox # same, over your own SSH config
```
`deploy` is the only install path, and it is a reconcile: run it against a box
in any state and it ends in the same one. Termio reads `~/.ssh/config` to find
the host, so a machine you can already `ssh` to needs nothing new. See
[Devices](/docs/devices).
## Keeping it running
```bash
termiod service install # launchd on macOS, systemd --user on Linux
```
Without a service the server lives as long as your login session. With one it
comes back after a crash and after a reboot, which is what you want on a box you
only ever reach remotely.
## Updating without dropping sessions
A new build replaces the running one **in place**: the process keeps its id and
its PTYs, and the sessions never notice.
```bash
termiod handoff
```
That is also what happens when the app updates itself — the reason a Termio
update does not cost you your running agents. See [What
survives](/docs/persistence) for every case, including the ones a handoff cannot
cover.
## Looking at it
Three commands answer nearly every "is it me or is it Termio" question:
| Command | What it tells you |
| --- | --- |
| `termiod status` | What this machine has: the binary, its version, and whether a daemon is answering on the socket. |
| `termiod list` | The sessions the host is holding — add `--host ` to ask another machine. |
| `termiod logs` | What the server recorded while nobody was attached. `-f` follows it; `--path` prints the file for a bug report. |
`termiod` is the server's own command, and separate from the
[`termio` command-line tool](/docs/cli) the app installs for you. You rarely
need `termiod` by hand — the app runs these for you — but it is there when a
box is misbehaving and you want to look rather than guess.
## Stopping it
```bash
termiod stop
```
It declines while a command is still running or a viewer is attached, so a stray
`stop` cannot take an agent's work with it. Sessions end when their process ends,
or when you close them.
---
# What survives
URL: https://www.termio.sh/docs/persistence
Markdown: https://www.termio.sh/docs/persistence.md
Termio’s one structural promise is that a session outlives the thing you were
looking at it through. Close the window, quit the app, let it update itself,
drop the SSH link to a VPS — the agent keeps working. This page is the honest
version of that claim: what survives each event, what doesn’t, and what it costs
when a process really does go away.
## The rule
Every session runs inside `termiod`, the [Termio server](/docs/server), on the
machine that owns it — including the ones on this Mac. The app, the CLI, and the
iOS app are all *viewers*. A viewer attaches to a session and detaches
from it; it never holds the process.
So the question “does this kill my agent?” always reduces to one thing: **did
the process die, or did a viewer just let go?** Only two things end a session on
purpose — **Close Session** (`⌘W`), and the process exiting by itself.
## What survives what
| What happens | Agent keeps running | Layout comes back | Screen comes back | Conversation continues |
| --- | --- | --- | --- | --- |
| Switch sessions, hide the window, use another app | Yes | — | Yes | Yes |
| Close the window | Yes | Yes | Yes | Yes |
| Quit Termio (`⌘Q`) and relaunch | Yes | Yes | Yes | Yes |
| Termio updates itself | Yes | Yes | Yes | Yes |
| The SSH link to a remote machine drops | Yes | Yes | Yes | Yes |
| `termiod handoff` (server updates in place) | Yes | Yes | Yes | Yes |
| The server stops, or the machine reboots | No | Yes | No | On this Mac, where the agent supports [resume](/docs/atp#resume). Never on another machine |
| **Close Session** (`⌘W`) | No | — | — | — |
The top six rows are one event under six names: a viewer went away and the
process didn’t notice. Reattaching repaints the screen from the
server’s own scrollback, so you come back to the output you’d have seen if you
had never left.
“Layout comes back” means the sidebar tree, the split groups, and the
inspector tab you left each workspace on. That structure is what Termio writes
to disk; your terminals’ contents are not part of it.
## Detaching is the default
Nothing casual ends a session. Closing the window, quitting the app, a self-update,
a laptop that lost Wi-Fi mid-`ssh` — every one of them detaches. The next launch
reattaches to the sessions that are still there rather than starting new ones,
which is why a long agent run is safe to walk away from.
Ending a session is the deliberate act: `⌘W` **closes the focused session**, the
way `⌘W` closes a browser tab, and its pane collapses with it. Once no session is
left, `⌘W` closes the window and the app keeps running with everything else alive.
See [Keyboard shortcuts](/docs/keyboard#panes).
Want to leave a machine but keep the work? Just quit. There is no “detach”
command to remember, because detaching is what quitting already does.
## When the process really does go away
A server that stops — a reboot, a `termiod stop`, a box that lost power — takes
its PTYs with it. There is no way around that; a process is a process. What
Termio does is make coming back cheap:
- **The tree is restored.** Projects, worktrees, workspaces, split groups, and the
session you left each workspace on all come back, because they live in a file
rather than in the daemon.
- **Sessions restart in their own directory.** A shell comes back as a shell,
rooted where it was.
- **Agents on this Mac that can resume, do.** Reopening a session relaunches the
agent into the *same conversation* rather than a blank one.
Resume is **exact-or-nothing**: Termio either continues the precise conversation a
session was bound to, or it starts fresh. It never guesses at a nearby one. Which
agents can do this is a fact about each agent’s own CLI, declared in its
[manifest](/docs/atp#resume) — see the resume column in
[Running multiple agents](/docs/agents#what-each-agent-reports).
## Surviving a reboot
Without a service, the server lives as long as your login session. That is usually
fine on the Mac in front of you and almost never fine on a box you only ever reach
over SSH, where nobody logs in interactively at all.
```bash
termiod service install # launchd on macOS, systemd --user on Linux
termiod service status
```
With a service installed the server is brought back after a crash, and started
again at login after a reboot. On Linux, surviving your logout also needs
lingering enabled for the user (`loginctl enable-linger`); without it the unit
stops with your session.
Be clear about what this buys: the *server* comes back, not the processes it was
holding. A reboot still ends every PTY on that machine. A service is what stops
“nothing is listening on that box” from being a permanent state — see
[Troubleshooting](/docs/troubleshooting).
## Sleep
A sleeping machine isn’t running anything, so the honest rule for a laptop is:
sessions don’t die when the Mac sleeps, they pause with it and pick up when it
wakes.
Termio does hold the machine out of *idle* sleep while there is work worth
protecting. While any session is working or waiting on you, the server renews a
short `caffeinate` lease, so a Mac plugged into power stays reachable for the
agent that went quiet on a permission prompt — exactly the moment your phone
needs to answer it. On battery it takes no lease at all, and closing the lid
sleeps the machine regardless: nothing in user space can override clamshell
sleep.
If you want a session you can always reach, run it on a machine that stays
up — a VPS, or the Mac mini on the desk — and pair your
[iPhone](/docs/iphone) against that rather than against a laptop.
## Updating without dropping sessions
A new server build replaces the running one **in place**: the process keeps its
id and its PTYs, and the sessions never notice.
```bash
termiod handoff
```
That is also what happens when the app updates itself, which is why a Termio
update doesn’t cost you your running agents. A machine being set up or updated
from **Settings ▸ Remote Hosts** takes the same path, and an update that *would*
interrupt work in progress says so and names the session first.
## What Termio does not keep
Being clear about the edges is part of the promise:
- **Termio keeps no transcript of your terminals.** Scrollback lives in the
server for the session’s lifetime and goes away with it; nothing durable is
written. The one exception is a handoff, which passes each session’s replay
buffer to the incoming server through a temporary file it unlinks as it
goes — in flight for the length of an update, never a stored record.
- **An agent’s conversation belongs to the agent.** Resume works by handing the
agent back its own session id; the conversation is in the agent’s store, under
your own account, exactly as it would be from a plain terminal.
- **Nothing syncs anywhere.** State is per machine, on that machine. There is no
Termio cloud holding a copy — see [Devices](/docs/devices).
What `termiod` is, how it gets onto a machine, and how to look at it.
The manifest that declares how an agent launches, reports, and resumes.
Sessions missing after a reboot, and the rest of the usual surprises.
---
# The iOS app
URL: https://www.termio.sh/docs/iphone
Markdown: https://www.termio.sh/docs/iphone.md
Termio for iPhone is a companion, not a separate service. It connects to
machines you own — your Mac, or a box that Mac set up — over your local network
or a tunnel you configure. There’s no relay of ours in between.
## Pairing
1. On your Mac, open **Settings ▸ Mobile** and pick the machine your iPhone
should connect to. Each one serves its own sessions.
2. Press **Publish** for a machine that isn’t reachable yet, then scan the QR
code from the iOS app: tap the machine pill ▸ **Scan QR Code**. The camera
reads the code on-device; nothing is stored or transmitted.
Once paired, the phone reaches that machine’s sessions whenever it can dial the
address it was paired against. **Rotate Token…** signs out every paired iPhone
and requires a fresh scan — that’s the revoke.
A phone can attach straight to a machine that isn’t this Mac, over the same
session protocol the Mac uses. Your laptop doesn’t have to be awake to stand in
the middle.
## What you can do from the phone
- **See your projects and sessions**, with the same live statuses as on the Mac.
Home is one workspace at a time — swipe from the left edge for the workspace
rail, the way Slack switches — and each section names the machine its
workspace is on. The “Needs You” strip stays outside that scope on purpose, so
an agent blocked in another workspace still reaches you.
- **Type straight into a session.** The phone drives the real terminal — you’re
typing into the agent exactly as on the desktop, with a key-bar for scrollback
and jump keys, and hold-to-talk voice dictation if you enable it.
- **Preview files** read-only over the same connection.
## Reaching a machine remotely
On the same Wi-Fi, pairing is all you need. To reach a machine from anywhere,
give it an address: **Settings ▸ Mobile ▸ Tunnel** starts one for you, or
**Custom** runs the tunnel command you choose — a named `cloudflared` tunnel, or
your own relay — and matches the URL it prints. Termio never routes your traffic
through servers of ours; a hosted tunnel provider terminates on theirs, which is
why Custom is there.
A Mac that’s asleep can’t answer the phone, and nothing in the app can wake it
from outside. If you want a session you can always reach, run it on a machine
that stays up — a VPS, or the Mac mini on the desk — and pair against that.
---
# Command-line tool
URL: https://www.termio.sh/docs/cli
Markdown: https://www.termio.sh/docs/cli.md
`termio` is the shell side of the app: it opens projects, and through
`termio sessions` it is the orchestration API one agent uses to see and steer
its siblings. The switch that exposes it is
[Session control](/docs/session-control).
Turn on **Settings ▸ Server ▸ Command line ▸ Command-line tool**. It symlinks a `termio`
command onto your `PATH` (at `/usr/local/bin/termio`; macOS asks for permission
once), and turning it off removes the link again.
## Open a project
```bash
termio # open the current directory as a project
termio ~/code/myapp # open a specific folder
```
Run it in any directory and Termio brings that folder into the sidebar — the
shell equivalent of **Open Project**.
## The orchestration model
The `termio sessions` family is Termio’s orchestration API: it lets one agent —
or your own scripts — see and steer sibling sessions in the same project. Before
the verb reference, the design rules everything below follows:
- **Project-scoped by default.** Every command resolves the caller to its own
project (via the session id the PTY carries, or the working directory) and can
only see and drive siblings there — never sessions in unrelated projects.
- **One address, one target.** Sessions are addressed by
`termio://session/` deep links minted at creation and printed by
`list` (a bare id or unique id-prefix works too). The link names the pane,
never its mutable contents, so a copied address survives the session
promoting, demoting, or being renamed — and stays self-describing when
pasted anywhere.
- **Transcript as truth.** An agent’s *result* is read from its own structured
transcript (the path and line range the replies hand back), never scraped off
the screen. The screen is the result channel only for plain `run` terminals,
which have no transcript.
- **Waiting is explicit.** No command blocks unless you pass `--wait`, and
`--wait` always means the same thing: wait for the turn’s *outcome*, fail
fast when no outcome can come, and split the result across exit codes.
- **Signal, never kill.** The supervision plane observes and reports — `watch`
events, the `stalled` alarm — but never terminates or auto-answers a session.
Acting on a signal is always the supervisor’s decision.
## Drive sessions
Add `--json` to any command for machine-readable output.
| Command | What it does |
| --- | --- |
| `termio sessions list` | List the sessions in this project with their live status. |
| `termio sessions watch` | Block and stream one line per status change — the push alternative to polling `list`. |
| `termio sessions spawn ""` | Start a new agent session on the prompt; replies immediately with its session link. |
| `termio sessions run ""` | Start a new plain terminal session typing that shell command — a dev server, a test run — in a visible pane, no LLM. |
| `termio sessions send ""` | Type text into an existing session and submit it with a real Return keypress — a prompt to drive it, or a menu choice (`"1"`, `"yes"`) to answer a permission prompt. |
| `termio sessions read ` | Print the session’s current screen without focusing it (`--lines N` keeps the tail) — the result channel for `run` sessions. |
| `termio sessions close ` | Close one or more session tabs. |
| `termio sessions focus ` | Bring a session to the front in the app. |
`` is the `termio://session/` address `list` prints (a bare id,
unique id-prefix, or session title also works). Commands are scoped to the
current project automatically, and `termio sessions --help` prints
focused help for any one verb.
`answer` is a deprecated alias of `send` (meaningful for agent sessions only), and `send` with no target behaves like
`spawn` — both kept so existing scripts keep working. Prefer `spawn` and `send`.
## Spawning without waiting
`spawn` returns the moment the session exists: the reply carries the new
session’s link, and the prompt itself is typed in once the agent finishes
booting. Add
`--agent ` (e.g. `codex`, `grok`, `pi`) to choose the agent; it defaults to
the caller’s own kind.
## Running plain commands
Not everything in a fleet is an agent. `run` starts a *terminal* session on a
shell command — a dev server, a test watcher, a build — in a visible pane you
can watch and take over, and `read` prints any session’s current screen
without focusing it. A plain command has no transcript, so its screen is its
result channel; agent results keep flowing through transcripts.
```bash
termio sessions run "pnpm test"
termio sessions read 1a2b3c4d --lines 40
```
## Waiting for the outcome: `--wait`
Waiting is always explicit, and it means the same thing everywhere: `--wait`
waits for the *outcome* of the turn, never for plumbing. Both `send` and `spawn`
accept it (with an optional `--timeout `, default 300000, clamped
1000–600000):
```bash
termio sessions send termio://session/ab12cd34-9f2e-4c31-b8d7-3e5a12c90f44 "run the tests" --wait
termio sessions spawn "summarize the failing CI job" --wait --timeout 120000
```
The call blocks until the turn settles — the session was seen `working` and has
rested off it — and the reply carries the final `status`, the `transcript` path,
and the `cursor`..`cursor_end` line range the response landed in, so the caller
reads exactly the new content with its own file tools. Two special cases:
- A session that stops to ask something short-circuits the wait immediately:
the reply is `status: "needs-you"` with the on-screen question in `prompt` —
answer it with another `send`.
- A plain terminal with no status signal falls back to screen settling: the
screen changed after the send, then went still.
Waits also **fail fast instead of burning the timeout** when no outcome can
come:
- A prompt that shows no effect within 5 seconds — no status move, no screen
change — errors as `prompt_stalled`: the input was eaten (an agent still
booting, a program that ignores typed text), and no turn is coming.
- A session that closes mid-wait errors as `session_closed`; an agent that
exits back to its shell mid-wait errors as `agent_gone`.
On timeout the reply still comes back (`timed_out: true`) with whatever the
current status is; the session keeps running. The exit code splits the three
outcomes so scripts can branch without parsing: **0** settled, **1** error
(including stalled/vanished), **3** timed out.
## Supervising with `watch`
`watch` is built so an agent can supervise a fleet without polling:
- **Snapshot on attach.** It first prints one line per session with its
*current* status (tagged `"snapshot":true` in `--json`), so a supervisor
attaching late still learns a session is already waiting on input. Skip it
with `--no-snapshot`.
- **Filtered by default.** Live events default to the two states a supervisor
acts on, `done` and `needs-you`; widen with
`--state working,idle,done,needs-you`.
- **Stall alarm, opt-in.** `--state stalled` adds a watch-plane signal for the
unattended runaway: a session still `working` that has made **no repo change
and next-to-no transcript growth for 20+ minutes**. Sustained output — a long
build streaming logs — suppresses it. The event carries the detector’s
reasoning in `evidence` (`"working 42m, no repo change, transcript +3
lines"`), fires once per quiet stretch, and re-arms when progress resumes.
Termio only signals; it never kills — the session’s real status stays
`working` and the sidebar is untouched. Not in the default filter.
- **Actionable events.** A `needs-you` event carries the on-screen question in
`prompt`; a `done` event carries the session’s `transcript` path and
`cursor_end` — enough to answer, or to read the result, without another
round-trip.
- **Heartbeat.** In `--json` mode the app writes `{"heartbeat":true}` after 30
seconds of silence, so a quiet stream is distinguishable from a dead one.
- **Exit codes.** `0` after Ctrl-C (the normal end of supervision), `2` when
the stream closed from the app side — “I chose to stop” and “Termio went
away” are different outcomes.
## Spawned sessions know their caller
When one Termio session spawns another, the delivered prompt opens with a short
provenance envelope: it names the caller’s link and allows exactly one
back-channel — a mid-task question, or a one-line completion ping, via
`termio sessions send …`. Nothing else travels that way: no
conversation, no delegating tasks back. Results stay transcript-as-truth — the
supervisor reads the worker’s transcript, with `watch` as the backstop. A
`spawn` from a plain shell (outside any Termio session) gets no envelope.
## Fail loud
The CLI is built for callers that trust exit codes, not prose. A command that
runs and fails — an error reply, an empty one — exits `1`; a command it cannot
parse exits `2` without contacting the app. Flags take either spelling,
`--agent codex` or `--agent=codex`, and `--` ends them, which is how text
beginning with a dash reaches a session:
```sh
termio sessions send -- --force
```
One-shot commands give up after a 15-second client timeout rather than hanging
on a busy app (override with the `TERMIO_CLI_TIMEOUT` environment variable; a
`--wait` call widens the client timeout automatically to outlive the
server-side wait).
## JSON contract
Agents code against `--json` output, so its shapes are pinned, not
reverse-engineered. Every JSON reply carries `"schema_version": 1`; fields may
be *added* within a version, never renamed or removed. Optional fields are
omitted when unknown (never emitted as `""` or `null`).
Every error, on any verb, has one shape — and the exit code is nonzero:
```json
{"ok": false, "error": "", "message": "", "schema_version": 1}
```
The stable codes: `disabled`, `no_scope`, `bad_op`, `bad_request`, `no_text`,
`no_target`, `not_found`, `ambiguous`, `wrong_agent`, `bad_agent`, `no_agent`,
`start_failed`, `not_live` — plus, on `--wait` only, `prompt_stalled`,
`session_closed`, and `agent_gone`.
Success replies, per verb (`schema_version` and `"ok": true` omitted below for
brevity):
| Verb | Reply fields |
| --- | --- |
| `list` | `project`, `sessions: [{link, id, title, agent, status, description, transcript?}]` — `link` is the canonical address, `id` its 8-char short form |
| `spawn` / `run` | `target`, `title`, `created: true`, `queued: true` — the payload is still being delivered |
| `send` | `target`, `title`, `transcript?`, `cursor?` — read the response from `cursor` onward |
| `read` | `target`, `title`, `screen` — the current viewport, right-trimmed, trailing blank rows dropped |
| `send`/`spawn`/`run` `--wait` | `target`, `title`, `status`, `timed_out`, `created?`, `transcript?`, `cursor?`, `cursor_end?`, `prompt?` |
| `close` | `closed`, `title` |
| `focus` | `focused` |
The two wait verbs share one reply shape (`--wait` means the same thing
everywhere): the final `status`, and the transcript line range
`cursor`..`cursor_end` holding the response. `prompt` appears only on a
`needs-you` outcome — the question the session is showing on screen.
`watch` streams newline-delimited JSON events rather than one reply:
```json
{"schema_version": 1, "link": "termio://session/ab12cd34-9f2e-4c31-b8d7-3e5a12c90f44",
"status": "done", "title": "…", "cwd": "…", "snapshot": true, "prompt": "…",
"transcript": "…", "cursor_end": 42}
```
`cwd` is omitted until the shell reports one; `snapshot` marks the on-attach
roster lines; `prompt` rides on `needs-you` events, `transcript` +
`cursor_end` on `done` events (exactly as in wait replies), and `evidence` on
`stalled` events — the stall detector’s reasoning, e.g.
`"working 42m, no repo change, transcript +3 lines"`. `stalled` appears only
on the watch stream, never as a session’s `status` in `list` or wait replies.
After 30 seconds of silence the app writes `{"heartbeat": true}`.
This is what lets one agent hand work to another — `spawn` a task in a sibling
session, then `watch` (or poll `list`) until it reports **done** and read the
result. Watching several at once, `termio sessions watch` prints the link the
moment any of them turns **done** or **needs-you** — one agent supervising a
fleet, all on your own machine.
---
# Agent Terminal Protocol
URL: https://www.termio.sh/docs/atp
Markdown: https://www.termio.sh/docs/atp.md
Editor protocols re-render your agent inside an editor pane. ATP takes the
opposite bet: the agent’s own TUI already is the interface, so it stays in a real
terminal, and the protocol standardizes only the thin layer around it —
**launch**, **live status**, and **exact resume**. Every agent Termio ships is
defined by this same manifest; there is no privileged internal API.
## The manifest
One JSON file per agent. This is Termio’s bundled Grok manifest, verbatim:
```json
{
"id": "grok",
"order": 95,
"name": "Grok",
"wire": "grok",
"command": "grok",
"permissionBypassFlag": "--yolo",
"resume": {
"create": "--session-id {id}",
"resume": "--resume {id}",
"storeRoot": "~/.grok/sessions",
"storeMatch": "dir:{id}"
},
"icon": { "vector": "grok" },
"install": "https://x.ai/cli",
"titleStatus": {
"attention": ["Action Required"]
},
"hooks": {
"type": "json",
"file": "~/.grok/hooks/termio.json",
"dialect": "grok",
"conversation": "sessionId",
"events": [
{ "on": "SessionStart", "state": "idle" },
{ "on": "UserPromptSubmit", "state": "working" },
{ "on": "PreToolUse", "state": "working" },
{ "on": "PostToolUse", "state": "working" },
{ "on": "Stop", "state": "done" }
]
}
}
```
| Field | Meaning |
| --- | --- |
| `id` | Stable identifier. Sessions persist it, so it never changes once shipped. |
| `name` | Display name in the picker and sidebar. |
| `command` | The CLI to launch, resolved on your login shell’s `PATH`. |
| `permissionBypassFlag` | The vendor’s skip-permissions flag, wired to a one-click toggle. Optional. |
| `icon` | `vector` (a built-in brand mark), `path` (your PNG/SVG file), or `symbol` (an SF Symbol), with an optional `tint`. |
| `install` | The vendor’s install page, offered when the command isn’t found. Optional. |
| `order` | Position in the agent picker. Optional; omitted manifests sort last. |
| `skills` | The agent’s user-level skills directory (`{"dir": "~/.claude/skills"}`), which is where Termio writes its session-control skill. Optional; omit it for an agent with no skills ecosystem. |
| `configHome` | The environment variable the agent documents for moving its config tree, and the path that variable replaces (`{"env": "CLAUDE_CONFIG_DIR", "path": "~/.claude"}`). Set it and Termio installs where the agent actually reads, rather than at the literal default. Optional. |
## Status
A session is always in one of four states — `working`, `attention` (blocked on
you), `done`, or `idle` — shown in the sidebar, the menu bar, and on your
iPhone. The manifest declares how the agent reports them, through three
channels:
| Channel | Role |
| --- | --- |
| `hooks` | The precise channel: Termio installs the agent’s own hook configuration (JSON, TOML, or a plugin, per `dialect`) so the agent itself reports each `on → state` event. When hooks are declared they are the single source of truth. |
| `titleStatus` | Regex rules over the agent’s live terminal title (OSC 0/2) — the in-band signal some agents broadcast. Coexists with hooks and corrects a missed event the instant the title flips. |
| `status` | Screen-classification regexes for agents with no hook system at all. Ignored when hooks are declared. |
`hooks.conversation` names where the agent’s own session id appears in its hook
payloads (a stdin JSON field for shell hooks, an event key path for plugins).
With it, each status report also carries conversation *identity* — so when the
agent rotates to a new conversation mid-session (`/new`, `/clear`), Termio
re-binds the tab to the live one the moment the next event fires.
## Resume
Reopening a session relaunches the agent into the same conversation. Resume is
**exact-or-nothing**: Termio either resumes the precise conversation a tab is
bound to, or launches fresh — it never guesses. Two families, inferred from
which fields are present:
**Pinned id** — the CLI accepts a session id at launch. `create` starts a fresh
conversation under a Termio-minted id and `resume` continues it; `storeRoot` +
`storeMatch` describe the agent’s on-disk session store so Termio can tell the
two apart (creating a duplicate id errors, as does resuming a missing one).
**Discovered id** — the agent mints the id itself. `discover` describes the
mechanism, never an agent: where session records live, how a record is read, and
key paths to the id and working directory. A record is either `jsonl` (the first
line of a log that is itself the transcript) or `json` (a standalone metadata
file). Termio recovers the id, binds it to the tab, and resumes exactly from
then on.
```json
"resume": {
"resume": "resume {id}",
"discover": {
"root": "~/.codex/sessions",
"format": "jsonl",
"id": "payload.id",
"cwd": "payload.cwd"
}
}
```
## Add your own agent
Drop a manifest at `~/.termio/config/agents/.json` and restart Termio — it
appears in the picker alongside the built-ins, with the same status dots and
resume behavior. A minimal manifest is just an id, a name, and a command; add
status and resume as the CLI supports them.
The [Custom agents](/docs/custom-agents) guide walks through this end to end —
icons, screen-scrape status rules, and overriding a built-in.
---
# Troubleshooting
URL: https://www.termio.sh/docs/troubleshooting
Markdown: https://www.termio.sh/docs/troubleshooting.md
Almost everything that looks like a Termio bug is one of the cases below, and
each one has a way to check rather than guess. Start here before filing anything.
## Start with these three
On any machine, in any state, these answer most of the question:
```bash
termiod status # what's installed here, and is a daemon answering?
termiod list # what sessions is it holding?
termiod logs -n 200 # what happened while nobody was watching
```
`termiod status` in particular ends a surprising share of “it broke” reports: it
says which binary is installed, which version, and whether anything is actually
listening on the socket. To ask about another machine, `termiod list --host
` queries that box's daemon over your own SSH config.
## A status dot never changes
The sidebar’s [status dots](/docs/sidebar#status) come from hooks installed into
each agent’s own config, and they are installed **per machine**. An agent on a
box you set up before turning the feature on has no hooks, so it reports nothing
and Termio falls back to reading the screen.
Open **Settings ▸ Remote Hosts**, pick the machine, open its **Agents** page and
press **Reinstall Hooks and Skill**. For this Mac the same repair is
**Install on This Mac**, on **Settings ▸ Agents**. See [Status hooks & session
control](/docs/session-control).
## The agent is sitting on a “trust these hooks?” prompt
Some agents verify their hook files and refuse to run hooks whose contents
changed — Codex will show **Modified since last trusted** and wait. This one is
nasty because the hooks *are* the status channel: until you answer, the agent
reports nothing, so the sidebar shows the last status it knew rather than
**needs you**.
If a session looks idle but nothing is happening, look at the pane itself. Answer
the prompt once and status resumes. Termio re-asserts its hooks on launch (that
is what keeps other tools from quietly replacing them), so the prompt can come
back after an update.
## An agent won’t launch, or exits instantly
A pane that dies at once with “failed to launch” is nearly always `PATH`, not the
agent. Termio starts agents through your real login shell, so anything your
profile sets up is available — but a CLI installed somewhere your profile doesn’t
add is still invisible.
Open **Settings ▸ Agents** and look at the agent’s row: Termio resolves your login
shell’s `PATH` and flags a command it can’t find. Either fix your profile, or
paste the absolute path (`/opt/homebrew/bin/codex`) into the command field. The
same field is per machine, so do it on the machine that’s failing.
## Status hooks were working and stopped
Agent config files are shared ground — other agent managers write to the same
`~/.claude/settings.json` and `~/.codex/hooks.json`, and some replace the whole
hooks block rather than merging into it. When that happens Termio’s reports stop
arriving and status falls back to screen-reading.
Termio re-asserts its own hooks when it launches and when you bring it back to
the front, so on this Mac it usually heals by itself. That sweep is local only:
another machine keeps whatever is on it until you repair it there, with
**Reinstall Hooks and Skill** on that machine’s **Agents** page.
## `termio sessions` says `disabled`
The orchestration API is opt-in. Turn on **Settings ▸ Agents ▸ Session control**,
which also installs the `termio` skill that teaches agents the commands exist.
Every CLI error has the same shape and a nonzero exit code, so a script can
branch on `error` rather than parse prose — the codes are listed in the
[JSON contract](/docs/cli#json-contract).
## `termio: command not found`
The command is a symlink the app installs for you: **Settings ▸ Server ▸ Command
line ▸ Command-line tool**, which links `termio` into `/usr/local/bin` (macOS
asks once). If the toggle is already on, open a new shell — a session started
before the link existed has a stale `PATH` hash. `termiod` is a different command
that lives on each machine; see [The Termio server](/docs/server).
## Sessions are gone after a reboot
Without a service, the server lives as long as your login session. Install one
and it survives logouts, crashes and reboots:
```bash
termiod service install
termiod service status
```
This is the usual surprise on a remote box, where nobody logs in interactively.
See [The Termio server](/docs/server) and [What survives](/docs/persistence).
## A machine stopped answering
Work from the outside in. If plain `ssh` fails, Termio will too — it uses your
own `~/.ssh/config` and never carries its own credentials.
```bash
ssh mybox true # does SSH itself work?
termiod list --host mybox # is the server answering there?
termiod deploy --host mybox # reinstall and verify it
```
`deploy` is a reconcile, so running it against a box in a bad state is safe. A key
is the credential that works everywhere, including for sessions already running on
the box; a passphrase-protected key needs to be loaded into `ssh-agent` first, the
same as it would for any non-interactive `ssh`. The machine’s own page has a
**Test** button that names which outcome you have — see [Remote
hosts](/docs/remote-hosts#testing-the-route).
## The iPhone app can’t reach a machine
Pairing binds the phone to one address. Check them in order:
1. **Is the machine awake?** A sleeping Mac answers nothing, and nothing in the
app can wake it from outside. Pair against a machine that stays up if you want
this to always work — see [What survives](/docs/persistence#sleep).
2. **Is it published?** **Settings ▸ Mobile** must have the machine published; on
the same Wi-Fi that’s all it takes.
3. **From elsewhere, is there a tunnel?** Off your own network the machine needs
an address — **Settings ▸ Mobile ▸ Tunnel**, or your own.
4. **Was the token rotated?** **Rotate Token…** signs out every paired phone by
design. Scan the QR code again.
See [The iOS app](/docs/iphone).
## Notifications don’t appear
Task notifications are a system permission like any other: macOS asks once, and
if it was declined the app can’t ask again. Check **System Settings ▸
Notifications ▸ Termio**, and check that **Task completion** is on under
**Settings ▸ Agents ▸ Notifications**.
They’re also deliberately quiet. A turn that finished while Termio was the
frontmost app doesn’t notify — you were already looking at it — and neither does
a turn that took only a moment.
## Something looks wrong and you want evidence
The server keeps a log of what happened while nobody was attached — which is
exactly the window a bug tends to fall into.
```bash
termiod logs -n 200 # the recent tail
termiod logs -f # follow it live
termiod logs --path # the file itself, for a bug report
```
Reproducing something intermittent? Leave `termiod logs -f` running in a pane of
its own. The log is the one record that spans the moment you weren’t attached.
## Still stuck
Open an issue at
[github.com/termio-sh/termio](https://github.com/termio-sh/termio/issues) with
the output of `termiod status` and the tail of `termiod logs`. Those two answer
most of the questions a maintainer would otherwise have to ask. Add the macOS
version, the Termio version, and whether the session was local or on another
machine.