docs: add a Mintlify documentation site

38 pages under docs/, written against the source rather than the README:
config keys and their clamps from core::config, default keybindings from
ui::keymap, every CLI verb and flag from tty7-cli, agent aliases and
hook/fork/resume support from core::cli_agent, and Settings paths taken
from the actual en-US strings.

docs/features.md and its zh-CN translation are retired — everything in
them now lives in a page of its own, plus the two things they carried
that nothing else did (IME input, the performance notes). README and
README.zh-CN point at docs/ instead.

Screenshots and videos are placeholders for now: docs/images/placeholder.svg
with a caption naming what each shot should be.
This commit is contained in:
l0ng-ai
2026-08-11 00:35:44 +08:00
parent 12df66fb0f
commit 4ba8bf44d0
47 changed files with 3763 additions and 311 deletions
+5 -2
View File
@@ -54,8 +54,11 @@ Native builds for each platform on [**Releases**](https://github.com/l0ng-ai/tty
| **CLI + Skills** | bundled `tty7` CLI · [agent skill](skills/tty7/SKILL.md) · pane/workspace control · real PTY commands · output, process, port, and agent status |
| **SSH** | native russh stack: profiles with keychain secrets · SFTP panel · port forwarding · jump hosts · one-time, unprivileged `tty7-server` install |
Terminal and keybinding reference: [docs/features.md](docs/features.md). The agent-facing CLI
interface is documented in [skills/tty7/SKILL.md](skills/tty7/SKILL.md).
Full documentation lives in [**`docs/`**](docs/) —
[keyboard shortcuts](docs/reference/keyboard-shortcuts.mdx) ·
[config.json](docs/reference/configuration.mdx) ·
[CLI reference](docs/cli/reference.mdx). The agent-facing CLI interface is also
documented in [skills/tty7/SKILL.md](skills/tty7/SKILL.md).
Install the skill with:
+4 -1
View File
@@ -54,7 +54,10 @@
| **CLI + Skills** | 安装包自带 `tty7` CLI · [agent skill](skills/tty7/SKILL.md) · pane/工作区控制 · 真实 PTY 命令 · 输出、进程、端口和 agent 状态 |
| **SSH** | 原生 russh 栈:profile 凭据进 keychain · SFTP 面板 · 端口转发 · 跳板机 · 一次无 sudo 安装 `tty7-server` |
终端和快捷键参考:[docs/features.zh-CN.md](docs/features.zh-CN.md)。面向 agent 的 CLI 接口见
完整文档在 [**`docs/`**](docs/)(英文)——
[快捷键](docs/reference/keyboard-shortcuts.mdx) ·
[config.json](docs/reference/configuration.mdx) ·
[CLI 参考](docs/cli/reference.mdx)。面向 agent 的 CLI 接口另见
[skills/tty7/SKILL.md](skills/tty7/SKILL.md)。
通过以下命令安装 skill
+103
View File
@@ -0,0 +1,103 @@
---
title: "Orchestrating agents"
description: "One agent opening a pane for another, waiting on it, and reading the result."
---
Once an agent's status is a thing a program can ask about, one agent can run
another. tty7 gives that loop a primitive instead of leaving it to screen
scraping.
## The loop
```bash
# 1. give the worker a pane
PANE=$(tty7 split --v)
# 2. hand it a task
tty7 send "$PANE" 'claude -p "add tests for the parser"' --enter
# 3. sleep until it needs you or finishes
tty7 wait "$PANE" --until waiting,done --changed --timeout 600
# 4. read what happened
tty7 capture "$PANE" --plain
# 5. clean up
tty7 pane close "$PANE"
```
That is the whole shape. The interesting step is the third.
## `tty7 wait`
```bash
tty7 wait [%PANE] [--until STATE,…] [--changed] [--timeout SECS] [--interval MS]
```
Blocks until the pane's agent reaches one of the states you named.
| Flag | Default | |
|---|---|---|
| `--until` | `waiting,done,exit` | Which states end the wait: `idle`, `working`, `waiting`, `done`, `exit` |
| `--changed` | off | Ignore the state the pane was *already* in — only wake on one it moved into after the wait began |
| `--timeout` | none | Give up after this many seconds, exiting 124 |
| `--interval` | 500 ms | How often to poll |
Exit codes are made for scripts:
| Code | Meaning |
|---|---|
| `0` | A state you asked for was reached |
| `124` | Timed out — the `timeout(1)` convention, so "not yet" is distinguishable from "broken" |
| `1` | The worker died first; the JSON says `"status": "exit"` |
The reply carries the agent's own message and its native session id, so a
wake-up is directly actionable.
### Why `--changed` matters
The status the server keeps is a **level, not an event**. `done` stands until
the next turn begins; `waiting` stands until the agent moves again.
So a `wait` issued immediately after a `send` can answer with the *previous*
turn's state, before the worker has even read the input. `--changed` refuses the
state the pane was already in, which is what every round after the first needs.
Without it, the JSON's `stale` flag tells you whether that happened.
## Watching everything at once
```bash
tty7 agents # every agent on the machine: pane, agent, status, message
tty7 agents --json # the same, parseable
```
If you are an agent yourself, you are in that list too.
## The Claude Code skill
**Settings → Agents** has a switch that installs
`~/.claude/skills/tty7-orchestration` — a skill teaching a *primary* Claude Code
agent this whole delegation loop: open a worker pane, send it one bounded task,
`wait` on it, answer what it asks, collect the result, close the pane.
It is a skill rather than a global instruction on purpose. Only its one-line
description rides in context until something reaches for it, and worker agents
never inherit orchestration authority.
Uninstalling removes the file tty7 wrote and refuses to touch one it did not, so
a hand-written skill that happens to share the directory name survives.
## Rules of the road
<Warning>
The panes on a machine are somebody's real work, and some of them are other
agents mid-task. Treat anything you did not create as read-only.
</Warning>
- **Never `send` into a pane you did not open.** Check `tty7 agents` first.
- **Never close a pane, tab, or workspace you did not create.**
- **Never `server stop` or `server restart`.** Every pane on the machine dies
with it.
- **Clean up what you did create** — `tty7 pane close %83` when you are done.
The full agent-facing contract is in [the skill](/cli/agent-skill).
+107
View File
@@ -0,0 +1,107 @@
---
title: "Coding agents"
description: "What tty7 does around Claude Code, Codex, and 16 others — without ever wrapping them."
---
tty7 recognises coding agents running in a pane and builds around them. It does
not wrap them, proxy them, or replace their interface: the agent you start is
the agent you get, running in a normal PTY, with its own UI. tty7 adds the
things a terminal is in a position to add — who is running where, what they
need, and what changed.
<Frame caption="Placeholder — screenshot: a sidebar of agent sessions across several repos, each with a brand avatar and status dot">
<img src="/images/placeholder.svg" alt="Agent sessions in the tty7 sidebar" />
</Frame>
## Which agents
Eighteen CLIs are recognised on sight, by the command running in the pane:
| Agent | Command |
|---|---|
| Claude Code | `claude`, `claude-code` |
| Codex | `codex`, `codex-cli` |
| Gemini | `gemini`, `gemini-cli` |
| Copilot | `copilot` |
| Cursor | `cursor-agent` |
| Amp | `amp` |
| OpenCode | `opencode` |
| Aider | `aider`, `aider-chat` |
| Goose | `goose` |
| Droid | `droid` |
| Grok | `grok` |
| Qwen Code | `qwen`, `qwen-code` |
| Auggie | `auggie` |
| Hermes | `hermes` |
| Vibe | `vibe`, `vibe-acp` |
| Antigravity | `agy`, `antigravity` |
| Pi | `pi` |
| Oh My Pi | `omp` |
Detection sees through the usual disguises: a full path, a `.cmd` or `.exe` on
Windows, leading environment assignments, and an interpreter in front
(`node .../claude/cli.js`).
### Your own wrapper
If you launch agents through a wrapper script, map its name to an agent in
`config.json`:
```json
{
"agent_commands": {
"cc": "claude",
"work": "codex"
}
}
```
The key is your command's name; the value is one of the slugs above (`claude`,
`codex`, `gemini`, `aider`, `amp`, `opencode`, `copilot`, `cursor`, `goose`,
`droid`, `pi`, `auggie`, `hermes`, `vibe`, `antigravity`, `grok`, `qwen`,
`omp`).
## What you get for free
Just by running an agent in a pane:
<CardGroup cols={2}>
<Card title="Brand avatars" icon="id-badge">
The tab chip and sidebar row show which agent runs where, so ten tabs stay
legible.
</Card>
<Card title="Git context" icon="code-branch">
The branch and working-tree diff on the row, refreshed as the agent works.
</Card>
<Card title="Session resume" icon="rotate-left">
A pane lost to a reboot relaunches the conversation, carrying its original
flags. [More →](/agents/sessions)
</Card>
<Card title="Context feed" icon="paper-plane">
Palette commands that hand the current selection or the repo's `git diff` to
the running agent as a prompt.
</Card>
</CardGroup>
## What needs a hook
Live status — **working**, **needs your input**, **done** — comes from the agent
itself, over a channel tty7 installs into that agent's configuration. It powers
the status dots, the notifications, the tray icon, and `tty7 wait`.
Installing takes one click per agent under **Settings → Agents**.
[Status and notifications →](/agents/status)
## Where to go next
<CardGroup cols={3}>
<Card title="Status and notifications" icon="circle-dot" href="/agents/status">
Hooks, status dots, the tray icon.
</Card>
<Card title="Sessions" icon="code-branch" href="/agents/sessions">
Resume, fork, and copying a session id.
</Card>
<Card title="Orchestration" icon="diagram-project" href="/agents/orchestration">
One agent driving another with `tty7 wait`.
</Card>
</CardGroup>
+60
View File
@@ -0,0 +1,60 @@
---
title: "Agent sessions"
description: "Resuming a conversation after a reboot, forking a live one, and getting at the session id."
---
Coding agents keep their own conversation history, addressed by a session id.
Because tty7's hooks learn that id, it can do three things with it.
## Resume after a restart
When the server goes away — a reboot, a crash, a deliberate restart — the shells
go with it. Panes that were running an agent relaunch the conversation on
restore instead of coming back to a bare prompt:
```bash
claude --dangerously-skip-permissions --resume 8f3c…
```
The original launch flags are replayed, so the pane comes back the way you
started it, not the way the defaults would.
Supported for Claude Code, Codex, Gemini, OpenCode, Amp, Cursor, Copilot, Grok,
Pi, and Oh My Pi. Turn it off with `restore_agent_sessions: false`.
<Note>
Resume needs the agent's hooks installed, since the session id comes from
them. [Installing hooks →](/agents/status)
</Note>
## Fork a live session
Forking branches a running conversation into a second, independent one. The
original keeps going untouched; both continue separately from the same history.
Right-click a **pane** to fork into a split — the menu offers a placement —
or right-click the **tab or sidebar row** to open the fork in a new tab.
| Agent | What tty7 runs |
|---|---|
| Claude Code | `claude --resume <id> --fork-session` |
| Codex | `codex fork <id>` |
| Grok | `grok --resume <id> --fork-session` |
| OpenCode | `opencode --session <id> --fork` |
| Oh My Pi | `omp --fork <id>` |
It is the agent's own fork command, run in a new pane — nothing is copied by
tty7 itself.
<Warning>
A fork duplicates the whole transcript in the agent's session store, so
forking repeatedly costs real disk. A pane on a
[remote machine](/remote/workspaces) cannot fork, because the command would
run against the local agent.
</Warning>
## Copy the session id
**Copy Session ID** — in the pane's right-click menu, beside *Copy Working
Directory*, and in the command palette — puts the agent's native id on the
clipboard. Paste it into `codex resume`, a bug report, or another tool.
+83
View File
@@ -0,0 +1,83 @@
---
title: "Status and notifications"
description: "Installing the hooks, reading the dots, and being told when an agent needs you."
---
An agent working for two minutes and an agent that stopped ninety seconds ago
waiting for permission look identical from outside. tty7 fixes that by letting
the agent say which one it is.
## Installing the hooks
**Settings → Agents** lists every agent that can report status, with an
**Install** button beside each:
| Agent | |
|---|---|
| Claude Code · Codex · Copilot · OpenCode · Pi · Grok · Oh My Pi | Hooks available |
| Gemini · Aider · Amp · Cursor · Goose · Droid · Auggie · Hermes · Vibe · Antigravity · Qwen Code | Detected and labelled, but no status channel yet |
Installing writes into that agent's own configuration directory and can be
undone from the same row — the button becomes **Uninstall**, and reads
**Outdated** with an **Update** when tty7 ships a newer hook.
<Note>
The hooks only do anything inside tty7. Running the same agent in another
terminal is unaffected.
</Note>
Connected [remote machines](/remote/workspaces) get their own row, so an agent
running on a dev box reports status to the window you are watching it from.
## The status dot
Every tab chip and sidebar row carries a dot:
| Dot | Meaning |
|---|---|
| 🔵 **Blue** | Working |
| 🟠 **Amber** | Needs your input — a permission prompt, a question |
| 🟢 **Green** | Done with this turn |
<Frame caption="Placeholder — screenshot: three sidebar rows, one working, one waiting, one done">
<img src="/images/placeholder.svg" alt="Agent status dots" />
</Frame>
The same three states are what `tty7 agents` reports as `running` / `waiting` /
`idle`, and what [`tty7 wait`](/agents/orchestration) blocks on.
## Notifications
Two, both following your **Settings → Window & Tabs → Notifications** policy:
- **"needs your permission…"** the moment an agent blocks on you
- **"finished after 42s"** at the end of a turn
Which means that by default — *When unfocused* — you are told the instant you
are the bottleneck, and left alone while you are watching.
## The tray icon
tty7 keeps a status item in the system tray (menu bar on macOS). It flips to an
attention state the moment *any* agent anywhere needs input, so you can see it
without the window in front of you.
Its menu lists every agent pane with its brand avatar and status dot — click one
to reveal it — and also holds the notification policy switch and **Quit and Stop
Server…**.
Turn it off with **Settings → Window & Tabs → Show tray icon**
(`show_tray_icon: false`).
## Sending an agent some context
Two command-palette entries hand what is in front of you to the agent running in
the pane, as a ready-made prompt:
| Command | Sends |
|---|---|
| **Agent: Send Selection** | The current terminal selection |
| **Agent: Send Git Diff for Review** | The repository's `git diff` |
If nothing recognisable is running, tty7 says *"No running coding agent found"*
rather than typing into your shell.
+84
View File
@@ -0,0 +1,84 @@
---
title: "The agent skill"
description: "Teaching a coding agent to use tty7 properly — including when not to."
---
The CLI is only half of the story. An agent has to know *when* reaching for a
pane beats running a command, and — more importantly — which panes it must not
touch. That is what the skill is for.
## Installing it
```bash
npx skills add l0ng-ai/tty7
```
The source lives at
[`skills/tty7/`](https://github.com/l0ng-ai/tty7/tree/main/skills/tty7) in the
repository: a `SKILL.md` and a full command reference.
<Note>
This is separate from the **orchestration** skill installed from **Settings →
Agents**, which teaches a *primary* agent to delegate to worker agents. This
one teaches any agent to drive tty7 at all.
[Orchestration →](/agents/orchestration)
</Note>
## What it teaches
### When to use a pane instead of a plain command
The Bash-style tool an agent already has is right for anything that starts, does
its job, and exits. A pane is right when:
- **It should not block.** A dev server, a watcher, `tail -f`, a long test run.
- **It is interactive or stateful.** A REPL, `ssh`, a database shell — anything
where you send, read, then send again. A pane keeps the session alive between
turns; a one-shot call cannot.
- **It needs a real TTY.** Programs that detect a pipe and change behaviour —
colour, progress bars, TUIs, `top`, raw mode.
- **The user should be able to watch.** Anything in a pane shows up live in
their window. That is often the whole point.
- **You are being asked about something you did not start.** "What's running in
that pane?", "why is port 3000 taken?", "what are my agents doing?"
### The safety rules
<Warning>
The panes on this machine are the user's real work, and some of them are other
coding agents mid-task. Anything the agent did not create is read-only.
</Warning>
- **Never `send` into a pane you did not open.** Keystrokes land in the middle
of whatever is happening there. Check `tty7 agents` first.
- **Never close a pane, tab, or workspace you did not create.**
- **Never `server stop` or `server restart`.** Every pane on the machine dies
with the server, including yours.
- **Never `tty7 server start` on your own initiative** when `doctor` says the
server is unreachable — starting one the user did not ask for changes what
their GUI attaches to. Tell them instead.
- **Clean up what you did create.** `tty7 pane close %83` when the scratch pane
is done with.
### The reliable idioms
Rather than screen-scraping, the skill points agents at the primitives that
actually answer the question:
```bash
# is it finished? — when only the depth-0 shell is left, yes
tty7 procs %83 --json
# the answer, not the view
tty7 send "$PANE" 'cargo test > /tmp/t.log 2>&1; echo $? > /tmp/t.rc' --enter
# wake up exactly when the other agent needs something
tty7 wait %3 --until waiting,done --changed --timeout 600
```
## For humans writing their own tooling
The same material is worth reading even if you are not an agent — it is the
shortest description of how to use tty7 as a job runner. Start with the
[CLI overview](/cli/overview), then the
[command reference](/cli/reference).
+147
View File
@@ -0,0 +1,147 @@
---
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`, `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.
<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>
+253
View File
@@ -0,0 +1,253 @@
---
title: "Command reference"
description: "Every verb, its flags, and the JSON it emits under --json."
---
## Global flags
Accepted anywhere on the line, before or after the subcommand.
| Flag | Effect |
|---|---|
| `-m, --machine <MACHINE>` | Route to a linked machine over the local server's existing link. Matches the full link key (`me@devbox:22`) or the bare host (`devbox`). SSH links only; a down link, or a jump/proxy chain, is refused with a reason rather than dialled fresh. |
| `--json` | One JSON object on stdout instead of the human table. |
| `-q, --quiet` | No output on success. Errors still go to stderr. |
## Environment
Set inside every tty7 pane, inherited by anything launched from one.
| Variable | Meaning |
|---|---|
| `TTY7_PANE` | This pane's id, e.g. `71` or `%71` (both accepted). Default target of `split`, `send`, `capture`, `procs`, `pane close`. |
| `TTY7_WS` | This pane's workspace id. Default for `run --keep`, `tab new`, `ws tree`. |
| `TTY7_CONFIG_DIR` | The server's config dir — how the CLI finds the right server. You never pass a socket path. |
Outside a tty7 shell, address-taking verbs fail with
`not inside a tty7 shell — pass an explicit %pane/@tab/workspace`.
## Exit codes
| Code | Meaning |
|---|---|
| `0` | Success |
| `1` | The command failed; one line on stderr, prefixed `tty7:` |
| `2` | Usage error — unknown verb, missing argument, bad type |
| `124` | `tty7 wait` timed out (the `timeout(1)` convention) |
| `141` | Unix only: the reader hung up — piping into `head -1`, say — and SIGPIPE ended it, exactly as it ends `cat`. Not a failure. Windows reports 0 for the same thing, having no signal to imitate. |
| *other* | Only from `tty7 run`, which passes the child's exit code through |
If `run` cannot learn the child's code it prints a note to stderr and exits 1
with `"exit_code_known": false` in the JSON — that is how you tell a real 1 from
a stand-in.
## Top-level verbs
### `tty7 ls`
Same as `ws ls`. Table: `WORKSPACE NAME TABS PANES ATTACHED`.
JSON: `{"workspaces":[{"id","name","tabs","panes","attached"}]}`.
`ATTACHED` names the host holding the workspace — a GUI window, or another
client — and is `-` when nobody is.
### `tty7 run [--keep] [--cwd DIR] [--ws WORKSPACE] -- CMD...`
Spawns a pane running `CMD`, streams its output to stdout, waits, and exits with
its code. The command must come after `--`.
- `--keep` leaves the pane alive as a new tab afterwards. Needs a workspace, so
it requires `--ws` or `$TTY7_WS` — without one it is an error, not a silent
fallback.
- `--cwd` sets the working directory. `--ws` also sets the pane's `TTY7_WS`.
- Interrupting `run` can leave the pane behind as an orphan — see
`pane ls --all`.
JSON: `{"pane","exit","exit_code_known","kept"}`, printed **after** the streamed
output. The combined stream is not valid JSON — read the last line.
### `tty7 new [PATH] [--open]`
Creates a workspace plus its first tab and shell, at `PATH` if given. Prints the
workspace id. JSON: `{"id","pane","opened"}`.
`--open` also puts a window on it, if a GUI is running on this machine. Without
it the workspace still appears in the switcher; it just waits to be opened.
### `tty7 split [%PANE] (--v|--h) [--ratio R]`
Alias of `pane split`. Splits `%PANE` (default `$TTY7_PANE`), spawning a shell
in the same cwd. Exactly one axis is required — `--v`/`--vertical` puts the new
pane below, `--h`/`--horizontal` to the right. `--ratio` (default `0.5`) is the
share kept by the *existing* pane. Prints `%NN`. JSON: `{"pane"}`.
### `tty7 send [%PANE] TEXT [--enter]`
Types `TEXT` into the pane as keystrokes; `--enter` appends CR. With one
argument the text is the argument and the pane comes from `$TTY7_PANE` — but a
lone `%42` is rejected as a missing-text error rather than typed.
JSON: `{"pane","sent","enter"}`.
### `tty7 capture [%PANE] [--plain] [--scrollback]`
The pane's replay. Two independent choices:
**How much** — the newest scrollback segment by default, the whole ring with
`--scrollback`. The ring splits into segments on resize, so for a pane that was
never resized the two are identical.
**In what form** — without `--plain`, the stored bytes with ANSI escapes intact,
decoded as UTF-8 (invalid bytes become U+FFFD). With `--plain`, those bytes
replayed through a terminal grid and printed as the text they produced.
Either way it is a snapshot, not a stream: it collects the replay, settles for
~300 ms, and returns. Call it again for a newer one.
JSON: `{"pane","text"}`.
### `tty7 procs [%PANE]`
The process tree inside the pane, indented by depth, `*` on the foreground
process — then a second table of ports those processes are listening on. Prints
`nothing running in this pane` when both are empty.
JSON: `{"procs":[{"pid","name","depth","foreground"}],"ports":[{"port","pid","name"}]}`.
### `tty7 agents`
Every pane running a recognised coding agent. Table:
`PANE AGENT STATUS MESSAGE`, status one of `running` / `waiting` / `idle`.
JSON: `{"agents":[...]}`.
### `tty7 wait [%PANE] [--until STATE,…] [--changed] [--timeout SECS] [--interval MS]`
Blocks until the pane's agent reaches one of the named states.
| Flag | Default | |
|---|---|---|
| `--until` | `waiting,done,exit` | `idle`, `working`, `waiting`, `done`, `exit` |
| `--changed` | off | Only wake on a state the pane moved into *after* the wait began |
| `--timeout` | none | Give up after N seconds, exiting `124` |
| `--interval` | `500` | Poll interval in ms (503,600,000) |
The reply carries the agent's message and native session id. The JSON's `stale`
flag says whether the answer might belong to the previous turn.
[Orchestration →](/agents/orchestration)
### `tty7 events`
Streams server events until interrupted, one per line — pane exits, agent status
changes, workspace preemption, layout deltas. `--json` makes it NDJSON. Blocks
forever; run it with a timeout or in the background.
### `tty7 status`
Same as `server status`: pid, uptime, pane count, dialect versions, build,
socket path. JSON is the `ServerStatus` object itself (`pid`, `uptime_secs`,
`panes`, `control_version`, `protocol_version`, `build`, `socket`).
### `tty7 doctor`
The install check: the three environment variables, whether the server answers,
whether its control and protocol versions match this binary, pid/uptime/panes,
and how many machine links exist. Adds a note when you are not inside a tty7
shell.
JSON: `{"context":{"config_dir","workspace","pane"},"server":{"reachable","dialect_ok","build","status","routes"}}`
— the context fields are booleans, not values.
## `ws` — workspaces
Address a workspace by name, by full id, or by a unique id prefix (the 8-char
prefix `tty7 ls` prints). An ambiguous name or prefix is an error that lists the
candidates.
| Command | Effect | JSON |
|---|---|---|
| `ws ls` | Every workspace | `{"workspaces":[...]}` |
| `ws tree [WORKSPACE]` | One workspace as a tree: tabs, split axes and ratios, panes with cwds | The whole workspace object: `{"id","name","last_active","tabs":[{"id","name","sidebar_group","root",…}]}` |
| `ws new [NAME]` | An empty workspace (no tab, no pane) | `{"id","name"}` |
| `ws rename WORKSPACE NAME` | Name or rename | `{"id","name"}` |
| `ws rm WORKSPACE` | Delete the workspace | `{"removed"}` |
| `ws attach WORKSPACE` | Become its controlling client | `{"attached","took_over_from"}` |
| `ws detach WORKSPACE` | Let go without interrupting anything | `{"detached"}` |
<Warning>
`ws rm` does **not** kill the panes it held — they keep running as orphans
with no workspace. Find them with `pane ls --all` and close them one by one.
</Warning>
Prefer `tty7 new <path>` over `ws new` when you want something usable: `ws new`
leaves an empty workspace you then have to populate, while `tty7 new --json`
hands back both ids at once.
The `root` node in `ws tree --json` is externally tagged, so a leaf is
`{"Leaf":{"pane":31}}` and a split is `{"Split":{"axis","ratio","a","b"}}` with
`a`/`b` nested the same way.
## `tab` — tabs
`@N` numbers tabs across the **whole machine** in tree order, densely from `@1`.
The numbering shifts whenever any workspace or tab is created or removed, so
resolve it immediately before use. A full tab UUID also works: `@<uuid>`.
| Command | Effect | JSON |
|---|---|---|
| `tab ls [WORKSPACE]` | Tabs of a workspace | `{"workspace","tabs":[{"ordinal","id","name","label","agent","group","panes":[…]}]}` |
| `tab new [WORKSPACE] [--cwd DIR]` | Add a tab with a fresh shell | `{"tab","pane"}` |
| `tab close @TAB` | Close the tab and every pane in it | `{"closed"}` |
| `tab rename @TAB NAME` | Name or rename | `{"tab","name"}` |
| `tab move @TAB INDEX` | Reposition within its workspace | `{"tab","to"}` |
`GROUP` is the heading the GUI's sidebar files the tab under, shown by its last
segment. Read-only from here: with the default repo grouping the GUI recomputes
it from the tab's working directory.
`label` falls back through the best evidence available — the name if someone set
one, else the agent running there, else the last segment of the cwd, else the
foreground process. `name` stays literal, so a script can tell a real name from
a stand-in.
## `pane` — panes
| Command | Effect | JSON |
|---|---|---|
| `pane ls [WORKSPACE]` | Panes with their workspace, tab, cwd, live flag | `{"panes":[…]}` |
| `pane ls --all` | The server's whole pane registry, including orphans | `{"panes":[…],"orphans":N}` |
| `pane split …` | Identical to top-level `split` | `{"pane"}` |
| `pane close [%PANE]` | Close the pane; its shell is hung up | `{"closed"}` |
`--all` is the one that shows leaks. Each entry is
`{"pane","workspace","orphan","owner","title","cwd","live"}`: `owner` is
`tty7-cli` for panes this CLI spawned, and `orphan: true` means no workspace
holds it. An interrupted `run` and a removed workspace both leave orphans here.
`title` is usually the running command — `claude`, `nvim`, `cargo` — which makes
`pane ls --all --json` a quick way to find "the pane running X".
## `machine` — remotes
`machine ls` lists the local machine plus every link the server holds:
`MACHINE KIND CONNECTED`. JSON: `{"machines":[{"key","kind","connected"}]}`.
## `server` — the daemon
| Command | Effect |
|---|---|
| `server status` | Same as `tty7 status` |
| `server logs` | Tail the server log; prints the path, and says so when logging was never enabled (`TTY7_LOG=info` before the server starts) |
| `server start` | Bring up a server on this machine |
| `server stop` | Stop it — **every pane on the machine dies** |
| `server restart` | Stop, then start — same consequence |
<Warning>
Do not run `start`, `stop`, or `restart` on someone else's behalf. They change
or destroy what the user's GUI is attached to.
</Warning>
## Not implemented yet
These parse and then exit 1 with an explanation:
- `ws stop` — the control dialect has no workspace-stop request yet
- `machine connect` / `machine disconnect` — use the GUI's connection manager
- bare `tty7 <path>` (launch or focus the GUI)
+75
View File
@@ -0,0 +1,75 @@
---
title: "Fonts"
description: "The bundled default, fallback chains, ligatures, and why CJK needs a word."
---
**Settings → Appearance → Typography** covers the everyday choices; the rest is
`config.json`.
| Setting | Default | |
|---|---|---|
| **Font family** | Hack | Picked from fonts installed on your system |
| **Font size** | 15 px | The terminal grid |
| **Interface font size** | 16 px | Everything outside the grid (1224) |
| **Line height** | 1.4 | A multiple of the font size |
| **Bold font** / **Italic font** | — | Distinct faces, when you want them |
| **Font ligatures** | off | Contextual alternates stay off unless you ask |
## Hack is bundled
The default font ships inside the binary. It renders identically on every
machine without relying on a system install, so a fresh laptop looks like the
one you set up last year.
## Fallbacks
`font_family` is the primary face; `font_fallbacks` is an ordered list tried in
turn for anything the primary lacks.
```json
{
"font_family": "JetBrains Mono",
"font_fallbacks": ["Maple Mono NF CN", "PingFang SC", "Apple Color Emoji"]
}
```
The defaults name faces the host OS actually ships — PingFang SC and Apple Color
Emoji on macOS, Microsoft YaHei and Segoe UI Emoji on Windows, Noto on Linux.
Those stock names are appended to whatever list you write, too, so a
`config.json` copied from another platform still resolves.
## OpenType features
`font_features` passes tags straight through to the shaper:
```json
{
"font_features": { "calt": true, "liga": 1, "ss01": true, "zero": false }
}
```
A tag must be four alphanumeric characters; `true`/`false` map to `1`/`0`.
Anything malformed is skipped with a log line rather than failing the whole
config.
## CJK and the two-column grid
<Info>
A cell is one advance of the primary face, and a wide (CJK) character is
pinned to exactly two of them. A CJK fallback sits flush in its slot only if
its ideographs advance **twice** the primary's Latin advance.
</Info>
Bundled Hack advances 0.60205em, so a two-column slot is 1.2041em — while every
stock CJK face (Microsoft YaHei, PingFang SC, Noto Sans CJK) advances 1.0em.
Those glyphs get left-aligned in the slot, leaving a ~0.2em gap on the right of
every character.
[Maple Mono NF CN](https://github.com/subframe7536/maple-font) is tried first on
every platform for exactly this reason: 0.6em Latin, 1.2em CJK, an exact
two-cell fit against Hack. It is referenced by name only, never bundled (~20 MB
per weight) — install it and tty7 picks it up with no config change.
If you want CJK set *tight* rather than merely even, change the **primary** face
instead. One that advances 0.5em — Sarasa Mono SC, say — makes two columns
exactly 1.0em.
+94
View File
@@ -0,0 +1,94 @@
---
title: "Keybindings"
description: "Rebinding anything, chord sequences, and the tmux preset."
---
**Settings → Keybindings** (<kbd>⌘ ,</kbd>) lists every shortcut in the app,
grouped the same way the command palette is.
## Rebinding
Click a shortcut and press the new keys. It saves after a brief pause.
| | |
|---|---|
| Press keys | Set the binding |
| Press more keys | Chain a sequence — <kbd>⌃ B</kbd> then <kbd>X</kbd> |
| <kbd>Esc</kbd> | Cancel |
| <kbd>⌫</kbd> | Remove the last key — or, pressed first, reset the shortcut to its default |
**Restore all defaults** at the bottom undoes every rebinding at once. There is
no undo for that one.
<Frame caption="Placeholder — screenshot: the Keybindings page mid-capture, showing “Press keys…”">
<img src="/images/placeholder.svg" alt="Rebinding a shortcut" />
</Frame>
## Actions with no default key
Some actions ship deliberately unbound, because there is no obvious key left to
take: pane resize and swap, workspace selection, most git commands, SFTP, and
the panel tabs. They are all in the command palette, and all bindable here.
## Editing `config.json` instead
```json
{
"keybindings": {
"SplitRight": "cmd-d",
"ResizePaneLeft": "ctrl-alt-left",
"ToggleSftp": "cmd-shift-u"
}
}
```
The syntax is modifiers joined by `-`, then the key. Chords are separated by a
space.
| Token | Means |
|---|---|
| `secondary` | <kbd>⌘</kbd> on macOS, <kbd>Ctrl</kbd> elsewhere |
| `cmd` · `ctrl` · `alt` · `shift` | Literal modifiers |
| `ctrl-b n` | A two-key sequence |
An unknown action name or an invalid keystroke is skipped with a warning in the
log rather than breaking the rest of your bindings.
The full action list is on the [keyboard shortcuts](/reference/keyboard-shortcuts)
page.
## The tmux preset
**Settings → Keybindings → Preset → tmux** remaps pane and tab actions onto a
prefix — <kbd>⌃ B</kbd> by default, changeable in the **Prefix** field beside
it.
| | |
|---|---|
| <kbd>⌃ B</kbd> <kbd>C</kbd> · <kbd>X</kbd> | New tab · close tab |
| <kbd>⌃ B</kbd> <kbd>%</kbd> · <kbd>"</kbd> | Split right · split down |
| <kbd>⌃ B</kbd> <kbd>←→↑↓</kbd> | Move focus |
| <kbd>⌃ B</kbd> <kbd>⌃ ←→↑↓</kbd> | Resize the pane |
| <kbd>⌃ B</kbd> <kbd>O</kbd> · <kbd>;</kbd> | Next pane · previous pane |
| <kbd>⌃ B</kbd> <kbd>&#123;</kbd> · <kbd>&#125;</kbd> | Swap with the previous · next pane |
| <kbd>⌃ B</kbd> <kbd>Z</kbd> | Zoom the pane |
| <kbd>⌃ B</kbd> <kbd>N</kbd> · <kbd>P</kbd> | Next tab · previous tab |
| <kbd>⌃ B</kbd> <kbd>1</kbd>…<kbd>9</kbd> | Jump to a tab |
Two details that make it livable:
- A **bare prefix** reaches the shell after about a second, so <kbd>⌃ B</kbd>
still works as "back one character" when you meant it.
- **Prefix plus an unbound key** is passed straight through to the terminal, so
a tmux binding you did not remap still lands in whatever is running.
## Some non-obvious defaults
| | |
|---|---|
| <kbd>⇧ ⏎</kbd> · <kbd>⌥ ⏎</kbd> | Insert a newline at the prompt instead of submitting (`InsertNewline`) |
| <kbd>⌘ ⇧ ⏎</kbd> | Zoom the focused pane |
| <kbd>⌘ ⇧ E</kbd> | Toggle the code panel |
| <kbd>⌘ ⇧ R</kbd> | Restart the SSH session in this pane |
| <kbd>⌘ ⇧ O</kbd> | Workspace switcher |
| <kbd>⌘ ⇧ N</kbd> | New workspace |
+86
View File
@@ -0,0 +1,86 @@
---
title: "Settings"
description: "What lives in each section, and how the settings file works."
---
<kbd>⌘ ,</kbd> opens Settings. There is a search box at the top that matches
setting names *and* keywords, which is usually faster than remembering which
section something is in.
<Frame caption="Placeholder — screenshot: the Settings window with the section list on the left">
<img src="/images/placeholder.svg" alt="tty7 Settings" />
</Frame>
## The eight sections
<CardGroup cols={2}>
<Card title="Appearance" icon="palette" href="/customization/themes">
Theme, sync with system, typography, cursor, transparency, language.
</Card>
<Card title="Terminal" icon="terminal">
Shell and start directory, scrollback and scrolling, mouse, bell, per-pane
history.
</Card>
<Card title="Input" icon="keyboard">
Prompt features, selection & clipboard, keyboard (Option as Meta), links.
</Card>
<Card title="SSH" icon="server" href="/remote/ssh">
Hosts, defaults, security, and every per-profile field.
</Card>
<Card title="Agents" icon="robot" href="/agents/status">
Hook installation per agent and per machine, the orchestration skill, the
CLI on PATH.
</Card>
<Card title="Window & Tabs" icon="window-maximize">
Startup window, tab bar position and grouping, notifications, tray icon.
</Card>
<Card title="Keybindings" icon="command" href="/customization/keybindings">
Every shortcut, the tmux preset, the prefix.
</Card>
<Card title="About" icon="circle-info" href="/reference/updates">
Version, update channel, and the updater.
</Card>
</CardGroup>
## The settings file
Everything the Settings window writes goes to one file:
| | |
|---|---|
| macOS / Linux | `~/.config/tty7/config.json` |
| Windows | `%APPDATA%\tty7\config.json` |
Set `TTY7_CONFIG_DIR` to point the whole directory — config, themes, state —
somewhere else.
You can edit the file by hand; a handful of options exist only there. See the
[configuration reference](/reference/configuration) for every key, its type, and
its default.
### How it handles mistakes
The file is written atomically, and read forgivingly:
- **A missing key** means the default — you only have to write what you change.
- **An out-of-range number** is clamped into its band, not rejected.
- **An unrecognised enum value** falls back to the default with a log line,
rather than failing the whole file.
- **An unparseable file** is not overwritten. tty7 starts on defaults, keeps a
copy at `config.json.corrupt`, and says so in the log.
<Note>
A UTF-8 BOM at the start of the file is tolerated, which matters if you edited
it in a Windows editor.
</Note>
## Language
**Settings → Appearance → Language** switches the interface between English,
简体中文, and 日本語. The choice is explicit — the system language is never
inferred — and CLI output stays English regardless, so agent and script
integrations keep a stable surface.
```json
{ "gui_language": "zh-CN" }
```
+113
View File
@@ -0,0 +1,113 @@
---
title: "Themes"
description: "Nine built-ins, your own YAML themes, iTerm2 imports, and a colour editor."
---
**Settings → Appearance → Theme** — or **Change Theme…** in the command
palette — opens the theme picker.
## Built in
| Light | Dark |
|---|---|
| Light *(default)* · One Light · Catppuccin Latte · Rosé Pine Dawn | Dark · Dracula · Harbor · One Dark Pro · Rosé Pine |
<Frame caption="Placeholder — screenshot: the theme picker with light and dark sections">
<img src="/images/placeholder.svg" alt="The tty7 theme picker" />
</Frame>
## Following the system
Turn on **Sync with system** and pick a theme for each appearance. tty7 follows
the OS live — no restart, no reload.
```json
{
"theme_follow_system": true,
"theme_preset_light": "one_light",
"theme_preset_dark": "dracula"
}
```
## Legible bright colours
Some palettes put a bright ANSI colour so close to their own background that it
disappears. **Legible bright colors** (on by default) brightens or darkens those
just enough to be readable. Turn it off with `theme_legible_palette: false` if
you want the palette exactly as authored.
## Transparency
**Settings → Appearance → Transparency**:
| | |
|---|---|
| **Opacity** | 0.21.0, applied to every theme. *Follow theme* hands the decision back to the theme's own `opacity`. |
| **Blur** | Blurs whatever is behind a translucent window (macOS). |
| **Background material** | Windows only: *Auto*, *Blur*, *Mica*, *Mica Alt*, *Acrylic*, *Off*. Only the presets your Windows build supports are listed. |
## Writing your own
**Open themes folder** in Settings takes you to:
| | |
|---|---|
| macOS / Linux | `~/.config/tty7/themes/` |
| Windows | `%APPDATA%\tty7\themes\` |
Drop a `.yaml` file in and it appears in the picker. The file name is the
theme's id; `name` is what is shown.
```yaml
name: "Midnight"
background: "#0d1117"
foreground: "#c9d1d9"
accent: "#3fdd8c"
cursor: "#3fdd8c"
selection: "#264f78"
opacity: 0.95
blur: true
ansi:
normal: ["#484f58", "#ff7b72", "#3fb950", "#d29922", "#58a6ff", "#bc8cff", "#39c5cf", "#b1bac4"]
bright: ["#6e7681", "#ffa198", "#56d364", "#e3b341", "#79c0ff", "#d2a8ff", "#56d4dd", "#f0f6fc"]
```
Everything except `background`, `foreground`, `accent`, and `ansi` is optional.
### Gradients and images
`background` also takes two colours:
```yaml
background: { top: "#0d1117", bottom: "#161b22" }
# or
background: { left: "#0d1117", right: "#161b22" }
```
And a theme can carry an image behind the terminal:
```yaml
background_image:
path: "/Users/me/Pictures/wall.jpg"
opacity: 0.25
```
The Settings panel has a picker for both, so you rarely have to write this by
hand.
## Editing in the app
Select a built-in theme and hit **Duplicate to edit** — built-ins are read-only,
so the editor works on your copy. From there you get every colour, including the
sixteen ANSI slots, plus the background image controls. Changes are written back
to your themes folder as YAML.
## Importing from iTerm2
Drop an `.itermcolors` file into the themes folder and tty7 reads it directly —
no conversion step.
<Note>
A theme file tty7 could not load is listed in Settings under **Not loaded from
the themes folder**, with the reason, rather than silently ignored.
</Note>
+142
View File
@@ -0,0 +1,142 @@
{
"$schema": "https://mintlify.com/docs.json",
"theme": "mint",
"name": "tty7",
"description": "A terminal workbench: persistent sessions, remote work, agents.",
"colors": {
"primary": "#0FA968",
"light": "#3FDD8C",
"dark": "#0FA968"
},
"favicon": "/favicon.ico",
"logo": {
"light": "/logo/logo.svg",
"dark": "/logo/logo.svg",
"href": "https://github.com/l0ng-ai/tty7"
},
"navigation": {
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Getting started",
"pages": [
"index",
"getting-started/installation",
"getting-started/first-launch",
"getting-started/concepts"
]
},
{
"group": "The window",
"pages": [
"window/tabs-and-splits",
"window/sidebar",
"window/command-palette",
"window/search",
"window/side-panel"
]
},
{
"group": "The terminal",
"pages": [
"terminal/prompt",
"terminal/history",
"terminal/selection-and-clipboard",
"terminal/links",
"terminal/mouse-and-scrolling"
]
},
{
"group": "Coding agents",
"pages": [
"agents/overview",
"agents/status",
"agents/sessions",
"agents/orchestration"
]
},
{
"group": "Remote work",
"pages": [
"remote/ssh",
"remote/sftp",
"remote/port-forwarding",
"remote/workspaces"
]
},
{
"group": "Git",
"pages": [
"git/source-control",
"git/diffs",
"git/worktrees"
]
},
{
"group": "Customization",
"pages": [
"customization/settings",
"customization/themes",
"customization/fonts",
"customization/keybindings"
]
}
]
},
{
"tab": "CLI",
"groups": [
{
"group": "The tty7 command",
"pages": [
"cli/overview",
"cli/reference",
"cli/agent-skill"
]
}
]
},
{
"tab": "Reference",
"groups": [
{
"group": "Reference",
"pages": [
"reference/configuration",
"reference/keyboard-shortcuts",
"reference/shell-integration",
"reference/updates",
"reference/privacy",
"reference/troubleshooting"
]
}
]
}
]
},
"navbar": {
"links": [
{
"label": "GitHub",
"href": "https://github.com/l0ng-ai/tty7"
},
{
"label": "Discord",
"href": "https://discord.gg/s3dethqz2V"
}
],
"primary": {
"type": "button",
"label": "Download",
"href": "https://github.com/l0ng-ai/tty7/releases"
}
},
"footer": {
"socials": {
"github": "https://github.com/l0ng-ai/tty7",
"discord": "https://discord.gg/s3dethqz2V"
}
}
}
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

-158
View File
@@ -1,158 +0,0 @@
# Features
<sub>English · [简体中文](features.zh-CN.md)</sub>
## Input
- **Ghost suggestions** — your history completes the whole line as you type; <kbd>→</kbd> to accept
- **Explained tab completion** — every flag and subcommand with its description, for ~100 common commands; when tty7 has nothing to offer the Tab falls through to your shell's own completion, and the whole feature can be turned off (Settings → Input → Prompt, or `tab_completion` in `config.json`)
- **Syntax highlighting** — as you type, nothing to install
- **Fuzzy history search** — <kbd>⌃ R</kbd> shows what you ran, where, and whether it failed; turn it off (Settings → Input → Prompt, or `history_search` in `config.json`) and <kbd>⌃ R</kbd> goes to your shell instead, so an fzf / percol binding keeps working
- **History from day one** — your existing shell history works as-is and carries across sessions
- **Line editing** — click to place the caret, mouse selection, word motion, undo
- **Multi-line editing** — wrapped and multi-line commands edit in place; the grid shifts to keep the caret visible. <kbd>⇧ ⏎</kbd> · <kbd>⌥ ⏎</kbd> insert a newline instead of submitting (rebindable as `InsertNewline`); a plain <kbd>⏎</kbd> submits the whole buffer
## In the window
- **Tabs & splits** — always open in the current directory
- **Rearrange splits by dragging** — hover a pane and a small grip appears along its top edge; drag it over the layout to put the pane somewhere else in the tab. Dropping on a pane's side goes in beside it — taking an equal share of the row or column it joins, or splitting that pane in half when the side faces across the layout rather than along it — dropping on its middle trades the two panes' places, and carrying it past a pane's outer side — the one facing the window rather than another pane — makes it a full-width or full-height band beside everything else, sized to an even share of what that side already holds — so a pane in the middle of a 2×2 becomes a full-height third column in one drag. The landing lights up while you drag, and only ever lights up when the drop would really change the layout
- **Repo-grouped sidebar** — the left tab sidebar groups rows under a header per git repository, non-repo tabs in a trailing *Scratch* section; branch switches and in-repo `cd`s never move a row (`sidebar_grouping` in `config.json`: `repo` default, `none` for a flat list)
- **Command palette** <kbd>⌘ P</kbd> · scrollback search <kbd>⌘ F</kbd>
- **⌘/Ctrl-click links** (⌘ on macOS, Ctrl on Windows/Linux) · desktop notifications · copy on select (opt-in, Settings → Input → Selection & clipboard)
- **Smart double-click selection** — double-click grabs the whole URL, file path, bracket/quote pair, or dictionary-segmented CJK word under the cursor; Shift-click extends a selection (toggle in Settings → Input → Selection & clipboard; word separators via `word_separators` in `config.json`)
- **Nine themes, plus your own** — YAML seed themes with solid, gradient, or image backgrounds; iTerm2 `.itermcolors` import; in-app color editor with a background-image picker
- **Sync with system** — Settings → Appearance; pick separate light and dark themes and tty7 follows the OS appearance live (`theme_follow_system`, `theme_preset_light` / `theme_preset_dark` in `config.json`)
- **Window opacity & blur** — Settings → Appearance → Transparency; applies to every theme, *Follow theme* returns to the theme's own `opacity` / `blur`
- **CJK / IME input**
- **Windows Explorer menu** — the installer offers *Add “Open in tty7” to the folder context menu* as a setup task, off by default, and the uninstaller always takes it back out. Writing shell verbs is an install-time decision, so there is no runtime setting; a portable-zip install can do it itself with `tty7-app.exe --register-explorer-menu` (or `--unregister-explorer-menu`). Either way the keys land under `HKCU`, so only your own Windows account is affected
## Fonts
- **Hack is bundled** — it ships inside the binary, so the default renders identically everywhere without relying on a system install
- **Primary + ordered fallbacks** — `font_family` and `font_fallbacks` in `config.json`; optional `font_family_bold` / `font_family_italic` for distinct faces, and `font_features` to pass OpenType features through (contextual ligatures stay off unless you ask for them)
- **Platform-aware defaults** — the fallback list names faces the host OS actually ships (PingFang SC / Apple Color Emoji on macOS, Microsoft YaHei / Segoe UI Emoji on Windows, Noto on Linux). Those stock names are appended to a hand-written list too, so a `config.json` written on another platform still resolves
### CJK and the two-column grid
A cell is one advance of the primary face, and a wide (CJK) character is pinned
to exactly two of them. A CJK fallback therefore sits flush in its slot only if
its ideographs advance **twice** the primary's Latin advance.
Bundled Hack advances 0.60205em, so a two-column slot is 1.2041em — while every
stock CJK face (Microsoft YaHei, PingFang SC, Noto Sans CJK) advances 1.0em.
Those glyphs get left-aligned in the slot and the leftover ~0.2em lands as a gap
on the right of every character.
[Maple Mono NF CN](https://github.com/subframe7536/maple-font) is tried first on
every platform for exactly this reason — 0.6em Latin, 1.2em CJK, an exact
two-cell fit against Hack. It is referenced by name only, never bundled (~20MB
per weight): install it and tty7 picks it up with no config change.
For CJK set *tight* rather than merely even, change the primary face instead —
one that advances 0.5em (Sarasa Mono SC, say) makes two columns exactly 1.0em.
## Coding agents
tty7 recognizes third-party coding agents running in a pane (Claude Code,
Codex, Gemini CLI, Aider, Amp, OpenCode, and 12 more) and adds around them —
it never wraps or replaces the agent.
- **Brand avatars** — the tab chip / sidebar row shows which agent runs where; custom wrappers map in via `agent_commands` in `config.json`
- **Status dot** — working (blue) / needs your input (amber) / done (green), driven by agent-reported events over an OSC channel; Settings → Agents installs the hooks that feed it (Claude Code, Codex, Copilot CLI, OpenCode, Pi, Grok Build, Oh My Pi)
- **Notifications** — "needs your permission…" the moment an agent blocks on you, and "finished after Ns" per turn, honoring your notification policy
- **Branch at a glance** — each sidebar row shows its pane's git branch and working-tree diff (`+N M`), refreshed on `cd` and when a command finishes; clicking the counts opens the diff overlay, and turning that off (Settings → Window & Tabs, or `sidebar_diff_preview: false` in `config.json`) keeps the readout while making it non-clickable
- **Session resume** — panes lost to a reboot re-launch their agent conversation on restore, carrying the original launch flags (`claude --dangerously-skip-permissions --resume …`) (`restore_agent_sessions`, on by default)
- **Fork session** — branch a live agent conversation into a second, independent one by shelling the agent's own fork command (`codex fork <id>`, `claude --resume <id> --fork-session`, also OpenCode, Grok Build, and Oh My Pi); the original is untouched and both continue separately. Right-click a pane to pick a split placement, or right-click the tab / sidebar row to open the fork in a new tab. Needs the agent's hooks installed, since the fork targets the session id they report; a remote pane can't fork, because the command would run against the local agent — and note a fork copies the whole transcript, so repeated forking costs real disk in the agent's own session store
- **Copy Session ID** — put the agent's native session id on the clipboard, beside *Copy Working Directory*, for pasting into `codex resume`, a bug report, or another tool
- **Context feed** — palette commands send the current selection or the repo's `git diff` to the running agent as a ready-made prompt
- **Tray icon** — a system tray / menu bar item that flips to an attention state the moment any agent needs your input; its menu lists every agent pane (brand avatar + status dot, click to reveal), switches the notification policy, and offers *Quit and Stop Server…* alongside the plain session-keeping quit (`show_tray_icon`, on by default)
- **`tty7 wait`** — the CLI's orchestration primitive: block until a pane's agent needs input or finishes its turn (`tty7 wait %3 --until waiting,done --changed --timeout 600`, exit 124 on timeout), so one agent can sleep until its peer blocks on a permission prompt instead of screen-scraping — then `tty7 capture %3 --plain` to read the result. The agent status is a level, not an event, so `--changed` ignores the state the pane was already in when the wait began; without it, the JSON's `stale` flag says whether the answer might belong to the previous turn
- **`tty7` on PATH** — the CLI ships inside every installer and is put on PATH at launch, so a script or a coding agent can drive tty7 from any terminal. Inside a tty7 pane it works regardless, since panes inherit the app's environment. On Unix it is a symlink into whichever of `/opt/homebrew/bin`, `/usr/local/bin`, `~/.local/bin`, `~/bin`, `~/.cargo/bin` your PATH already covers; on Windows the install directory is appended to your user PATH, and the uninstaller takes it back out. A `tty7` you installed yourself is left alone, never replaced. Off via Settings → Agents or `install_cli_on_path: false` in `config.json`
## SSH
A native Rust SSH stack (russh) is the **only** path — profiles, credentials,
and SFTP without shelling out to `ssh`. There is no system-ssh compat mode.
- **QuickConnect** — type `user@host[:port]` in the palette and connect; IPv6 `[::1]:port` supported
- **Saved profiles** — full connection config with passwords / passphrases in the OS keychain, never on disk
- **`~/.ssh/config` aliases** — type one to connect (resolved natively — common fields, best-effort — over russh), or import them as profiles in Settings
- **GUI auth** — in-pane sheets for password, key passphrase, 2FA, and host-key confirmation (new vs. changed)
- **Built-in SFTP** — a slide-in file panel: browse, upload / download, rename / delete / chmod, drag to Finder
- **Port forwarding** — Local / Remote / Dynamic, preconfigured or added live, plus ⌘/Ctrl-click `localhost:PORT` to auto-forward
- **Jump hosts & proxies** — multi-hop via profile references or `ProxyJump`, ProxyCommand, SOCKS5 / HTTP
| Entry point | Connects via |
|---|---|
| Saved profiles · QuickConnect · typed `user@host[:port]` | Native russh — SFTP · keychain · GUI auth · L/R/D forwards |
| `~/.ssh/config` aliases | Resolved natively, then russh (`Match`/canonicalize/GSSAPI unsupported — no fallback) |
## Keybindings
Keys are shown in macOS notation — on Windows and Linux, read <kbd>⌘</kbd> as
<kbd>Ctrl</kbd>. The essentials:
| | |
|---|---|
| <kbd>⌘ T</kbd> · <kbd>⌘ W</kbd> · <kbd>⌘ ⇧ T</kbd> | new tab · close tab · reopen closed tab |
| <kbd>⌘ 1</kbd>…<kbd>⌘ 9</kbd> | jump to tab 19 |
| <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | hold to walk the switcher forwards · backwards; it commits when you let go |
| <kbd>⌘ D</kbd> · <kbd>⌘ ⇧ D</kbd> | split right · split down |
| <kbd>⌘ ]</kbd> · <kbd>⌘ [</kbd> | next pane · previous pane |
| <kbd>⌘ ⌥ ←→↑↓</kbd> | focus the pane in that direction |
| <kbd>⌘ ⏎</kbd> · <kbd>⌘ ⇧ ⏎</kbd> | toggle fullscreen · zoom pane |
| <kbd>⌘ K</kbd> | clear scrollback |
| <kbd>⌘ P</kbd> | command palette |
| <kbd>⌘ F</kbd> | search the scrollback |
| <kbd>⌃ R</kbd> | fuzzy-search shell history |
| <kbd>⌘ +</kbd> · <kbd>⌘ </kbd> · <kbd>⌘ 0</kbd> | font size up · down · reset |
| <kbd>⌘</kbd> + wheel | zoom the font by scrolling over a terminal |
**Settings → Keybindings** (<kbd>⌘ ,</kbd>) lists every shortcut. Click one,
press the new keys (<kbd>Esc</kbd> cancels, <kbd>Backspace</kbd> resets to
default), and it takes effect immediately. Pane resize and swap have no default
keys — bind them here or run them from the command palette.
**tmux preset** — remaps pane/tab actions onto a prefix (default <kbd>⌃ B</kbd>):
<kbd>⌃ B</kbd> <kbd>C</kbd> opens a tab, <kbd>⌃ B</kbd> <kbd>%</kbd> splits,
<kbd>⌃ B</kbd> then an arrow moves focus. A bare prefix reaches the shell after
a brief pause; `prefix` + an unbound key passes straight through.
## Performance notes
- The PTY is read at device speed and parsed in large batches, off the render path
- Hot paths are lock-free — a big `cat` never waits on drawing
- The server buffers up to 16 MiB ahead of the window before backpressure applies
## macOS privacy
Panes are forked from the bundled executable, so macOS attributes a program's
request for a protected resource to tty7.app. tty7 declares the matching TCC
usage strings (camera, microphone, contacts, calendar, reminders, photos,
location, local network, Bluetooth, speech recognition, Apple Events, system
administration) so that program gets the normal one-time prompt instead of
being denied outright with no prompt at all.
Not covered by usage strings:
- **Full Disk Access** — Apple defines no usage-string key for it. Reaching
`~/Library/Mail`, `~/Library/Messages`, `~/Library/Safari` or
`~/Library/Containers` needs a manual grant in System Settings.
Declaring a usage string is not the same as holding the permission: tty7.app
itself is granted none of these resources. Every prompt you see belongs to
whatever you ran in the pane, and you can revoke it under Privacy & Security.
## Localization
The GUI ships English, Simplified Chinese and Japanese strings. Pick one in
Settings → Appearance → Language, or in `config.json`:
```json
{ "gui_language": "zh-CN" }
```
`en`, `zh-CN` and `ja-JP` are the only accepted values; anything else falls back
to `en`.
The choice is explicit — the system language is never inferred. CLI output stays
English so agent and script integrations keep a stable, predictable surface.
-150
View File
@@ -1,150 +0,0 @@
# 功能
<sub>[English](features.md) · 简体中文</sub>
## 输入
- **影子建议** —— 边打字边用你的历史补全整条命令,<kbd>→</kbd> 接受
- **带说明的 Tab 补全** —— 每个 flag、每个子命令都带说明,覆盖约 100 个常用命令;tty7 没有候选时 Tab 自动交给 shell 自己的补全,整个功能也可关闭(设置 → 输入 → 提示符,或 `config.json` 里的 `tab_completion`
- **语法高亮** —— 边打边亮,什么都不用装
- **模糊历史搜索** —— <kbd>⌃ R</kbd> 看到每条命令在哪跑的、什么时候、有没有失败;关掉它(设置 → 输入 → 提示符,或 `config.json` 里的 `history_search`)后 <kbd>⌃ R</kbd> 直接交给 shell,你绑的 fzf / percol 照常可用
- **历史开箱即用** —— 你已有的 shell 历史直接生效,并跨会话延续
- **行编辑** —— 点击定位光标、鼠标选区、词级移动、撤销
- **多行编辑** —— 折行和多行命令原地编辑;网格自动上移,光标始终可见。<kbd>⇧ ⏎</kbd> · <kbd>⌥ ⏎</kbd> 插入换行而不提交(可改绑,动作名 `InsertNewline`),单独按 <kbd>⏎</kbd> 提交整个缓冲区
## 窗口
- **标签页与分屏** —— 永远开在当前目录
- **拖动重排分屏** —— 鼠标移到某个 pane 上,它顶边中间会浮出一个小抓手;拖着它在布局里走,就能把这个 pane 挪到标签页内的别处。落在某个 pane 的某一侧=插到它旁边:那一侧要是朝着同一排的邻居,就并入那一排、和它们等分;要是横着切过这一排(没有排可并),才是把那个 pane 一分为二、自己占住那一半。落在它正中=两个 pane 互换位置;继续推到某个 pane 朝着窗口那一侧的外缘(不是朝着另一个 pane 的那侧)=变成贴着窗口某一边、跨满整行或整列的一条,宽度按那条轴上已有的份数均分 —— 2×2 里的一个 pane 一次拖动就能变成通高的第三列(各占三分之一),而不是独占半屏。拖动过程中落点会高亮,且只有当这一放确实会改变布局时才会亮
- **侧栏按仓库分组** —— 左侧标签栏按 git 仓库分组、每组一个标题行,不在仓库里的标签归入末尾的 *草稿* 组;切分支、仓库内 `cd` 都不会挪动行(`config.json``sidebar_grouping`:默认 `repo``none` 恢复扁平列表)
- **命令面板** <kbd>⌘ P</kbd> · scrollback 搜索 <kbd>⌘ F</kbd>
- **⌘ 点击打开链接** · 桌面通知 · 划选即复制(可选,设置 → 输入 → 选择与剪贴板)
- **智能双击选中** —— 双击直接选中整条 URL、文件路径、括号/引号对,中文按词典分词出词;Shift 点击扩展选区(设置 → 输入 → 选择与剪贴板可开关;分隔符用 `config.json``word_separators` 配置)
- **9 套主题,也能自定义** — YAML 种子主题,背景支持纯色、渐变或图片;可导入 iTerm2 `.itermcolors`;应用内颜色编辑器带背景图选择
- **跟随系统外观** — 设置 → 外观;分别选好浅色和深色主题,tty7 随系统深浅模式实时切换(`config.json` 中的 `theme_follow_system``theme_preset_light` / `theme_preset_dark`
- **窗口透明与模糊** — 设置 → 外观 → 透明度;对所有主题生效,*跟随主题* 恢复主题自带的 `opacity` / `blur`
- **CJK / 输入法输入**
- **Windows 资源管理器右键菜单** —— 安装程序提供 *Add “Open in tty7” to the folder context menu* 这个安装任务,默认不勾选,卸载时一律移除。写 shell verb 是安装期的决定,所以没有运行时开关;用 portable zip 的话可以自己执行 `tty7-app.exe --register-explorer-menu`(或 `--unregister-explorer-menu`)。两种方式写入的键都在 `HKCU` 下,只影响你自己的 Windows 账户
## 字体
- **内置 Hack** —— 打包进二进制,默认配置在各平台渲染完全一致,不依赖系统安装
- **主字体 + 有序 fallback** —— `config.json` 里的 `font_family``font_fallbacks`;可选 `font_family_bold` / `font_family_italic` 指定独立字面,`font_features` 透传 OpenType 特性(上下文连字默认关闭)
- **默认列表按平台分支** —— fallback 只写宿主系统真正自带的字体(macOS 用 PingFang SC / Apple Color EmojiWindows 用 Microsoft YaHei / Segoe UI EmojiLinux 用 Noto)。这些名字也会追加到你手写的列表后面,所以在别的平台写出来的 `config.json` 一样能落地
### 中文与两列网格
一个格子等于主字体的一个 advance,宽字符(CJK)被钉死在正好两格上。所以中文
fallback 只有在**汉字 advance 等于主字体西文 advance 的两倍**时,才能严丝合缝地
填满自己的槽。
内置 Hack 的 advance 是 0.60205em,两格就是 1.2041em —— 而系统自带的中文字体
Microsoft YaHei、PingFang SC、Noto Sans CJK)全都是 1.0em。这些字形在槽里左
对齐,多出来的约 0.2em 就变成每个字右边的一道空隙。
[Maple Mono NF CN](https://github.com/subframe7536/maple-font) 在所有平台都排在
第一位正是因为这个 —— 西文 0.6em、中文 1.2em,对上 Hack 正好两格。它只按名字引
用,不打包(每字重约 20MB):装上即生效,不用改配置。
想让中文排得**紧**而不只是均匀,要换的是主字体:选一个 advance 为 0.5em 的
(比如 Sarasa Mono SC 更纱黑体等宽),两格就正好 1.0em。
## Coding agent
tty7 能识别 pane 里跑着的第三方 coding agentClaude Code、Codex、Gemini CLI、
Aider、Amp、OpenCode 等共 18 个)并在其外围加功能 —— 绝不包裹或替代 agent 本身。
- **品牌头像** —— 标签 chip / 侧栏行显示每个 pane 跑的是哪个 agent;自定义包装命令可通过 `config.json``agent_commands` 映射
- **状态点** —— 工作中(蓝)/ 等你输入(琥珀)/ 完成(绿),由 agent 自己上报的 OSC 事件驱动;在 设置 → Agents 一键装好对应 hooksClaude Code、Codex、Copilot CLI、OpenCode、Pi、Grok Build、Oh My Pi
- **通知** —— agent 卡在等你批准的那一刻弹 "needs your permission…",每轮结束弹 "finished after Ns",遵循你的通知策略
- **一眼看分支** —— 侧栏每行显示该 pane 的 git 分支和工作区改动(`+N M`),`cd` 或命令跑完时自动刷新;点改动数字会打开 diff 浮层,关掉它(设置 → 窗口与标签页,或 `config.json``sidebar_diff_preview: false`)分支和数字照常显示,只是不再可点
- **会话恢复** —— 重启后无法重连的 pane 会自动续上 agent 对话,并带上原始启动 flags(`claude --dangerously-skip-permissions --resume …``restore_agent_sessions`,默认开启)
- **Fork 会话** —— 直接调 agent 自己的 fork 命令(`codex fork <id>``claude --resume <id> --fork-session`OpenCode、Grok Build 和 Oh My Pi 同样支持),把当前对话分叉成一个独立会话;原会话原封不动,两边各自往下走。在 pane 上右键可选择分屏位置,在标签 / 侧栏行上右键则直接开新标签。需要先装好该 agent 的 hooksfork 认的是 hooks 上报的 session id);远程 pane 不能 fork,因为命令会跑在本机的 agent 上;另外 fork 会整份复制对话历史,反复 fork 会在 agent 自己的会话目录里占掉不少磁盘
- **复制会话 ID** —— 把 agent 的原生 session id 复制到剪贴板,就在 *复制工作目录* 旁边,方便粘进 `codex resume`、bug 报告或别的工具
- **上下文回填** —— 面板命令把当前选区或仓库 `git diff` 打包成 prompt 直接喂给正在跑的 agent
- **托盘图标** —— 系统托盘 / 菜单栏常驻图标,任何 agent 等你输入时立即切换为提醒态;菜单列出所有 agent pane(品牌头像 + 状态点,点击直达)、可切换通知策略,并在保留会话的普通退出之外提供 *退出并停止服务器…*`show_tray_icon`,默认开启)
- **`tty7 wait`** —— CLI 的编排原语:阻塞到某个 pane 的 agent 等待输入或完成一轮(`tty7 wait %3 --until waiting,done --changed --timeout 600`,超时退出码 124),让一个 agent 睡到同伴卡在权限确认的那一刻,而不是抓屏猜——然后 `tty7 capture %3 --plain` 收结果。agent 状态是电平不是边沿,所以 `--changed` 会忽略 wait 开始时 pane 本来就处在的那个状态;不加它的话,JSON 里的 `stale` 标记会告诉你这个答案是不是上一轮留下的
- **`tty7` 上 PATH** —— CLI 随每个安装包一起发布,启动时自动放到 PATH 上,脚本和 coding agent 在任何终端里都能驱动 tty7。tty7 自己的 pane 里则一定可用,因为 pane 继承 app 的环境。Unix 上是往 `/opt/homebrew/bin``/usr/local/bin``~/.local/bin``~/bin``~/.cargo/bin` 中你 PATH 已经覆盖的那个目录里放一个软链;Windows 上是把安装目录追加到用户 PATH,卸载时再摘掉。你自己装的 `tty7` 一律保持原样,不会被覆盖。关掉:设置 → Agents,或 `config.json``install_cli_on_path: false`
## SSH
**唯一**路径就是原生 Rust SSH 栈(russh)—— profile、凭据、SFTP 全部内置,
不 shell 出 `ssh`,也没有系统 ssh 兼容模式。
- **QuickConnect** —— 面板里打 `user@host[:port]` 回车即连;支持 IPv6 `[::1]:port`
- **保存 profile** —— 完整连接配置,密码 / passphrase 进 OS keychain,不落盘
- **`~/.ssh/config` alias** —— 直接输入 alias 即连(原生解析常用字段,尽力而为,走 russh),也可在设置页一键导入为 profile
- **GUI 认证** —— pane 内 sheet 输入密码、私钥 passphrase、2FA,并确认主机密钥(新主机 vs 已变更)
- **内置 SFTP** —— 滑入式文件面板:浏览、上传 / 下载、重命名 / 删除 / chmod,可拖进 Finder
- **端口转发** —— Local / Remote / Dynamic,预配置或运行时增删,外加 ⌘ 点击 `localhost:PORT` 一键转发
- **跳板与代理** —— 经 profile 引用或 `ProxyJump` 多跳、ProxyCommand、SOCKS5 / HTTP
| 入口 | 连接方式 |
|---|---|
| 保存 profile · QuickConnect · 输入 `user@host[:port]` | 原生 russh —— SFTP · keychain · GUI 认证 · L/R/D 转发 |
| `~/.ssh/config` alias | 原生解析后走 russh`Match`/canonicalize/GSSAPI 不支持,且无回退) |
## 快捷键
下表按 macOS 记法书写 —— 在 Windows 和 Linux 上,把 <kbd>⌘</kbd> 读作
<kbd>Ctrl</kbd>。最常用的几个:
| | |
|---|---|
| <kbd>⌘ T</kbd> · <kbd>⌘ W</kbd> · <kbd>⌘ ⇧ T</kbd> | 新建标签页 · 关闭标签页 · 恢复关闭的标签页 |
| <kbd>⌘ 1</kbd>…<kbd>⌘ 9</kbd> | 跳到第 19 个标签页 |
| <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | 按住不放在切换面板里向后 · 向前走,松手即切换 |
| <kbd>⌘ D</kbd> · <kbd>⌘ ⇧ D</kbd> | 向右分屏 · 向下分屏 |
| <kbd>⌘ ]</kbd> · <kbd>⌘ [</kbd> | 下一个窗格 · 上一个窗格 |
| <kbd>⌘ ⌥ ←→↑↓</kbd> | 按方向切换焦点窗格 |
| <kbd>⌘ ⏎</kbd> · <kbd>⌘ ⇧ ⏎</kbd> | 切换全屏 · 缩放窗格 |
| <kbd>⌘ K</kbd> | 清除 scrollback |
| <kbd>⌘ P</kbd> | 命令面板 |
| <kbd>⌘ F</kbd> | 搜索 scrollback |
| <kbd>⌃ R</kbd> | 模糊搜索 shell 历史 |
| <kbd>⌘ +</kbd> · <kbd>⌘ </kbd> · <kbd>⌘ 0</kbd> | 字号增大 · 减小 · 重置 |
| <kbd>⌘</kbd> + 滚轮 | 在终端上滚动缩放字号,演示时随手放大 |
**设置 → 按键绑定**<kbd>⌘ ,</kbd>)列出全部快捷键。点一行、按下新键即可
<kbd>Esc</kbd> 取消,<kbd>Backspace</kbd> 恢复默认),改完立即生效。窗格缩放与
交换默认不绑定键 —— 在这里绑定,或从命令面板执行。
**tmux 预设** —— 把窗格/标签页操作映射到前缀键(默认 <kbd>⌃ B</kbd>):
<kbd>⌃ B</kbd> <kbd>C</kbd> 新建标签页,<kbd>⌃ B</kbd> <kbd>%</kbd> 分屏,
<kbd>⌃ B</kbd> 接方向键切换焦点。单独按前缀键会在短暂延迟后送达 shell,
`前缀` + 未绑定的键原样透传给终端。
## 性能说明
- 以设备速度读取 PTY,在渲染路径之外成批解析
- 热路径全程无锁 —— 再大的 `cat` 也不会阻塞在渲染上
- 触发背压前,服务器最多可领先窗口缓冲 16 MiB
## macOS 隐私
窗格是从 app bundle 里的可执行文件 fork 出来的,所以程序申请受保护资源时,
macOS 会把这次请求算到 tty7.app 头上。tty7 声明了对应的 TCC usage strings
(摄像头、麦克风、通讯录、日历、提醒、照片、定位、本地网络、蓝牙、语音识别、
Apple Events、系统管理),这样程序才能正常弹出一次性授权窗口,而不是连弹窗都
没有就被直接拒绝。
不受 usage strings 覆盖的:
- **完全磁盘访问** —— 苹果没有为它定义 usage-string 键。要读写
`~/Library/Mail``~/Library/Messages``~/Library/Safari`
`~/Library/Containers`,需要在「系统设置」中手动授权。
声明 usage string 不等于持有权限:tty7.app 自己一项都没有拿到。你看到的每个
授权弹窗都属于你在窗格里运行的那个程序,也可以在「隐私与安全性」中撤销。
## 本地化
GUI 目前提供英文、简体中文和日文三套文案。在「设置 → 外观 → 语言」中选择,或直接改
`config.json`
```json
{ "gui_language": "zh-CN" }
```
只接受 `en``zh-CN``ja-JP` 三个值,其它值一律回落到 `en`。语言必须显式指定,不会
去猜系统语言。CLI 输出保持英文,保证 agent、脚本和开发者工作流的输出稳定可预测。
+108
View File
@@ -0,0 +1,108 @@
---
title: "Core concepts"
description: "Workspaces, tabs, panes — and the background server that owns them all."
---
Four words explain most of tty7. Three of them you can see; the fourth is the
reason the other three survive a reboot.
## Pane
A **pane** is one terminal: one shell (or one program) attached to one PTY. It
is the only thing in tty7 that actually runs something.
Panes have stable ids — `%42` — for their whole life. That id is what the
[CLI](/cli/overview) addresses, and what `$TTY7_PANE` holds inside the pane
itself.
## Tab
A **tab** is a layout of panes. One pane to start with; split it and the tab
holds two, arranged in rows and columns you can drag around.
Tabs appear in the sidebar (or the top strip, if you move it there). A tab's
label is the best evidence tty7 has: a name you set, else the coding agent
running in it, else the last segment of its working directory.
## Workspace
A **workspace** is a named set of tabs — a project, usually. One window shows
one workspace at a time, and <kbd>⌘ ⇧ O</kbd> opens the switcher to move between
them or open a second window on another one.
Workspaces are how tty7 keeps ten repositories from becoming forty
indistinguishable tabs. They also travel: a workspace on a remote machine is
still a workspace, opened from the same switcher.
<Frame caption="Placeholder — screenshot: the workspace switcher, workspaces on the left, that workspace's tabs on the right">
<img src="/images/placeholder.svg" alt="The tty7 workspace switcher" />
</Frame>
## The server
Here is the part that matters. **The window does not own your shells — a
background server does.**
Quitting tty7 closes the window and leaves that server running. Your build keeps
building, your agent keeps working, your SSH session stays up. Open tty7 again
and it reattaches to exactly what was there.
This is also why:
- **`tty7` works from any terminal.** The CLI talks to the same server. The GUI
does not have to be running at all.
- **A crash is not a catastrophe.** Panes come back showing what was on them:
a capped tail of each pane's output is kept on disk and handed to the pane
that reopens on its id.
- **Stopping is explicit.** *Quit and Stop Server…* in the tray menu is the only
ordinary way to end everything, and it warns you first.
<Warning>
Restarting the server ends every process in every pane on that machine —
shells, agents, and SSH sessions alike. Layouts are kept and come back with
fresh shells. Never do it on someone else's behalf without asking.
</Warning>
### What survives what
| | Close a tab | Quit tty7 | Stop the server | Reboot |
|---|:--:|:--:|:--:|:--:|
| The shell keeps running | ✗ | ✓ | ✗ | ✗ |
| The layout comes back | ✗ | ✓ | ✓ | ✓ |
| What was on screen comes back | ✗ | ✓ | ✓ | ✓ <sup>1</sup> |
| A supported agent session resumes | ✗ | ✓ | ✓ | ✓ |
<sub><sup>1</sup> A capped tail of each pane, restored once. See
[session restore](/reference/troubleshooting#panes-came-back-empty).</sub>
## Machines
Everything above exists per **machine**. Your laptop is one; a dev box you
connect to over SSH is another, with its own server, its own workspaces, and its
own panes.
The switcher lists them together, and the CLI reaches them with `-m`:
```bash
tty7 -m devbox ls
```
Remote panes run on the remote machine — the files, the repository, the git
data, and the process tree are all over there.
[Remote workspaces →](/remote/workspaces)
## The three environment variables
Every pane exports these, and anything you launch from one inherits them:
| Variable | What it holds |
|---|---|
| `TTY7_PANE` | This pane's id — the default target of `tty7 split`, `send`, `capture`, `procs`. |
| `TTY7_WS` | This pane's workspace id. |
| `TTY7_CONFIG_DIR` | The config directory, which is how the CLI finds the right server. |
`echo $TTY7_PANE` is the fastest way to tell whether you are inside tty7 at all.
<Card title="Drive it from a script" icon="terminal" href="/cli/overview">
Those ids are the whole interface. The CLI page starts there.
</Card>
+126
View File
@@ -0,0 +1,126 @@
---
title: "First launch"
description: "The handful of settings worth changing before you start working."
---
Open tty7 and you get a window with one tab and one shell, and a tab sidebar
down the left. Everything below is optional — but these are the settings people
end up changing anyway, so they are worth five minutes now.
Open Settings with <kbd>⌘ ,</kbd> (<kbd>Ctrl ,</kbd> on Windows and Linux), or
from the command palette (<kbd>⌘ P</kbd> → *Settings*).
<Frame caption="Placeholder — screenshot: the Settings window, Appearance section">
<img src="/images/placeholder.svg" alt="tty7 Settings" />
</Frame>
## 1. Pick a theme
**Settings → Appearance → Theme.** Nine themes ship built in — Light, One Light,
Catppuccin Latte, Rosé Pine Dawn, Dark, Dracula, Harbor, One Dark Pro, and
Rosé Pine. The default is **Light**.
Turn on **Sync with system** to pick a light theme and a dark theme separately;
tty7 then follows the OS appearance live.
Transparency lives on the same page, under **Transparency** — opacity applies to
every theme, and *Follow theme* hands the decision back to the theme's own
setting. On Windows there is also a **Background material** picker (Mica,
Acrylic, and friends).
[More about themes →](/customization/themes)
## 2. Choose your shell
**Settings → Terminal → Shell.** Leave **Program** empty to use the platform
default. Otherwise it takes an executable name on PATH or an absolute path
(`zsh`, `fish`, `pwsh`, `nu`, `/opt/homebrew/bin/bash`), plus space-separated
**Arguments** — `-l` for a login shell, say.
**Start in** decides what a *fresh* shell opens in: tty7's launch directory
(the default), your home folder, or a fixed path. New tabs and splits keep
inheriting the active pane's directory either way.
## 3. macOS only: decide what Option does
**Settings → Input → Keyboard → Option (⌥) acts as Meta.**
Off (the default), <kbd>⌥ B</kbd> types `∫`, which is what macOS has always
done. On, it sends the escape chord shells expect, so <kbd>⌥ B</kbd> moves back
a word and <kbd>⌥ ⌫</kbd> deletes one. Turn it on if you live in readline;
leave it off if you type accented characters.
## 4. If you use coding agents, install the hooks
**Settings → Agents.** tty7 detects 18 coding CLIs by process name on its own —
you get brand avatars and tab labels for free. The *status dots*, the "needs
your permission" notifications, and `tty7 wait` all need one more thing: a small
hook the agent calls to report what it is doing.
Click **Install** next to Claude Code, Codex, Copilot CLI, OpenCode, Pi, Grok
Build, or Oh My Pi. It writes into that agent's own config directory and can be
removed from the same row.
[More about agents →](/agents/status)
## 5. Know what Quit does
Plain **Quit** closes the window and leaves the background server running.
Your shells, builds, and agent turns keep going, and reopening tty7 reattaches
to them.
To actually stop everything, use **Quit and Stop Server…** from the tray icon's
menu. It says so plainly before it does it: anything still running in your
shells is terminated, while your tabs and layout are kept and reopen with fresh
shells.
<Note>
This is why there is no tmux in the picture. The persistence is not a feature
of your shell setup — it belongs to the server underneath.
[Core concepts →](/getting-started/concepts)
</Note>
## 6. Tune the notifications
**Settings → Window & Tabs → Notifications.** By default tty7 posts a desktop
notification when a foreground command that ran longer than 10 seconds
finishes — but only while the window is unfocused. Set **Notify on command
finish** to *Never* or *Always*, and move the threshold if 10 seconds is the
wrong number for your work.
Agent notifications ("needs your permission…", "finished after 42s") follow the
same policy.
## 7. Coming from tmux?
**Settings → Keybindings → Preset → tmux** remaps pane and tab actions onto a
prefix, <kbd>⌃ B</kbd> by default. <kbd>⌃ B</kbd> <kbd>C</kbd> opens a tab,
<kbd>⌃ B</kbd> <kbd>%</kbd> splits, <kbd>⌃ B</kbd> then an arrow moves focus.
A bare prefix reaches the shell after about a second, and prefix plus an unbound
key passes straight through — so a tmux binding you did not remap still lands in
whatever is running.
[More about keybindings →](/customization/keybindings)
## Where things live
| | macOS / Linux | Windows |
|---|---|---|
| Settings file | `~/.config/tty7/config.json` | `%APPDATA%\tty7\config.json` |
| Custom themes | `~/.config/tty7/themes/` | `%APPDATA%\tty7\themes\` |
Everything in the Settings window writes to `config.json`, and you can edit it
by hand instead — see the [configuration reference](/reference/configuration).
Set `TTY7_CONFIG_DIR` to move the whole directory somewhere else.
## Next
<CardGroup cols={2}>
<Card title="Core concepts" icon="cube" href="/getting-started/concepts">
Workspaces, tabs, panes, and the server that owns them.
</Card>
<Card title="The prompt" icon="terminal" href="/terminal/prompt">
Suggestions, completion, and history search — the part you touch most.
</Card>
</CardGroup>
+151
View File
@@ -0,0 +1,151 @@
---
title: "Installation"
description: "Native builds for macOS, Windows, and Linux — plus building from source."
---
Every release publishes native builds on
[**GitHub Releases**](https://github.com/l0ng-ai/tty7/releases). There is no
runtime to install first: fonts are embedded in the binary, and the Linux
AppImage bundles its own X11/Wayland/font libraries.
<Tabs>
<Tab title="macOS">
Download the DMG that matches your Mac and drag **tty7** into Applications.
| Mac | File |
|---|---|
| Apple silicon (M1 and later) | `tty7-<version>-macos-arm64.dmg` |
| Intel | `tty7-<version>-macos-x86_64.dmg` |
Builds are signed with a Developer ID certificate and notarized by Apple, so
Gatekeeper opens them without a right-click dance.
<Note>
Builds are produced on macOS 14 and macOS 15. macOS 14 (Sonoma) or later
is the tested range.
</Note>
</Tab>
<Tab title="Windows">
Two shapes, both x86-64:
| File | Use it when |
|---|---|
| `tty7-<version>-windows-x86_64-setup.exe` | You want a normal install with Start-menu entries and an uninstaller. |
| `tty7-<version>-windows-x86_64.zip` | You want it portable — unzip anywhere and run `tty7-app.exe`. |
The installer offers one optional setup task, off by default: **Add "Open in
tty7" to the folder context menu**. It writes shell verbs under `HKCU`, so
only your own Windows account is affected, and the uninstaller always takes
them back out.
A portable install can add or remove the same entries itself:
```powershell
tty7-app.exe --register-explorer-menu
tty7-app.exe --unregister-explorer-menu
```
<Note>
The Windows package also carries a Linux `tty7-server` binary so a WSL
distro can be served locally instead of downloading one. See
[Remote workspaces](/remote/workspaces).
</Note>
</Tab>
<Tab title="Linux">
| File | Use it when |
|---|---|
| `tty7-<version>-linux-x86_64.AppImage` | Almost always. `chmod +x` and run — the X11, Wayland, xkb, and font libraries are bundled, so it works on Fedora, Arch, Debian and friends, not just Ubuntu. |
| `tty7-<version>-linux-x86_64.tar.gz` | You would rather unpack the plain binary and place it yourself. |
```bash
chmod +x tty7-*-linux-x86_64.AppImage
./tty7-*-linux-x86_64.AppImage
```
</Tab>
</Tabs>
## The `tty7` command
Every installer ships the `tty7` CLI beside the app, and the app puts it on your
PATH the first time it launches. That is what lets a script — or a coding agent
in some other terminal — open panes and read them back.
- **On Unix** it is a symlink into whichever of `/opt/homebrew/bin`,
`/usr/local/bin`, `~/.local/bin`, `~/bin`, or `~/.cargo/bin` your PATH already
covers.
- **On Windows** the install directory is appended to your user PATH, and the
uninstaller removes it again.
A `tty7` you installed yourself — a `cargo install` build, a package manager's
copy — is never replaced. To turn the whole thing off, uncheck **Settings →
Agents → Install the tty7 command on PATH**.
<Tip>
Inside a tty7 pane the CLI works regardless of PATH, because panes inherit the
app's environment.
</Tip>
## Updating
tty7 checks for updates every six hours and can update itself: **Settings →
About → Check now**, then **Update and relaunch**. Releases are downloaded and
verified in the background so applying one is just a restart.
Pick **Stable** or **Nightly** under **Settings → About → Update channel**. See
[Updates and channels](/reference/updates) for what each feed publishes and how
switching behaves.
## Building from source
You need a stable Rust toolchain. The build is a plain `cargo build`; the app
binary is `tty7-app`.
<Tabs>
<Tab title="macOS / Windows">
```bash
git clone https://github.com/l0ng-ai/tty7
cd tty7
cargo build --release
```
</Tab>
<Tab title="Linux">
gpui resolves its X11/Wayland/font backends through `pkg-config` at build
time, so the development packages have to be present:
```bash
sudo apt-get install -y pkg-config cmake clang \
libxkbcommon-dev libxkbcommon-x11-dev \
libfontconfig1-dev libfreetype6-dev \
libwayland-dev libx11-dev libxcb1-dev \
libzstd-dev libssl-dev libkrb5-dev
cargo build --release
```
</Tab>
</Tabs>
<Warning>
A source build does not update itself, and it will not replace an installed
copy's server. If you run both, see
[Troubleshooting](/reference/troubleshooting).
</Warning>
## Uninstalling
<AccordionGroup>
<Accordion title="macOS">
Quit tty7 (use **Quit and Stop Server** from the tray menu so the background
server stops too), then drag the app to the Trash. Your settings live in
`~/.config/tty7` and are left alone; delete that folder to remove them.
</Accordion>
<Accordion title="Windows">
Use **Add or remove programs**. The uninstaller removes the PATH entry and
any Explorer context-menu keys it added. Settings live in
`%APPDATA%\tty7`.
</Accordion>
<Accordion title="Linux">
Delete the AppImage or the unpacked directory. Settings live in
`~/.config/tty7`.
</Accordion>
</AccordionGroup>
+48
View File
@@ -0,0 +1,48 @@
---
title: "Diffs"
description: "The diff overlay: side-by-side or unified, from the sidebar or the panel."
---
## Opening one
| From | How |
|---|---|
| The sidebar | Click a row's `+N M` counts |
| Source Control | **Open Changes** on a file, or click the row |
| History | Click a file inside a commit's detail view |
The overlay covers the window; <kbd>Esc</kbd> closes it.
<Frame caption="Placeholder — screenshot: the diff overlay, side-by-side, several files">
<img src="/images/placeholder.svg" alt="The tty7 diff overlay" />
</Frame>
## Side-by-side or unified
**Toggle Unified / Side-by-Side Diff** in the command palette switches between
the two. The choice is global — one setting for every diff, the same call VS
Code's `diffEditor.renderSideBySide` makes — and persists as `diff_view` in
`config.json`.
## What it shows
- Every changed file, with its status and `+N M`
- **Untracked files** as a preview of their contents, up to 4 MB — past that the
card says the read failed rather than showing a silently cut-off file
- A commit's files, when the diff came from the history
Two limits keep a huge diff from becoming a huge wait:
| Limit | Value | What happens |
|---|---|---|
| Files rendered | 300 | *"Showing the first 300 of N changes."* |
| Lines before auto-collapse | 400 per file | Big files start collapsed; expand the ones you care about |
Both are stated in the overlay when they apply — nothing is dropped silently.
## Turning the sidebar shortcut off
If you would rather the sidebar's counts not be clickable, turn off **Settings →
Window & Tabs → Open diff preview from sidebar counts**
(`sidebar_diff_preview: false`). The branch and counts stay on the row; they
just stop opening the overlay.
+86
View File
@@ -0,0 +1,86 @@
---
title: "Source control"
description: "Stage, commit, branch, and push from the panel beside your terminal."
---
The **Source Control** tab of the [side panel](/window/side-panel) (<kbd>⌘ J</kbd>)
is a full git client for whichever repository the focused pane is in. It follows
the pane: `cd` into another repository and the panel switches with you.
<Frame caption="Placeholder — screenshot: the Source Control panel with staged and unstaged groups and a commit box">
<img src="/images/placeholder.svg" alt="The tty7 source control panel" />
</Frame>
## Changes
Files are grouped by what git thinks of them:
| Group | |
|---|---|
| **Merge Changes** | Conflicts, with *Resolve Conflict* and *Mark as Resolved* |
| **Staged Changes** | What the next commit will contain |
| **Changes** | Modified but not staged |
| **Untracked** | New files |
Each row has **Stage Changes**, **Unstage Changes**, **Discard Changes**, and
**Open Changes** — which opens the [diff](/git/diffs). Group-level *Stage All*,
*Unstage All*, and *Discard All* sit on the headers, and the destructive ones
confirm first.
## Committing
Write the message in the box at the top and pick a commit action:
| | |
|---|---|
| **Commit** | Commit what is staged |
| **Commit All** | Stage everything, then commit |
| **Commit (Amend)** | Replace the last commit — confirms first, because anyone who already has it has to reconcile |
| **Commit & Push** | Commit, then push |
| **Commit & Sync** | Commit, then pull and push |
<kbd>⌘ ⏎</kbd> commits while the caret is in the message box. **Stash All** is
there too.
## Branches and remotes
| | |
|---|---|
| **Checkout to…** | Switch branches, with a search box; offers **Stash & Switch** when the tree is dirty |
| **Create Branch…** | From here, or from any commit in the history |
| **Publish Branch** | For a branch with no upstream yet |
| **Sync Changes** | Pull, then push |
| **Push** · **Pull** · **Fetch** | Individually |
All of these are also in the command palette under **Git**, so they are
bindable.
When a repository is mid-operation — merging, rebasing, cherry-picking,
reverting, bisecting, applying — the panel says so instead of pretending
everything is normal.
## History
**Git: Toggle Commit History** (or the *History* section header) opens the
commit graph: branches drawn as lanes, a filter box, **Current Branch** or
**All Branches**, and *Load more* at the bottom.
Click a commit for its detail view — message, parents, and the files it touched,
each openable as a diff. From a commit's menu:
| | |
|---|---|
| **Checkout Commit** · **Create Branch Here…** | Move to it |
| **Cherry Pick** · **Revert Commit** | Apply or undo it here |
| **Reset (Soft / Mixed / Hard)** | Move the branch to it — Hard confirms, since commits after it fall off the branch and uncommitted changes are discarded |
| **Copy Commit SHA** | |
The history section starts collapsed and remembers whether you opened it
(`scm_graph_expanded`).
## In the sidebar
You do not have to open the panel to know where you stand: every
[sidebar row](/window/sidebar) carries its pane's branch and a `+N M` count of
the working tree, refreshed on `cd` and when a command finishes. Clicking the
counts opens the diff overlay.
+56
View File
@@ -0,0 +1,56 @@
---
title: "Worktrees"
description: "An isolated checkout on a fresh branch, in one dialog and one tab."
---
Running two agents on the same repository at once means they fight over the
working tree. A git worktree is the fix, and tty7 makes it a single dialog.
## Creating one
**New Worktree Tab…** — in the command palette, the tab's right-click menu, and
the application menu — asks three things:
| Field | Default |
|---|---|
| **Worktree Name** | A fresh name that does not collide with an existing branch or directory |
| **New Branch** | The same name, editable |
| **Start From** | The branch you are currently on |
Each field opens on a suggestion you can accept or type straight over.
<Frame caption="Placeholder — screenshot: the New Worktree Tab dialog">
<img src="/images/placeholder.svg" alt="Creating a worktree" />
</Frame>
Confirm and tty7 creates the worktree, opens a tab in it, and starts a shell
there. The [sidebar](/window/sidebar) files it under the same repository group as
its parent, on its own branch.
## Where they go
Worktrees land inside the repository, under:
```
<repo>/.tty7/worktrees/<name>
```
`<repo>/.tty7/.gitignore` is created with `*` in it the first time, so the
directory never shows up as an untracked mess in your own repository.
## Removing one
Closing a worktree tab offers to remove the worktree with it:
- **Clean tree** — *Remove Worktree* or *Keep*.
- **Dirty tree** — the dialog says so, and removing requires the explicit
*Discard Changes & Remove*.
Nothing is removed silently, and *Keep* leaves the worktree on disk for `git
worktree list` to find later.
<Tip>
Pair this with [agent sessions](/agents/sessions): a worktree per agent means
two Claude Codes can work on the same repository without stepping on each
other's files.
</Tip>
Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

+12
View File
@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 675" width="1200" height="675" role="img" aria-label="Screenshot placeholder">
<rect width="1200" height="675" rx="12" fill="#0f1113"/>
<rect x="1" y="1" width="1198" height="673" rx="12" fill="none" stroke="#2a2e33" stroke-width="2"/>
<g fill="#3FDD8C" opacity="0.9">
<circle cx="44" cy="40" r="7"/>
<circle cx="68" cy="40" r="7" opacity="0.45"/>
<circle cx="92" cy="40" r="7" opacity="0.25"/>
</g>
<line x1="0" y1="76" x2="1200" y2="76" stroke="#2a2e33" stroke-width="2"/>
<text x="600" y="340" text-anchor="middle" fill="#5c646d" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="34">screenshot coming soon</text>
<text x="600" y="386" text-anchor="middle" fill="#3a4046" font-family="ui-monospace, SFMono-Regular, Menlo, monospace" font-size="20">tty7 docs</text>
</svg>

After

Width:  |  Height:  |  Size: 881 B

+105
View File
@@ -0,0 +1,105 @@
---
title: "tty7"
sidebarTitle: "Introduction"
description: "A terminal workbench: persistent sessions, remote work, agents."
mode: "wide"
---
<Frame>
<img src="/images/hero.webp" alt="tty7 showing a sidebar of agent sessions across several repositories" />
</Frame>
tty7 is a terminal you can leave running. Close the window, reboot the machine,
walk to a different laptop — the shells you started are still there, and so are
the coding agents you left working in them.
It is written in Rust, renders on the GPU through Zed's
[gpui](https://github.com/zed-industries/zed), and parses VT with Alacritty's
terminal core. In practice that means roughly twice the throughput of Alacritty,
Ghostty, or Kitty on a big `cat`, and a frame rate that does not fall over when
something floods the screen.
## What makes it different
<CardGroup cols={2}>
<Card title="Sessions that outlive the app" icon="power-off" href="/getting-started/concepts">
A background server owns your shells, not the window. Quit tty7 and your
builds keep building. No tmux to learn or configure.
</Card>
<Card title="Editor-grade input" icon="keyboard" href="/terminal/prompt">
Ghost suggestions from your history, tab completion that explains each flag,
syntax highlighting, click-to-place-caret, real multi-line editing.
</Card>
<Card title="Agent-aware" icon="robot" href="/agents/overview">
18 coding CLIs are recognised on sight. Per-pane status dots, notifications
when one needs you, git context, and session resume after a reboot.
</Card>
<Card title="Remote work that feels local" icon="server" href="/remote/workspaces">
A native Rust SSH stack with profiles, SFTP, and port forwarding — plus
remote workspaces where files, repos, and panes all stay on the far machine.
</Card>
<Card title="Git where you are looking" icon="code-branch" href="/git/source-control">
Branch and diff counts on every sidebar row, a source control panel, a diff
overlay, and worktrees in one dialog.
</Card>
<Card title="Scriptable" icon="terminal" href="/cli/overview">
A bundled `tty7` CLI that opens panes, sends keys, reads screens, and blocks
until an agent needs you — so scripts and agents can drive the workbench.
</Card>
</CardGroup>
## Start here
<Steps>
<Step title="Install it">
Native builds for macOS, Windows, and Linux.
[Installation →](/getting-started/installation)
</Step>
<Step title="Set it up">
Five minutes of settings that pay for themselves.
[First launch →](/getting-started/first-launch)
</Step>
<Step title="Learn the three words">
Workspace, tab, pane — and the server underneath them.
[Core concepts →](/getting-started/concepts)
</Step>
</Steps>
## How fast, exactly
Same machine, same day, same 155×40 grid — Apple M1 Pro, macOS 26.3.1,
five-run averages.
| | **tty7** | Alacritty | Ghostty | Kitty |
|---|---:|---:|---:|---:|
| Plaintext I/O — 11 MB `cat` <sub>(lower is better)</sub> | **95 ms** | 239 ms | 179 ms | 185 ms |
| [DOOM-fire](https://github.com/const-void/DOOM-fire-zig) frame rate <sub>(higher is better)</sub> | **888 fps** | 485 fps | 552 fps | 617 fps |
| Cold-launch memory | 116 MB <sup>1</sup> | 105 MB | 128 MB | 130 MB |
<sub><sup>1</sup> GUI 105 MB plus the persistent server at 11 MB.</sub>
The methodology and a one-command reproduction live in
[`scripts/bench/`](https://github.com/l0ng-ai/tty7/tree/main/scripts/bench).
Three decisions account for most of it:
- **The PTY is read at device speed** and parsed in large batches, off the
render path — so drawing never throttles reading.
- **The hot paths are lock-free.** A big `cat` never waits on the renderer.
- **The server buffers up to 16 MiB** ahead of the window before backpressure
applies, which is enough that a flood finishes writing while the window is
still catching up.
## Getting help
<CardGroup cols={3}>
<Card title="Discord" icon="discord" href="https://discord.gg/s3dethqz2V">
Ask a question, show what you built.
</Card>
<Card title="Issues" icon="github" href="https://github.com/l0ng-ai/tty7/issues">
Bugs and feature requests.
</Card>
<Card title="Changelog" icon="list" href="https://github.com/l0ng-ai/tty7/blob/main/CHANGELOG.md">
Everything that shipped, release by release.
</Card>
</CardGroup>
+6
View File
@@ -0,0 +1,6 @@
<svg width="120" height="120" viewBox="8 6 80 80" xmlns="http://www.w3.org/2000/svg">
<!-- "Duo" mark on transparent — same geometry as app-icon.svg, no tile. -->
<rect x="28" y="16" width="58" height="46" rx="12" fill="#3FDD8C" opacity="0.8"/>
<rect x="10" y="30" width="58" height="46" rx="12" fill="#17171A"/>
<path d="M24 42 L35 53 L24 64" fill="none" stroke="#ECEAE4" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 456 B

+183
View File
@@ -0,0 +1,183 @@
---
title: "config.json"
description: "Every key tty7 reads, its type, and its default."
---
| | |
|---|---|
| macOS / Linux | `~/.config/tty7/config.json` |
| Windows | `%APPDATA%\tty7\config.json` |
| Override the whole directory | `TTY7_CONFIG_DIR` |
Every key is optional — a missing one means its default, so you only write what
you change. Out-of-range numbers are clamped rather than rejected, and an
unrecognised enum value falls back to the default with a log line instead of
failing the file.
```json
{
"font_family": "JetBrains Mono",
"font_size": 14,
"theme_follow_system": true,
"theme_preset_light": "one_light",
"theme_preset_dark": "dracula",
"macos_option_as_alt": true,
"scrollback_limit": 50000
}
```
## Typography
| Key | Type | Default | |
|---|---|---|---|
| `font_family` | string | `"Hack"` | Primary face. Hack is bundled. |
| `font_fallbacks` | string[] | platform list | Ordered fallbacks. Stock platform faces are appended to whatever you write. |
| `font_family_bold` | string | — | A distinct bold face. |
| `font_family_italic` | string | — | A distinct italic face. |
| `font_features` | object | — | OpenType tags, e.g. `{"calt": true, "liga": 1}`. Four alphanumeric characters per tag. |
| `font_size` | number | `15` | Terminal text size in px (4256). |
| `line_height` | number | `1.4` | Multiple of the font size (0.54). |
| `ui_font_size` | number | `16` | The interface's root size in px (1224). |
[More about fonts →](/customization/fonts)
## Theme and window
| Key | Type | Default | |
|---|---|---|---|
| `theme_preset` | string | `"light"` | Active theme id. |
| `theme_follow_system` | bool | `false` | Follow the OS appearance. |
| `theme_preset_light` | string | `"light"` | Used when following the system. |
| `theme_preset_dark` | string | `"dark"` | Used when following the system. |
| `theme_legible_palette` | bool | `true` | Brighten or darken bright ANSI colours that would be unreadable on the background. |
| `window_opacity` | number | — | 0.21.0. Unset means "follow the theme". |
| `window_blur` | bool | — | Blur behind a translucent window (macOS). Unset means "follow the theme". |
| `window_backdrop` | enum | `"auto"` | Windows only: `auto`, `blur`, `mica`, `mica-alt`, `acrylic`, `off`. |
| `dim_inactive_panes` | bool | `true` | Dim panes that are not focused. |
| `startup_mode` | enum | `"normal"` | `normal`, `maximized`, `fullscreen`. |
| `remember_window_size` | bool | `true` | Reopen at the last size and position. |
| `restore_session` | bool | `true` | Reopen the last window's tabs, splits, and directories. |
| `gui_language` | enum | `"en"` | `en`, `zh-CN`, `ja-JP`. Anything else falls back to `en`. |
Built-in theme ids: `light`, `one_light`, `catppuccin_latte`, `rose_pine_dawn`,
`dark`, `dracula`, `harbor`, `one_dark_pro`, `rose_pine`. Your own themes take
their id from the file name. [More about themes →](/customization/themes)
## Tabs, sidebar, panels
| Key | Type | Default | |
|---|---|---|---|
| `tab_bar_position` | enum | `"left"` | `left` (sidebar) or `top` (strip). |
| `new_tab_position` | enum | `"after-current"` | Or `end`. |
| `sidebar_grouping` | enum | `"repo"` | Or `none` for a flat list. |
| `sidebar_diff_preview` | bool | `true` | Clicking a row's `+N M` opens the diff overlay. |
| `sidebar_width` | number | `220` | Pixels (1002000). |
| `sidebar_collapsed` | bool | `false` | |
| `right_panel_visible` | bool | `false` | |
| `right_panel_width` | number | `260` | Pixels (1002000). |
| `right_panel_tab` | enum | `"info"` | `info`, `changes`, `files`. |
| `diff_view` | enum | `"split"` | Or `unified`. Global, not per file. |
| `scm_graph_expanded` | bool | `false` | Whether the history section starts open. |
| `show_tray_icon` | bool | `true` | The tray / menu bar status item. |
## Terminal
| Key | Type | Default | |
|---|---|---|---|
| `shell` | object | — | `{"program": "fish", "args": ["-l"]}`. Unset uses the platform default. |
| `working_directory` | object | `{"strategy":"inherit"}` | `strategy` is `inherit`, `home`, or `custom`; `path` is used when custom. |
| `env` | object | `{}` | Extra environment variables for every pane. |
| `scrollback_limit` | number | `10000` | Lines per pane (100100,000). New panes only. |
| `cursor_style` | enum | `"block"` | `block`, `bar`, `underline`. |
| `cursor_blink` | bool | `true` | |
| `bell` | enum | `"visual"` | `none`, `visual`, `audible`, `both`. |
| `per_pane_history` | bool | `false` | Give each pane its own shell history file. |
## Mouse and scrolling
| Key | Type | Default | |
|---|---|---|---|
| `mouse_scroll_multiplier` | number | `1.0` | 0.110. |
| `smooth_scroll` | bool | `true` | Ease each wheel notch. Trackpads unaffected. |
| `mouse_reporting` | bool | `true` | Let full-screen apps handle clicks and scrolling. |
| `mouse_hide_while_typing` | bool | `true` | |
| `focus_follows_mouse` | bool | `false` | |
## Input and clipboard
| Key | Type | Default | |
|---|---|---|---|
| `tab_completion` | bool | `true` | tty7's completion menu on <kbd>⇥</kbd>. Off hands the key to the shell. |
| `history_search` | bool | `true` | tty7's fuzzy history on <kbd>⌃ R</kbd>. Off hands the key to the shell. |
| `smart_select` | bool | `true` | Double-click grabs URLs, paths, bracket pairs, CJK words. |
| `word_separators` | string | see below | Characters that end a word. Used when smart selection is off. |
| `copy_on_select` | bool | `false` | |
| `clipboard_trim_trailing_spaces` | bool | `false` | |
| `macos_option_as_alt` | bool | `false` | <kbd>⌥</kbd>+key sends the escape chord instead of typing a special character. |
| `keybindings` | object | `{}` | `{"SplitRight": "cmd-d"}`. [Syntax →](/customization/keybindings) |
| `keybinding_preset` | string | `"default"` | Or `"tmux"`. |
| `prefix` | string | `"ctrl-b"` | The tmux preset's prefix. |
The default `word_separators` are a comma, a box-drawing bar, a backtick, a
pipe, a colon, both quote characters, a space, the six bracket characters, the
angle brackets, and a tab:
```json
{ "word_separators": ",│`|:\"' ()[]{}<>\t" }
```
## Links
| Key | Type | Default | |
|---|---|---|---|
| `link_url` | bool | `true` | Underline and open URLs on ⌘/Ctrl-click. |
| `link_file_command` | string | — | Command for file links. `{path}`, `{line}`, `{column}` are substituted; a flag whose value is missing is dropped. |
| `ssh_loopback_forward` | bool | `false` | Open `localhost:PORT` links through a temporary forward when the pane is in SSH. |
## Notifications
| Key | Type | Default | |
|---|---|---|---|
| `notify_on_command_finish` | enum | `"unfocused"` | `never`, `unfocused`, `always`. |
| `notify_threshold_secs` | number | `10` | How long a command must run to qualify (13600). |
## Agents
| Key | Type | Default | |
|---|---|---|---|
| `agent_commands` | object | `{}` | Map a wrapper command to an agent slug: `{"cc": "claude"}`. |
| `restore_agent_sessions` | bool | `true` | Relaunch an agent conversation when a lost pane is restored. |
| `install_cli_on_path` | bool | `true` | Put the bundled `tty7` command on PATH at launch. |
[Agent slugs →](/agents/overview#your-own-wrapper)
## SSH
| Key | Type | Default | |
|---|---|---|---|
| `ssh_profiles` | array | `[]` | Managed from **Settings → SSH**. Secrets live in the OS keychain, never here. |
| `verify_host_keys` | bool | `true` | |
| `ssh_warn_on_close` | bool | `false` | Confirm before closing a live connection. |
## Updates and network
| Key | Type | Default | |
|---|---|---|---|
| `check_for_updates` | bool | `true` | |
| `update_channel` | enum | `"stable"` | Or `nightly`. |
| `auto_download_updates` | bool | `true` | Fetch and verify in the background so installing is a restart. Packages are ~2530 MB and a check happens every six hours. |
| `http_proxy` | string | — | For tty7's *own* traffic only — update checks, downloads, remote-server installs. `http://…` or `socks5://…`. Programs in a pane are unaffected. |
[Updates →](/reference/updates)
## Keys tty7 manages itself
`ssh_profile_frecency` and `command_frecency` record how often and how recently
you use a profile or command, so the pickers can rank them. They are written by
the app; there is no reason to edit them.
<Note>
If the file cannot be parsed, tty7 starts on defaults, keeps your original at
`config.json.corrupt`, and logs the reason. It never silently overwrites what
you wrote.
</Note>
+103
View File
@@ -0,0 +1,103 @@
---
title: "Keyboard shortcuts"
description: "Every default binding, plus the action names for rebinding."
---
**Settings → Keybindings** (<kbd>⌘ ,</kbd>) is the live version of this page —
it shows what *your* copy is bound to. This is the shipped default.
## Tabs and workspaces
| Action | macOS | Windows / Linux |
|---|---|---|
| New Tab | <kbd>⌘ T</kbd> | <kbd>Ctrl ⇧ T</kbd> |
| Close Pane / Tab | <kbd>⌘ W</kbd> | <kbd>Ctrl ⇧ W</kbd> |
| Reopen Closed Tab | <kbd>⌘ ⇧ T</kbd> | <kbd>Alt ⇧ T</kbd> |
| Next Tab · Previous Tab | <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | same |
| Go to Tab 19 | <kbd>⌘ 1</kbd>…<kbd>⌘ 9</kbd> | <kbd>Alt 1</kbd>…<kbd>Alt 9</kbd> |
| New Workspace | <kbd>⌘ ⇧ N</kbd> | <kbd>Ctrl ⇧ N</kbd> |
| Switch Workspace | <kbd>⌘ ⇧ O</kbd> | <kbd>Ctrl ⇧ O</kbd> |
## Panes
| Action | macOS | Windows / Linux |
|---|---|---|
| Split Right | <kbd>⌘ D</kbd> | <kbd>Ctrl ⇧ D</kbd> |
| Split Down | <kbd>⌘ ⇧ D</kbd> | <kbd>Ctrl Alt ⇧ D</kbd> |
| Next Pane · Previous Pane | <kbd>⌘ ]</kbd> · <kbd>⌘ [</kbd> | <kbd>Ctrl ⇧ ]</kbd> · <kbd>Ctrl ⇧ [</kbd> |
| Focus Pane Left / Right / Up / Down | <kbd>⌘ ⌥ ←→↑↓</kbd> | <kbd>Alt ←→↑↓</kbd> |
| Zoom Pane | <kbd>⌘ ⇧ ⏎</kbd> | <kbd>Ctrl ⇧ ⏎</kbd> |
| Enter Full Screen | <kbd>⌘ ⏎</kbd> | <kbd>F11</kbd> |
## View
| Action | macOS | Windows / Linux |
|---|---|---|
| Command Palette | <kbd>⌘ P</kbd> | <kbd>Ctrl ⇧ P</kbd> |
| Toggle Left Sidebar | <kbd>⌘ B</kbd> | <kbd>Ctrl ⇧ B</kbd> |
| Toggle Right Panel | <kbd>⌘ J</kbd> | <kbd>Ctrl ⇧ J</kbd> |
| Toggle Code Panel | <kbd>⌘ ⇧ E</kbd> | <kbd>Ctrl ⇧ E</kbd> |
| Font Size Up · Down · Reset | <kbd>⌘ +</kbd> · <kbd>⌘ </kbd> · <kbd>⌘ 0</kbd> | <kbd>Ctrl +</kbd> · <kbd>Ctrl </kbd> · <kbd>Ctrl 0</kbd> |
| Zoom the font | <kbd>⌘</kbd> + wheel | <kbd>Ctrl</kbd> + wheel |
## Terminal
| Action | macOS | Windows / Linux |
|---|---|---|
| Find in Terminal | <kbd>⌘ F</kbd> | <kbd>Ctrl ⇧ F</kbd> |
| Find Next · Previous | <kbd>⌘ G</kbd> · <kbd>⌘ ⇧ G</kbd> | <kbd>F3</kbd> · <kbd>⇧ F3</kbd> |
| Clear Scrollback | <kbd>⌘ K</kbd> | <kbd>Ctrl ⇧ K</kbd> |
| Copy · Paste | <kbd>⌘ C</kbd> · <kbd>⌘ V</kbd> | <kbd>Ctrl ⇧ C</kbd> · <kbd>Ctrl ⇧ V</kbd> · <kbd>⇧ Insert</kbd> |
| Insert Newline (at the prompt) | <kbd>⇧ ⏎</kbd> · <kbd>⌥ ⏎</kbd> | same |
| Fuzzy history search | <kbd>⌃ R</kbd> | same |
| Accept ghost suggestion | <kbd>→</kbd> | same |
| Completion menu | <kbd>⇥</kbd> | same |
## Git and SSH
| Action | macOS | Windows / Linux |
|---|---|---|
| Commit (caret in the message box) | <kbd>⌘ ⏎</kbd> | <kbd>Ctrl ⏎</kbd> |
| Save (in the editor) | <kbd>⌘ S</kbd> | <kbd>Ctrl S</kbd> |
| Restart SSH Session | <kbd>⌘ ⇧ R</kbd> | <kbd>Ctrl ⇧ R</kbd> |
## Application
| Action | macOS | Windows / Linux |
|---|---|---|
| Settings | <kbd>⌘ ,</kbd> | <kbd>Ctrl ,</kbd> |
| Keyboard Shortcuts | <kbd>⌘ /</kbd> | — |
| Hide tty7 · Hide Others · Minimize | <kbd>⌘ H</kbd> · <kbd>⌘ ⌥ H</kbd> · <kbd>⌘ M</kbd> | — |
| Quit | <kbd>⌘ Q</kbd> | <kbd>Ctrl ⇧ Q</kbd> |
## Actions with no default key
All of these are in the command palette, and all are bindable under **Settings →
Keybindings**:
| Group | Actions |
|---|---|
| Tabs | `RenameTab` · `NewWorktreeTab` · `CloseOtherTabs` · `CloseTabsToTheRight` · `CopyWorkingDirectory` · `MarkTabUnread` · `ToggleTabSidebar` |
| Panes | `ResizePaneLeft/Right/Up/Down` · `SwapPaneNext` · `SwapPanePrev` |
| Workspaces | `SelectWorkspace1`…`SelectWorkspace9` · `RenameWorkspace` · `StopWorkspace` · `DeleteWorkspace` |
| Agents | `ForkAgentSession` (+ `Right` / `Left` / `Down` / `Up`) · `CopyAgentSessionId` |
| Git | `ScmStageAll` · `ScmUnstageAll` · `ScmDiscardAll` · `ScmCommitAmend` · `ScmRefresh` · `ScmSync` · `ScmPush` · `ScmPull` · `ScmFetch` · `ScmCheckoutBranch` · `ScmCreateBranch` · `ScmToggleGraph` · `ToggleDiffViewMode` |
| Panels | `ShowRightPanelInfo` · `ShowRightPanelChanges` · `ShowRightPanelFiles` |
| SSH | `ToggleSftp` · `ShowSshForwards` · `OpenSshProfiles` |
| Application | `About` · `CheckForUpdates` · `OpenDocumentation` · `OpenDiscord` · `ReportIssue` · `ShowAll` · `ZoomWindow` |
## Rebinding syntax
```json
{
"keybindings": {
"ResizePaneLeft": "ctrl-alt-left",
"ToggleSftp": "secondary-shift-u",
"ScmSync": "ctrl-b s"
}
}
```
`secondary` means <kbd>⌘</kbd> on macOS and <kbd>Ctrl</kbd> elsewhere. A space
separates the steps of a chord.
[More →](/customization/keybindings)
+60
View File
@@ -0,0 +1,60 @@
---
title: "Privacy and permissions"
description: "What macOS asks you, why, and what tty7 itself holds."
---
## Why macOS asks tty7 for permission
Panes are forked from tty7's own bundled executable, so when a program you run
asks macOS for a protected resource, macOS attributes the request to **tty7.app**
— not to the program.
If tty7 declared no usage strings, that request would be **denied outright with
no prompt at all**, and the program would look broken for no visible reason.
So tty7 declares the matching usage strings, and you get the normal one-time
prompt:
<CardGroup cols={2}>
<Card title="Devices" icon="camera">
Camera · microphone · Bluetooth · location · motion
</Card>
<Card title="Personal data" icon="address-book">
Contacts · calendars · reminders · photo library
</Card>
<Card title="System" icon="gear">
Local network · Apple Events · speech recognition · system administration
</Card>
</CardGroup>
<Warning>
**Declaring a usage string is not the same as holding the permission.**
tty7.app itself is granted none of these. Every prompt you see belongs to
whatever you ran in the pane, and you can revoke it under **System Settings →
Privacy & Security**.
</Warning>
### Full Disk Access
Apple defines no usage-string key for it. Reaching `~/Library/Mail`,
`~/Library/Messages`, `~/Library/Safari`, or `~/Library/Containers` needs a
manual grant in **System Settings → Privacy & Security → Full Disk Access**.
## What leaves your machine
| | |
|---|---|
| **Update checks** | A request to the GitHub releases API every six hours, plus the download when you accept one. Turn it off with `check_for_updates: false`. |
| **Remote server installs** | Downloading a `tty7-server` binary for a machine you connected to — or, for WSL, copying the one already bundled with your install. |
| **Everything else** | Nothing. There is no telemetry, no analytics, and no account. |
Both of the above honour `http_proxy`. [Updates →](/reference/updates#proxies)
## What is stored, and where
| | |
|---|---|
| Settings, themes, window state | `~/.config/tty7/` (`%APPDATA%\tty7\` on Windows) |
| SSH passwords and key passphrases | The **OS keychain** — never `config.json`, never plain text on disk |
| Pane scrollback tails | `<config>/scrollback/*.bin`, mode `0600` on Unix and behind the config directory's ACL on Windows. 256 KiB per pane, kept only until something can no longer ask for it: closing a pane deletes its file at once, a restore consumes it, and a periodic pass collects the rest. |
| Shell history | Your shell's own file, exactly as before — unless you turned on per-pane history, which merges back into it. |
+73
View File
@@ -0,0 +1,73 @@
---
title: "Shell integration"
description: "What tty7 injects into your shell, and what it buys you."
---
A terminal that only sees bytes cannot tell a prompt from output, or a finished
command from a hung one. tty7's shell integration closes that gap: the shell
reports where prompts begin, what was submitted, what it exited with, and where
it is.
**You do not install it.** It is injected when the pane's shell starts, and
removes itself from the equation if you run the same shell elsewhere.
## Which shells
| Shell | How it is injected |
|---|---|
| **zsh** | A throwaway `ZDOTDIR` whose files source yours first, then tty7's. Your `TTY7_USER_ZDOTDIR` is preserved. |
| **bash** | An rcfile that sources your own first. |
| **fish** | A `-C` init command. |
| **PowerShell** | An encoded init command that wraps your existing `prompt` function and PSReadLine's line reader. |
| **WSL** *(Windows)* | The distro's shell is bootstrapped with the same scripts. |
| **Remote panes** | The same three POSIX shells, bootstrapped over the SSH connection. Toggle per profile with **Settings → SSH → Session → Shell integration**. |
`TTY7_SHELL_INTEGRATION` is set once it is active, and guards against a second
injection when shells nest.
<Note>
A shell launched with custom arguments is left alone for bash and PowerShell,
because tty7's injection would conflict with the flags you chose.
</Note>
## What it reports
| Signal | Sequence | Used for |
|---|---|---|
| Prompt begins / input begins | `OSC 133;A`, `133;B` | The [prompt layer](/terminal/prompt): suggestions, completion, multi-line editing |
| Command submitted | `OSC 133;C` | Knowing a command is running; agent detection on Windows, where ConPTY exposes no foreground process group |
| Command finished, with exit code | `OSC 133;D` | The "finished after 42s" notification, failure marks in [history](/terminal/history) |
| Working directory | `OSC 7` | New tabs and splits opening in the right place, the sidebar's repo grouping, the git branch readout |
| Editing mode (vi / emacs) | `OSC 133;V` | Matching tty7's key handling to your shell's mode |
| Window title | `OSC 0` | Tab labels |
## What turns off without it
Run a shell tty7 does not integrate with, and everything below still works —
it just falls back to less precise sources:
- Ghost suggestions, the completion menu, and <kbd>⌃ R</kbd>'s fuzzy history
- "Command finished" notifications and the failure marks in history search
- Exact working-directory tracking (tty7 falls back to inspecting the process)
Panes, splits, scrollback, search, SSH, and the CLI are unaffected.
## Per-pane history
When `per_pane_history` is on, the integration is also what makes it work. It
runs *after* your own rc file — which is the only reason it can: `$HISTFILE` is
yours to set, wherever you like, and nothing outside the shell knew where it
pointed until then.
The sequence is: seed the pane's private file from your real history so it does
not start blank, record how much was seeded, repoint `$HISTFILE`, and merge
everything past that mark back when the pane closes.
[More about history →](/terminal/history#one-history-or-one-per-pane)
## Remote shells
For a remote workspace or an SSH pane, the same scripts are sent over the
connection at login, so a remote pane reports its cwd, exit codes, and prompt
marks exactly like a local one. Turn it off for a particular host under that
profile's **Advanced → Session**.
+137
View File
@@ -0,0 +1,137 @@
---
title: "Troubleshooting"
description: "The things that go wrong, and what they actually mean."
---
## Start here
```bash
tty7 doctor
```
One table: whether the server is reachable, whether its wire dialect matches
your binary, the three environment variables, pid/uptime/panes, and how many
machine links exist. Most of what follows is a specific answer this gives you.
## `tty7: command not found`
The CLI is put on PATH the first time the app launches. If it is missing:
- Check **Settings → Agents → Install the tty7 command on PATH** is on.
- On Unix it symlinks into whichever of `/opt/homebrew/bin`, `/usr/local/bin`,
`~/.local/bin`, `~/bin`, `~/.cargo/bin` your PATH already covers — if none of
those are on your PATH, add one.
- On Windows the install directory is appended to your user PATH, which needs a
new shell to take effect.
- A `tty7` you installed yourself is never replaced, so an old one earlier in
PATH will win.
Inside a tty7 pane it works regardless, since panes inherit the app's
environment.
## The server is unreachable
`tty7 doctor` says so, and the GUI cannot open panes.
Start it with `tty7 server start`. If you are an agent or a script,
**do not** — tell the user instead. Starting a server they did not ask for
changes what their GUI attaches to.
For logs:
```bash
TTY7_LOG=info # must be set before the server starts
tty7 server logs
```
## Panes came back empty
A crash, a `kill -9`, or a reboot takes the shells with it — that part is
unavoidable. The *screens* should come back: tty7 keeps a capped tail of each
pane's output (256 KiB) and hands it to the pane that reopens on that id.
It is consumed once. If a pane was restored, then closed, then reopened, the
second time there is nothing left to restore — that is by design, not a bug.
## "The background server is still running &lt;build&gt;"
tty7 updated in place, so the app is new and your panes are still served by the
previous build. Restarting the server picks up the new one and **ends every
process in every pane**. There is no hurry — do it when your panes are idle.
[Updates →](/reference/updates)
## A remote machine will not connect
| Message | What it means |
|---|---|
| *"running an old tty7 server that this copy cannot talk to"* | The server there predates your client's protocol. Let tty7 update it — this ends every session on that machine. |
| *"running a newer tty7 server than this copy"* | Update tty7 here instead, or replace the server there. |
| *"answered, but not as a tty7 server"* | Something else is listening, or the binary is not what tty7 expects. |
| *"tty7 no longer has a way to reach &lt;machine&gt;"* | The SSH link dropped. Reconnect from the switcher. |
`tty7 -m <machine>` never dials a fresh connection by design — it uses a link
the local server already holds. Connect from the GUI first.
## <kbd>⇥</kbd> or <kbd>⌃ R</kbd> is not doing what I expect
Both are switches, and turning one off hands the key straight back to your
shell:
- **Settings → Input → Prompt → Tab completion**
- **Settings → Input → Prompt → History search**
If they do nothing at all in a particular pane, the shell there probably has no
[shell integration](/reference/shell-integration) — nushell, elvish, xonsh and
friends run fine but do not get the prompt layer.
## <kbd>⌥ B</kbd> types `∫` instead of moving a word
That is macOS's default. Turn on **Settings → Input → Keyboard → Option (⌥) acts
as Meta**.
## CJK characters have a gap on the right
Your CJK fallback advances 1.0em while the primary face advances 0.60205em, so
the glyph does not fill its two-column slot. Install
[Maple Mono NF CN](https://github.com/subframe7536/maple-font) — it is already
first in the fallback chain and fits Hack exactly — or change the primary face.
[The full explanation →](/customization/fonts#cjk-and-the-two-column-grid)
## A theme in my themes folder is not showing up
Settings lists it under **Not loaded from the themes folder**, with the reason.
Usually a missing required key: `background`, `foreground`, `accent`, and `ansi`
(with eight `normal` and eight `bright` entries) are all mandatory.
## My `config.json` edits did nothing
If the file cannot be parsed, tty7 starts on defaults and keeps your original at
`config.json.corrupt` — check for that file. Otherwise:
- An out-of-range number is **clamped**, not applied literally.
- An unrecognised enum value falls back to the default with a log line.
- `scrollback_limit` applies to **new** panes only.
- An unknown action name in `keybindings` is skipped with a warning.
## Selecting text inside vim / less selects the app's own thing
Hold <kbd>⇧</kbd> while dragging to keep the gesture local, or turn off
**Settings → Terminal → Mouse → Report mouse to apps**.
## `tty7 capture … | head -1` printed a Rust panic
An old build's behaviour when the reader hangs up. The data you asked for still
arrived. On such a build, redirect to a file and slice the file instead of
piping into `head`. Current builds exit `141` on Unix, which is exactly what
`cat` does.
## Still stuck
<CardGroup cols={2}>
<Card title="Discord" icon="discord" href="https://discord.gg/s3dethqz2V">
Ask — someone has probably hit it.
</Card>
<Card title="Report an issue" icon="github" href="https://github.com/l0ng-ai/tty7/issues/new">
Include `tty7 doctor` output and your platform.
</Card>
</CardGroup>
+97
View File
@@ -0,0 +1,97 @@
---
title: "Updates"
description: "How tty7 updates itself, and what the two channels mean."
---
**Settings → About** is where everything lives: the version you are on, the
channel you follow, and the button that installs what is waiting.
## How it works
<Steps>
<Step title="Check">
tty7 checks at launch and every six hours after that. Turn it off entirely
with `check_for_updates: false`, or check on demand with **Check now**.
</Step>
<Step title="Download and verify, in the background">
A found release is fetched and verified before you are asked to do anything
— which turns "spend five minutes downloading" into "press restart".
Nothing is ever *installed* without an explicit choice; the staged package
waits in Settings.
Turn this off on a metered connection: the packages run 2530 MB.
(`auto_download_updates: false`)
</Step>
<Step title="Update and relaunch">
A dedicated `tty7-updater` helper verifies the release checksum, the bundle
version, and — on macOS — the code-signing requirement, before replacing the
installation. If the relaunch fails, it puts the previous copy back.
</Step>
</Steps>
Declining an update defers it rather than retiring it; it comes back later.
## Channels
**Settings → About → Update channel.**
| | |
|---|---|
| **Stable** *(default)* | Published releases. Reads `/releases/latest`, which excludes prereleases. |
| **Nightly** | Rebuilt from the latest code every night — newer, but not release-tested. Reads the rolling `nightly` tag. |
The channel is a property of your **installation**, not something inferred from
how version numbers sort. Neither feed can hand the other an update, so a
Nightly is never walked back onto Stable by an update it did not ask for, and an
installation only changes channel when you change it.
Switching channels invalidates what the old feed produced: the staged package,
the deferred prompt, and any transfer still in flight.
<Note>
A stable release outranks every dated build of its core version, which is how
switching back to Stable *graduates* rather than downgrades.
</Note>
## Platform notes
<AccordionGroup>
<Accordion title="macOS">
The new GUI reuses a running local server when its wire protocol is
compatible, so your shells survive the update. An incompatible server keeps
its shells too and raises an explicit keep-or-restart prompt.
</Accordion>
<Accordion title="Windows">
Windows cannot replace a running daemon's image, so the install path stops
the service first — and the dialog says so before you agree.
An all-users `C:\Program Files` install cannot be updated in place and keeps
the release-page fallback instead. Running Setup as the signed-in user would
either install a second copy beside the real one, or put a bare UAC prompt
in front of someone whose GUI just vanished.
</Accordion>
<Accordion title="Linux">
AppImage and tarball installs are replaced by downloading the new file.
</Accordion>
</AccordionGroup>
## "The background server is still running &lt;build&gt;"
If Settings tells you this, tty7 was updated in place: the app is the new build,
but your panes are still served by the previous one. Restarting the server picks
up the new one — **and ends every process running in your panes**, shells,
agents, and SSH sessions alike.
There is no hurry. Pick a moment when your panes are idle.
## Proxies
Update checks and downloads resolve a proxy from, in order:
1. `http_proxy` in `config.json` — `http://127.0.0.1:7890` or
`socks5://127.0.0.1:1080`
2. The platform system proxy (Windows registry, macOS `SCDynamicStore`)
3. `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`
Programs running in a pane are deliberately unaffected — they inherit their
proxy from their own environment, as in any other terminal.
+61
View File
@@ -0,0 +1,61 @@
---
title: "Port forwarding"
description: "Local, remote, and dynamic forwards — preconfigured or added mid-session."
---
## The three kinds
| | What it does |
|---|---|
| **Local** (`L`) | A port on this machine reaches a service on the remote side |
| **Remote** (`R`) | A port on the remote machine reaches a service here |
| **Dynamic** (`D`) | A SOCKS proxy on this machine, routed through the connection |
## Adding one to a profile
**Settings → SSH →** a profile **→ Port forwarding → + Add rule**. Rules saved
here open with the connection, every time.
A Local or Remote rule needs a listen port and a target; a Dynamic rule needs
only the listen port. An incomplete rule tells you so rather than being saved
half-configured.
Each rule takes an optional description — *"what it's for"* — because six months
later `8080 → 3000` explains nothing.
## Adding one mid-session
*SSH: Port Forwarding* in the command palette opens the **Forwards** panel for
the current connection. Add a rule there and it starts immediately; remove it
and it stops. These live only as long as the session unless you save them into
the profile.
<Frame caption="Placeholder — screenshot: the Forwards panel with a local and a dynamic rule">
<img src="/images/placeholder.svg" alt="The forwards panel" />
</Frame>
## The one-click shortcut
<kbd>⌘</kbd>-clicking a `localhost:PORT` link inside an SSH pane can open a
temporary forward for exactly that port and then open the browser — turn on
**Settings → Input → Links → Forward SSH loopback links**.
That is the right tool for "let me look at this dev server once". For something
you use every day, put it in the profile.
## Jump hosts and proxies
Multi-hop connections are configured per profile:
- **Jump host** — point at another saved profile, or use a `ProxyJump` chain
- **ProxyCommand** — an arbitrary transport command, with `%h`, `%p`, `%r`
substituted
- **SOCKS5 proxy** / **HTTP proxy** — `host:port`, under **Advanced → Proxies**
<Note>
These proxy settings are for reaching the SSH server. tty7's *own* network
traffic — update checks, release downloads, remote-server installs — uses
`http_proxy` in `config.json`, the system proxy, or the `HTTP_PROXY` family.
Programs running in a pane are unaffected either way; they inherit their proxy
from their own environment, as in any terminal.
</Note>
+48
View File
@@ -0,0 +1,48 @@
---
title: "SFTP"
description: "A file browser for the machine on the other end of the connection."
---
While a pane is in an SSH session, *SSH: Remote Files* (command palette) slides
a file panel in over it. It is a real SFTP client on the same connection — no
second login, no second password.
<Frame caption="Placeholder — screenshot: the SFTP panel over a pane, with a transfer in progress">
<img src="/images/placeholder.svg" alt="The tty7 SFTP panel" />
</Frame>
## Browsing
The panel opens on the remote home directory. **Go to Shell Directory** in the
overflow menu jumps it to wherever the pane's shell currently is, which is
usually where you actually want to be.
Right-click a row for **Open**, **Follow Symlink**, **Rename**, **chmod…**, and
delete. The overflow menu adds **New Folder**, **New File**, **Upload…**, and
**Refresh**.
## Transferring
| Direction | How |
|---|---|
| Download | Drag a file out of the panel into Finder or Explorer |
| Upload | **Upload…**, or drag files into the panel |
Uploads are written under a temporary name and renamed into place at the end, so
a half-finished file never looks like a finished one. A name that is already
taken asks before replacing.
**Transfer History** in the overflow menu shows every transfer with its
progress, and lets you cancel one in flight. The panel header summarises what is
happening — *"2 transferring · 64%"*.
## Permissions
**chmod…** takes an octal mode (`755`, `600`). The current mode is shown in the
row's editor before you change it.
<Tip>
For files in a [remote workspace](/remote/workspaces) you can often skip SFTP
entirely — the Files panel reads and writes across the link directly, and the
built-in editor saves back to the remote machine.
</Tip>
+115
View File
@@ -0,0 +1,115 @@
---
title: "SSH"
description: "A native Rust SSH stack: quick connects, saved profiles, keychain credentials, jump hosts."
---
tty7 speaks SSH itself, over [russh](https://github.com/Eugeny/russh). It never
shells out to the `ssh` binary, and there is no compatibility mode that does.
That is what makes the rest possible: credentials in the OS keychain,
[SFTP](/remote/sftp) in a side panel, [port forwards](/remote/port-forwarding)
you can add mid-session, and authentication prompts drawn as sheets in the pane
instead of a password echoing into your shell.
<Frame caption="Placeholder — screenshot: an SSH connection sheet asking for a key passphrase inside a pane">
<img src="/images/placeholder.svg" alt="Connecting over SSH in tty7" />
</Frame>
## Four ways to connect
<AccordionGroup>
<Accordion title="QuickConnect — type an address">
Open the palette (<kbd>⌘ P</kbd>) and type an address. IPv6 works with
brackets.
```
me@devbox
me@devbox:2222
me@[2001:db8::1]:22
```
</Accordion>
<Accordion title="A saved profile">
Profiles live in **Settings → SSH → Hosts**. Start typing the name in the
palette, or open the *SSH: Manage Profiles…* command.
</Accordion>
<Accordion title="An alias from ~/.ssh/config">
Type an alias you already have and tty7 resolves it natively — common fields,
best effort — then connects over russh. **Settings → SSH → Import from
~/.ssh/config** turns aliases into real profiles.
<Note>
`Match`, `canonicalize*`, and GSSAPI directives are not supported, and
there is no fallback to the system `ssh` when one appears.
</Note>
</Accordion>
<Accordion title="A remote workspace">
The same connection can host whole workspaces on the far machine rather than
a single shell. [Remote workspaces →](/remote/workspaces)
</Accordion>
</AccordionGroup>
## Profiles
**Settings → SSH → Hosts** holds the full connection config. The basics:
| Field | |
|---|---|
| **Name** | A label for this connection |
| **Host** | Hostname or IP |
| **User** | Login user — blank resolves at connect time |
| **Auth** | *Auto* (tries every applicable method), *Password*, *Key*, *Agent*, or *2FA* |
| **Jump host** | Another profile, or a `ProxyJump` chain |
| **Port forwarding** | Rules opened with the connection |
**Defaults** at the top of the list is inherited by every host, so a setting you
want everywhere is set once.
Passwords and key passphrases go in the **OS keychain**, never in
`config.json` and never on disk in plain text. **Forget Password** in a
profile's menu removes the stored one.
### Advanced
Behind **Advanced** on a profile, grouped:
| Group | Fields |
|---|---|
| **Authentication** | Identity files (one path per line, `%h`/`%r` expand), agent forwarding |
| **Proxies** | ProxyCommand (`%h`/`%p`/`%r` substituted), SOCKS5 proxy, HTTP proxy |
| **Algorithms** | KEX algorithms, ciphers, MACs, host-key algorithms, compression |
| **Connection** | Keepalive interval and count, connect timeout, X11 forwarding |
| **Session** | Shell integration, login scripts, skip banner |
Everything blank means "the library default", so you only fill in what you
actually need to override.
## Authentication prompts
Password, key passphrase, and 2FA prompts appear as sheets inside the pane, with
a **Remember (keychain)** option where it makes sense.
## Host keys
Host keys are verified against `known_hosts` by default. A first connection asks
you to confirm the fingerprint; a **changed** key is a much louder prompt that
makes you type `yes` to override, because that is what a changed key deserves.
**Settings → SSH → Security → Verify host keys** turns verification off
entirely. It is on for a reason.
Also under Security: **Warn before closing** a live connection, off by default.
## Reconnecting
<kbd>⌘ ⇧ R</kbd> — or *SSH: Reconnect* in the palette — restarts the session in
the current pane. Useful after a laptop sleeps or a network changes.
## What is not supported
- No fallback to the system `ssh` binary
- No `Match`, `canonicalize*`, or GSSAPI directives from `~/.ssh/config`
- Kerberos `gssapi-with-mic` is offered by the desktop app for managed
connections, but is not part of the `~/.ssh/config` resolution path
+114
View File
@@ -0,0 +1,114 @@
---
title: "Remote workspaces"
description: "Whole workspaces hosted on another machine — files, repos, panes, and git all stay over there."
---
An SSH pane runs one shell on a remote machine. A **remote workspace** goes
further: tty7 runs a server on the far machine, and the whole workbench points
at it. Tabs, splits, the file tree, the git panel, the diff overlay, the process
list — all of it is the remote machine's, rendered here.
Nothing is synced or copied. The repository stays where it is.
<Frame caption="Placeholder — screenshot: a remote workspace open, sidebar showing remote repos, with the machine name in the strip">
<img src="/images/placeholder.svg" alt="A remote workspace in tty7" />
</Frame>
## Connecting
<Steps>
<Step title="Open the switcher">
<kbd>⌘ ⇧ O</kbd>. Machines are listed alongside your local workspaces —
*This Computer* first, then every saved SSH profile and, on Windows, every
WSL distribution.
</Step>
<Step title="Pick a machine">
tty7 connects over the same SSH stack as everything else, so profiles,
keychain credentials, and jump hosts all apply.
</Step>
<Step title="Approve the server install, once">
The first connection asks:
> tty7 will write its server binary to *devbox* so this machine can host
> workspaces there. Nothing else on *devbox* is touched, and no sudo is
> used.
It shows the exact path, version, size, source, and SHA-256 before you
agree. Later upgrades on that machine install silently.
</Step>
<Step title="Open a workspace">
From then on the machine's workspaces are in the switcher, and a new one
opens like a local one.
</Step>
</Steps>
## What gets installed
| | |
|---|---|
| **What** | A single static `tty7-server` binary |
| **Where** | `~/.local/share/tty7/bin/tty7-server-c<control>p<protocol>` |
| **Privileges** | None. No sudo, nothing outside your home directory |
| **Hosts** | Linux, x86-64 or aarch64 |
The binary is named after the wire dialect it speaks, so a client and a server
that disagree never quietly half-work — tty7 installs the matching one instead.
On Windows, a WSL distribution is handed the Linux server the installer already
shipped, so a WSL workspace needs no network access at all.
## Reattaching
Remote workspaces are the point at which persistence pays off twice: the panes
survive on the remote machine whether or not your laptop is awake, and you can
reattach from a different client entirely.
A strip along the top of the window says what the connection is doing —
*connecting*, *reconnecting (attempt 3)*, *disconnected*, or *taken over by
someone else*. Reconnection is automatic; a workspace another client has claimed
says so by name rather than fighting over it.
## Keeping the server current
Two dialogs you may meet:
<AccordionGroup>
<Accordion title="Update tty7's server on “devbox”?">
The machine is serving sessions from a build whose protocol this client
cannot speak. tty7 has already installed a matching server, but the one
already running is the one your sessions are on. **Update Server** replaces
it and **ends every session it is hosting** — including ones this window is
not showing. Cancel leaves the machine exactly as it is.
</Accordion>
<Accordion title="Restart tty7's server on “devbox”?">
Same consequence, deliberately: every shell on that machine ends. Workspaces
and layouts are kept and come back with fresh shells.
</Accordion>
</AccordionGroup>
<Warning>
Both of these end other people's work if the machine is shared. tty7 spells
out what will happen before either one runs — read it.
</Warning>
## What a remote pane cannot do
- **Fork an agent session.** The fork command would run against the *local*
agent, so tty7 does not offer it.
- **Move very large files through the Files panel.** Drag-and-drop across the
link is capped at what one control frame can carry; past that the panel tells
you to use [SFTP](/remote/sftp).
## From the CLI
```bash
tty7 machine ls # this machine plus every link the server holds
tty7 -m devbox ls # route any command to a linked machine
tty7 -m devbox run -- cargo test
```
`-m` matches the full link key (`me@devbox:22`) or just the host. It uses a link
the local server *already* holds — it will not dial a fresh connection, and it
says so rather than guessing. Connect from the switcher first.
[CLI overview →](/cli/overview)
+57
View File
@@ -0,0 +1,57 @@
---
title: "History"
description: "Fuzzy history search, and whether each pane gets its own."
---
## Fuzzy search with <kbd>⌃ R</kbd>
<kbd>⌃ R</kbd> opens a fuzzy search over what you have actually run. Type any
fragment — the letters do not have to be adjacent — and the list narrows.
Each row carries the context the plain shell version throws away:
- **where** you ran it, so `npm run dev` from three repositories is three
distinct entries
- **when**, as a relative time
- **whether it failed**, from the exit code
Ranking mixes frequency with recency, and commands you ran in the *current*
directory are pushed up — the thing you want is usually the thing you last did
here.
<Frame caption="Placeholder — screenshot: ⌃R open, matching rows showing directory, time, and a failed exit">
<img src="/images/placeholder.svg" alt="Fuzzy history search" />
</Frame>
<kbd>⏎</kbd> puts the command on the prompt. <kbd>Esc</kbd> closes without
touching it.
### Handing <kbd>⌃ R</kbd> back
If you already have an fzf, percol, atuin or McFly binding you like, turn off
**Settings → Input → Prompt → History search** (`history_search: false`).
<kbd>⌃ R</kbd> then goes to the shell, and whatever you bound there keeps
working.
## Where the history comes from
Your existing shell history file, as-is. Nothing is imported or converted, there
is no separate store to warm up, and a history written outside tty7 shows up
immediately.
## One history, or one per pane
By default every pane shares your shell's history file, which is what a terminal
has always done: a command typed in one pane is available in the next.
**Settings → Terminal → Give each pane its own shell history**
(`per_pane_history: true`) changes that. Each pane gets a private history file:
- **seeded** from your real history when the pane opens, so it is not blank
- **merged back** into your real history when the pane closes, so nothing typed
is lost
Useful when several agents or several tasks are running side by side and you do
not want their commands interleaved in your <kbd>↑</kbd> key. Off by default,
because someone who has not asked for it would experience the change as their
history mysteriously forgetting the other window.
+51
View File
@@ -0,0 +1,51 @@
---
title: "Links"
description: "Opening URLs, files, and localhost ports straight out of the terminal."
---
Hold <kbd>⌘</kbd> (<kbd>Ctrl</kbd> on Windows and Linux) and links under the
pointer underline; click to open one.
## URLs
Anything that looks like a URL is detected, including one the shell wrapped
across two lines — tty7 stitches it back together before opening it.
Turn detection off with **Settings → Input → Links → Detect URLs**
(`link_url: false`).
## Files
A file path in the output — a compiler error, a test failure, a `grep -n` hit —
opens in your default application for that file type.
To send it somewhere specific instead, set **Settings → Input → Links → Open
files with**. The command runs with placeholders substituted:
```
code --goto {path}:{line}:{column}
zed {path}:{line}
herdr edit {path} --line={line}
```
`{path}`, `{line}`, and `{column}` are filled in from the link. A flag whose
value is not available is dropped rather than passed empty — so
`--line={line}` simply disappears when the link had no line number. Leave the
field blank to go back to the default application.
The same setting is `link_file_command` in `config.json`.
## localhost ports
<kbd>⌘</kbd>-clicking `localhost:3000` opens it in your browser, which is only
useful if the server is on this machine.
When the pane is inside an SSH session it usually is not. Turn on **Settings →
Input → Links → Forward SSH loopback links** (`ssh_loopback_forward: true`) and
tty7 opens a temporary port forward through that connection first, so the link
reaches the server on the remote machine.
<Tip>
For a forward you want to keep, set one up properly instead —
[port forwarding](/remote/port-forwarding).
</Tip>
+69
View File
@@ -0,0 +1,69 @@
---
title: "Mouse, scrolling, and the bell"
description: "How the pointer, the wheel, and ^G behave — and how to change each."
---
## Scrolling
The wheel and trackpad scroll the pane's scrollback. All of it lives under
**Settings → Terminal → Scrolling**:
| Setting | Default | What it does |
|---|---|---|
| **Scrollback** | 10,000 lines | How much history each pane keeps, up to 100,000. Applies to new panes. |
| **Scroll speed** | 1.0 | A multiplier on wheel scrolling (0.110). |
| **Smooth scrolling** | On | Eases each wheel notch into place over a few frames instead of jumping the whole way. Trackpads scroll continuously already and are unaffected. |
<kbd>⌘</kbd> plus the wheel zooms the font instead of scrolling.
## The pointer
Under **Settings → Terminal → Mouse**:
| Setting | Default | What it does |
|---|---|---|
| **Focus follows mouse** | Off | Hovering a pane focuses it without a click. |
| **Hide mouse while typing** | On | The pointer disappears as you type and returns on the next move. |
| **Report mouse to apps** | On | Full-screen programs — vim, tmux, `htop` — get clicks and scroll events themselves. Hold <kbd>⇧</kbd> to keep a gesture local and select text instead. |
<Tip>
If selecting text inside `vim` or `less` grabs the app's own selection instead
of yours, hold <kbd>⇧</kbd> while you drag.
</Tip>
## Font size
| | |
|---|---|
| <kbd>⌘ +</kbd> · <kbd>⌘ </kbd> | Bigger · smaller |
| <kbd>⌘ 0</kbd> | Back to the configured size |
| <kbd>⌘</kbd> + wheel | Zoom by scrolling over a terminal |
The base size is **Settings → Appearance → Typography → Font size**, 15 px by
default. The rest of the interface has its own size — **Interface font size**,
16 px, adjustable from 12 to 24 — so you can scale the chrome without touching
the terminal grid, or the other way round.
## The bell
**Settings → Terminal → Bell → Terminal bell** decides what `^G` does:
| Mode | Behaviour |
|---|---|
| **Off** | Nothing |
| **Visual** *(default)* | A brief flash |
| **Audible** | The system sound |
| **Both** | Flash and sound |
## Command-finished notifications
**Settings → Window & Tabs → Notifications** posts a desktop notification when a
foreground command finishes:
- **Notify on command finish** — *Never*, *When unfocused* (default), or
*Always*
- **Notify threshold** — how long a command has to run to qualify, 10 seconds by
default
Coding agents use the same policy for their own notifications.
[Agent status →](/agents/status)
+130
View File
@@ -0,0 +1,130 @@
---
title: "The prompt"
description: "Ghost suggestions, tab completion that explains itself, syntax highlighting, and real multi-line editing."
---
tty7 puts an editor at the shell prompt. Nothing to install, no plugin to source
— the moment a supported shell starts in a pane, the prompt behaves like this.
<Frame caption="Placeholder — video: typing a command with a ghost suggestion, accepting it, then opening the completion menu">
<img src="/images/placeholder.svg" alt="The tty7 prompt" />
</Frame>
## Ghost suggestions
As you type, the rest of the line is filled in from your history, greyed out
ahead of the cursor.
| | |
|---|---|
| <kbd>→</kbd> | Accept the whole suggestion |
| Keep typing | The suggestion narrows |
| <kbd>Esc</kbd> or anything that does not match | It disappears |
Your existing shell history is what feeds it — there is no separate database to
build up first, and it carries across sessions and reboots.
## Tab completion, with descriptions
<kbd>⇥</kbd> opens a completion menu that knows what it is offering:
- **Commands** from your PATH and your shell's builtins
- **Files and directories**, with `cd`, `pushd`, `popd`, and `rmdir` offering
directories only
- **Flags and subcommands** with their descriptions, for about 100 common
commands — `git`, `cargo`, `docker`, `kubectl`, `npm`, `brew` and the rest
- **Values** where a flag only takes certain ones
<Frame caption="Placeholder — screenshot: the completion menu on `git c`, each subcommand with its description">
<img src="/images/placeholder.svg" alt="Explained tab completion" />
</Frame>
When tty7 has nothing useful to offer, the <kbd>⇥</kbd> falls through to your
shell's own completion, so a carefully configured zsh setup is not lost.
To hand <kbd>⇥</kbd> back to the shell entirely, turn off **Settings → Input →
Prompt → Tab completion** (`tab_completion` in `config.json`).
## Syntax highlighting
The line you are typing is coloured as you type it: the command, its flags, its
arguments, paths, quoted strings, operators, comments. It is a fast tokenizer,
not a shell parser — it never changes what gets run.
## Line editing
The prompt behaves like a text field, because it is one:
- **Click to place the caret** anywhere in the line
- **Select with the mouse**, drag to extend
- **Word motion** and word delete
- **Undo**
Everything readline does still works — this sits on top, it does not replace it.
<Tip>
On macOS, turn on **Settings → Input → Keyboard → Option (⌥) acts as Meta** if
you want <kbd>⌥ B</kbd> / <kbd>⌥ F</kbd> to move by word instead of typing
`∫` and `ƒ`.
</Tip>
## Typing with an IME
Pinyin, Kana, Hangul and the rest work in a pane the way they do in a text
field: the composition is drawn in place at the cursor and only the committed
text reaches the program.
Two rules decide who gets a keystroke:
- **A plain printable key goes to the IME.** A key held with <kbd>⌃</kbd>,
<kbd>⌘</kbd>, <kbd>fn</kbd>, or <kbd>⌥</kbd> does not — those are chords, not
characters.
- **A program that asks for every key gets every key.** When something turns on
the kitty keyboard protocol's report-all-keys mode, the IME steps aside so the
program sees raw input.
<Note>
On macOS with **Option (⌥) acts as Meta** turned on, <kbd>⌥</kbd> chords
bypass the IME entirely, so <kbd>⌥ B</kbd> reaches your shell as meta-b
instead of being eaten as a dead key.
</Note>
Rendering CJK well is a separate question — see
[fonts and the two-column grid](/customization/fonts#cjk-and-the-two-column-grid).
## Multi-line commands
A command that wraps, or one you deliberately break across lines, edits in
place. The grid shifts to keep the caret visible instead of scrolling the whole
screen away.
| | |
|---|---|
| <kbd>⇧ ⏎</kbd> · <kbd>⌥ ⏎</kbd> | Insert a newline instead of submitting |
| <kbd>⏎</kbd> | Submit the whole buffer, however many lines it is |
The newline key is rebindable as `InsertNewline` under **Settings →
Keybindings**.
## Which shells
The prompt features arrive through tty7's shell integration, which is injected
automatically — nothing to add to your rc file — for **zsh**, **bash**,
**fish**, **PowerShell**, and **WSL**. Other shells (nushell, elvish, xonsh, and
the rest) run perfectly well in a pane; they simply do not get the prompt layer.
The integration is also what reports the working directory, the exit code of
each command, and where prompts begin — which is what the sidebar's branch
readout, the "command finished" notification, and `tty7 procs` are built on.
[How shell integration works →](/reference/shell-integration)
## Turning it off
Both prompt features are switches, and turning one off hands its key straight
back to the shell:
| Setting | Key it releases |
|---|---|
| **Settings → Input → Prompt → Tab completion** | <kbd>⇥</kbd> → your shell's completion |
| **Settings → Input → Prompt → History search** | <kbd>⌃ R</kbd> → your shell's reverse-i-search, or your fzf binding |
+62
View File
@@ -0,0 +1,62 @@
---
title: "Selection and clipboard"
description: "Smart double-click, copy on select, and the settings around them."
---
## Selecting
| | |
|---|---|
| Drag | Select a range |
| Double-click | Select the thing under the cursor — see below |
| Triple-click | Select the line |
| <kbd>⇧</kbd>-click | Extend the current selection to where you clicked |
| <kbd>⌘ A</kbd> *(macOS)* | Select all — also in the right-click menu and the command palette on every platform |
### Smart double-click
A double-click does not just grab a word bounded by spaces. It works out what
you are pointing at:
| Under the cursor | What you get |
|---|---|
| A URL | The whole URL — including one the shell wrapped across two lines |
| A file path | The whole path |
| An email address | The whole address |
| A bracket or quote | The matching pair, and everything between them |
| CJK text | The word, segmented by dictionary rather than by character |
Turn it off with **Settings → Input → Selection & clipboard → Smart selection**.
With it off, double-click falls back to plain word selection using the
`word_separators` list from `config.json` — by default:
```
,│`|:"' ()[]{}<>⇥
```
## Copying and pasting
| | macOS | Windows / Linux |
|---|---|---|
| Copy | <kbd>⌘ C</kbd> | <kbd>Ctrl ⇧ C</kbd> |
| Paste | <kbd>⌘ V</kbd> | <kbd>Ctrl ⇧ V</kbd> · <kbd>⇧ Insert</kbd> |
Two settings shape what lands where, both under **Settings → Input → Selection
& clipboard**:
<CardGroup cols={2}>
<Card title="Copy on select" icon="clipboard">
Selecting with the mouse copies immediately, no <kbd>⌘ C</kbd>. Off by
default.
</Card>
<Card title="Trim trailing spaces on copy" icon="scissors">
Strips trailing whitespace from every copied line — useful when copying out
of a TUI that pads to the pane width. Off by default.
</Card>
</CardGroup>
<Note>
Pasting multiple lines sends them as typed input, exactly as any terminal
does. If the shell supports bracketed paste it will treat the block as one
paste rather than running each line.
</Note>
+58
View File
@@ -0,0 +1,58 @@
---
title: "Command palette"
description: "One key for every action in the app — including the ones with no shortcut."
---
<kbd>⌘ P</kbd> opens the command palette. Type to filter, <kbd>↑</kbd>
<kbd>↓</kbd> to move, <kbd>⏎</kbd> to run.
Every action tty7 can perform is in here, whether or not it has a keybinding —
which makes it the fastest way to reach the ones that deliberately ship unbound,
like pane resize and swap.
<Frame caption="Placeholder — screenshot: the command palette open over a terminal, grouped results">
<img src="/images/placeholder.svg" alt="The tty7 command palette" />
</Frame>
## What is in it
Results are grouped, and the groups are the map of the app:
| Group | Examples |
|---|---|
| **Tabs & Panes** | New Tab · New Worktree Tab… · Split Right · Zoom Pane · Focus Pane Left · Resize Pane Up · Swap Pane Next · Reopen Closed Tab · Copy Working Directory · Fork Session |
| **Workspaces** | New Workspace · Switch Workspace… · Rename Workspace… · Stop Workspace… · Delete Workspace… |
| **View** | Show/Hide Left Sidebar · Show/Hide Right Panel · Show Code Panel · Tab Bar: Move to Top · Right Panel: Info / Changes / Files · Change Theme… · Enter Full Screen · Toggle Unified / Side-by-Side Diff |
| **Git** | Commit · Stage All Changes · Unstage All · Discard All · Checkout to… · Create Branch… · Sync · Push · Pull · Fetch · Toggle Commit History |
| **Terminal** | Clear Scrollback · Find in Terminal… · Find Next / Previous · Copy · Cut · Paste · Select All |
| **SSH** | Add Connection… · Manage Profiles… · Reconnect · Remote Files · Port Forwarding |
| **Agents** | Send Selection · Send Git Diff for Review · Copy Session ID |
| **Application** | Settings… · Keyboard Shortcuts · Check for Updates… · Documentation · Join the Discord · Report an Issue… · Restart Server… |
Entries that do something destructive say so under the name — *Delete
Workspace…* is subtitled "ends its shells and forgets the layout", *Restart
Server…* is "ends every running shell; layout is kept".
## Connecting from the palette
Type an SSH address and the palette offers to connect to it:
```
me@devbox
me@devbox:2222
[::1]:22
```
Saved profiles and `~/.ssh/config` aliases show up the same way — start typing
the name. [SSH →](/remote/ssh)
## Sending context to an agent
Two palette commands hand what is in front of you to the coding agent running in
the pane, as a ready-made prompt:
- **Agent: Send Selection** — the current selection.
- **Agent: Send Git Diff for Review** — the repository's `git diff`.
If no agent is running, tty7 says so rather than typing into your shell.
[Agents →](/agents/overview)
+50
View File
@@ -0,0 +1,50 @@
---
title: "Search"
description: "Finding text in the scrollback, and everything else the search boxes cover."
---
## In the terminal
<kbd>⌘ F</kbd> opens the find bar over the focused pane and searches its whole
scrollback, not just the visible screen.
| | |
|---|---|
| <kbd>⏎</kbd> · <kbd>⌘ G</kbd> | Next match |
| <kbd>⇧ ⏎</kbd> · <kbd>⌘ ⇧ G</kbd> | Previous match |
| <kbd>Esc</kbd> | Close the find bar |
Two toggles sit in the bar:
- **Match case** — off by default, so a lowercase query matches anything.
- **Use regular expression** — the query becomes a regex. An invalid pattern is
shown as an error rather than silently matching nothing.
Matches are highlighted in place and the view scrolls to each one as you step
through. On Windows and Linux the shortcuts are <kbd>Ctrl ⇧ F</kbd> to open,
<kbd>F3</kbd> and <kbd>⇧ F3</kbd> to step.
<Frame caption="Placeholder — screenshot: the find bar with matches highlighted in the scrollback">
<img src="/images/placeholder.svg" alt="Searching the scrollback" />
</Frame>
<Tip>
How much there is to search is **Settings → Terminal → Scrolling →
Scrollback** — 10,000 lines per pane by default, up to 100,000. The change
applies to new panes.
</Tip>
## Everywhere else
tty7 leans on the same pattern in a lot of places. All of them are
type-to-filter, no button to press:
| Where | What it searches |
|---|---|
| <kbd>⌘ P</kbd> | Commands — and SSH addresses you type in full |
| <kbd>⌘ ⇧ O</kbd> | Workspaces, tabs, and machines |
| <kbd>⌃ R</kbd> | Your shell history, fuzzily — [see History](/terminal/history) |
| Settings search box | Every setting, by name and by keyword |
| Files panel | Files under the workspace root |
| Theme picker | Built-in and custom themes |
| Font picker | Fonts installed on your system |
+66
View File
@@ -0,0 +1,66 @@
---
title: "The side panel"
description: "Info, Source Control, and Files — plus the built-in editor."
---
<kbd>⌘ J</kbd> opens a panel on the right of the window with three tabs. It is
hidden by default; whichever tab you leave it on is where it opens next time.
<Frame caption="Placeholder — screenshot: the side panel showing the Info tab beside a terminal">
<img src="/images/placeholder.svg" alt="The tty7 side panel" />
</Frame>
## Info
Everything tty7 knows about the focused pane, in one column:
| Section | What it shows |
|---|---|
| **Session** | working directory, shell, SSH connection, git branch, `+N M` changes, and the coding agent with its status (idle / working / waiting / done) |
| **Processes** | the process tree inside the pane, with the foreground process marked |
| **Ports** | every port those processes are listening on |
The working directory row has **Reveal in Finder** / **Open Folder** beside it.
The ports section is the quickest answer to "what is this pane serving, and
where" — the same data `tty7 procs` prints.
## Source Control
The git panel for the focused pane's repository: staged changes, unstaged
changes, untracked files, and merge conflicts, each in its own group. Write a
message, commit, and push without leaving the window.
[Source control →](/git/source-control)
## Files
A file tree rooted at the pane's working directory, with git status decorations
on every row and a search box at the top.
- **Click a file** to open it in the built-in editor.
- **Drag a file out** to Finder or Explorer to copy it there.
- **Drag files in** from the desktop to copy them into the folder under the
cursor. A folder row takes them itself, a file row stands in for the folder
holding it, and the empty space below the tree means the top of it. Folders
come in whole, the executable bit survives, and a name that is already taken
is asked about rather than replaced.
Both directions work over a [remote workspace](/remote/workspaces) too, reading
on one machine and writing on the other, up to the size one control frame can
carry — past that the panel tells you to use [SFTP](/remote/sftp).
## The editor
<kbd>⌘ ⇧ E</kbd> toggles the code panel; clicking a file in the Files tab opens
it there. It is a real editor — syntax highlighting, line and column readout,
wrap toggle, and a Markdown preview — meant for the edit you would otherwise
have opened `vim` for.
| | |
|---|---|
| <kbd>⌘ S</kbd> | Save |
| <kbd>Esc</kbd> | Back to the terminal |
Files are watched on disk: a change underneath you is picked up, and closing
with unsaved edits asks before discarding them. Files over 4 MB and anything
that looks binary are refused with a note rather than opened badly.
+76
View File
@@ -0,0 +1,76 @@
---
title: "The sidebar"
description: "Tabs grouped by repository, with branch, diff counts, and agent status on every row."
---
The left sidebar is tty7's tab bar, and it is the default because a vertical row
has room for things a horizontal chip does not: the repository a tab belongs to,
the branch it is on, how much has changed there, and what a coding agent in it
is doing.
<kbd>⌘ B</kbd> shows and hides it. Drag its right edge to resize.
<Frame caption="Placeholder — screenshot: the sidebar with two repo groups, agent avatars and status dots, branch and +N M counts">
<img src="/images/placeholder.svg" alt="The tty7 tab sidebar" />
</Frame>
## Grouped by repository
Rows sit under a header per git repository, with everything else collected in a
trailing **Scratch** section. The grouping follows the tab's working directory,
not its history — switching branches or `cd`-ing around inside a repository
never moves a row out from under its header.
**Settings → Window & Tabs → Sidebar grouping** switches between *By repo* (the
default) and *Flat*.
## What a row tells you
<CardGroup cols={2}>
<Card title="Who is running there" icon="robot">
A brand avatar when a coding agent is in the tab, plus a status dot —
blue for working, amber for needs-your-input, green for done.
</Card>
<Card title="Where it is" icon="code-branch">
The pane's git branch, refreshed on `cd` and whenever a command finishes.
</Card>
<Card title="What changed" icon="plus-minus">
The working-tree diff as `+N M`. Click the counts to open the
[diff overlay](/git/diffs).
</Card>
<Card title="Whether you have looked" icon="circle-dot">
An unread marker on tabs that produced output while you were elsewhere.
*Mark as Unread* is in the right-click menu.
</Card>
</CardGroup>
If you would rather the counts not be clickable, turn off **Settings → Window &
Tabs → Open diff preview from sidebar counts**. The branch and the numbers stay;
they simply stop opening the overlay.
## Rearranging
Drag a row to reorder it, or drag a whole group header to move the group.
Dropping a row into another group moves the tab there.
## Naming
Almost no tab has a name of its own, so the label falls back through the best
evidence available, in order:
1. a name you set (right-click → **Rename Tab…**)
2. the coding agent running in the tab — "Claude Code"
3. the last segment of the working directory
4. the foreground process
That order is also what `tty7 tab ls` reports as `label`, with `name` left
literal so a script can tell a real name from a stand-in.
## The switcher
<kbd>⌘ ⇧ O</kbd> opens the workspace switcher: every workspace on every machine
you are connected to on the left, that workspace's tabs on the right. Type to
filter both, <kbd>⇥</kbd> to cross into the tab column, <kbd>⏎</kbd> to open.
From here you can also rename a workspace, open one in a new window, stop one,
or connect to a machine you have a profile for.
+99
View File
@@ -0,0 +1,99 @@
---
title: "Tabs and splits"
description: "Opening, arranging, and rearranging the panes in a tab."
---
<Note>
Keys are written in macOS notation. On Windows and Linux, read <kbd>⌘</kbd> as
<kbd>Ctrl ⇧</kbd> for most window actions — the exact chords are on the
[keyboard shortcuts](/reference/keyboard-shortcuts) page.
</Note>
## Tabs
| | |
|---|---|
| <kbd>⌘ T</kbd> | New tab |
| <kbd>⌘ W</kbd> | Close the tab (or the focused pane, if the tab has more than one) |
| <kbd>⌘ ⇧ T</kbd> | Reopen the tab you just closed |
| <kbd>⌘ 1</kbd> … <kbd>⌘ 9</kbd> | Jump to tab 19 |
| <kbd>⌃ ⇥</kbd> · <kbd>⌃ ⇧ ⇥</kbd> | Hold to walk the switcher forwards or backwards; it commits when you let go |
A new tab always opens in the current pane's directory. Where it lands in the
list is **Settings → Window & Tabs → New tab position** — *After current* by
default, or *At end*.
Right-click a tab for the rest: rename, close others, close to the right, copy
the working directory, mark unread, and — when a coding agent is running there —
fork its session.
## Splits
| | |
|---|---|
| <kbd>⌘ D</kbd> | Split right |
| <kbd>⌘ ⇧ D</kbd> | Split down |
| <kbd>⌘ ]</kbd> · <kbd>⌘ [</kbd> | Next pane · previous pane |
| <kbd>⌘ ⌥ ← → ↑ ↓</kbd> | Focus the pane in that direction |
| <kbd>⌘ ⇧ ⏎</kbd> | Zoom the focused pane to fill the tab |
| <kbd>⌘ ⏎</kbd> | Fullscreen the window |
A split inherits the current pane's working directory, so splitting inside a
repository keeps you in the repository.
Resizing and swapping panes have no default keys — bind them under **Settings →
Keybindings**, or run them from the command palette (*Resize Pane Left*, *Swap
Pane Next*, and friends).
Inactive panes are dimmed slightly so the focused one is obvious. Turn that off
with **Settings → Appearance → Dim inactive panes**.
## Rearranging by dragging
Hover a pane and a small grip appears along its top edge. Drag it to move that
pane somewhere else in the tab.
<Frame caption="Placeholder — video: dragging a pane by its grip, with the landing highlighted">
<img src="/images/placeholder.svg" alt="Dragging a pane to a new position" />
</Frame>
Where you drop it decides what happens:
<AccordionGroup>
<Accordion title="On a pane's side">
The pane goes in **beside** that one. If it is facing a neighbour in the
same row or column, it joins that row and takes an equal share of it. Only
when it faces across the layout — where there is no row to join — does it
split that pane in half.
</Accordion>
<Accordion title="On a pane's middle">
The two panes **trade places**.
</Accordion>
<Accordion title="Past a pane's outer side">
The band beyond the outermost edge — the side facing the window rather than
another pane — makes the dragged pane a full-width or full-height band
beside everything else, sized to an even share of what that side already
holds. A pane in the middle of a 2×2 becomes a full-height *third* column in
one drag, rather than taking half the window.
</Accordion>
</AccordionGroup>
The landing lights up while you drag, and only ever lights up when the drop
would actually change the layout.
## Closing something that is busy
Closing a pane or tab with a command still running asks first, and says what is
running: *"cargo is still running. Closing ends it."* When a coding agent is
mid-turn it says that instead — *"Claude Code is still working. Closing ends its
turn."*
## Where the tabs live
**Settings → Window & Tabs → Tab bar position** puts tabs in a vertical sidebar
on the left (the default) or a horizontal strip on top. The sidebar has room for
things a strip does not — git branch, diff counts, agent status — so most of
[its own page](/window/sidebar) is about that.
<kbd>⌘ B</kbd> toggles the sidebar; <kbd>⌘ J</kbd> toggles the
[side panel](/window/side-panel) on the right.