diff --git a/README.md b/README.md index c9430037..0ba8a115 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/README.zh-CN.md b/README.zh-CN.md index e7ca60fd..56a5ab7a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -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: diff --git a/docs/agents/orchestration.mdx b/docs/agents/orchestration.mdx new file mode 100644 index 00000000..eae1acc6 --- /dev/null +++ b/docs/agents/orchestration.mdx @@ -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 + + + 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. + + +- **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). diff --git a/docs/agents/overview.mdx b/docs/agents/overview.mdx new file mode 100644 index 00000000..903c94a6 --- /dev/null +++ b/docs/agents/overview.mdx @@ -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. + + + Agent sessions in the tty7 sidebar + + +## 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: + + + + The tab chip and sidebar row show which agent runs where, so ten tabs stay + legible. + + + The branch and working-tree diff on the row, refreshed as the agent works. + + + A pane lost to a reboot relaunches the conversation, carrying its original + flags. [More →](/agents/sessions) + + + Palette commands that hand the current selection or the repo's `git diff` to + the running agent as a prompt. + + + +## 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 + + + + Hooks, status dots, the tray icon. + + + Resume, fork, and copying a session id. + + + One agent driving another with `tty7 wait`. + + diff --git a/docs/agents/sessions.mdx b/docs/agents/sessions.mdx new file mode 100644 index 00000000..7f70818f --- /dev/null +++ b/docs/agents/sessions.mdx @@ -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`. + + + Resume needs the agent's hooks installed, since the session id comes from + them. [Installing hooks →](/agents/status) + + +## 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 --fork-session` | +| Codex | `codex fork ` | +| Grok | `grok --resume --fork-session` | +| OpenCode | `opencode --session --fork` | +| Oh My Pi | `omp --fork ` | + +It is the agent's own fork command, run in a new pane — nothing is copied by +tty7 itself. + + + 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. + + +## 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. diff --git a/docs/agents/status.mdx b/docs/agents/status.mdx new file mode 100644 index 00000000..d9799e53 --- /dev/null +++ b/docs/agents/status.mdx @@ -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. + + + The hooks only do anything inside tty7. Running the same agent in another + terminal is unaffected. + + +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 | + + + Agent status dots + + +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. diff --git a/docs/cli/agent-skill.mdx b/docs/cli/agent-skill.mdx new file mode 100644 index 00000000..12697ffc --- /dev/null +++ b/docs/cli/agent-skill.mdx @@ -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. + + + 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) + + +## 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 + + + 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. + + +- **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). diff --git a/docs/cli/overview.mdx b/docs/cli/overview.mdx new file mode 100644 index 00000000..ac1c7336 --- /dev/null +++ b/docs/cli/overview.mdx @@ -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. + + + Everything after `--` belongs to the child: `tty7 run -- cargo test --keep` + passes `--keep` to cargo, not to tty7. + + +### 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. + + + 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 + ``` + + +## 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) + + + Every verb, flag, and JSON shape. + diff --git a/docs/cli/reference.mdx b/docs/cli/reference.mdx new file mode 100644 index 00000000..254abd98 --- /dev/null +++ b/docs/cli/reference.mdx @@ -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 ` | 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 (50–3,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"}` | + + + `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. + + +Prefer `tty7 new ` 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: `@`. + +| 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 | + + + Do not run `start`, `stop`, or `restart` on someone else's behalf. They change + or destroy what the user's GUI is attached to. + + +## 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 ` (launch or focus the GUI) diff --git a/docs/customization/fonts.mdx b/docs/customization/fonts.mdx new file mode 100644 index 00000000..544fc5ba --- /dev/null +++ b/docs/customization/fonts.mdx @@ -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 (12–24) | +| **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 + + + 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. + + +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. diff --git a/docs/customization/keybindings.mdx b/docs/customization/keybindings.mdx new file mode 100644 index 00000000..84386b0c --- /dev/null +++ b/docs/customization/keybindings.mdx @@ -0,0 +1,94 @@ +--- +title: "Keybindings" +description: "Rebinding anything, chord sequences, and the tmux preset." +--- + +**Settings → Keybindings** (⌘ ,) 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 — ⌃ B then X | +| Esc | Cancel | +| | 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. + + + Rebinding a shortcut + + +## 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` | on macOS, Ctrl 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 — ⌃ B by default, changeable in the **Prefix** field beside +it. + +| | | +|---|---| +| ⌃ B C · X | New tab · close tab | +| ⌃ B % · " | Split right · split down | +| ⌃ B ←→↑↓ | Move focus | +| ⌃ B ⌃ ←→↑↓ | Resize the pane | +| ⌃ B O · ; | Next pane · previous pane | +| ⌃ B { · } | Swap with the previous · next pane | +| ⌃ B Z | Zoom the pane | +| ⌃ B N · P | Next tab · previous tab | +| ⌃ B 19 | Jump to a tab | + +Two details that make it livable: + +- A **bare prefix** reaches the shell after about a second, so ⌃ B + 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 + +| | | +|---|---| +| ⇧ ⏎ · ⌥ ⏎ | Insert a newline at the prompt instead of submitting (`InsertNewline`) | +| ⌘ ⇧ ⏎ | Zoom the focused pane | +| ⌘ ⇧ E | Toggle the code panel | +| ⌘ ⇧ R | Restart the SSH session in this pane | +| ⌘ ⇧ O | Workspace switcher | +| ⌘ ⇧ N | New workspace | diff --git a/docs/customization/settings.mdx b/docs/customization/settings.mdx new file mode 100644 index 00000000..a260f6eb --- /dev/null +++ b/docs/customization/settings.mdx @@ -0,0 +1,86 @@ +--- +title: "Settings" +description: "What lives in each section, and how the settings file works." +--- + +⌘ , 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. + + + tty7 Settings + + +## The eight sections + + + + Theme, sync with system, typography, cursor, transparency, language. + + + Shell and start directory, scrollback and scrolling, mouse, bell, per-pane + history. + + + Prompt features, selection & clipboard, keyboard (Option as Meta), links. + + + Hosts, defaults, security, and every per-profile field. + + + Hook installation per agent and per machine, the orchestration skill, the + CLI on PATH. + + + Startup window, tab bar position and grouping, notifications, tray icon. + + + Every shortcut, the tmux preset, the prefix. + + + Version, update channel, and the updater. + + + +## 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. + + + A UTF-8 BOM at the start of the file is tolerated, which matters if you edited + it in a Windows editor. + + +## 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" } +``` diff --git a/docs/customization/themes.mdx b/docs/customization/themes.mdx new file mode 100644 index 00000000..06774279 --- /dev/null +++ b/docs/customization/themes.mdx @@ -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 | + + + The tty7 theme picker + + +## 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.2–1.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. + + + 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. + diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 00000000..762af9e8 --- /dev/null +++ b/docs/docs.json @@ -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" + } + } +} diff --git a/docs/favicon.ico b/docs/favicon.ico new file mode 100644 index 00000000..f4cbfc6d Binary files /dev/null and b/docs/favicon.ico differ diff --git a/docs/features.md b/docs/features.md deleted file mode 100644 index 7310b805..00000000 --- a/docs/features.md +++ /dev/null @@ -1,158 +0,0 @@ -# Features - -English · [简体中文](features.zh-CN.md) - -## Input - -- **Ghost suggestions** — your history completes the whole line as you type; 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** — ⌃ R shows what you ran, where, and whether it failed; turn it off (Settings → Input → Prompt, or `history_search` in `config.json`) and ⌃ R 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. ⇧ ⏎ · ⌥ ⏎ insert a newline instead of submitting (rebindable as `InsertNewline`); a plain 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** ⌘ P · scrollback search ⌘ F -- **⌘/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 `, `claude --resume --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 as -Ctrl. The essentials: - -| | | -|---|---| -| ⌘ T · ⌘ W · ⌘ ⇧ T | new tab · close tab · reopen closed tab | -| ⌘ 1⌘ 9 | jump to tab 1–9 | -| ⌃ ⇥ · ⌃ ⇧ ⇥ | hold to walk the switcher forwards · backwards; it commits when you let go | -| ⌘ D · ⌘ ⇧ D | split right · split down | -| ⌘ ] · ⌘ [ | next pane · previous pane | -| ⌘ ⌥ ←→↑↓ | focus the pane in that direction | -| ⌘ ⏎ · ⌘ ⇧ ⏎ | toggle fullscreen · zoom pane | -| ⌘ K | clear scrollback | -| ⌘ P | command palette | -| ⌘ F | search the scrollback | -| ⌃ R | fuzzy-search shell history | -| ⌘ + · ⌘ − · ⌘ 0 | font size up · down · reset | -| + wheel | zoom the font by scrolling over a terminal | - -**Settings → Keybindings** (⌘ ,) lists every shortcut. Click one, -press the new keys (Esc cancels, Backspace 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 ⌃ B): -⌃ B C opens a tab, ⌃ B % splits, -⌃ B 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. diff --git a/docs/features.zh-CN.md b/docs/features.zh-CN.md deleted file mode 100644 index abeda9e7..00000000 --- a/docs/features.zh-CN.md +++ /dev/null @@ -1,150 +0,0 @@ -# 功能 - -[English](features.md) · 简体中文 - -## 输入 - -- **影子建议** —— 边打字边用你的历史补全整条命令, 接受 -- **带说明的 Tab 补全** —— 每个 flag、每个子命令都带说明,覆盖约 100 个常用命令;tty7 没有候选时 Tab 自动交给 shell 自己的补全,整个功能也可关闭(设置 → 输入 → 提示符,或 `config.json` 里的 `tab_completion`) -- **语法高亮** —— 边打边亮,什么都不用装 -- **模糊历史搜索** —— ⌃ R 看到每条命令在哪跑的、什么时候、有没有失败;关掉它(设置 → 输入 → 提示符,或 `config.json` 里的 `history_search`)后 ⌃ R 直接交给 shell,你绑的 fzf / percol 照常可用 -- **历史开箱即用** —— 你已有的 shell 历史直接生效,并跨会话延续 -- **行编辑** —— 点击定位光标、鼠标选区、词级移动、撤销 -- **多行编辑** —— 折行和多行命令原地编辑;网格自动上移,光标始终可见。⇧ ⏎ · ⌥ ⏎ 插入换行而不提交(可改绑,动作名 `InsertNewline`),单独按 提交整个缓冲区 - -## 窗口 - -- **标签页与分屏** —— 永远开在当前目录 -- **拖动重排分屏** —— 鼠标移到某个 pane 上,它顶边中间会浮出一个小抓手;拖着它在布局里走,就能把这个 pane 挪到标签页内的别处。落在某个 pane 的某一侧=插到它旁边:那一侧要是朝着同一排的邻居,就并入那一排、和它们等分;要是横着切过这一排(没有排可并),才是把那个 pane 一分为二、自己占住那一半。落在它正中=两个 pane 互换位置;继续推到某个 pane 朝着窗口那一侧的外缘(不是朝着另一个 pane 的那侧)=变成贴着窗口某一边、跨满整行或整列的一条,宽度按那条轴上已有的份数均分 —— 2×2 里的一个 pane 一次拖动就能变成通高的第三列(各占三分之一),而不是独占半屏。拖动过程中落点会高亮,且只有当这一放确实会改变布局时才会亮 -- **侧栏按仓库分组** —— 左侧标签栏按 git 仓库分组、每组一个标题行,不在仓库里的标签归入末尾的 *草稿* 组;切分支、仓库内 `cd` 都不会挪动行(`config.json` 的 `sidebar_grouping`:默认 `repo`,`none` 恢复扁平列表) -- **命令面板** ⌘ P · scrollback 搜索 ⌘ F -- **⌘ 点击打开链接** · 桌面通知 · 划选即复制(可选,设置 → 输入 → 选择与剪贴板) -- **智能双击选中** —— 双击直接选中整条 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 Emoji,Windows 用 Microsoft YaHei / Segoe UI Emoji,Linux 用 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 agent(Claude Code、Codex、Gemini CLI、 -Aider、Amp、OpenCode 等共 18 个)并在其外围加功能 —— 绝不包裹或替代 agent 本身。 - -- **品牌头像** —— 标签 chip / 侧栏行显示每个 pane 跑的是哪个 agent;自定义包装命令可通过 `config.json` 的 `agent_commands` 映射 -- **状态点** —— 工作中(蓝)/ 等你输入(琥珀)/ 完成(绿),由 agent 自己上报的 OSC 事件驱动;在 设置 → Agents 一键装好对应 hooks(Claude 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 `、`claude --resume --fork-session`,OpenCode、Grok Build 和 Oh My Pi 同样支持),把当前对话分叉成一个独立会话;原会话原封不动,两边各自往下走。在 pane 上右键可选择分屏位置,在标签 / 侧栏行上右键则直接开新标签。需要先装好该 agent 的 hooks(fork 认的是 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 上,把 读作 -Ctrl。最常用的几个: - -| | | -|---|---| -| ⌘ T · ⌘ W · ⌘ ⇧ T | 新建标签页 · 关闭标签页 · 恢复关闭的标签页 | -| ⌘ 1⌘ 9 | 跳到第 1–9 个标签页 | -| ⌃ ⇥ · ⌃ ⇧ ⇥ | 按住不放在切换面板里向后 · 向前走,松手即切换 | -| ⌘ D · ⌘ ⇧ D | 向右分屏 · 向下分屏 | -| ⌘ ] · ⌘ [ | 下一个窗格 · 上一个窗格 | -| ⌘ ⌥ ←→↑↓ | 按方向切换焦点窗格 | -| ⌘ ⏎ · ⌘ ⇧ ⏎ | 切换全屏 · 缩放窗格 | -| ⌘ K | 清除 scrollback | -| ⌘ P | 命令面板 | -| ⌘ F | 搜索 scrollback | -| ⌃ R | 模糊搜索 shell 历史 | -| ⌘ + · ⌘ − · ⌘ 0 | 字号增大 · 减小 · 重置 | -| + 滚轮 | 在终端上滚动缩放字号,演示时随手放大 | - -**设置 → 按键绑定**(⌘ ,)列出全部快捷键。点一行、按下新键即可 -(Esc 取消,Backspace 恢复默认),改完立即生效。窗格缩放与 -交换默认不绑定键 —— 在这里绑定,或从命令面板执行。 - -**tmux 预设** —— 把窗格/标签页操作映射到前缀键(默认 ⌃ B): -⌃ B C 新建标签页,⌃ B % 分屏, -⌃ B 接方向键切换焦点。单独按前缀键会在短暂延迟后送达 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、脚本和开发者工作流的输出稳定可预测。 diff --git a/docs/getting-started/concepts.mdx b/docs/getting-started/concepts.mdx new file mode 100644 index 00000000..1f6bb2ec --- /dev/null +++ b/docs/getting-started/concepts.mdx @@ -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 ⌘ ⇧ O 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. + + + The tty7 workspace switcher + + +## 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. + + + 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. + + +### 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 | ✗ | ✓ | ✓ | ✓ 1 | +| A supported agent session resumes | ✗ | ✓ | ✓ | ✓ | + +1 A capped tail of each pane, restored once. See +[session restore](/reference/troubleshooting#panes-came-back-empty). + +## 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. + + + Those ids are the whole interface. The CLI page starts there. + diff --git a/docs/getting-started/first-launch.mdx b/docs/getting-started/first-launch.mdx new file mode 100644 index 00000000..cb83ceb7 --- /dev/null +++ b/docs/getting-started/first-launch.mdx @@ -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 ⌘ , (Ctrl , on Windows and Linux), or +from the command palette (⌘ P → *Settings*). + + + tty7 Settings + + +## 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), ⌥ B types `∫`, which is what macOS has always +done. On, it sends the escape chord shells expect, so ⌥ B moves back +a word and ⌥ ⌫ 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. + + + 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) + + +## 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, ⌃ B by default. ⌃ B C opens a tab, +⌃ B % splits, ⌃ B 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 + + + + Workspaces, tabs, panes, and the server that owns them. + + + Suggestions, completion, and history search — the part you touch most. + + diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx new file mode 100644 index 00000000..4f5f3e59 --- /dev/null +++ b/docs/getting-started/installation.mdx @@ -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. + + + + Download the DMG that matches your Mac and drag **tty7** into Applications. + + | Mac | File | + |---|---| + | Apple silicon (M1 and later) | `tty7--macos-arm64.dmg` | + | Intel | `tty7--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. + + + Builds are produced on macOS 14 and macOS 15. macOS 14 (Sonoma) or later + is the tested range. + + + + + Two shapes, both x86-64: + + | File | Use it when | + |---|---| + | `tty7--windows-x86_64-setup.exe` | You want a normal install with Start-menu entries and an uninstaller. | + | `tty7--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 + ``` + + + 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). + + + + + | File | Use it when | + |---|---| + | `tty7--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--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 + ``` + + + +## 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**. + + + Inside a tty7 pane the CLI works regardless of PATH, because panes inherit the + app's environment. + + +## 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`. + + + + ```bash + git clone https://github.com/l0ng-ai/tty7 + cd tty7 + cargo build --release + ``` + + + 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 + ``` + + + + + 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). + + +## Uninstalling + + + + 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. + + + Use **Add or remove programs**. The uninstaller removes the PATH entry and + any Explorer context-menu keys it added. Settings live in + `%APPDATA%\tty7`. + + + Delete the AppImage or the unpacked directory. Settings live in + `~/.config/tty7`. + + diff --git a/docs/git/diffs.mdx b/docs/git/diffs.mdx new file mode 100644 index 00000000..5c2441f3 --- /dev/null +++ b/docs/git/diffs.mdx @@ -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; Esc closes it. + + + The tty7 diff overlay + + +## 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. diff --git a/docs/git/source-control.mdx b/docs/git/source-control.mdx new file mode 100644 index 00000000..da3f23f2 --- /dev/null +++ b/docs/git/source-control.mdx @@ -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) (⌘ J) +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. + + + The tty7 source control panel + + +## 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 | + +⌘ ⏎ 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. diff --git a/docs/git/worktrees.mdx b/docs/git/worktrees.mdx new file mode 100644 index 00000000..74d33edb --- /dev/null +++ b/docs/git/worktrees.mdx @@ -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. + + + Creating a worktree + + +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: + +``` +/.tty7/worktrees/ +``` + +`/.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. + + + 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. + diff --git a/docs/images/hero.webp b/docs/images/hero.webp new file mode 100644 index 00000000..416a5a41 Binary files /dev/null and b/docs/images/hero.webp differ diff --git a/docs/images/placeholder.svg b/docs/images/placeholder.svg new file mode 100644 index 00000000..38ee5e64 --- /dev/null +++ b/docs/images/placeholder.svg @@ -0,0 +1,12 @@ + + + + + + + + + + screenshot coming soon + tty7 docs + diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 00000000..d70cc602 --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,105 @@ +--- +title: "tty7" +sidebarTitle: "Introduction" +description: "A terminal workbench: persistent sessions, remote work, agents." +mode: "wide" +--- + + + tty7 showing a sidebar of agent sessions across several repositories + + +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 + + + + A background server owns your shells, not the window. Quit tty7 and your + builds keep building. No tmux to learn or configure. + + + Ghost suggestions from your history, tab completion that explains each flag, + syntax highlighting, click-to-place-caret, real multi-line editing. + + + 18 coding CLIs are recognised on sight. Per-pane status dots, notifications + when one needs you, git context, and session resume after a reboot. + + + 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. + + + Branch and diff counts on every sidebar row, a source control panel, a diff + overlay, and worktrees in one dialog. + + + 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. + + + +## Start here + + + + Native builds for macOS, Windows, and Linux. + [Installation →](/getting-started/installation) + + + Five minutes of settings that pay for themselves. + [First launch →](/getting-started/first-launch) + + + Workspace, tab, pane — and the server underneath them. + [Core concepts →](/getting-started/concepts) + + + +## 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` (lower is better) | **95 ms** | 239 ms | 179 ms | 185 ms | +| [DOOM-fire](https://github.com/const-void/DOOM-fire-zig) frame rate (higher is better) | **888 fps** | 485 fps | 552 fps | 617 fps | +| Cold-launch memory | 116 MB 1 | 105 MB | 128 MB | 130 MB | + +1 GUI 105 MB plus the persistent server at 11 MB. + +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 + + + + Ask a question, show what you built. + + + Bugs and feature requests. + + + Everything that shipped, release by release. + + diff --git a/docs/logo/logo.svg b/docs/logo/logo.svg new file mode 100644 index 00000000..0ec488f2 --- /dev/null +++ b/docs/logo/logo.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/reference/configuration.mdx b/docs/reference/configuration.mdx new file mode 100644 index 00000000..81851282 --- /dev/null +++ b/docs/reference/configuration.mdx @@ -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 (4–256). | +| `line_height` | number | `1.4` | Multiple of the font size (0.5–4). | +| `ui_font_size` | number | `16` | The interface's root size in px (12–24). | + +[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.2–1.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 (100–2000). | +| `sidebar_collapsed` | bool | `false` | | +| `right_panel_visible` | bool | `false` | | +| `right_panel_width` | number | `260` | Pixels (100–2000). | +| `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 (100–100,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.1–10. | +| `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 . Off hands the key to the shell. | +| `history_search` | bool | `true` | tty7's fuzzy history on ⌃ R. 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` | +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 (1–3600). | + +## 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 ~25–30 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. + + + 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. + diff --git a/docs/reference/keyboard-shortcuts.mdx b/docs/reference/keyboard-shortcuts.mdx new file mode 100644 index 00000000..495e2445 --- /dev/null +++ b/docs/reference/keyboard-shortcuts.mdx @@ -0,0 +1,103 @@ +--- +title: "Keyboard shortcuts" +description: "Every default binding, plus the action names for rebinding." +--- + +**Settings → Keybindings** (⌘ ,) 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 | ⌘ T | Ctrl ⇧ T | +| Close Pane / Tab | ⌘ W | Ctrl ⇧ W | +| Reopen Closed Tab | ⌘ ⇧ T | Alt ⇧ T | +| Next Tab · Previous Tab | ⌃ ⇥ · ⌃ ⇧ ⇥ | same | +| Go to Tab 1–9 | ⌘ 1⌘ 9 | Alt 1Alt 9 | +| New Workspace | ⌘ ⇧ N | Ctrl ⇧ N | +| Switch Workspace | ⌘ ⇧ O | Ctrl ⇧ O | + +## Panes + +| Action | macOS | Windows / Linux | +|---|---|---| +| Split Right | ⌘ D | Ctrl ⇧ D | +| Split Down | ⌘ ⇧ D | Ctrl Alt ⇧ D | +| Next Pane · Previous Pane | ⌘ ] · ⌘ [ | Ctrl ⇧ ] · Ctrl ⇧ [ | +| Focus Pane Left / Right / Up / Down | ⌘ ⌥ ←→↑↓ | Alt ←→↑↓ | +| Zoom Pane | ⌘ ⇧ ⏎ | Ctrl ⇧ ⏎ | +| Enter Full Screen | ⌘ ⏎ | F11 | + +## View + +| Action | macOS | Windows / Linux | +|---|---|---| +| Command Palette | ⌘ P | Ctrl ⇧ P | +| Toggle Left Sidebar | ⌘ B | Ctrl ⇧ B | +| Toggle Right Panel | ⌘ J | Ctrl ⇧ J | +| Toggle Code Panel | ⌘ ⇧ E | Ctrl ⇧ E | +| Font Size Up · Down · Reset | ⌘ + · ⌘ − · ⌘ 0 | Ctrl + · Ctrl − · Ctrl 0 | +| Zoom the font | + wheel | Ctrl + wheel | + +## Terminal + +| Action | macOS | Windows / Linux | +|---|---|---| +| Find in Terminal | ⌘ F | Ctrl ⇧ F | +| Find Next · Previous | ⌘ G · ⌘ ⇧ G | F3 · ⇧ F3 | +| Clear Scrollback | ⌘ K | Ctrl ⇧ K | +| Copy · Paste | ⌘ C · ⌘ V | Ctrl ⇧ C · Ctrl ⇧ V · ⇧ Insert | +| Insert Newline (at the prompt) | ⇧ ⏎ · ⌥ ⏎ | same | +| Fuzzy history search | ⌃ R | same | +| Accept ghost suggestion | | same | +| Completion menu | | same | + +## Git and SSH + +| Action | macOS | Windows / Linux | +|---|---|---| +| Commit (caret in the message box) | ⌘ ⏎ | Ctrl ⏎ | +| Save (in the editor) | ⌘ S | Ctrl S | +| Restart SSH Session | ⌘ ⇧ R | Ctrl ⇧ R | + +## Application + +| Action | macOS | Windows / Linux | +|---|---|---| +| Settings | ⌘ , | Ctrl , | +| Keyboard Shortcuts | ⌘ / | — | +| Hide tty7 · Hide Others · Minimize | ⌘ H · ⌘ ⌥ H · ⌘ M | — | +| Quit | ⌘ Q | Ctrl ⇧ Q | + +## 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 on macOS and Ctrl elsewhere. A space +separates the steps of a chord. +[More →](/customization/keybindings) diff --git a/docs/reference/privacy.mdx b/docs/reference/privacy.mdx new file mode 100644 index 00000000..9b405502 --- /dev/null +++ b/docs/reference/privacy.mdx @@ -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: + + + + Camera · microphone · Bluetooth · location · motion + + + Contacts · calendars · reminders · photo library + + + Local network · Apple Events · speech recognition · system administration + + + + + **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**. + + +### 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 | `/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. | diff --git a/docs/reference/shell-integration.mdx b/docs/reference/shell-integration.mdx new file mode 100644 index 00000000..9e732d29 --- /dev/null +++ b/docs/reference/shell-integration.mdx @@ -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. + + + A shell launched with custom arguments is left alone for bash and PowerShell, + because tty7's injection would conflict with the flags you chose. + + +## 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 ⌃ R'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**. diff --git a/docs/reference/troubleshooting.mdx b/docs/reference/troubleshooting.mdx new file mode 100644 index 00000000..e06c3746 --- /dev/null +++ b/docs/reference/troubleshooting.mdx @@ -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 <build>" + +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 <machine>"* | The SSH link dropped. Reconnect from the switcher. | + +`tty7 -m ` never dials a fresh connection by design — it uses a link +the local server already holds. Connect from the GUI first. + +## or ⌃ R 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. + +## ⌥ B 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 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 + + + + Ask — someone has probably hit it. + + + Include `tty7 doctor` output and your platform. + + diff --git a/docs/reference/updates.mdx b/docs/reference/updates.mdx new file mode 100644 index 00000000..fc877fda --- /dev/null +++ b/docs/reference/updates.mdx @@ -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 + + + + 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**. + + + 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 25–30 MB. + (`auto_download_updates: false`) + + + 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. + + + +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. + + + A stable release outranks every dated build of its core version, which is how + switching back to Stable *graduates* rather than downgrades. + + +## Platform notes + + + + 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. + + + 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. + + + AppImage and tarball installs are replaced by downloading the new file. + + + +## "The background server is still running <build>" + +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. diff --git a/docs/remote/port-forwarding.mdx b/docs/remote/port-forwarding.mdx new file mode 100644 index 00000000..b23cbc95 --- /dev/null +++ b/docs/remote/port-forwarding.mdx @@ -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. + + + The forwards panel + + +## The one-click shortcut + +-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** + + + 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. + diff --git a/docs/remote/sftp.mdx b/docs/remote/sftp.mdx new file mode 100644 index 00000000..b7963868 --- /dev/null +++ b/docs/remote/sftp.mdx @@ -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. + + + The tty7 SFTP panel + + +## 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. + + + 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. + diff --git a/docs/remote/ssh.mdx b/docs/remote/ssh.mdx new file mode 100644 index 00000000..ed44a8ab --- /dev/null +++ b/docs/remote/ssh.mdx @@ -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. + + + Connecting over SSH in tty7 + + +## Four ways to connect + + + + Open the palette (⌘ P) and type an address. IPv6 works with + brackets. + + ``` + me@devbox + me@devbox:2222 + me@[2001:db8::1]:22 + ``` + + + + Profiles live in **Settings → SSH → Hosts**. Start typing the name in the + palette, or open the *SSH: Manage Profiles…* command. + + + + 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. + + + `Match`, `canonicalize*`, and GSSAPI directives are not supported, and + there is no fallback to the system `ssh` when one appears. + + + + + The same connection can host whole workspaces on the far machine rather than + a single shell. [Remote workspaces →](/remote/workspaces) + + + +## 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 + +⌘ ⇧ R — 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 diff --git a/docs/remote/workspaces.mdx b/docs/remote/workspaces.mdx new file mode 100644 index 00000000..ff24780d --- /dev/null +++ b/docs/remote/workspaces.mdx @@ -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. + + + A remote workspace in tty7 + + +## Connecting + + + + ⌘ ⇧ O. Machines are listed alongside your local workspaces — + *This Computer* first, then every saved SSH profile and, on Windows, every + WSL distribution. + + + tty7 connects over the same SSH stack as everything else, so profiles, + keychain credentials, and jump hosts all apply. + + + 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. + + + From then on the machine's workspaces are in the switcher, and a new one + opens like a local one. + + + +## What gets installed + +| | | +|---|---| +| **What** | A single static `tty7-server` binary | +| **Where** | `~/.local/share/tty7/bin/tty7-server-cp` | +| **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: + + + + 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. + + + Same consequence, deliberately: every shell on that machine ends. Workspaces + and layouts are kept and come back with fresh shells. + + + + + 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. + + +## 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) diff --git a/docs/terminal/history.mdx b/docs/terminal/history.mdx new file mode 100644 index 00000000..4b3f1433 --- /dev/null +++ b/docs/terminal/history.mdx @@ -0,0 +1,57 @@ +--- +title: "History" +description: "Fuzzy history search, and whether each pane gets its own." +--- + +## Fuzzy search with ⌃ R + +⌃ R 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. + + + Fuzzy history search + + + puts the command on the prompt. Esc closes without +touching it. + +### Handing ⌃ R back + +If you already have an fzf, percol, atuin or McFly binding you like, turn off +**Settings → Input → Prompt → History search** (`history_search: false`). +⌃ R 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 key. Off by default, +because someone who has not asked for it would experience the change as their +history mysteriously forgetting the other window. diff --git a/docs/terminal/links.mdx b/docs/terminal/links.mdx new file mode 100644 index 00000000..0b8c3d34 --- /dev/null +++ b/docs/terminal/links.mdx @@ -0,0 +1,51 @@ +--- +title: "Links" +description: "Opening URLs, files, and localhost ports straight out of the terminal." +--- + +Hold (Ctrl 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 + +-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. + + + For a forward you want to keep, set one up properly instead — + [port forwarding](/remote/port-forwarding). + diff --git a/docs/terminal/mouse-and-scrolling.mdx b/docs/terminal/mouse-and-scrolling.mdx new file mode 100644 index 00000000..fa842da3 --- /dev/null +++ b/docs/terminal/mouse-and-scrolling.mdx @@ -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.1–10). | +| **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. | + + 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 to keep a gesture local and select text instead. | + + + If selecting text inside `vim` or `less` grabs the app's own selection instead + of yours, hold while you drag. + + +## Font size + +| | | +|---|---| +| ⌘ + · ⌘ − | Bigger · smaller | +| ⌘ 0 | Back to the configured size | +| + 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) diff --git a/docs/terminal/prompt.mdx b/docs/terminal/prompt.mdx new file mode 100644 index 00000000..e430c190 --- /dev/null +++ b/docs/terminal/prompt.mdx @@ -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. + + + The tty7 prompt + + +## Ghost suggestions + +As you type, the rest of the line is filled in from your history, greyed out +ahead of the cursor. + +| | | +|---|---| +| | Accept the whole suggestion | +| Keep typing | The suggestion narrows | +| Esc 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 + + 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 + + + Explained tab completion + + +When tty7 has nothing useful to offer, the falls through to your +shell's own completion, so a carefully configured zsh setup is not lost. + +To hand 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. + + + On macOS, turn on **Settings → Input → Keyboard → Option (⌥) acts as Meta** if + you want ⌥ B / ⌥ F to move by word instead of typing + `∫` and `ƒ`. + + +## 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 , + , fn, or 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. + + + On macOS with **Option (⌥) acts as Meta** turned on, chords + bypass the IME entirely, so ⌥ B reaches your shell as meta-b + instead of being eaten as a dead key. + + +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. + +| | | +|---|---| +| ⇧ ⏎ · ⌥ ⏎ | Insert a newline instead of submitting | +| | 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** | → your shell's completion | +| **Settings → Input → Prompt → History search** | ⌃ R → your shell's reverse-i-search, or your fzf binding | diff --git a/docs/terminal/selection-and-clipboard.mdx b/docs/terminal/selection-and-clipboard.mdx new file mode 100644 index 00000000..30856770 --- /dev/null +++ b/docs/terminal/selection-and-clipboard.mdx @@ -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 | +| -click | Extend the current selection to where you clicked | +| ⌘ A *(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 | ⌘ C | Ctrl ⇧ C | +| Paste | ⌘ V | Ctrl ⇧ V · ⇧ Insert | + +Two settings shape what lands where, both under **Settings → Input → Selection +& clipboard**: + + + + Selecting with the mouse copies immediately, no ⌘ C. Off by + default. + + + Strips trailing whitespace from every copied line — useful when copying out + of a TUI that pads to the pane width. Off by default. + + + + + 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. + diff --git a/docs/window/command-palette.mdx b/docs/window/command-palette.mdx new file mode 100644 index 00000000..154eb289 --- /dev/null +++ b/docs/window/command-palette.mdx @@ -0,0 +1,58 @@ +--- +title: "Command palette" +description: "One key for every action in the app — including the ones with no shortcut." +--- + +⌘ P opens the command palette. Type to filter, + to move, 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. + + + The tty7 command palette + + +## 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) diff --git a/docs/window/search.mdx b/docs/window/search.mdx new file mode 100644 index 00000000..ad4d4f81 --- /dev/null +++ b/docs/window/search.mdx @@ -0,0 +1,50 @@ +--- +title: "Search" +description: "Finding text in the scrollback, and everything else the search boxes cover." +--- + +## In the terminal + +⌘ F opens the find bar over the focused pane and searches its whole +scrollback, not just the visible screen. + +| | | +|---|---| +| · ⌘ G | Next match | +| ⇧ ⏎ · ⌘ ⇧ G | Previous match | +| Esc | 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 Ctrl ⇧ F to open, +F3 and ⇧ F3 to step. + + + Searching the scrollback + + + + 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. + + +## 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 | +|---|---| +| ⌘ P | Commands — and SSH addresses you type in full | +| ⌘ ⇧ O | Workspaces, tabs, and machines | +| ⌃ R | 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 | diff --git a/docs/window/side-panel.mdx b/docs/window/side-panel.mdx new file mode 100644 index 00000000..11bacee7 --- /dev/null +++ b/docs/window/side-panel.mdx @@ -0,0 +1,66 @@ +--- +title: "The side panel" +description: "Info, Source Control, and Files — plus the built-in editor." +--- + +⌘ J 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. + + + The tty7 side panel + + +## 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 + +⌘ ⇧ E 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. + +| | | +|---|---| +| ⌘ S | Save | +| Esc | 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. diff --git a/docs/window/sidebar.mdx b/docs/window/sidebar.mdx new file mode 100644 index 00000000..9b49e427 --- /dev/null +++ b/docs/window/sidebar.mdx @@ -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. + +⌘ B shows and hides it. Drag its right edge to resize. + + + The tty7 tab sidebar + + +## 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 + + + + 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. + + + The pane's git branch, refreshed on `cd` and whenever a command finishes. + + + The working-tree diff as `+N −M`. Click the counts to open the + [diff overlay](/git/diffs). + + + An unread marker on tabs that produced output while you were elsewhere. + *Mark as Unread* is in the right-click menu. + + + +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 + +⌘ ⇧ O 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, to cross into the tab column, 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. diff --git a/docs/window/tabs-and-splits.mdx b/docs/window/tabs-and-splits.mdx new file mode 100644 index 00000000..423fe05d --- /dev/null +++ b/docs/window/tabs-and-splits.mdx @@ -0,0 +1,99 @@ +--- +title: "Tabs and splits" +description: "Opening, arranging, and rearranging the panes in a tab." +--- + + + Keys are written in macOS notation. On Windows and Linux, read as + Ctrl ⇧ for most window actions — the exact chords are on the + [keyboard shortcuts](/reference/keyboard-shortcuts) page. + + +## Tabs + +| | | +|---|---| +| ⌘ T | New tab | +| ⌘ W | Close the tab (or the focused pane, if the tab has more than one) | +| ⌘ ⇧ T | Reopen the tab you just closed | +| ⌘ 1⌘ 9 | Jump to tab 1–9 | +| ⌃ ⇥ · ⌃ ⇧ ⇥ | 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 + +| | | +|---|---| +| ⌘ D | Split right | +| ⌘ ⇧ D | Split down | +| ⌘ ] · ⌘ [ | Next pane · previous pane | +| ⌘ ⌥ ← → ↑ ↓ | Focus the pane in that direction | +| ⌘ ⇧ ⏎ | Zoom the focused pane to fill the tab | +| ⌘ ⏎ | 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. + + + Dragging a pane to a new position + + +Where you drop it decides what happens: + + + + 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. + + + The two panes **trade places**. + + + 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. + + + +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. + +⌘ B toggles the sidebar; ⌘ J toggles the +[side panel](/window/side-panel) on the right.