mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
Every "how did the last command end?" query went through `| tail -n 5`, which ships the whole grid down a pipe to throw most of it away — and on Windows, where `capture` is just as useful, there is no `tail(1)` to pipe to. `--tail N` keeps the last N lines of the answer instead. It is a third independent choice beside `--scrollback` (how much of the ring) and `--plain` (in what form), and it composes with both. The trim runs last, after `--plain` has decided what a line is: a shell line the pane wrapped over three rows is one line to the grid and three to a byte counter, so `--plain --tail 1` hands back the whole of the last line rather than its final row. The raw form counts the same way `tail` does, splitting on the newline and leaving the CR of a CRLF attached to the line it ended, so the bytes stay the pane's own. A trailing newline terminates the last line rather than opening an empty one, which is the difference between `--tail 1` answering the last line and answering nothing. `N` must be at least 1: a tail of zero lines would print an empty result that reads exactly like the blank-pane ambiguity this issue is about, so it is a usage error (exit 2) instead. `--json` reports the tail in `text` but leaves `bytes` the size of the whole replay, so the pair still says "this was trimmed" rather than "this came back short". Left alone deliberately: the daemon still replays the entire ring on every observe, so the saving here is the pipe, not the wire. Bounding what crosses the wire means teaching `ClientMsg::Observe` a limit and versioning the protocol for it, which is a much larger change than the papercut warrants — and the default (newest-segment) form has always received the whole ring and discarded most of it, so this adds no new cost. The docs say so rather than implying otherwise. Claude-Session: https://claude.ai/code/session_01UUyWQXzcBAoBzaSX8pc7nU
158 lines
5.1 KiB
Plaintext
158 lines
5.1 KiB
Plaintext
---
|
|
title: "The tty7 command"
|
|
description: "Driving the workbench from a script, a Makefile, or another agent."
|
|
---
|
|
|
|
`tty7` is a thin, non-interactive client of the tty7 server. Every verb runs and
|
|
exits; `--json` makes the output machine-readable. **The GUI does not have to be
|
|
running** — the server is what owns the panes.
|
|
|
|
It ships inside every installer and is put on PATH at launch, so it works from
|
|
any terminal on the machine. [Installation →](/getting-started/installation#the-tty7-command)
|
|
|
|
## Start with `doctor`
|
|
|
|
```bash
|
|
tty7 doctor
|
|
```
|
|
|
|
One table that answers everything you need before doing anything else: whether a
|
|
server is reachable, whether its wire dialect matches this binary, and whether
|
|
`TTY7_CONFIG_DIR` / `TTY7_WS` / `TTY7_PANE` are set — that is, whether you are
|
|
running *inside* a tty7 pane.
|
|
|
|
Being inside a pane matters because the address-taking verbs (`split`, `send`,
|
|
`capture`, `procs`, `wait`, `pane close`) default to `$TTY7_PANE`, and
|
|
`run --keep` files its pane into `$TTY7_WS`. Outside one you must name a target,
|
|
and the error says so rather than guessing.
|
|
|
|
## Addresses
|
|
|
|
| Shape | Means | Stable? |
|
|
|---|---|---|
|
|
| `%42` | A pane | **Yes** — a pane keeps its id for its whole life |
|
|
| `@7` | A tab, numbered across the whole machine in tree order | **No** — it shifts whenever any workspace or tab appears or disappears |
|
|
| `api` · `76698a44` · a full UUID | A workspace, by name, unique id prefix, or id | Yes |
|
|
|
|
Re-resolve `@N` immediately before using it. Pane and workspace ids are safe to
|
|
remember.
|
|
|
|
## Two ways to run something
|
|
|
|
### Blocking, with a real exit code
|
|
|
|
```bash
|
|
tty7 run -- cargo test # streams to stdout, exits with cargo's code
|
|
tty7 run --cwd /path -- make
|
|
tty7 run --keep -- cargo build # leaves the pane behind as a new tab
|
|
```
|
|
|
|
The closest thing to running the command yourself — the difference is that it
|
|
gets a real PTY (so colour, progress bars, and TUIs behave), and that you can
|
|
watch it happen in the window.
|
|
|
|
<Note>
|
|
Everything after `--` belongs to the child: `tty7 run -- cargo test --keep`
|
|
passes `--keep` to cargo, not to tty7.
|
|
</Note>
|
|
|
|
### Non-blocking: a pane you talk to over time
|
|
|
|
This is the one worth reaching for. Get a pane, give it work, come back.
|
|
|
|
```bash
|
|
PANE=$(tty7 split --v) # or --h; prints "%83"
|
|
tty7 send "$PANE" 'npm run dev' --enter
|
|
# ... later
|
|
tty7 capture "$PANE" --plain
|
|
tty7 pane close "$PANE"
|
|
```
|
|
|
|
If you are not inside a tty7 pane there is nothing to split, so make your own
|
|
place to work:
|
|
|
|
```bash
|
|
tty7 new --json /path/to/repo # {"id": "...", "pane": 83}
|
|
```
|
|
|
|
## Reading a pane
|
|
|
|
```bash
|
|
tty7 capture %83 --plain
|
|
```
|
|
|
|
`capture` returns what the server stored. Without `--plain` that is the raw
|
|
bytes, escapes and all. With `--plain` those bytes are replayed through a real
|
|
terminal grid and you get the text that produced — which is not the same as
|
|
stripping escapes yourself:
|
|
|
|
- A line the shell wrapped at the pane width comes back as **one** line
|
|
- A progress bar that rewrote itself with `\r` reads as its **final** value
|
|
- Cursor addressing puts text **where the program put it**, so a TUI's screen
|
|
lands where it was drawn
|
|
|
|
Use `--plain` whenever a human would want to read the output.
|
|
|
|
When all you want is how the last command ended, ask for that much:
|
|
|
|
```bash
|
|
tty7 capture %83 --plain --tail 5
|
|
```
|
|
|
|
`--tail N` keeps the last N lines of the answer, counted after `--plain` has
|
|
resolved the wraps — so no pipe through `tail(1)`, which is one less thing to
|
|
have on PATH.
|
|
|
|
<Warning>
|
|
A screen is a rectangle. Whatever scrolled off the top is gone, and an exit
|
|
code was never on it. When you want the *answer* rather than the *view*, have
|
|
the shell write it somewhere clean:
|
|
|
|
```bash
|
|
tty7 send "$PANE" 'cargo test > /tmp/t.log 2>&1; echo $? > /tmp/t.rc' --enter
|
|
```
|
|
</Warning>
|
|
|
|
## Knowing when something finished
|
|
|
|
```bash
|
|
tty7 procs %83
|
|
```
|
|
|
|
The process tree inside the pane, indented by depth, with `*` on the foreground
|
|
process — plus the ports those processes are listening on. **When the only entry
|
|
left is the depth-0 shell, the command is done.** That is far more reliable than
|
|
grepping the screen for a sentinel that can wrap or echo twice.
|
|
|
|
For agents specifically, use [`tty7 wait`](/agents/orchestration) instead of
|
|
polling.
|
|
|
|
## Looking around
|
|
|
|
```bash
|
|
tty7 ls # every workspace: tabs, panes, who's attached
|
|
tty7 ws tree api # one workspace as a tree
|
|
tty7 pane ls --all # every pane, including orphans no workspace holds
|
|
tty7 agents # every coding agent and its status
|
|
tty7 status # server pid, uptime, pane count, build, socket
|
|
tty7 machine ls # this machine plus any linked remotes
|
|
tty7 events # stream server events until interrupted
|
|
```
|
|
|
|
`--json` on any of them, `-q` to suppress success output (errors still print).
|
|
|
|
## Remote machines
|
|
|
|
```bash
|
|
tty7 -m devbox ls
|
|
tty7 -m devbox run -- cargo test
|
|
```
|
|
|
|
`-m` routes over a link the local server already holds. It will not dial a fresh
|
|
connection — connect from the GUI first.
|
|
[Remote workspaces →](/remote/workspaces)
|
|
|
|
<Card title="Full command reference" icon="book" href="/cli/reference">
|
|
Every verb, flag, and JSON shape.
|
|
</Card>
|