# Terminus on Linux — guide for AI agents

> Terminus is a desktop app that runs many terminal panes side by side: plain shells and AI coding agents (Claude Code, Codex, Antigravity). An agent can drive it: open panes, give them work, read their output, send input, and close them, through an MCP server that ships with Terminus.

**Status: DRAFT.** Linux support is being built and tested. Every step marked NOT VERIFIED YET has not been run on Linux. Do not treat it as working. If a step fails, stop and report the exact error to your user.

## 1. What you need

- 64-bit Intel or AMD processor (x86_64). There is no ARM build yet.
- Tested on Ubuntu 22.04 and 24.04, and Debian 12. Other recent apt-based distributions may work.
- A desktop, or a virtual display (Xvfb) if the machine has no screen.
- Claude Code, Codex or Antigravity installed if you want agent panes. Plain terminal panes need nothing extra.

## 2. Get the installer

Terminus is in early access, so downloads need an access code from your user. Ask them for it once. Then fetch the file without a browser:

### With an access code (today) (NOT VERIFIED YET)

```sh
TERMINUS_CODE='<code from your user>'
curl -fL -X POST \
  -H "Authorization: Bearer $TERMINUS_CODE" \
  "https://terminus.click/api/download?asset=linux-appimage" \
  -o Terminus-latest-x86_64.AppImage
```

### Without a code (only once downloads are open) (NOT VERIFIED YET)

```sh
curl -fL -X POST \
  -H 'content-type: application/json' \
  -d '{"name":"<name of your user>","email":"<email of your user>"}' \
  "https://terminus.click/api/download?asset=linux-appimage" \
  -o Terminus-latest-x86_64.AppImage
```

Only when Terminus has opened downloads to everyone (not yet). No code, but the download still asks for a name and an email.

For the .deb package use `asset=linux-deb` and `-o Terminus-latest-amd64.deb`. The file names match the commands in step 3. A wrong code, a closed download or a missing file returns HTTP 404. Read each Linux asset's version and checksum from the public release details; Linux may be newer than the Windows/macOS release tag.

### Check the file (NOT VERIFIED YET)

```sh
expected=$(curl -fsS https://terminus.click/api/release-meta \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)["assets"]["linux-appimage"]["sha256"])')
echo "$expected  Terminus-latest-x86_64.AppImage" | sha256sum -c -
```

Release details are public: no code needed to read the checksum. For the .deb, read `assets["linux-deb"]`.

Files: `Terminus-<version>-x86_64.AppImage`, `Terminus-<version>-amd64.deb`.

## 3. Install

AppImage:

- Make the file runnable. (NOT VERIFIED YET)

```sh
chmod +x Terminus-*-x86_64.AppImage
```

- Start it. (NOT VERIFIED YET)

```sh
./Terminus-*-x86_64.AppImage
```

.deb:

- Install the package. apt also installs what it needs. (NOT VERIFIED YET)

```sh
sudo apt install ./Terminus-*-amd64.deb
```

- Start Terminus from your app menu, or run it by name. (NOT VERIFIED YET)

```sh
terminus
```

## 4. Run it in a VM with no screen

### One-command install (NOT VERIFIED YET)

```sh
TERMINUS_ACCESS_CODE='<code from your user>' bash install.sh --start
```

install.sh is not published at a public address yet. It reads the checksum from the public release details, downloads through the same gate as step 2 (your code, or no code once downloads are open), checks the file and installs what it needs. Install-only is the safe default; --start opts into a detached launch and waits for exact backend health. Add --deb to use the .deb package. When run as root, it creates or uses the unprivileged terminus account and prints that account's launcher path.

Options:

- --deb installs from the .deb package instead of the AppImage.
- --start launches in a detached session, waits for exact backend health, and then returns. Without it, installation does not launch Terminus.
- --no-start and --no-launch explicitly select the install-only default.
- --skip-deps uses the system packages already installed.
- It never overwrites an existing install. To upgrade, set TERMINUS_VM_INSTALL to a new folder.
- State lives in ~/.local/state/terminus-vm. Set TERMINUS_VM_STATE (the same value for start, health and mcp) to run a second, separate Terminus.

### Start without a screen

```sh
$HOME/.local/share/terminus-vm/bin/terminus-headless start
```

Runs in the foreground under a private virtual display (Xvfb). Keep the session open, or run it under your service manager.

### Check it is running

```sh
$HOME/.local/share/terminus-vm/bin/terminus-headless health
```

Prints JSON once the running Terminus proves it is this install. Healthy does not yet mean a pane opened: open one to be sure.

### Prove a pane opens

Call spawn_agent with agent_type "bash" and a folder that exists as cwd. Then send_to_agent with printf '\nTERMINUS_%s\n' 'MCP_OK', and read_agent until you see TERMINUS_MCP_OK on its own line. The command is split on purpose: the echo of what you typed never contains TERMINUS_MCP_OK, so seeing it proves the shell ran it. Keep that transcript as your proof, then close_agent the pane. A spawn that has not answered yet is not a success: do not repeat it blindly.

## 5. Connect to Terminus over MCP

Terminus ships an MCP server (stdio) named `terminus`. `terminus-headless mcp` runs it and finds the Terminus started by `terminus-headless start` on this machine. If you set `TERMINUS_VM_STATE` for `start`, set the same value for `mcp`.

### Claude Code (NOT VERIFIED YET)

```sh
claude mcp add terminus -- $HOME/.local/share/terminus-vm/bin/terminus-headless mcp
```

Uses the MCP bridge packaged with Terminus. No separate Node.js is needed.

### Codex (~/.codex/config.toml)

