# clackatoo

> A fast, lightweight terminal workspace, built around a daemon that owns every terminal.

---

# What is clackatoo

_clackatoo / Getting started_

> clackatoo is a terminal workspace aiming to be very fast and very light. Its daemon owns every terminal, and windows, scripts and agents are all clients of it.

**clackatoo** is a terminal workspace: a terminal emulator that grows into a place to work, with an agent harness built in. Its first priority is to be very fast and very light.

## The daemon owns the terminals

Most terminal emulators are one program: close the window and your shells die with it. In clackatoo, a background **daemon** owns every terminal. That means the pseudo-terminal each program runs on (its PTY), the program itself, and the grid of characters its output builds.

Everything else is a **client** of the daemon and talks to it over one protocol: the window, the `clackatoo` command, and later agents. Clients can be on [another machine](/clackatoo/reference/remote.md), reaching the daemon through ssh. Because the daemon owns the terminals:

- closing a window closes nothing, and reopening it brings everything back;
- a script can do anything a window can, because it uses the same requests;
- an agent is just another client. It can start terminals, type into them and read their screens.

The daemon is one thread waiting on every PTY and connection at once. A program's output goes straight from its PTY into the grid on that thread, with no locks and no handoff between threads.

## What exists today

The daemon; [a window](/clackatoo/reference/window.md) that shows workspaces of tabs and split panes, with Vim-like modes for scrolling, selecting, copying and searching, and a command line for opening panes; and a command-line client that can start terminals, type into them, show their screens and close them. Both clients work with a daemon on [another machine](/clackatoo/reference/remote.md). The [quick start](/clackatoo/getting-started/quick-start.md) walks through both.

clackatoo runs on Linux.

---

# Quick start

_clackatoo / Getting started_

> Build clackatoo, open a window on your shell, and drive terminals from the command line by starting one, typing into it, reading its screen and closing it.

## Build it

clackatoo is a Cargo workspace. Build the `clackatoo` command in release mode:

```sh
cargo build --release -p clackatoo
```

The binary is `target/release/clackatoo`.

## Open a window

```sh
clackatoo --font "JetBrains Mono"
```

That opens a window on your shell, starting the daemon in the background if it isn't running. Leave out `--font` for your system's monospace font, or choose one for good with `clackatoo config font "JetBrains Mono"`. Close the window and the shell keeps running; open another and you're back.

Ctrl+; switches to normal mode, where you scroll, select and search history with Vim's keys, and `i` switches back. From normal mode, `:|` Enter splits the pane with a new terminal on its right, `:-` below it, and `:Terminal` opens one in a new tab. Ctrl+H, Ctrl+J, Ctrl+K and Ctrl+L move between panes, and `:1`, `:2` and so on between tabs. [The window](/clackatoo/reference/window.md) has all its keys.

## Start the daemon yourself

```sh
clackatoo daemon
```

To watch the daemon's log, run it in the foreground instead. It logs to stderr; set `RUST_LOG=debug` for more detail. Only one daemon runs per user: a second one refuses to start. Stopping the daemon (Ctrl-C, or `clackatoo stop`) hangs up every terminal it owns.

## Drive a terminal

From another terminal, start one. `new` prints the new terminal's ID:

```sh
$ clackatoo new
1
```

It runs your `$SHELL` in the current directory, at 80×24. Type a command into it and press Enter with `run`:

```sh
clackatoo run 1 ls --color=always
```

See what it shows with `capture`. Add `--ansi` to keep the colours:

```sh
clackatoo capture 1 --ansi
```

`send` types text without pressing Enter, which is how you drive full-screen programs:

```sh
clackatoo run 1 htop
clackatoo send 1 q
```

List terminals, and close one when you're done:

```sh
clackatoo list
clackatoo close 1
```

Closing a terminal hangs up its program, as closing a terminal window would. A terminal whose program exits stays listed, with its exit status and last screen, until you close it.

## From another machine

With clackatoo built on both machines, and ssh able to log in to the other one without asking for anything:

```sh
clackatoo --remote desktop
```

That opens a window on `desktop`'s workspaces, starting the daemon there if it isn't running. Every command takes `--remote` too, as in `clackatoo list --remote desktop`. If ssh's commands on the other machine can't find clackatoo, [another machine](/clackatoo/reference/remote.md) explains how to point at it.

---

# The clackatoo command

_clackatoo / Reference_

> Every subcommand of clackatoo (daemon, upgrade, stop, bridge, config, language, list, new, edit, run, send, capture, resize, close and ping), their options, and where the daemon keeps its socket.

```
clackatoo [--font <FAMILY>] [--font-size <POINTS>] [--renderer <API>] [--remote <HOST>]
clackatoo <COMMAND> [OPTIONS]
```

With no command, `clackatoo` opens [the window](/clackatoo/reference/window.md), starting the daemon first if none is running. With `--remote`, the daemon is [on another machine](/clackatoo/reference/remote.md).

On failure, every command prints `clackatoo: <what went wrong>` to stderr and exits with status 1.

## daemon

```
clackatoo daemon [--scrollback-compaction <WHEN>]
```