```toml
[mcp_servers.terminus]
command = "/home/<you>/.local/share/terminus-vm/bin/terminus-headless"
args = ["mcp"]
```

TOML does not expand $HOME: write your real home path. Check it with `codex mcp get terminus`: it should say enabled.

## 6. The tools

| Tool | What it does |
|---|---|
| `list_agents` | Every pane Terminus controls, with status counts and each agent's model. |
| `spawn_agent` | Open a new pane. agent_type is one of claude, codex, antigravity, bash, zsh, powershell, cmd. initial_prompt starts the task. |
| `read_agent` | Read a pane's recent output as plain text. Pass since_cursor to get only what is new. |
| `send_to_agent` | Type a prompt or text into a pane. If a person is typing there, it is held instead (parked=true). Do not resend it. |
| `send_control` | Send a key: interrupt, escape, enter, ctrl-c or ctrl-d. |
| `wait_for_agent` | Wait until a pane stops working (done, needs input, or idle) and get the end of its output. |
| `close_agent` | Close a pane you opened and end its process. It cannot close your own pane. |
| `set_model` | Change the model or effort of a running Claude pane without restarting it. |
| `read_question` | Read a question a Codex pane is asking, with its options. |
| `answer_question` | Answer that exact question. Never a blind default. |
| `inspect_question` | Open a Codex pane's queued question so it can be read. It never answers. |
| `master_wake` | Get told when a worker pane finishes, instead of checking by hand. |
| `show_file` | Open a file in a viewer pane: text, images, video, audio or PDF. |
| `take_agent` | Take control of a pane a person opened. Only when that person asks. |
| `relinquish_agent` | Hand a pane back to the person once its work is done. |
| `get_company_context` | Read the workspace's private notes. With no topic you get the index. |
| `search_company` | Search those notes and get back pointers to the right note. |

### A normal loop

1. `list_agents` — see what is open.
2. `spawn_agent` with `agent_type` and `initial_prompt` — start a pane on a task. It returns a `termId`.
3. `wait_for_agent` with that `termId` — wait until it is done or needs input.
4. `read_agent` — read the output. Pass back the `cursor` you got as `since_cursor` to read only new output next time.
5. `send_to_agent` — answer it or give the next task.
6. `close_agent` — close the pane when you no longer need it.

A spawn confirms the process started, not that the agent accepted the task. Read the output to check.

## 7. Which panes work on Linux

- Terminal (bash): works
- Claude Code: NOT TESTED YET
- Codex: NOT TESTED YET
- Antigravity: NOT TESTED YET

## 8. Safety

- The Terminus backend listens on 127.0.0.1 only, so its control API cannot be reached from another machine.
- send_to_agent never types over a person: while someone is typing in that pane, your text is held (parked=true). Do not send it again.
- close_agent cannot close your own pane.
- take_agent (taking over a pane a person opened) is for when that person asks. The tool says so; it is a rule for you, not a lock.
- answer_question must name the exact question it answers.
- Agent panes run with the permissions of the Linux user that started Terminus. Give that user only what the work needs.
- The virtual display (Xvfb) accepts no network connections and requires xauth. (NOT VERIFIED YET)
- No remote viewer (VNC) is installed. If you add one, require a password, bind it to 127.0.0.1 and reach it through SSH. Never forward the control API to a network. (NOT VERIFIED YET)
- The local control API trusts every process of the same Linux user. It is not a login between users: use one Linux user, or one VM, per person or team you trust. (NOT VERIFIED YET)
- The Chromium sandbox stays on. Terminus never adds --no-sandbox and never changes the machine's security policy for you.
- When the installer is run as root, downloading, extraction and Electron launch move to the unprivileged terminus account; renderer processes do not run as root.
- Close only your own Terminus. Never stop processes by program name. (NOT VERIFIED YET)
- Each agent (Claude Code, Codex, Antigravity) needs its own Linux version installed and signed in inside the VM. Never copy another person's login tokens into it. (NOT VERIFIED YET)
- Your user's rules still apply: ask before sending, publishing, spending or deleting anything on their behalf.

## 9. Troubleshooting

- **The MCP server says it cannot find Terminus.** Terminus is not running, it is running as a different user, or `TERMINUS_VM_STATE` differs between `start` and `mcp`. Run `terminus-headless health`.
- **`xvfb-run` is missing.** Install the `xvfb` and `xauth` packages.
- **"The SUID sandbox helper binary was found, but is not configured correctly".** Electron needs user namespaces or a setuid `chrome-sandbox`, and Ubuntu 23.10 and later restrict the first. Terminus keeps the sandbox on, so a hardened image may need an administrator to allow it. Ask your user. The exact fix is NOT VERIFIED YET.
- **A pane's status is `attention`.** The agent in it is asking something. `read_agent` shows the question.

## 10. Build and test Terminus from source

### Start and check a source checkout

```sh
export TERMINUS_SOURCE_ROOT=$HOME/Terminus
export TERMINUS_VM_STATE=$HOME/.local/state/terminus-vm-dev
bash "$TERMINUS_SOURCE_ROOT/scripts/linux/terminus-headless" start
# In a second shell, with the same two variables set:
bash "$TERMINUS_SOURCE_ROOT/scripts/linux/terminus-headless" health
```

### Run the Linux tests

```sh
cd "$TERMINUS_SOURCE_ROOT" && node node_modules/vitest/vitest.mjs run tests/linux-headless.test.mjs --maxWorkers=2
```

- Use a Linux filesystem and Node.js 22.12 or newer. Never reuse node_modules from Windows: node-pty must be built for Linux.
- The downloaded app does not include the tests. Running them needs a source checkout.
- Size test runs to the question: focused tests while editing, one full run at the end, never two full runs at once.