Runs the daemon in the foreground until interrupted. Its log goes to stderr, at the level `RUST_LOG` sets (default `info`). Terminals live exactly as long as the daemon does: when it stops, their programs are hung up. [Upgrading](#upgrade) it doesn't stop it.

`CLACKATOO_AGENT` sets the agent [chats](/clackatoo/reference/agents.md) talk to: a command speaking ACP, split at spaces. By default it's Claude Code's adapter, run with `npx`.

`CLACKATOO_UDP_PORTS` sets the UDP ports connections from other machines may [move onto](/clackatoo/reference/remote.md#moving-onto-udp): a port, a range such as `60000-61000` (the default), or `off`.

| Option | |
|---|---|
| `--scrollback-compaction <WHEN>` | When scrollback is compacted to save memory: `lazy` (the default), `eager` or `off`. See [scrollback](/clackatoo/reference/terminal-emulation.md). |

The daemon keeps its files in a per-user **runtime directory**: `$XDG_RUNTIME_DIR/clackatoo/`, or `/tmp/clackatoo-<uid>/` if `XDG_RUNTIME_DIR` is unset.

| File | |
|---|---|
| `daemon.sock` | The Unix socket clients connect to. |
| `daemon.lock` | Locked while a daemon runs, so only one runs at a time. A second `clackatoo daemon` fails with "a clackatoo daemon is already running". |
| `shell/` | The scripts that make shells [mark their commands](/clackatoo/reference/window.md#commands), written by the daemon when it starts. |

Anyone who can connect to the socket can run commands as you, so the directory must be accessible only by you. The daemon creates it with mode `0700`, and refuses to start if it exists with any group or other permissions, or belongs to someone else.

Unix socket paths are limited to about 100 bytes. A very long `XDG_RUNTIME_DIR` makes the daemon fail with "path must be shorter than SUN_LEN".

A daemon the window starts runs in the background, with its log in `daemon.log` in the runtime directory.

## upgrade

```
clackatoo upgrade
```

Moves the running daemon onto this build of clackatoo without stopping it. Its terminals, the programs in them, their history and the workspaces all carry on. Run it after building or installing a new clackatoo.

The daemon replaces its own program with this one, keeping its process, so the programs in its terminals stay its children. It hands its state over as it goes: everything on screen and in history, the cursor and modes, workspaces and tabs. Every connection drops for a moment. Windows on this machine reconnect by themselves; windows on other machines say the connection was lost, and Enter reconnects them. Recordings stop.

Before handing anything over, the daemon checks that the new program is a clackatoo that can take over from it. If it isn't, or starting it fails, the daemon carries on as it was, and `upgrade` says why; the daemon's log has the details. A daemon won't upgrade to an older build. Daemons from before protocol 11 can't upgrade at all: `upgrade` says so, and `clackatoo stop` is the way to replace them, closing their terminals.

## stop

```
clackatoo stop
```

Stops the daemon, closing every terminal and hanging up their programs. It works on a daemon from any build of clackatoo, which matters because a build refuses to talk to a daemon speaking another version of the protocol: other commands then fail with "the daemon is from another version of clackatoo", and suggest `clackatoo upgrade` where the daemon can upgrade. If no daemon is running, `stop` says so and succeeds.

## config

```
clackatoo config [<NAME> [<VALUE>]] [--unset <NAME>]
```

Shows or changes the window's [settings](/clackatoo/reference/window.md#settings) in `client/startup.toml`:

- With no arguments, prints every setting as the file would hold it, with those the file doesn't set commented out at their defaults.
- With a name, prints that setting's value, without quotes, so scripts can use it.
- With a name and a value, sets it. The value is checked first: `clackatoo config renderer metal` fails, saying which values `renderer` takes.
- With `--unset`, removes the setting, so it takes its default.

Changes keep the rest of the file, comments included, as it was, and the file is replaced all at once, so a window starting at the same moment reads either the old file or the new one.

## language

```
clackatoo language list
clackatoo language install <NAME>... | --all
```

The languages files are [parsed and coloured](/clackatoo/reference/editor.md#languages) in. Each is a pack in `~/.local/share/clackatoo/languages` (or `$XDG_DATA_HOME/clackatoo/languages`).

- `list` lists the languages clackatoo knows where to get, with where each comes from, marking those installed, then any other packs installed.
- `install` downloads each language's grammar and highlight queries, with curl, from the grammar's own repository, at the version clackatoo was tried with, and checks each file is exactly what was tried. `--all` installs every language `list` lists. Installing one again replaces it.

Files opened after a language is installed are parsed in it. A file that was already open is parsed once a window attaches to it again, as when the window is next opened.

## bridge

```
clackatoo bridge [--replace]
```

Passes everything on its standard input to the daemon, and everything the daemon sends to its standard output, starting the daemon first if none is running. It exits when either side hangs up. `--remote` runs it on the other machine through ssh; see [another machine](/clackatoo/reference/remote.md).

| Option | |
|---|---|
| `--replace` | Replace the daemon that's running first, as when it's from another build: [upgrade](#upgrade) it if it can, keeping its terminals, or else stop it, closing them. |

## Client commands

The other commands talk to a running daemon. Each accepts:

| Option | |
|---|---|
| `--socket <PATH>` | Connect to this socket instead of the one in your runtime directory. |
| `--remote <HOST>` | Use the daemon on another machine, through ssh, starting it there if it isn't running. See [another machine](/clackatoo/reference/remote.md). |
| `--remote-command <COMMAND>` | With `--remote`, the command that runs clackatoo there. Default: `clackatoo`, or `CLACKATOO_REMOTE_COMMAND`. |

Terminals are named by the number `new` prints and `list` shows. Numbers are never reused while the daemon runs.

### list

```
clackatoo list
```

One line per terminal: its ID, the PID of its program (`-` for a [chat](/clackatoo/reference/agents.md), which has none), its size, its status (`running`, `exited (<code>)` or `killed (signal <n>)`), and the window title its program set.

### new

```
clackatoo new [--size <COLSxROWS>] [--cwd <DIR>] [--record <FILE>] [-- <COMMAND>...]
```

Starts a terminal and prints its ID.

| Option | |
|---|---|
| `--size <COLSxROWS>` | The terminal's size, like `120x40`. Default `80x24`. Each dimension must be between 1 and 4096. |
| `--cwd <DIR>` | The working directory. Default: the current directory. |
| `--record <FILE>` | Write everything the program prints to `FILE`, as a [recording](/clackatoo/reference/recordings.md). The path is on the daemon's machine. |
| `-- <COMMAND>...` | The program to run, and its arguments. Default: the daemon's `$SHELL`, or `/bin/sh`. |

What a program sees in its environment is described in [terminal emulation](/clackatoo/reference/terminal-emulation.md).

### edit

```
clackatoo edit <PATH>
```

[Edits the file](/clackatoo/reference/editor.md) at `PATH`, from the current directory, in a new tab of the workspace last used, or in a new workspace if there are none. A file already being edited gets another pane on it. The window doesn't switch to the new tab yet.

### run

```
clackatoo run <TERMINAL> <WORDS>...
```

Types the words, joined with spaces, into the terminal, then presses Enter (a carriage return, `\r`).

### send

```
clackatoo send <TERMINAL> <TEXT>
```

Types `TEXT` exactly as given, without pressing Enter. To send control keys, use your shell's escapes: `\e` (Escape) or `\r` (Enter) in fish, `$'\e'` in bash. Fails if the terminal's program has exited.

### capture

```
clackatoo capture <TERMINAL> [--ansi]
```

Prints what the terminal is showing: one line per row, without trailing spaces or trailing blank lines. With `--ansi`, colours and attributes are kept as escape sequences, so printing the result in another terminal reproduces the screen. A terminal whose program has exited still shows its last screen.

### resize

```
clackatoo resize <TERMINAL> <COLSxROWS>
```

Changes the terminal's size, such as `clackatoo resize 1 120x40`. Its program is told (the kernel sends it `SIGWINCH`), and lines on the screen are rewrapped to the new width. A recording started with `new --record` stops here, because a recording has a single size.

### close

```
clackatoo close <TERMINAL>
```

Closes the terminal and forgets it. Its program is hung up (sent `SIGHUP` by the kernel), as when a terminal window closes.

### ping

```
clackatoo ping [--count <N>] [--interval <MS>]
```

Times how long the daemon takes to answer, and how long a key takes to come back from a program, which is most useful with `--remote`. It starts a terminal of its own running `cat`, and for each round:

1. waits, then times a request that does nothing, which is what the connection costs;
2. waits again, then times a key, from sending it to the update that shows `cat`'s echo, which is what typing costs.

With `--remote`, each round also says whether it went [over UDP or through ssh](/clackatoo/reference/remote.md#moving-onto-udp). To the same machine, where the network costs next to nothing:

```
$ clackatoo ping --remote localhost --count 5
connected to localhost in 146.1 ms
PING localhost: a request, then a key echoed by cat
round 1: request 0.168 ms, key echo 0.216 ms (over UDP)
round 2: request 0.147 ms, key echo 0.226 ms (over UDP)
round 3: request 0.158 ms, key echo 0.187 ms (over UDP)
round 4: request 0.147 ms, key echo 0.187 ms (over UDP)
round 5: request 0.151 ms, key echo 0.178 ms (over UDP)

--- localhost ping statistics ---
request   min 0.147 ms, median 0.151 ms, p90 0.168 ms, max 0.168 ms
key echo  min 0.178 ms, median 0.187 ms, p90 0.226 ms, max 0.226 ms
```

The connection time includes ssh logging in. When it's done, ping closes its terminal.

| Option | |
|---|---|
| `-c`, `--count <N>` | How many rounds. Default: 10. |
| `-i`, `--interval <MS>` | The wait before each request and each key, in milliseconds. Default: 200. The first message after a pause is the slowest, because whatever carries it has to wake up, and typing always has pauses, so a short interval flatters the result. |

---

# The window

_clackatoo / Reference_

> clackatoo's window shows your workspaces, their tabs and their panes of terminals, and sends your keys to the focused one. Insert mode types into the program; normal mode moves a cursor of clackatoo's own through history, and runs the window's commands.

The window is a client of the daemon, like the `clackatoo` command. It shows a workspace's terminals, keeps up with them as their programs print, and sends your keys to the one with the focus. Closing the window closes nothing: the terminals keep running in the daemon, and the next window shows them again, laid out as they were.

## Opening it

```sh
clackatoo
```

With no command, `clackatoo` opens a window on your [workspaces](#workspaces-tabs-and-panes): the one last used, at the tab and pane last used. If there are none, it makes one, showing your newest running terminal, such as one started with `clackatoo new`, or else a new terminal running your `$SHELL`. If no daemon is running, it starts one in the background first. Until the daemon answers, the window says it's connecting, and Esc quits. That daemon logs to `daemon.log` in the [runtime directory](/clackatoo/reference/cli.md), and keeps running after the window closes.

The window sizes itself so that its one pane holds 100×30 cells. Resizing the window resizes each pane's terminal to fit, and its program is told the new size. When several windows show one terminal, the terminal takes the size of whichever was last opened on it, resized or focused.

With `--remote <HOST>`, the window shows the workspaces on another machine, through ssh. See [another machine](/clackatoo/reference/remote.md).

| Option | Environment variable | [Setting](#settings) | |
|---|---|---|---|
| `--font <FAMILY>` | `CLACKATOO_FONT` | `font` | The font family. Default: your system's monospace font. |
| `--font-size <POINTS>` | `CLACKATOO_FONT_SIZE` | `font-size` | The text size. Default: 12. |
| `--renderer <API>` | `CLACKATOO_RENDERER` | `renderer` | The graphics API to draw with: `vulkan` (the default) or `opengl`. See [drawing](#drawing). |
| `--remote <HOST>` | | | Use the daemon on another machine. |
| `--remote-command <COMMAND>` | `CLACKATOO_REMOTE_COMMAND` | | The command that runs clackatoo there. Default: `clackatoo`. |

An option on the command line wins over its environment variable, which wins over the setting.

## The frame

The window draws its own frame, in place of the desktop's:

- **The top bar** holds your [workspaces](#workspaces-tabs-and-panes), each with a coloured dot and how many tabs it has (unless its name is just a number, as unnamed workspaces' are), the one shown picked out, and a `+` after them that makes a new one. The window's buttons sit at whichever end your desktop puts them, in the same order. Drag the bar to move the window, double-click it to maximise or restore it, and right-click it for the desktop's window menu.
- **The bottom bar** starts with the focused pane's [mode](#modes), then the tabs of the workspace shown, with a `+` after them that opens a new one. On its right are how far the view is [scrolled](#scrolling) or a message (such as why a command failed), how many panes the tab has, and a reminder of the key to press next. When the tabs need the room, the reminder goes first, then the count of panes.
- **The panes** fill the space between the bars, side by side with a thin line between them. When a tab has more than one, a gold line runs round the focused pane.
- **The palette** opens in the middle of the window for anything typed to clackatoo itself: [commands](#the-command-line), [searches](#searching) and new names.

Drag the window's edges or corners to resize it. Interface text, such as the names in the bars, is in your desktop's interface font. On GNOME, both the font and where the buttons go come from its settings.

With the mouse:

- Click a workspace or tab to show it, and a pane to focus it.
- Click a `+` to make a workspace, or open a tab.
- Right-click a workspace or tab for **Rename** or **Close**. Rename opens the palette with the name to change. Clearing a tab's name names it after its focused pane's program again. Close closes every pane in it, hanging up their programs.

## Settings

Settings you rarely change, or change to tune how the window starts, live in `startup.toml`: `$XDG_CONFIG_HOME/clackatoo/client/startup.toml`, or `~/.config/clackatoo/client/startup.toml`. The window reads it before anything else starts, which takes a few microseconds.

```toml
font = "JetBrains Mono"
font-size = 12
renderer = "opengl"
```

`clackatoo config` shows and changes them, keeping the rest of the file, comments included, as it was:

```sh
clackatoo config renderer opengl   # set one
clackatoo config renderer          # show one
clackatoo config --unset renderer  # back to its default
clackatoo config                   # all of them, and where the file is
```

A mistake in the file never stops the window opening. Each setting with a mistake, such as an unknown name or a value of the wrong kind, is reported on stderr and left at its default, and the rest still apply.

Keys, and what the window does, are configured in Lua instead, in `init.lua` beside `startup.toml`. See [configuration](/clackatoo/reference/config.md).

The settings are on this machine. With [`--remote`](/clackatoo/reference/remote.md), the window's settings come from the machine the window opens on.

A font family is named as fontconfig knows it, as `fc-list : family` prints. Characters the font doesn't have, such as CJK text or emoji, come from fallback fonts, found the same way.

### A daemon from another build

Each build of clackatoo speaks one version of the protocol between windows and the daemon, and refuses a daemon speaking another. When the window finds a daemon from another build, it says so, and what Enter does depends on which is older:

- **An older daemon, from protocol 11 on:** Enter upgrades it. The daemon becomes this build without stopping, so its terminals and the programs in them carry on, and the window opens on them.
- **An older daemon, from before protocol 11:** it's too old to upgrade, so Enter stops it and starts a new one. This closes every terminal in the old daemon, and hangs up their programs.
- **A daemon newer than the window:** the window says to open a window from the newer build instead. It never offers to replace a newer daemon.

Esc quits, leaving the daemon running. [`clackatoo upgrade`](/clackatoo/reference/cli.md#upgrade) and `clackatoo stop` do the same from the command line.

### Losing the daemon

If the connection to the daemon is lost, because the daemon stopped or, [on another machine](/clackatoo/reference/remote.md), the network went, the window says so and why, in place of the terminal:

- Enter reconnects, starting a daemon if none is running. If the workspace shown is still there, the window returns to it, with each pane as it was, in the same mode and scrolled to the same place.
- Esc quits.

When the daemon [upgrades](/clackatoo/reference/cli.md#upgrade), every connection to it drops for a moment. A window whose daemon on this machine is still there afterwards reconnects by itself, without asking.

## Modes

The window is in one of two modes, like Vim:

- **Insert mode**, where it starts. Keys go to the program.
- **Normal mode**. Keys drive clackatoo. The bottom bar starts `NORMAL`, on gold, in place of `INSERT`, on lilac. A visual selection shows `VISUAL` or `V-LINE`, on blue.

Each pane has its own mode.

| Key | In | Does |
|---|---|---|
| Ctrl+; | any | Switches to normal mode: from insert mode, and out of a visual selection, a search or the command line. |
| Shift+Esc or Ctrl+\\ | any | The same. |
| `i` | normal mode | Switches to insert mode, back at the live screen. |
| Ctrl+\\, twice | insert mode | Sends a real Ctrl+\\ to the program. |
| Ctrl+C | either | Interrupts the program. |

Plain Esc always goes to the program, since programs such as Vim and less use it. Ctrl+; costs programs nothing: a terminal has no code to send them for it, so none can be listening for it. It's found by where the key is, so on layouts where `;` takes Shift, such as German, it's Ctrl and the key to the right of L.

## Workspaces, tabs and panes

The window shows your work at three levels:

- **Workspaces**, one for each piece of distinct work, such as a project. Their names run along the [top bar](#the-frame). Often there's just one.
- **Tabs**, inside a workspace. They run along the bottom bar, numbered, and named after the title of their focused pane's program, unless you've named them.
- **Panes**, which a tab is split into, side by side or stacked. Each shows a terminal, a [chat with an agent](/clackatoo/reference/agents.md), or a [file being edited](/clackatoo/reference/editor.md). A split shares its space equally until you [resize it](#resizing-panes).

The daemon keeps all of them, as it keeps terminals. Closing the window closes nothing, and the next window opens on the workspace, tab and pane last used. Each window shows one workspace, so two windows can show two workspaces, or two tabs of one.

A pane closes when its program exits, as in other terminals. A tab closes with its last pane, and a workspace with its last tab. When the last workspace closes, so does the window.

### The command line

In normal mode, `:` opens a command line, in the palette in the middle of the window. Enter runs the command. Esc, or Backspace past the colon, closes it.

| Command | Does |
|---|---|
| `:Terminal` | Opens a terminal in a new tab. |
| `:\|Terminal` | Splits the focused pane, with a new terminal to its right. |
| `:\|<Terminal` | The same, to its left. |
| `:-Terminal` | The same, below it. |
| `:-^Terminal` | The same, above it. |
| `:Agent`, `:\|Agent`… | Opens a [chat with an agent](/clackatoo/reference/agents.md), in a new tab or beside the focused pane. |
| `:Editor PATH`, `:\|Editor PATH`…, `:e PATH` | [Edits the file](/clackatoo/reference/editor.md), in a new tab or beside the focused pane. |
| `:1`, `:2`… | Moves to that tab of the workspace shown. |
| `:workspace NAME`, `:ws NAME` | Switches to the workspace called NAME, making it if there's none. Without a name, makes a new workspace, named with the lowest number no workspace has. |
| `:rename NAME` | Renames the workspace shown. |
| `:close` | Closes the focused pane, hanging up its program. |
| `:q` | The same, unless the pane is the last showing a file with changes that aren't written. `:q!` closes it anyway, throwing them away. |
| `:w`, `:wq`, `:e!`… | Write the focused pane's file, and the rest of Vim's commands for files: see [Editing files](/clackatoo/reference/editor.md#writing). |
| `:reload` | Runs the [config](/clackatoo/reference/config.md) again, from the start. |

The first character of a split says which way the divider runs, `|` upright or `-` flat, and `<` or `^` after it puts the new pane first. A mark alone opens a terminal, so `:|` is `:|Terminal`. Capitalised words name kinds of pane: `Terminal`, `Agent` and `Editor`, which takes the file's path after it. Lowercase words are the window's own commands, and numbers are tabs.

A new terminal starts in the directory that the pane it splits is in. A new tab's terminal starts in the directory of the workspace's focused pane. That's the directory of whatever is in front in that pane, such as a nested shell or an editor that changed directory, or else the shell's own.

Workspace names are unique, and can't contain `@`, which will name workspaces on other machines. If a command fails, such as renaming a workspace to a name another has, the bottom bar says why until the next key.

### Resizing panes

Drag the gap between two panes to move it. It moves a whole column or row at a time, and the programs on either side are told their new size as it goes. A double click on the gap shares that split's space equally again.

In normal mode, Alt+H, Alt+J, Alt+K and Alt+L move the focused pane's edge on that side by a column or row, so Alt+L widens a pane with a pane to its right. A pane with no edge on that side moves its other edge instead, so Alt+L narrows the rightmost pane. No pane gets smaller than one cell.

A new pane in a split gets the split's average share, so a split you never resized stays equal. Sizes are kept by the daemon, like the rest of the layout, and other windows show the change.

### Moving around

In normal mode, Ctrl+H, Ctrl+J, Ctrl+K and Ctrl+L move to the pane to the left, below, above or right. Tabs are `:1`, `:2` and so on, and workspaces `:ws NAME`. [Ctrl+B](#ctrl-b) does all of this in either mode.

You stay in normal mode as you move: the pane you arrive at, including a pane a command just opened, is in normal mode too, and `i` starts typing into it. The pane you left keeps its mode and its place in history.

The window remembers where the focus has been, as Vim's jump list or a browser's history does:

| Keys | In | Does |
|---|---|---|
| Ctrl+O Ctrl+I | normal mode | Back and forward through the panes of the tab shown. |
| Ctrl+Shift+O Ctrl+Shift+I | either mode | Back and forward through everywhere: panes, tabs and workspaces. |

Going back and then moving somewhere new forgets the way forward, and panes that have closed are skipped. Each window has its own history.

### Ctrl+B

Ctrl+B and one more key work in either mode, as tmux's prefix does. While Ctrl+B waits, the bottom bar lists what it takes. Esc, or any other key, cancels. These keys, and the others on this page that the window takes rather than the program, are bindings like any you make, so [the config](/clackatoo/reference/config.md) can change them or take them away.

| Ctrl+B, then | Does |
|---|---|
| `1`–`9` | Moves to that tab. |
| `c` | Opens a new tab. |
| `]` `[` | Moves to the next tab, or the one before, round from the last to the first. |
| `,` | Renames the tab. |
| `p`, then `1`–`9` | Moves to that pane. Each pane shows its number meanwhile, counting left to right and top to bottom. |
| `h` `j` `k` `l` | Moves to the pane to the left, below, above or right. |
| `\|` `-` | Splits the pane, with the new one to the right or below. |
| `a` | Opens a [chat with an agent](/clackatoo/reference/agents.md) to the right. |
| `x` | Closes the pane. |
| `w` | Opens the workspace picker. |
| `o` `i` | Back and forward through the tab's panes, as Ctrl+O and Ctrl+I do in normal mode. |
| Ctrl+B | Sends Ctrl+B to the program. |

The workspace picker lists the workspaces whose names hold what you type, those starting with it first. Up and Down, or Tab and Shift+Tab, pick one, and Enter moves to it. If nothing matches, Enter makes a workspace by the name typed.

## Typing

In insert mode, keys reach the program the way xterm sends them, which nearly every program understands:

- Text types itself. Ctrl with a letter sends its control character (Ctrl+C, Ctrl+D…). Alt sends Escape before the key.
- Enter, Backspace, Tab, Shift+Tab and Escape send their usual codes. Ctrl+Backspace deletes a word in most shells.
- Arrows, Home, End, Insert, Delete, Page Up, Page Down and F1 to F12 send escape sequences, with any modifiers held.
- When a program asks for them, arrows and the number pad send application codes, which Vim and less expect.

Ctrl+Shift+V pastes, and Ctrl+Shift+C copies text [selected with the mouse](#the-mouse); neither reaches the program. If the program has asked for bracketed paste, the text is marked as pasted, so editors don't treat it as typing and shells don't run it line by line. Otherwise newlines are sent as Enter.

Typing while scrolled up returns the view to the live screen.

Text can be typed through an input method, as Chinese, Japanese and Korean are, such as IBus on GNOME. What's being composed shows underlined at the cursor, with the input method's choices beside it, and the text chosen is typed as if keyed. The input method is used in insert mode, the command line and searches; in normal mode, keys are clackatoo's commands, so it's left out.

## Scrolling

The mouse wheel scrolls history in either mode, in the pane under the pointer. On the alternate screen, which full-screen programs use and which has no history, the wheel sends arrow keys instead, so less, Vim and htop scroll themselves. A program that takes [the mouse](#the-mouse) gets the wheel itself.

While the view is scrolled up, the bottom bar's note shows how far, as `↑42`, and new output doesn't move what you're reading.

## The mouse

Clicking a pane focuses it. What else the mouse does depends on the program in the pane.

**Selecting.** Usually the mouse selects text:

- Dragging selects characters. Dragging past the top or bottom of the pane scrolls it.
- A double click selects a word. Paths and URLs count as one word, so `~/src/main.rs` comes whole.
- A triple click selects whole lines.

Letting go copies the selection to the primary selection, and a middle click pastes it, as elsewhere on Linux. Ctrl+Shift+C, or `y` in normal mode, copies it to the clipboard. The selection stays highlighted until the next key or click. In normal mode, a click also moves the cursor there.

**Programs that take the mouse.** Programs such as Neovim, htop and less can ask for clicks, drags and the wheel, and get them while their pane is in insert mode on the live screen. The pointer is then an arrow rather than a text cursor. Hold Shift to select text anyway. A drag stays with the pane it started in, even when the pointer leaves it.

Programs that ask are also told when their pane gains or loses the focus, which Neovim uses to reload files changed meanwhile.

## Normal mode

Normal mode has a cursor of its own, a yellow block, which moves through the terminal's history and screen as one document. It starts where the program's cursor is, and the program's cursor is hidden until you return to insert mode.

| Keys | Move |
|---|---|
| `h` `j` `k` `l`, arrows | Left, down, up, right. Moving up and down returns to the column you started from after passing shorter lines. |
| `w` `b` `e` | To the next word's start, this or the previous word's start, this or the next word's end. Words are runs of letters, digits and `_`, or runs of other characters; both cross line ends. |
| `0` `$`, Home End | The line's first column, its last character. |
| `gg` `G` | The oldest line of history, the screen's last line. |
| `H` `M` `L` | The top, middle and bottom lines in view. |
| Ctrl+D Ctrl+U | Half a screen down or up, view and cursor together. |
| Ctrl+F, Page Down Page Up | A screen down or up. Ctrl+B is [the window's](#ctrl-b). |
| Ctrl+E Ctrl+Y | The view a line down or up, with the cursor kept in it. |

Moving past the top of the view scrolls into history. In normal mode the view holds still while output arrives, so the cursor stays on its text. On the alternate screen the cursor moves over the screen only.

### Selecting and copying

| Keys | Does |
|---|---|
| `v` | Selects characters, from the cursor to wherever it moves. |
| `V` | Selects whole lines. |
| `o` | Moves the other end of the selection. |
| `y`, Ctrl+Shift+C | Copies the selection to the clipboard, and returns to normal mode. |
| `yy`, `Y` | Copies the cursor's line. |
| `y` and a motion | Copies what the motion moves over: `yw` to the next word, `y$` to the line's end, `yj` this line and the next, `ygg` up to the top. |
| `y` and a [text object](#text-objects) | Copies the object around the cursor: `yiw` the word, `yi"` what's in the quotes, `yib` a command's output. |
| Esc | Drops the selection. |

Any motion extends a selection, including `gg`, `G` and searches. What you copy to the clipboard lights up for a moment, so you can see what went. Copied text is what the program printed: a line that wrapped only because it reached the window's edge comes out as one line, blanks at the ends of lines are left out, and there is no newline after the last line, so pasting it into a shell never runs it.

### Text objects

After `y`, or in visual mode, `i` or `a` and one more key pick out a piece of text around the cursor, as in Vim. `i` takes what's inside, `a` takes it all. In visual mode, the object becomes the selection.

| Keys | Picks out |
|---|---|
| `iw` `aw` | A word, and with `a` the blanks after it (or before it, at the end of a line). |
| `iW` `aW` | The same, for anything between blanks. |
| `i"` `a"`, `i'` `a'`, `` i` `` `` a` `` | What's between the quotes around the cursor, or the next pair on its line; with `a`, the quotes too. |
| `i(` `a(`, `i[` `a[`, `i{` `a{`, `i<` `a<` | What's between the brackets around the cursor, across lines; with `a`, the brackets too. The closing bracket works as well. |
| `ib` `ab` | A [command's](#commands) output; with `a`, its prompt and command line too. |

In Vim, `b` is another name for `(`. Here it's the command, the more useful thing in a terminal, and `(` still means parentheses.

### Commands

When the shell marks its prompts and commands, clackatoo knows where each command's prompt and output are, and how it exited.

| Keys | Does |
|---|---|
| `[[` `]]` | Moves to the previous or next command's prompt. |
| `yib` | Copies the output of the command the cursor is in. |
| `yab` | Copies the command with its prompt and output, ready to paste into a bug report. |
| `vib` `vab` | Selects them. |

A command's output runs from its first line to the line before the next prompt, leaving out blank lines at the end. If the command printed nothing, `yib` says `no output`. If the cursor isn't in a command, the bottom bar says `no block here`.

Which shells mark their commands:

- **fish** 4 and later, by itself.
- **bash**, when it's your shell (`$SHELL`) and clackatoo starts it. clackatoo starts it with a startup script of its own, which reads your `~/.bashrc` first, then adds the marks to `PS1`, `PS0` and `PROMPT_COMMAND`. bash started any other way, such as `clackatoo new -- bash`, is left as it is.
- **zsh**, and fish before 4, don't yet.

Commands are kept through an [upgrade](/clackatoo/reference/cli.md#upgrade).

### Searching

| Keys | Does |
|---|---|
| `/` | Searches forward from the cursor. Type the text, shown in the palette, then Enter. Esc cancels. |
| `?` | Searches backward. |
| `n` `N` | Searches again, the same way or the other way. |
| Esc | Clears the highlight on the last match. |

A search ignores case unless the text has a capital letter in it. It covers all of history and wraps around at either end. The cursor moves to the match, which is highlighted. If there's none, the bottom bar says `not found`.

Copying and searching both happen in the daemon, which holds all of history, so neither waits for the window to fetch the lines involved.

## The cursor

In insert mode, the cursor has the shape the program asks for: a block, an underline or a bar, which Vim and shells in vi mode switch between. If the program hasn't asked, it's a bar. In normal mode it's always clackatoo's yellow block. In panes without the focus, and while the window isn't focused, either cursor is drawn as an outline.

## Drawing

The window draws with the GPU, and only when something changed: a quiet terminal costs no CPU. It redraws at most once per frame, so a flood of output costs one frame's drawing per frame.

On a machine with two GPUs, the window uses the one the system lists first, which is the one driving the screen: on a desktop, usually the graphics card the monitors are plugged into, and on a laptop, the integrated GPU. Frames drawn on the other GPU may never reach the screen. To choose differently, set `WGPU_POWER_PREF` to `high` for the faster GPU, or `low` for the one that uses less power.

The window draws with Vulkan, or with OpenGL where Vulkan can't draw. On some laptops with two GPUs, OpenGL opens a window much sooner: to choose between them, Vulkan asks both about themselves, and that can take more than a second, while OpenGL can use the screen's GPU without asking the other. When setting up the GPU takes over half a second, the window says so on stderr, and suggests trying OpenGL:

```sh
clackatoo config renderer opengl
```

If the API chosen can't draw, the window uses the other.

Box-drawing characters (`─` `│` `┌` `╭` `═`…) and block elements (`█` `▄` `░`…) are drawn by clackatoo rather than taken from the font, so that borders in htop, tmux or Vim join up across lines at any size. The diagonal lines come from the font.

A character with others joined to it, such as a letter with accents, an emoji sequence joined with zero-width joiners (👩‍💻), or a keycap (#️⃣), is shaped: the font's own rules decide which glyphs draw it and where they go. An emoji presentation selector (U+FE0F, as in ❤️) draws the character from an emoji font.

## Not supported yet

- Moving panes to another place in the layout.
- Panes from other machines, as `:Terminal@host` will open. Today the window says this isn't built yet.
- Flags (🇬🇧) and emoji with a skin tone (👍🏽) show as their parts: two letters for a flag, and the emoji then the tone. Terminals and programs disagree about how many columns these take, so they're printed as programs count them.
- History lines are shown at the width they were printed at. A line that wrapped in a narrower window doesn't rewrap when the window gets wider.

---

# Agents

_clackatoo / Reference_

> A pane can hold a chat with an agent such as Claude Code instead of a shell. How to open one, how typing and the agent's questions work, what the chat shows, which agent it talks to and what that costs, and what survives closing the window or upgrading.

A pane can hold a chat with an agent, such as Claude Code, instead of a shell. You type a message in the box along the bottom, the agent answers above it, and it works on the files in the pane's directory as it would in its own program.

Your messages are bubbles on the right. The agent's answers run the width of a column down the middle of the pane, with Markdown laid out as it's meant to look: headings, lists, quotes, links, and code in the terminal's font on a background of its own.

## Opening a chat

| | Does |
|---|---|
| Ctrl+B `a` | Opens a chat to the right of the focused pane. |
| `:Agent` | Opens a chat in a new tab. |
| `:\|Agent`, `:-Agent`… | Opens a chat beside the focused pane, as `:|Terminal` opens a terminal. |

A chat works in the directory a new terminal would start in: the one the pane it splits is in, or for a new tab, the one the workspace's focused pane is in. That's where the agent reads and changes files, and runs its commands.

Like any new pane, a chat opens in normal mode: `i` starts typing into it. The first chat starts the agent, which takes a second or two. Until then the chat says it's starting, and a message sent meanwhile waits and goes when it can.

## Typing

In insert mode, what you type goes in the box at the bottom.

| Key | Does |
|---|---|
| Enter | Sends the message. |
| Shift+Enter | Starts a new line in the message. |
| Ctrl+C | Copies what's selected in the message, or else, while the agent answers, asks it to stop. |
| Backspace, Delete | Delete a character, or with Ctrl, a word. |
| Left, Right | Move by a character, or with Ctrl, a word. With Shift, they select. |
| Up, Down, Home, End | Move by a line, or to its start or end. With Shift, they select. |
| Ctrl+A | Selects the whole message. |
| Shift+Tab | Moves the agent on to its next mode. |
| Esc | Normal mode. There's no program in a chat to need Esc. |

Clicking in the box puts the cursor there, dragging selects, and a double or triple click selects a word or a line. A paste goes into the message whole, lines and all, without sending it. Text can be typed through an input method: what's being composed shows in the box, at the cursor, until it's committed.

A message beginning with `/` goes to the agent as typed, which is how agents take their own commands, such as Claude Code's `/compact`.

One message is answered at a time. While the agent answers, the button in the box's corner is a stop button, and Enter keeps what you typed until it's done. Clicking the button with nothing being answered sends the message, as Enter does.

What you've typed but not sent belongs to the window: another window, or this one after it closes, starts with an empty box.

## The agent's settings

Along the bottom of the box are the agent's settings for the chat, as the agent offers them. For Claude Code:

- **On the left, its mode:** how much it does without asking. Manual asks before every change, Accept edits makes edits without asking, Plan plans before it acts, and Auto lets Claude decide what needs asking about. A new chat starts in Auto.
- **On the right, its model and effort**, such as `Sonnet 5.5 · High`.
- **Beside them, how much of the model's context the chat fills**, such as `38k / 200k`.

Click a setting to open its menu, above the box. Up and Down move through it, Enter or a click picks, and Esc or a click elsewhere closes it. Shift+Tab steps through the modes without opening the menu.

The model and effort share a menu:

- **Models by family:** Opus, Fable, Sonnet and Haiku, each with the version it picks and what it's for, and the one Claude Code recommends marked. Picking a family picks its newest version, and keeps up as new ones come out.
- **A family's versions:** the `▸` button at the end of its row, or Right, shows its other versions, to pin the chat to one. Left hides them. A chat on a pinned version opens the menu with that version shown and ticked.
- **Effort on a slider**, from Low to Max: click or drag along it, or with the slider picked out, press Left and Right. It stays open as you slide, so you can try a few. Default, beside it, leaves effort to Claude Code, which picks it for each model.

Models from another agent are grouped only when one choice in a family always means its newest version, as Claude Code's `opus` does. Otherwise they're listed one per row.

A chat keeps the settings you pick. If the agent stops, or the daemon is [upgraded](/clackatoo/reference/cli.md#upgrade), the chat asks for them again as its session reopens.

## Normal mode

In normal mode, keys move the view instead:

| Key | Does |
|---|---|
| `j` `k`, Down Up | A line down or up. |
| Ctrl+D Ctrl+U, Page Down Page Up | Half a view down or up. |
| `gg` `G` | The start or end of the chat. |
| `i`, `a`, Enter | Back to insert mode. |

The mouse wheel scrolls in either mode. While you're at the end, the view follows the answer as it grows. Scrolled up, it stays put, and the bottom bar says so.

## What the chat shows

Below each message you send, what the agent did with it, in order:

- its words
- its thinking, if it shows any, quieter and slanted
- each tool it uses, on a line of its own: `○` while it runs, then `✓` once it has finished, or `✗` if it failed
- each command it runs and each change it makes to a file, on a card of its own

A command's card has the command at its head, after `$`. While the command runs, the card says what it's for. Once it's done, it shows the last 8 lines of its output, which is where what went wrong usually is.

A change's card has what the agent did at its head, such as `Edit Cargo.toml`, and how many lines it adds and removes, such as `+3 −1`. Under that come the lines, with those added on green after `+` and those removed on red after `−`. A new file is all added. A card shows the part of the file changed, not the whole file. Once the change is made, Claude Code also gives 3 lines either side of it, and where in the file it is.

Lines are numbered by where they are in the file: added and unchanged lines by where they are now, and removed lines by where they were. Before the change is made, as while the agent asks to make it, an edit's lines have no numbers yet. A new file is numbered from 1.

Folded, a card shows its first 8 lines (for a command's output, its last 8), each cut off at the card's edge. Click a card with more to show to open it: its lines wrap, to be read whole. Click again to fold it. Whether a card is open belongs to the window.

The daemon keeps the last 32 KB of a command's output, and the first 256 KB of each side of a change.

While the agent works, the turn ends with `Working…`. A turn the agent didn't finish says why: `Stopped.`, or what went wrong. The tab is named after the title the agent gives the conversation.

### When the agent asks

In Manual mode, before it does anything that changes something, such as writing a file or running a command that isn't only reading, the agent asks. Other modes ask about less, and Auto leaves it to Claude. Its question takes the box's place, with an answer on each button: the first way to say yes in the accent colour, and the ways to say no quieter.

Click an answer, or press its number. Esc says no. Ctrl+C says no and stops the answer.

What it asks about follows the agent's own settings. Claude Code reads its usual settings files, so commands you've allowed in Claude Code are allowed here too.

## Which agent

Chats talk to Claude Code. clackatoo speaks ACP, the [Agent Client Protocol](https://agentclientprotocol.com), which Claude Code doesn't speak itself, so the daemon runs Claude Code's ACP adapter, `@agentclientprotocol/claude-agent-acp`, with `npx`. That needs Node.js, and the first chat downloads the adapter. The adapter runs a copy of Claude Code that comes with it, at a version tried with clackatoo, not the `claude` you may have installed.

It uses the account Claude Code is logged in to. Log in once with `claude` in a terminal, and every chat uses that login. With a Claude subscription, chats count towards the subscription's limits, as Claude Code does.

To talk to another agent that speaks ACP, set `CLACKATOO_AGENT` for the daemon to its command, split at spaces. The daemon reads it when it starts.

One agent process serves every chat. Claude Code's adapter starts a Claude Code process for each chat as well, of about 250 MB, and the adapter itself takes about 115 MB. Closing a chat's pane ends its process, and closing the last chat stops the agent.

## What lasts

The daemon keeps chats, as it keeps terminals. Closing the window closes nothing: a chat keeps answering, and the next window shows it.

[Upgrading](/clackatoo/reference/cli.md#upgrade) the daemon keeps each chat's conversation, but stops the agent. An answer that was under way is cut off, and the turn says so. The next message starts the agent again and carries on the same conversation. A chat from a build before chats were kept as turns comes through as one block of its text. Tools from a build before cards come through as lines, without their output or changes. Changes from a build before line numbers come through without them.

If the agent stops on its own, the chat says so, and the next message starts it again, carrying on the same conversation.

When the daemon itself stops, its chats go with its terminals.

`clackatoo capture` prints a chat as text: each message after a `>`, and what the agent said.

## Not yet

- Text in the conversation can't be selected or copied yet: only the message being typed.
- The thinking and tool calls of a turn don't fold away under a "Worked for…" line yet.
- Commands the agent runs show their output once they're done, not as they run. They'll run in terminals beside the chat, which you can watch and take over.
- Claude's 5-hour and weekly usage limits aren't shown.
- Images can't be pasted into a message.
- `/` commands aren't listed or completed as you type them.

---

# Another machine

_clackatoo / Reference_

> With --remote, the window and the clackatoo command use the daemon on another machine, reached with ssh. How it connects, how it moves onto UDP to survive network changes, what the other machine needs, and what happens when the connection drops.

The window and the `clackatoo` command can use the daemon on another machine instead of this one: your desktop from your laptop, or a server. The terminals run there, and keep running when you disconnect.

```sh
clackatoo --remote desktop                  # a window on desktop's workspaces
clackatoo list --remote desktop             # its terminals
clackatoo new --remote jack@server -- htop  # start one there
```

The host is anything `ssh` accepts: a hostname, `user@host`, or a name from your `~/.ssh/config`, where ports, keys and jump hosts belong.

## How it connects

`--remote host` runs `ssh host clackatoo bridge`. On the other machine, the bridge starts the daemon if it isn't running, then passes everything between ssh and the daemon.

Through it, the window or command first checks that the daemon speaks its version of the protocol, as it does on this one. Then the two talk over QUIC, a network protocol that runs several independent streams over one connection, carried inside the ssh connection.

ssh does the logging in, and it's how each end knows the other is genuine. The daemon only takes QUIC connections that come through ssh, and the window only trusts the certificate the daemon sent it through ssh. Anyone who can use the daemon could already log in to that machine as you.

Each terminal's screen updates come on a QUIC stream of their own, apart from everything else, so a lost packet holds up only the terminal it belonged to.

## Moving onto UDP

Once connected, the window or command tries reaching the daemon straight over UDP, at the address ssh reached that machine at. If that works, the connection moves there, out of ssh:

- **It survives your network changing,** such as moving from home Wi-Fi to a phone's hotspot, or a short sleep. ssh's connection dies when that happens; the QUIC connection carries on from the new network, and the window doesn't notice.
- **Keys come back a little faster,** with ssh out of the way.

It finds out with a few probes, small datagrams the daemon sends straight back, while the connection carries on inside ssh, so nothing waits on the attempt. If none comes back within two seconds, as when a firewall blocks UDP, the connection stays inside ssh. If UDP stops getting through later, the connection goes back into ssh after about ten seconds, if ssh's connection is still up, and tries UDP again a minute after.

[`clackatoo ping --remote host`](/clackatoo/reference/cli.md#ping) says which way each round went: `(over UDP)` or `(through ssh)`.

The daemon takes UDP on the first free port from 60000 to 61000, the same range as mosh, when the first connection through ssh arrives. It never takes a new connection over UDP, only packets for connections already set up through ssh. Probes carry a key the daemon only gives out through ssh, and it answers nothing else, so the port gives nothing away to a scan.

### Firewalls

A firewall on the other machine has to let UDP in on those ports. If it already does for mosh, there's nothing to do. Otherwise, with `ufw`:

```sh
sudo ufw allow 60000:61000/udp
```

or with `firewalld`:

```sh
sudo firewall-cmd --permanent --add-port=60000-61000/udp && sudo firewall-cmd --reload
```

To use other ports, or none, set `CLACKATOO_UDP_PORTS` for the daemon on the other machine: a port, a range such as `60100-60200`, or `off` to keep every connection inside ssh. The daemon reads it when it starts, and one started by `--remote` gets it from the remote command:

```sh
clackatoo --remote server --remote-command 'env CLACKATOO_UDP_PORTS=60100-60200 clackatoo'
```

Through a jump host (`ProxyJump`), ssh reached the other machine at an address on its own network, which you usually can't reach directly, so the connection stays inside ssh.

## What the other machine needs

- **clackatoo, built from the same version.** A daemon from another build speaks another protocol, which the window [offers to replace](#a-daemon-from-another-build).
- **clackatoo on the PATH ssh's commands get.** That is often shorter than an interactive shell's, and may leave out `~/.cargo/bin` or `~/.local/bin`. If it isn't there, the error is the remote shell's "command not found". `--remote-command` names it instead:

  ```sh
  clackatoo --remote desktop --remote-command ~/.cargo/bin/clackatoo
  ```

  The command is run by the remote shell, so it can be any command line that ends by running clackatoo, such as `env XDG_RUNTIME_DIR=/tmp/ck clackatoo`. Setting `CLACKATOO_REMOTE_COMMAND` saves typing it each time.

- **A way to log in.** A key loaded into `ssh-agent` logs in without asking anything. Otherwise, the window asks for ssh's password, or the key's passphrase, itself, in place of the terminal, and asks again on every reconnect. It also asks whether to trust a host ssh hasn't seen before. The `clackatoo` command leaves ssh to ask on the terminal it runs in.

- **On a server nobody is logged in to:** systemd removes your runtime directory (`/run/user/<uid>`) when your last session ends, taking the daemon's socket with it. The daemon keeps running, but the next connection can't find it and starts another. Run this once on the server so your runtime directory stays:

  ```sh
  loginctl enable-linger
  ```

## Options

| Option | Environment variable | |
|---|---|---|
| `--remote <HOST>` | | Use the daemon on `HOST`. |
| `--remote-command <COMMAND>` | `CLACKATOO_REMOTE_COMMAND` | The command that runs clackatoo on the other machine. Default: `clackatoo`. |
| | `CLACKATOO_SSH` | The program run as ssh. Default: `ssh`. It is given ssh's options, then `--`, the host and the command. |

clackatoo passes ssh a few options of its own:

- `-T`: no terminal on the other end; the bridge doesn't want one.
- `ConnectTimeout=10`: an unreachable host fails after 10 seconds.
- `ServerAliveInterval=5` and `ServerAliveCountMax=3`: a connection that has died, such as when the laptop sleeps or changes network, is noticed within 15 seconds. A connection that has moved onto UDP carries on without it.

## When the other machine stops answering

If what the window sends goes unanswered for about three seconds, a red notice at the right of the top bar says so: `No contact with desktop for 12 s`, counting up. The window stays as it was, and anything you type waits to be sent. The notice goes as soon as the other machine answers again, as when your laptop finds a network.

An idle window notices within about half a minute, at its next keep-alive.

## When the connection drops

A connection inside ssh drops when ssh's does. One on UDP drops after ten minutes without hearing from the other end, such as a laptop asleep for longer than that. An idle window stays connected: it sends the daemon a few bytes every 25 seconds, which also keeps your router from forgetting the connection.

When the connection drops, the window says so, and why:

- Enter reconnects, to the same terminal if it's still running. The window keeps its mode, its scroll position and the history it fetched.
- Esc quits.

Nothing on the other machine stops when the connection drops: the terminals carry on, and output that arrives meanwhile is there when you reconnect.

## A daemon from another build

If the daemon on the other machine speaks another version of the protocol, the window says so, as it does [on this machine](/clackatoo/reference/window.md#a-daemon-from-another-build). Enter replaces that daemon using the clackatoo installed there: it upgrades it if it can, keeping its terminals, or else stops it, closing them. If that clackatoo is itself from another build, the window says to update it.

The `clackatoo` command doesn't replace anything. On the other machine, `clackatoo upgrade` upgrades its daemon, and `clackatoo stop` stops it.

## Two windows on one terminal

A terminal has one size, so when windows of different sizes show the same terminal, such as your laptop's and your desktop's, they take turns: the terminal takes the size of whichever window last opened on it, was resized or was focused.

## Typing over a slow connection

Every key goes to the other machine and back before it shows, so a connection with 100 ms round trips shows each key 100 ms late. On a local network this is a millisecond or two.

[`clackatoo ping --remote host`](/clackatoo/reference/cli.md#ping) measures it: how long the daemon takes to answer, and how long a key takes to come back from a program. clackatoo's own share is small. To the same machine, where the network costs next to nothing, a key comes back in about a fifth of a millisecond over UDP, and a third through ssh, so nearly all of anything above that is the network.

---

# Configuration

_clackatoo / Reference_

> The window is configured in Lua, with an init.lua that binds keys, listens for key presses, and splits, focuses and closes panes. Where the files go, how a key travels through handlers and bindings, the API so far, and what happens when the config goes wrong.

The window's behaviour is configured in code: an `init.lua`, as Neovim's is, written in [Luau](https://luau.org), a fast dialect of Lua with optional types and a sandbox. Without one, the window works as it always does. Settings needed before the window starts, such as the font, stay in [`startup.toml`](/clackatoo/reference/window.md#settings), beside it.

```lua
-- ~/.config/clackatoo/client/init.lua

-- tmux's split keys, as well as clackatoo's own.
client.keys.map("niv", '<C-b>"', function(ev) ev.pane:split("below") end)
client.keys.map("niv", "<C-b>%", function(ev) ev.pane:split("right") end)

-- Closing a pane takes :close, not a key.
client.keys.unmap("niv", "<C-b>x")

-- x in normal mode closes a chat, and does what it always did elsewhere.
client.keys.map("n", "x", function(ev)
  if ev.pane.kind ~= "chat" then return ev:propagate() end
  ev.pane:close()
end)
```

## Where it goes

```
~/.config/clackatoo/        or $XDG_CONFIG_HOME/clackatoo/
  client/init.lua           the window's config
  client/lua/               modules for it to require
  client/startup.toml       the window's settings, needed before it starts
  lua/                      modules shared with the daemon's config, once it has one
```

`require("theme")` loads `theme.lua` from `client/lua/`, or else `lua/`, or `theme/init.lua` from either. A dot in a name is a directory: `require("keys.tmux")` is `keys/tmux.lua`. Each module runs once, and later requires get what it returned.

The config runs once for the client, the process that opens windows, so its bindings apply in every window. It runs on a thread of its own, alongside the window starting up, and a typical config takes under a millisecond. The window never waits for it to show itself: keys pressed before it has run do what they do without it.

`:reload` runs the config again, from the start. Everything the old config bound or listened for goes, and the window's own keys come back first.

## How a key travels

Each key you press goes through, in order:

1. **Keypress handlers**, added with `on("keypress", …)`, in the order they were added. A handler on a pane only hears keys pressed in that pane.
2. **Bindings**, the window's own and those made with `client.keys.map`.
3. **The pane**: the program in a terminal, the box in a chat, or the pane's normal mode.

A handler lets the key go on unless it calls `ev:stop()`, which ends its journey there. A binding stops its keys unless it calls `ev:propagate()`, which lets them go on to the pane, as if they weren't bound. If the binding took several keys, such as `<C-b>x`, they all go on, in order.

Whether a key goes on is decided when the handler finishes, or first waits for something, as `pane:split` waits for the new pane. Calling `stop` or `propagate` after that is a mistake. A key waits at most 300 milliseconds for the config to decide. After that, it's as if the config had done nothing more: a binding's keys stop there, and a key a handler heard goes on.

## Keys

`client.keys.map(modes, keys, function)` binds keys in one or more modes, replacing what they were bound to. `client.keys.unmap(modes, keys)` takes a binding away, the window's own included.

**Modes** are written as letters, together: `"niv"` is all three of normal, insert and visual. `c` is typing into a pane's prompt, such as a search.

**Keys** are written as in Vim. A character is itself, and `<…>` holds a key with modifiers or a named key:

| Written | Is |
|---|---|
| `<C-b>`, `<A-h>` | Ctrl+B, Alt+H |
| `<C-O>`, `<C-S-o>` | Ctrl+Shift+O. Shift is part of a letter: `<C-o>` is Ctrl+O. |
| `<S-Tab>` | Shift with a named key |
| `<Esc>`, `<CR>`, `<Tab>`, `<BS>`, `<Del>`, `<Insert>`, `<Space>` | Named keys. `<Enter>` and `<Return>` are `<CR>`. |
| `<Up>`, `<Down>`, `<Left>`, `<Right>`, `<Home>`, `<End>`, `<PageUp>`, `<PageDown>`, `<F1>`–`<F12>` | More named keys |
| `<lt>` | `<` |

Several keys in a row, such as `<C-b>"`, make a sequence. A key that starts one waits for the rest as long as it takes, as tmux's prefix does, and a key that matches nothing drops the keys typed so far. So a binding whose keys start a longer binding's, such as `<C-b>` beside `<C-b>x`, can't be reached.

## Events and handles

`client.on("keypress", function)` listens for keys anywhere. Windows, tabs and panes have `:on` too, to listen only there. Each returns a subscription, whose `:off()` stops it. A pane's or tab's handlers go when it closes.

A handler gets the event, `ev`:

| Field | Is |
|---|---|
| `ev.key` | The key, as a binding writes it, such as `<C-b>` or `q` |
| `ev.keys` | For a binding, all of its keys |
| `ev.text` | The text the key types, if any |
| `ev.mode` | The mode's letter |
| `ev.pane`, `ev.window` | Where it was pressed |

Windows, workspaces, tabs and panes are handles: values that stand for one of them, whose fields are read as they are at the time. Reading a pane that has closed is a mistake. Two handles to the same pane are equal.

| | Fields | Methods |
|---|---|---|
| `client` | `windows`, `focused` (the focused pane) | `on`, `keys.map`, `keys.unmap` |
| A window | `workspace`, `tab`, `pane` | `on` |
| A workspace | `name`, `tabs` | `on` |
| A tab | `name`, `panes`, `focused`, `workspace` | `on` |
| A pane | `kind` (`"terminal"`, `"chat"` or `"editor"`), `title`, `tab` | `on`, `split`, `close`, `focus` |

`pane:split(side)` puts a new pane on that side, `"right"`, `"left"`, `"below"` or `"above"`, of the same kind, or another with `{ kind = "chat" }`. An [editor](/clackatoo/reference/editor.md) can't be made from the config yet, so splitting one needs a kind. It waits for the daemon to make it, then focuses it, as Ctrl+B `|` does, and returns it:

```lua
client.keys.map("n", "<C-b>t", function(ev)
  local below = ev.pane:split("below")
  below:split("right")
end)
```

While it waits, the window carries on, and so does the key. Each handler and binding runs in a coroutine, a function that can pause partway and carry on later, so waiting holds up nothing else.

Nothing is shown while `init.lua` first runs, so `client.focused` is nil then: handles are for handlers.

## When it goes wrong

A mistake, such as a key that doesn't exist, a mode that isn't one, or a pane that has closed, is shown in the bottom bar, with the file and line, until the next key, and printed on stderr in full. A mistake in `init.lua` itself stops it there, as an error in any Lua program does: what it bound before that line stays bound.

A handler or binding that runs for more than a quarter of a second at a time, as a loop that never ends would, is stopped there, and its key is decided as if it had done nothing more.

`print` writes to stderr.

## Not yet

- The daemon has no config yet.
- `init.lua` isn't run again when it changes: use `:reload`.
- Bindings made in Lua don't show in Ctrl+B's list in the bottom bar.
- Keypresses are the only event.
- There are no type definitions for editors to complete the API with.

---

# Editing files

_clackatoo / Reference_

> An editor pane edits a file with Vim's keys. The daemon keeps the file's text and its undo history, so every window edits the same text, agents' edits and yours meet, and nothing is lost when the window closes or the daemon upgrades. How to open and write files, the languages files are coloured in, the keys, and what doesn't work yet.

An editor pane edits one file, with Vim's keys. Its text lives in the daemon, in a buffer, as a terminal's screen does, and each window showing it keeps a copy that it changes as you type, so a key never waits for the daemon.

```
:e src/main.rs          edit a file in a new tab
:|Editor src/main.rs    the same, beside the focused pane
:w                      write it
:q                      close the pane
```

## Opening a file

| Command | Does |
|---|---|
| `:e PATH`, `:edit PATH`, `:Editor PATH` | Opens the file in a new tab. |
| `:\|Editor PATH`, `:\|<Editor PATH`, `:-Editor PATH`, `:-^Editor PATH` | Opens it beside the focused pane: right, left, below or above, as the [split marks](/clackatoo/reference/window.md#the-command-line) do for terminals. |
| `clackatoo edit PATH` | From a shell: opens it in a new tab of the workspace last used. See [the CLI](/clackatoo/reference/cli.md#edit). |

A relative path is from the focused pane's directory: for a terminal, the directory of the program in front, as for a new terminal; for an editor, the directory its file was opened from. `~/` is your home directory.

A file that's already open is shown again, not read again: two panes on one file are two views of one buffer, each with its own cursor. A file that doesn't exist yet opens empty, and is made when it's first written. A directory, or a file that isn't UTF-8 text, can't be opened, and the bottom bar says so.

## Buffers

The daemon keeps each buffer, as it keeps terminals:

- **Every window edits the same text.** Edits made in one window, or by anyone else, show in the others, and move their cursors with the text around them.
- **Edits made at once meet.** If an agent edits a file while you type in it, both edits are kept, and every copy ends up with the same text. The daemon puts edits in order, and brings each one up to date past those made since, by operational transformation, as Google Docs does.
- **Undo undoes your edits, not anyone else's.** `u` takes back your last change, from whichever window you made it in, and leaves an agent's edits made since in place.
- **Nothing is lost when the window closes, or the daemon [upgrades](/clackatoo/reference/cli.md#upgrade).** Buffers come through an upgrade with their undo history.

Each command in normal mode is one undo step, as is each go of insert mode, with the command that started it, such as `cw`.

A buffer closes with its last pane, unless it has changes that aren't written. `:q` won't close that pane, and `:q!` throws the changes away. A pane closed another way, such as with Ctrl+B `x` or by closing its tab, keeps the buffer's changes, unseen: opening the file again shows them.

## Languages

A file in a language that's installed is coloured as the language's grammar reads it: keywords, strings, comments, functions, types and so on. Install languages with [`clackatoo language`](/clackatoo/reference/cli.md#language):

```
clackatoo language list            what can be installed, and what is
clackatoo language install rust    install one, or several
clackatoo language install --all   install every one
```

clackatoo knows where to get Bash, C, CSS, Go, HTML, JavaScript, JSON, Lua, Python, Rust, TOML, TypeScript (and TSX) and YAML. A file's language is found from its name: its extension, such as `.rs`, or the whole name, such as `.bashrc`.

How it works:

- **Grammars are [tree-sitter](https://tree-sitter.github.io)'s**, compiled to WebAssembly by their authors, and run in the daemon by [wasmtime](https://wasmtime.dev). A grammar can't reach anything outside itself, so one that goes wrong can't take the daemon, and every terminal it keeps, with it. Nor does a new language need a new clackatoo.
- **The daemon parses each file once, whichever windows show it**, on a thread of its own, so typing never waits for a parse. After an edit, only what it changed is read again: about a tenth of a millisecond in a big source file. The colours of text you've just typed follow within a frame or so; until then, the colours already there move with the text.
- **Opening a file reads it all**, which takes about 50 ms for an 80 KB file in a language not used since the daemon started, most of it getting the grammar ready, and less after.

### Language packs

Each language is a directory in `~/.local/share/clackatoo/languages` (or `$XDG_DATA_HOME/clackatoo/languages`), which `clackatoo language install` writes, and you can write yourself for a language clackatoo doesn't know:

| File | |
|---|---|
| `language.toml` | `grammar`: the grammar's name, which the WebAssembly is built with (`tree_sitter_NAME`). `extensions`: the endings of the names of files it's for, without the dot. `filenames`: whole names of files it's for. |
| `grammar.wasm` | The grammar, as `tree-sitter build --wasm` makes it. Many grammars' releases have one. |
| `highlights.scm` | The [query](https://tree-sitter.github.io/tree-sitter/using-parsers/queries/index.html) naming what each piece of text is, as tree-sitter's own highlighter reads it: where two patterns name the same text, the first wins. |

```toml
grammar = "rust"
extensions = ["rs"]
filenames = []
```

A query's names are coloured by the first part: `@function.method.call` as a function, `@string.escape` as an escape. A pack the daemon can't load is passed over, and its log says why.

## Writing

| Command | Does |
|---|---|
| `:w`, `:write` | Writes the file. |
| `:w!` | Writes it even if it's changed on disk since it was read or last written. |
| `:wq`, `:x` | Writes it, then closes the pane. `:wq!` writes it even if it's changed. |
| `:q`, `:quit` | Closes the pane, unless it's the last showing a file with changes not written. |
| `:q!` | Closes the pane, throwing away the file's changes not written. |
| `:e!` | Throws away the changes not written, reading the file again. That's an edit like any other: `u` brings the changes back. |

Writing writes a new file beside the old one, with its permissions, then renames it over the old one, so the file is never half written. If something else has changed the file since it was read or last written, `:w` refuses, so as not to write over the change; `:w!` writes over it. The bottom bar shows `[+]` after the file's name while it has changes that aren't written.

## Keys

An editor starts in normal mode. The bottom bar shows the mode, the file's name, and the cursor's line and column.

### Moving

| Keys | Moves to |
|---|---|
| `h` `j` `k` `l`, the arrows | The next character or line. A count moves that many. |
| `w` `b` `e` | The start of the next word, the start of this one or the one before, the end of this one or the next. |
| `W` `B` `E` | The same, for anything between blanks. |
| `0` `^` `$` | The line's start, its first character that isn't blank, its last character. |
| `gg` `G` | The first line, the last. `12G` is line 12. |
| `f` `t` `F` `T` and a character | The next one on the line, or the character before it with `t`; back with `F` and `T`. `;` does it again, and `,` back. |
| `/` `?` | Searches forward or back. `n` and `N` find the next and previous match, going round the file's ends. Case is ignored unless the search has a capital letter. |
| Ctrl+D, Ctrl+U | Half a page down or up. |
| Ctrl+F, Page Down, Page Up | A page down, or up with Page Up. |
| `zt` `zz` `zb` | Scrolls the cursor's line to the top, middle or bottom. |

### Changing

| Keys | Does |
|---|---|
| `i` `a` `I` `A` | Insert mode before or after the cursor, at the line's first character that isn't blank, at its end. |
| `o` `O` | Insert mode on a new line below or above, indented as the cursor's line is. |
| `d`, `c`, `y` and a motion | Deletes, changes or copies what the motion moves over: `dw`, `c$`, `y2j`. |
| `d`, `c`, `y` and an object | The same, for a [text object](/clackatoo/reference/window.md#text-objects): `diw`, `ci"`, `ya(`. |
| `dd` `cc` `yy` | Whole lines: the cursor's, and with a count, those after it. |
| `x` `X` | Deletes the character under the cursor, or before it. |
| `D` `C` `Y` | Deletes or changes to the line's end, or copies the line. |
| `s` `S` | Changes the character under the cursor, or the line. |
| `r` and a character | Replaces the character under the cursor with it. |
| `J` | Joins the next line onto the cursor's, with a space. |
| `p` `P` | Puts what was last deleted or copied after the cursor, or before. Lines go on the line below or above. |
| `u` Ctrl+R | Undoes your last change, or redoes the last undone. |
| `.` | Makes the last change again, with what was typed in insert mode after it. A count makes it that many times. |
| `v` `V` | Visual mode, by characters or whole lines. `d`, `x`, `c`, `s` and `y` act on what's selected, `o` goes to its other end, and `i` or `a` and an object selects the object. |

`cw` changes to the end of the word, not up to the next word, as in Vim.

What `y` copies goes on the clipboard too. What `d` and `c` delete only goes where `p` puts from, so deleting doesn't lose what you copied from somewhere else.

### Insert mode

Keys type into the file. Esc, or Ctrl+; as in a terminal, goes back to normal mode.

| Keys | Does |
|---|---|
| Enter | A new line, indented as the line before. |
| Tab | Indents to the next multiple of 4 columns, with spaces, or with a tab on a line indented with tabs. |
| Backspace, Ctrl+H | Deletes the character before the cursor, joining the line to the one before at its start. |
| Delete | Deletes the character under the cursor. |
| Ctrl+W | Deletes the word before the cursor. |
| Ctrl+U | Deletes back to the end of the line's indent, or from there, to the line's start. |
| The arrows, Home, End | Move, and start a new undo step. |

A count before `i`, `a` or `o` types what's typed that many times: `3ix` Esc types `xxx`.

### The mouse

A click puts the cursor where it's clicked, and the wheel scrolls.

## Not yet

- Everything a language server gives: errors, completion, going to definitions. Language servers will run in the daemon.
- Text objects and indenting by the grammar, such as `af` for a function.
- Languages inside others, such as code in a Markdown file, or CSS in HTML: they're coloured as the outer language only. So there's no Markdown yet.
- Choosing the colours. They're the palette's named colours where there's one to suit, so they follow the terminal's.
- Line numbers. Long lines don't wrap: the view scrolls sideways to follow the cursor. A tab is always 4 columns wide.
- `$` and `l` stop at a line's last character that isn't blank, as on a terminal's screen, so blanks at a line's end can be reached only with `A` and `D`.
- Files with Windows line endings show `^M` at each line's end.
- `:e` opens a new tab, rather than replacing the file the pane shows, as Vim's does.
- `clackatoo edit` opens the tab, but the window doesn't switch to it.
- Only one register; no macros, marks, `:s`, `>>`, `~` or `%`.
- Dragging the mouse to select.
- A change typed just as the connection to the daemon drops can be lost.

---

# Terminal emulation

_clackatoo / Reference_

> The terminal clackatoo emulates. What programs find in their environment, which escape sequences and modes it supports, which queries it answers, and what it ignores so far.

clackatoo emulates enough of xterm for shells, ncurses programs such as htop, and vim, under `TERM=xterm-256color`. Sequences it doesn't support are ignored, as xterm ignores ones it doesn't know.

## The environment

A program started in a clackatoo terminal gets the daemon's environment, with these changes:

| Variable | Value |
|---|---|
| `TERM` | `xterm-256color` |
| `COLORTERM` | `truecolor` |
| `TERM_PROGRAM` | `clackatoo` |
| `TERM_PROGRAM_VERSION` | The clackatoo version. |

Variables describing whatever terminal the daemon was started in are removed, so programs don't think they're inside it. These include `TMUX`, `TMUX_PANE`, `STY`, `VTE_VERSION`, `KITTY_WINDOW_ID`, `WEZTERM_PANE`, `ALACRITTY_WINDOW_ID`, `ITERM_SESSION_ID`, `WT_SESSION`, `COLUMNS` and `LINES`.

The program runs in its own session, with the terminal as its controlling terminal, so job control and Ctrl-C work as usual.

## Screen behaviour

- **Scrollback**: the primary screen keeps the last 10,000 lines that scroll off its top. Lines only enter scrollback when the whole screen scrolls. Scrolling inside a region an app has narrowed (as vim does) discards them. Scrollback is stored as compact text, typically under 100 bytes a line, and remembers which lines were wrapped, so they can be rewrapped to another width. Compacting a line takes about as long as printing it did, so by default the daemon waits until a terminal's output has paused for 100 ms. Lines that scroll away during a flood (`cat` of a huge file) are never compacted at all. Until then, history keeps lines as they were on screen, about 8 bytes per column, capped at 16 MiB per terminal. The daemon's `--scrollback-compaction` option changes this: `eager` compacts each line as it scrolls off, which is the smallest but the slowest, and `off` never compacts, which is the fastest but the largest.
- **Resizing**: lines on the primary screen are rewrapped to the new width, and the cursor stays on the same character. When the screen gets shorter, blank lines below the cursor go first, then lines above it move into scrollback. The alternate screen is cut off or extended instead, because the full-screen programs that use it redraw after a resize.
- **Alternate screen**: full-screen programs switch to a second screen with no scrollback. Leaving it restores the primary screen and cursor exactly.
- **Wrapping**: printing in the last column leaves the cursor there, and the line wraps only when the next character arrives.
- **Background colour erase**: clearing part of the screen fills it with the current background colour.
- **Wide characters** take two columns. A wide character that doesn't fit at the end of a line wraps to the next one.
- **Combining characters** (accents, variation selectors) join the character before them. So do characters after a zero-width joiner, so emoji sequences such as 👩‍💻 stay one character.

## Supported sequences

| Kind | Supported |
|---|---|
| Control characters | BS, HT, LF, VT, FF, CR, SO, SI. BEL is accepted and ignored. |
| `ESC` | `7` `8` (save/restore cursor), `D` (index), `E` (next line), `H` (set tab stop), `M` (reverse index), `c` (full reset), `#8` (screen alignment test), `(` and `)` with `B` (ASCII) or `0` (DEC line drawing) |
| `CSI` cursor | `A` `B` `C` `D` `E` `F` `G` `H` `I` `Z` `` ` `` `a` `d` `e` `f`, and `s` `u` without parameters |
| `CSI` editing | `@` `J` (including `3J` to clear scrollback) `K` `L` `M` `P` `S` `T` `X` `b` (repeat) |
| `CSI` other | `g` (clear tab stops), `r` (scroll region), `m` (SGR), `!p` (soft reset), ` q` (cursor shape: 0 the user's default, 1 and 2 block, 3 and 4 underline, 5 and 6 bar; blinking isn't drawn) |
| `ESC` keypad | `=` and `>` (application and numeric keypad) |
| ANSI modes (`CSI h`/`l`) | 4 (insert), 20 (line feed is newline) |
| DEC modes (`CSI ? h`/`l`) | 1 (application cursor keys), 6 (origin), 7 (autowrap), 25 (show cursor), 47, 1047 and 1049 (alternate screen), 66 (application keypad), 1048 (save cursor), 2004 (bracketed paste), 9, 1000, 1002 and 1003 (mouse tracking), 1005, 1006 and 1015 (mouse encodings), 1004 (focus events) |
| SGR | Bold, dim, italic, blink, inverse, hidden, strikethrough and their resets. Underlines: single, double (`21` or `4:2`), curly (`4:3`), dotted (`4:4`), dashed (`4:5`). 8, 16, 256 and 24-bit colours for text, background and underline (`58`), in both the `;` and `:` spellings. |
| `OSC` | 0 and 2 (window title), 7 (working directory, as a `file://` URL), 133 (shell integration: `A` prompt, `C` output, with an optional `cmdline_url=`, and `D` exit status; see [commands](/clackatoo/reference/window.md#commands)) |

## Queries answered

| Query | Reply |
|---|---|
| `CSI c` (primary device attributes) | `CSI ? 62 ; 22 c`: a VT220 with colour |
| `CSI 5 n` (status) | `CSI 0 n` |
| `CSI 6 n` and `CSI ? 6 n` (cursor position) | `CSI <line> ; <col> R`, with `?` for the second form. Relative to the scroll region in origin mode. |
| `CSI > q` (terminal version) | `DCS > \| clackatoo <version> ST` |

## Not supported yet

These are ignored:

- OSC 8 (hyperlinks) and OSC 52 (clipboard).
- Secondary device attributes (`CSI > c`) and the kitty keyboard protocol.
- Left and right margins (`CSI s` with parameters).
- Variation selector 16 does not widen the character before it.

---

# Recordings

_clackatoo / Reference_

> The recording format clackatoo new --record writes. It's a text header with the terminal's size, then the raw output, and replaying it rebuilds the same screen.

`clackatoo new --record <FILE>` saves everything the terminal's program prints. Replaying a recording through clackatoo's emulator, at the recorded size, rebuilds the same screen every time, however the bytes are split up. clackatoo's tests are built on recordings of real programs, and its throughput benchmarks replay one too.

## Format

A single header line, then the program's output, byte for byte:

```text
clackatoo-recording v1 cols=80 rows=24
<raw output…>
```

The header is UTF-8 text ending in a newline. It starts with `clackatoo-recording v1`, followed by space-separated `key=value` fields:

| Field | |
|---|---|
| `cols` | The terminal's width. Required. |
| `rows` | The terminal's height. Required. |

Readers ignore fields they don't know, so later versions can add fields without breaking v1 readers. Everything after the header's newline is output, including any further newlines.

The output is written as it arrives, so a recording can be read while the program is still running. Input typed into the terminal is not recorded. A recording has one size, so resizing the terminal stops it.

## Privacy

A recording contains everything the program displayed: paths, hostnames, file contents and anything else that appeared on screen. Check a recording before sharing it.

---

# Benchmarks

_clackatoo / Development_

> Run clackatoo's benchmarks, read the results, and compare them across changes. Emulator throughput is measured next to alacritty_terminal, and an end-to-end harness measures the real binary's startup, latency, throughput, memory and idle wakeups, and its window.

clackatoo's goal is to be very fast and very light, so speed is measured, not assumed. There are two suites:

- **Emulator throughput** feeds terminal output straight into `clackatoo-term`, the crate that turns output into a grid of cells, and into `alacritty_terminal`, the emulator inside Alacritty, so the two can be compared on the same machine.
- **End to end** starts the real release binary and drives it the way a user would: it starts daemons, opens terminals and windows, types into them and floods them with output.

Both compare each run with the previous one, so a regression shows up as a number.

## Run everything

```sh
cargo bench
```

That builds in release mode and runs both suites, which takes a few minutes. To run one suite on its own:

```sh
cargo bench -p clackatoo-bench          # emulator throughput
cargo bench -p clackatoo --bench e2e    # end to end
```

Close anything busy first, such as builds, browsers playing video, or other benchmarks. A desktop's timings wander by a few percent anyway, with CPU frequency scaling and background load, and both suites treat changes under 5% as noise.

## Workloads

Both suites use the same workloads. They are generated in code from a fixed seed, so every run sees identical bytes and nothing large is committed. Each one leans on a different part of the emulator:

| Workload | Stands for |
|---|---|
| `ascii` | Plain text scrolling, like `cat` of a log. Lines vary in length, and some wrap. |
| `highlight` | Words in 24-bit colours, some bold, like syntax-highlighted code. |
| `dense` | A different style in every cell, redrawing the whole screen. The worst case for style handling. |
| `region` | Scrolling inside a scroll region, the way pagers and editors scroll part of the screen. |
| `unicode` | Wide CJK characters and emoji, combining accents, and emoji joined with ZWJ (zero width joiner). |
| `cursor` | Short writes at random places, sometimes recoloured, like a full-screen program redrawing. |
| `vim` | A recorded vim session (open, edit, quit), repeated. Emulator suite only. |

Generated workloads play in a 120x40 terminal, a typical window. The vim recording plays at the size it was recorded at.

## Emulator throughput

Each benchmark feeds a workload to one emulator in 64 KiB pieces, the size the daemon reads from a PTY at a time. The emulator is reused from one iteration to the next, so its 10,000-line scrollback is full and every new line pushes an old one out. That's the steady state of a terminal that has been open a while. Neither emulator does any I/O or drawing.

For each workload there are three results: `clackatoo`, `clackatoo-eager` and `alacritty`. A flood never pauses, so by default clackatoo never compacts its scrollback during the benchmark, much as alacritty never does. `clackatoo-eager` compacts every line as it scrolls off, so the cost of compacting stays measured.

```text
ascii/clackatoo         time:   [23.467 ms 23.522 ms 23.601 ms]
                        thrpt:  [84.745 MiB/s 85.029 MiB/s 85.228 MiB/s]
ascii/alacritty         time:   [15.675 ms 15.724 ms 15.780 ms]
                        thrpt:  [126.74 MiB/s 127.20 MiB/s 127.60 MiB/s]
```

`thrpt` is throughput: how much output the emulator gets through per second. The middle number is the best estimate, and the outer two bound it. The benchmarks use [criterion](https://github.com/criterion-rs/criterion.rs), which also prints how each result changed since the last run.

Before benchmarking a workload, the suite plays it through both emulators and checks they end up showing the same screen. Otherwise the two wouldn't be doing the same work. One difference is expected: alacritty draws a ZWJ emoji sequence such as 👩‍💻 as two separate emoji, while clackatoo joins it into one, as kitty, WezTerm and Ghostty do. So on `unicode`, alacritty's lines wrap sooner, and the suite prints a note saying so. Any other difference prints a warning with both screens.

alacritty also records which lines changed as it goes, for its renderer. clackatoo doesn't do that yet, so expect the gap to narrow a little once it does.

## End to end

The harness runs as a bench target of the `clackatoo` package. That way, cargo always builds a fresh release binary before measuring it. Every daemon it starts gets a temporary runtime directory of its own, so it never touches a daemon you are using. It prints one table:

```text
clackatoo end-to-end benchmarks
binary: /home/you/clackatoo/target/release/clackatoo
compared with baseline "base": commit d8c3b98, saved 2 hours ago

startup
  daemon start to accepting clients            677.2 µs  ±10.1%  -2.0% (noise)
```

Each line shows the median of several runs, then the spread: half the distance between the 10th and 90th percentiles, as a percentage of the median. The last column compares the result with the baseline.

What each group measures:

| Group | Measurement | How |
|---|---|---|
| `size` | Release binary | The size of `target/release/clackatoo`. |
| `startup` | Daemon start to accepting clients | From launching `clackatoo daemon` until a client can connect and say hello. |
| | CLI command | `clackatoo list`, from launch to exit, against a running daemon. |
| | New terminal | From asking for a terminal until it's running. |
| | New terminal to first text | Until your `$SHELL` shows any text, usually its prompt. Mostly the shell's own startup time. |
| `latency` | Keypress to echo in the grid | A program that echoes keys runs in a terminal. One key is sent, and the time is measured until a screen capture shows the echoed key. |
| | Screen capture round trip | One screen capture request and response. |
| | Keypress to view update | The same, following the terminal's view as the window does: timed until the update showing the echo arrives. This is the part of keypress-to-screen latency the daemon owns. |
| | Keypress to view update, while another floods | The same while a second terminal prints without end, which must not hold up the one being typed in. |
| | Keypress to view update, through the bridge | The same through `clackatoo bridge`, as [`--remote`](/clackatoo/reference/remote.md) connects, over QUIC, but with no ssh or network in between: what the bridge and QUIC add. |
| | Keypress to view update, through the bridge, while a followed terminal floods | The same while a second terminal the client follows prints without end, so its updates share the connection with the echoes. |
| `throughput` | Each workload | A script prints 16 MiB of the workload into a terminal. Timed from starting it until the daemon has processed everything. |
| | Bare PTY | The same script, read straight from a PTY and thrown away. This is the most any terminal could manage on this machine. |
| `cpu` | Daemon CPU per MiB | For each throughput workload, the CPU time the daemon spent per MiB of output, read to the nanosecond from `/proc`. Throughput stops at the PTY's limit, so this is where the daemon's own speed shows. |
| | Bare PTY reader per MiB | The CPU time the bare PTY reader spends per MiB: the floor, nearly all of it in the kernel's TTY layer. |
| `memory` | Daemon at rest | The daemon's resident memory with no terminals. |
| | Per terminal | The growth from opening 20 empty terminals, divided by 20. The programs running in them aren't counted. |
| | Per scrollback line | The growth from filling an 80-column terminal's scrollback with full lines, the worst case, divided by the number of lines. Measured once the daemon has compacted the scrollback, 300 ms after the output ends. |
| | Per scrollback line, before compaction | The same, measured as soon as the output ends. |
| | Per scrollback line, typical | The same as the first, with lines of varying length, averaging half the width. |
| `idle` | Wakeups | How often the daemon wakes up per second, with a client connected and three quiet terminals, one of which printed some history before going quiet. This should be zero. It is counted from the context switches in `/proc`. |
| `window` | Start to first frame | From starting `clackatoo` to the window's first frame, with the daemon already running. Most of it is the GPU driver starting up. |
| | Frame drawn, during a flood | How long the window takes over each frame, from starting on it to handing it to the compositor, while its terminal prints for two seconds without stopping. |
| | Wakeups of the window's own threads | How often the window wakes up per second with nothing to draw. This should be zero. |
| | Wakeups, the GPU driver's threads too | The same, counting threads the GPU driver started inside the window. Some drivers wake up on their own: NVIDIA's Vulkan driver about 110 times a second, even with no window at all. |

Most groups learn that a terminal has caught up by polling it. Throughput runs watch the terminal's title, which the script sets when it finishes, every 200 µs. The first keypress test captures the screen back to back, so its result includes up to one capture round trip on top of the true latency; the view-update tests don't poll.

The `window` group opens real windows, so it needs a display. Without one (no `WAYLAND_DISPLAY` or `DISPLAY`), it says so and is skipped. The windows open on the harness's own daemons, never on yours. The window logs its first frame, and how long each frame took when started with `RUST_LOG=clackatoo_gui::frame=trace`; the harness reads those from its output.

To run some groups only, name them:

```sh
cargo bench -p clackatoo --bench e2e -- startup memory
```

To measure a different build, such as another branch's, point the harness at its binary:

```sh
cargo bench -p clackatoo --bench e2e -- --binary ../other/target/release/clackatoo
```

## Compare changes

Both suites work like criterion's baselines:

- By default, each run is compared with the previous run and replaces it.
- `--save-baseline NAME` compares with the baseline called `NAME` and replaces it.
- `--baseline NAME` compares with `NAME` without saving anything.

To measure a change, save a baseline before making it, then compare against it afterwards:

```sh
cargo bench -- --save-baseline before
# … make the change …
cargo bench -- --baseline before
```

A change counts when it is bigger than 5% and outside both runs' spreads. Otherwise it's marked `(noise)`. Measurements that don't vary, like the binary's size, report any change.

Criterion keeps its baselines in `target/criterion/`. The end-to-end harness keeps its own in `target/clackatoo-bench/` as tab-separated files, along with the commit they were measured on.

## Profile a workload

To see where the time goes, replay one workload through the emulator in a loop and point a profiler at it:

```sh
cargo run --release -p clackatoo-bench --example replay -- dense 20
```

That plays `dense` for 20 seconds and prints its throughput. Release builds use link-time optimisation, so build with `CARGO_PROFILE_RELEASE_DEBUG=true` to get the function names back in a profiler.
