diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml
index 0b6d39a..2312aa8 100644
--- a/.github/workflows/pr.yml
+++ b/.github/workflows/pr.yml
@@ -364,6 +364,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: AxeForging/reviewforge@main
+ continue-on-error: true
with:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AI_PROVIDER: gemini
diff --git a/.structlint.yaml b/.structlint.yaml
index a935e05..7a36bd5 100644
--- a/.structlint.yaml
+++ b/.structlint.yaml
@@ -38,6 +38,7 @@ file_naming_pattern:
- ".gitignore"
- ".goreleaser.yml"
- "*.gif"
+ - "*.png"
- "LICENSE"
disallowed:
- "*.env*"
diff --git a/README.md b/README.md
index d831f11..1d1be9a 100644
--- a/README.md
+++ b/README.md
@@ -4,10 +4,22 @@
yoink
-Background process manager with interactive I/O. Spawn commands in the background, check their output, send input to hanging processes, and reattach with full terminal control.
+Run interactive commands in the background, inspect their output, send input, capture TUI screens, wait on them from scripts, and reattach when you need full control.
+
+`yoink` is a lightweight PTY process manager for developers, CI jobs, automation scripts, and AI-agent workflows. It fills the space between shell background jobs, `tmux`, `expect`, and full process supervisors: every process gets a real terminal, a name, logs, input control, lifecycle state, and a way back in.

+## Why yoink?
+
+- Keep long-running commands alive without dedicating a terminal tab.
+- Drive interactive CLIs from scripts with `send`, `send --key`, and `send-redacted`.
+- Read only new output with `log --new`, which is useful for polling and test reports.
+- Render TUI apps with `snapshot` instead of raw escape-sequence logs.
+- Coordinate dev servers, test runners, deploys, tunnels, model servers, and AI coding agents.
+
+See [Why yoink?](docs/WHY.md) for the deeper positioning and comparisons.
+
## Install
```bash
@@ -19,95 +31,55 @@ curl -Lo yoink.tar.gz https://github.com/AxeForging/yoink/releases/latest/downlo
tar xzf yoink.tar.gz && sudo mv yoink /usr/local/bin/
```
-## Usage
+## Quickstart
```bash
-# Spawn a command in the background
-yoink run "make deploy-staging"
-
-# Spawn with an alias for easier reference
+# Start a command under a managed PTY
yoink run --alias deploy "make deploy-staging"
-# List all managed processes
+# Check state and output
yoink ls
-
-# Check output of a process (by ID or alias)
-yoink log 1
-yoink log deploy --lines 100
-
-# Show only new lines since last --new call
yoink log deploy --new
-# Render the current screen of any process — works correctly for TUI apps
-# (yoink log gives raw escape garbage for TUIs; snapshot renders the actual frame)
-yoink snapshot htop
-yoink snap htop # short alias
-
-# Send input to a process waiting for confirmation
+# Answer a prompt without reattaching
yoink send deploy "y"
-# Send input character by character (simulates typing — great for asciinema recordings)
-yoink send deploy "hello world" --type
-
-# Control typing speed with --delay (default 50ms)
-yoink send deploy "hello world" --type --delay 100
-
-# Send without pressing Enter (useful for prompts that read a single key)
-yoink send deploy "y" --no-enter
-
-# Submit text with carriage-return Enter (useful for TUIs like Claude)
-yoink send claude "say only OK" --submit
-
-# Send a named key sequence (arrow keys, ctrl combos, function keys, etc.)
-yoink send deploy --key up
-yoink send deploy --key ctrl+c
-yoink send deploy --key pagedown
-yoink send deploy --key f5
-
-# Send sensitive input (passwords, tokens) — hidden from logs
-yoink send-redacted deploy "my-secret-token"
-
-# Flags work on send-redacted too
-yoink send-redacted deploy "my-secret" --type --delay 80
-
-# Full interactive reattach (Ctrl+] to detach)
-yoink attach deploy
-
-# Wait for a process to finish (streams output, exits with process exit code)
+# Wait from a script, streaming new output
yoink wait deploy --timeout 300 --poll 5
-# Wait for multiple processes at once (fail-fast on first failure)
-yoink wait build install runner-startup --timeout 300
-
-# Wait with custom timeout message
-yoink wait build --timeout 60 --message "Build exceeded 1min budget"
-
-# Kill a process
+# Reattach later, or clean up
+yoink attach deploy # Ctrl+] detaches
yoink kill deploy
-yoink kill 1 --force # SIGKILL
-
-# Remove finished processes
yoink clean
```
-> **More examples?** See [EXAMPLES.md](EXAMPLES.md) for real-world use cases — CI pipelines, interactive prompts, TUI monitoring, secret handling, and more.
-
-## How it works
-
-yoink runs a lightweight daemon that holds PTY (pseudo-terminal) sessions open for each background process. The CLI client communicates with the daemon over a Unix socket.
-
-- **run**: Spawns the command under a PTY in the daemon, returns an ID. Use `--alias` to name it
-- **ls**: Lists all processes with state (running/done/failed), PID, exit code
-- **log**: Shows buffered output (last 1000 lines kept per process). Use `--new` to see only lines since the last `--new` call
-- **wait**: Polls a process until it finishes, streaming new output to stdout on each cycle. Exits 0 on success, propagates the process exit code on failure, or exits 124 on timeout (matches GNU `timeout` convention). Flags: `--timeout` (seconds, 0=forever), `--poll` (seconds, default 2), `--message` (custom timeout error)
-- **snapshot** (`snap`): Renders the current VT100 screen of a process as plain text. Unlike `log`, this correctly handles TUI applications (htop, vim, Claude, etc.) by replaying their escape sequences through a VT100 emulator and returning only the rendered content. Works on both running and finished processes
-- **send**: Writes text to the process stdin (fire-and-forget). Flags: `--type` (character by character), `--delay` (ms between chars, default 50), `--no-enter` (omit trailing newline), `--submit` (append carriage-return Enter for TUIs like Claude), `--key` (send a named key sequence — e.g. `up`, `down`, `ctrl+c`, `f1`, `pageup`; implies `--no-enter`)
-- **send-redacted**: Same as send, but input is masked in logs. Supports the same flags
-- **attach**: Bridges your terminal to the process PTY for full interactive control
-- **kill**: Sends SIGTERM (or SIGKILL with --force)
-- **clean**: Removes finished processes from the list
-
-The daemon auto-starts when needed and stores its socket at `~/.yoink/yoink.sock`.
+## Common Commands
+
+| Task | Command |
+|------|---------|
+| Run in background | `yoink run --alias name "command"` |
+| List managed processes | `yoink ls` |
+| Read recent output | `yoink log name --lines 100` |
+| Read only new output | `yoink log name --new` |
+| Send input | `yoink send name "text"` |
+| Send a key | `yoink send name --key ctrl+c` |
+| Send secret input | `yoink send-redacted name "$TOKEN"` |
+| Render a TUI screen | `yoink snapshot name` |
+| Wait with timeout | `yoink wait name --timeout 300` |
+| Reattach interactively | `yoink attach name` |
+| Stop a process | `yoink kill name` |
+
+## Documentation
+
+- [Why yoink?](docs/WHY.md): what problem it solves and how it compares to common alternatives.
+- [Killer examples](docs/KILLER_EXAMPLES.md): compact real-world CI and automation cases where yoink removes painful glue code.
+- [Command reference](docs/COMMANDS.md): commands, aliases, flags, and behavior notes.
+- [Examples](EXAMPLES.md): real-world workflows for CI, prompts, TUIs, tunnels, AI agents, model servers, E2E tests, and more.
+- [Architecture](docs/README.md): daemon/client design, PTY handling, snapshots, protocol, and platform notes.
+
+## How It Works
+
+yoink runs a lightweight daemon that holds PTY sessions open for background processes. The CLI talks to the daemon over a Unix socket at `~/.yoink/yoink.sock`, which auto-starts when needed.
## Platform Support
diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md
new file mode 100644
index 0000000..d0004d0
--- /dev/null
+++ b/docs/COMMANDS.md
@@ -0,0 +1,205 @@
+# Command Reference
+
+This page covers the command surface. For longer workflows, see [Examples](../EXAMPLES.md).
+
+## Process Lifecycle
+
+### `yoink run`
+
+Spawn a command under a managed PTY.
+
+```bash
+yoink run "make deploy-staging"
+yoink run --alias deploy "make deploy-staging"
+```
+
+Aliases: `r`
+
+Flags:
+
+| Flag | Description |
+|------|-------------|
+| `--alias ` | Assign a stable name for later commands |
+
+### `yoink ls`
+
+List managed processes with state, PID, aliases, and exit information.
+
+```bash
+yoink ls
+```
+
+Aliases: `list`, `ps`
+
+### `yoink kill`
+
+Terminate a managed process.
+
+```bash
+yoink kill deploy
+yoink kill deploy --force
+```
+
+Aliases: `k`
+
+Flags:
+
+| Flag | Description |
+|------|-------------|
+| `--force`, `-f` | Send SIGKILL instead of SIGTERM |
+
+### `yoink clean`
+
+Remove finished or failed processes from the process list.
+
+```bash
+yoink clean
+```
+
+## Output
+
+### `yoink log`
+
+Show buffered output from a process. The daemon keeps the recent output for each managed process.
+
+```bash
+yoink log deploy
+yoink log deploy --lines 100
+yoink log deploy --new
+```
+
+Aliases: `l`
+
+Flags:
+
+| Flag | Description |
+|------|-------------|
+| `--lines`, `-n` | Number of output lines to show. Default: `50` |
+| `--new` | Show only lines not yet seen by a previous `--new` call |
+
+Use `log` for normal line-oriented programs. Use `snapshot` for TUIs.
+
+### `yoink snapshot`
+
+Render the current VT100 screen of a process as plain text.
+
+```bash
+yoink snapshot htop
+yoink snap htop
+```
+
+Aliases: `snap`
+
+This is useful for TUI apps such as `htop`, `vim`, `k9s`, chat CLIs, and AI coding agents. `log` shows raw terminal bytes for those programs; `snapshot` shows the rendered screen.
+
+## Input
+
+### `yoink send`
+
+Send text or key sequences to a running process.
+
+```bash
+yoink send deploy "y"
+yoink send repl "hello world" --type --delay 80
+yoink send claude "say only OK" --submit
+yoink send tui --key down
+yoink send tui --key ctrl+c
+```
+
+Aliases: `s`
+
+Flags:
+
+| Flag | Description |
+|------|-------------|
+| `--type` | Send input character by character |
+| `--delay`, `-d` | Milliseconds between characters with `--type`. Default: `50` |
+| `--no-enter` | Do not send a trailing newline |
+| `--submit` | Submit text with carriage-return Enter, useful for TUIs |
+| `--key`, `-k` | Send a named key sequence instead of text |
+
+Common keys include `up`, `down`, `left`, `right`, `enter`, `tab`, `escape`, `ctrl+c`, `f1`, and `pageup`.
+
+### `yoink send-redacted`
+
+Send sensitive input while masking it in yoink logs.
+
+```bash
+yoink send-redacted deploy "$DB_PASSWORD"
+yoink send-redacted deploy "$TOKEN" --type --delay 80
+```
+
+Flags:
+
+| Flag | Description |
+|------|-------------|
+| `--type` | Send input character by character |
+| `--delay`, `-d` | Milliseconds between characters with `--type`. Default: `50` |
+| `--no-enter` | Do not send a trailing newline |
+| `--submit` | Submit text with carriage-return Enter, useful for TUIs |
+
+## Waiting And Reattaching
+
+### `yoink wait`
+
+Wait for one or more processes to finish. Output is streamed while waiting.
+
+```bash
+yoink wait deploy --timeout 300 --poll 5
+yoink wait build test lint --timeout 600
+yoink wait migrate --timeout 600 --message "Migration exceeded 10min budget"
+```
+
+Aliases: `w`
+
+Flags:
+
+| Flag | Description |
+|------|-------------|
+| `--timeout`, `-t` | Maximum seconds to wait. `0` means forever |
+| `--poll`, `-p` | Seconds between state checks. Default: `2` |
+| `--message`, `-m` | Custom error message on timeout |
+
+Exit behavior:
+
+| Result | Exit code |
+|--------|-----------|
+| All processes succeeded | `0` |
+| A process failed | That process exit code |
+| Timeout | `124` |
+
+### `yoink attach`
+
+Bridge your terminal to the process PTY for full interactive control.
+
+```bash
+yoink attach deploy
+```
+
+Aliases: `a`
+
+Detach with `Ctrl+]`.
+
+## Other Commands
+
+### `yoink version`
+
+Show version information.
+
+```bash
+yoink version
+```
+
+### `yoink daemon`
+
+Start the daemon. This is normally auto-started by other commands.
+
+```bash
+yoink daemon
+```
+
+## Global Flags
+
+| Flag | Description |
+|------|-------------|
+| `--verbose` | Enable verbose logging |
diff --git a/docs/KILLER_EXAMPLES.md b/docs/KILLER_EXAMPLES.md
new file mode 100644
index 0000000..af74df2
--- /dev/null
+++ b/docs/KILLER_EXAMPLES.md
@@ -0,0 +1,211 @@
+# Killer Examples
+
+These are compact workflows where `yoink` replaces brittle shell glue with named, scriptable PTY sessions.
+
+For longer versions, see [Examples](../EXAMPLES.md).
+
+## CI: Dev Server + E2E Tests
+
+CI often needs a web server running while Playwright or Cypress tests execute. Plain `npm run dev &` makes it awkward to know whether the server is ready, capture the right logs, and clean up reliably.
+
+```bash
+yoink run --alias app "npm run dev"
+
+ready=0
+for i in $(seq 1 60); do
+ if yoink log app --new | grep -q "Local:.*http://localhost:3000"; then
+ ready=1
+ break
+ fi
+ sleep 1
+done
+
+if [ "$ready" -ne 1 ]; then
+ yoink log app
+ yoink kill app
+ exit 1
+fi
+
+yoink run --alias e2e "npx playwright test --reporter=list"
+yoink wait e2e --timeout 300 --poll 5
+status=$?
+
+if [ "$status" -ne 0 ]; then
+ yoink log app --new
+fi
+
+yoink kill app
+yoink clean
+exit "$status"
+```
+
+Why it hurts without yoink: readiness checks, server logs, test logs, and cleanup usually become separate ad hoc scripts.
+
+## CI: Parallel Build Matrix With Labeled Logs
+
+Shell background jobs can parallelize work, but debugging failures is painful when logs interleave or disappear.
+
+```bash
+yoink run --alias build-api "make build-api"
+yoink run --alias build-web "make build-web"
+yoink run --alias build-worker "make build-worker"
+
+yoink wait build-api build-web build-worker --timeout 600 --poll 5
+```
+
+Why it hurts without yoink: `wait` only tells you that something failed; yoink keeps each command named, stateful, and inspectable.
+
+## CI: Start Slow Setup Early, Join Later
+
+Some CI steps are slow because they download dependencies, pull models, warm caches, build assets, or boot local services. If later steps do not need them immediately, start them with `yoink`, keep doing other work, then wait only when the pipeline actually depends on the result.
+
+```yaml
+name: test
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-go@v5
+ with:
+ go-version: stable
+
+ - name: Install yoink
+ run: |
+ go install github.com/AxeForging/yoink@latest
+ echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"
+
+ - name: Start slow setup
+ run: |
+ yoink run --alias pg-image "docker pull postgres:16"
+ yoink run --alias redis-image "docker pull redis:7"
+ yoink run --alias web-deps "cd web && npm ci"
+
+ - name: Do independent checks while setup runs
+ run: |
+ test -z "$(gofmt -l .)"
+ go vet ./...
+ go test ./internal/...
+
+ - name: Join slow setup
+ run: |
+ yoink wait pg-image redis-image web-deps --timeout 900 --poll 10
+
+ - name: Run tests that need the setup
+ run: |
+ docker compose up -d postgres redis
+ (cd web && npm test)
+ (cd web && npx playwright test)
+```
+
+Why it hurts without yoink: shell `&` can start work in the background, but later steps lose a clean handle for logs, state, exit codes, and timeout behavior. yoink turns those slow setup tasks into named CI resources you can inspect and join later.
+
+## CI: Interactive Infrastructure Approval
+
+Infrastructure tools sometimes need a plan review before confirmation. That is a bad fit for fully headless scripts and a bad fit for manual terminal babysitting.
+
+```bash
+yoink run --alias infra "pulumi up --stack prod"
+sleep 10
+
+yoink log infra --new
+yoink send infra "details"
+sleep 5
+yoink log infra --new
+
+yoink send infra "yes"
+yoink wait infra --timeout 900 --poll 15 \
+ --message "Infrastructure apply exceeded 15min"
+```
+
+Why it hurts without yoink: `yes | pulumi up` skips review, while a manual run cannot be controlled cleanly by CI.
+
+## CI: Secret Entry For PTY-Aware Prompts
+
+Some tools demand secrets through an interactive prompt and will not behave correctly with a normal stdin pipe.
+
+```bash
+yoink run --alias release "vendor-cli deploy --prod"
+sleep 2
+
+yoink send-redacted release "$DEPLOY_TOKEN"
+yoink wait release --timeout 300
+```
+
+Why it hurts without yoink: `echo "$TOKEN" | command` can fail with PTY-aware prompts and can leak secrets through logs or shell history.
+
+## Local Dev: Port-Forward Stack
+
+Multiple `kubectl port-forward` processes are easy to start and easy to lose.
+
+```bash
+yoink run --alias pf-api "kubectl port-forward svc/api 8080:80"
+yoink run --alias pf-db "kubectl port-forward svc/postgres 5432:5432"
+yoink run --alias pf-grafana "kubectl port-forward svc/grafana 3000:3000"
+
+yoink ls
+yoink log pf-api --new
+
+yoink kill pf-api
+yoink kill pf-db
+yoink kill pf-grafana
+yoink clean
+```
+
+Why it hurts without yoink: when a port-forward drops, a background shell job rarely tells you clearly what died or why.
+
+## AI Agents: Multiple Interactive Workers
+
+AI coding agents are long-running, interactive, and often TUI-based. They need real terminals, but you still want to coordinate them from scripts.
+
+```bash
+yoink run --alias agent-api "cd worktree-api && claude"
+yoink run --alias agent-tests "cd worktree-tests && claude"
+
+yoink send agent-api "Add pagination to GET /users" --submit
+yoink send agent-tests "Wait for the API shape, then write tests" --submit
+
+yoink snapshot agent-api
+yoink wait agent-api --timeout 900 --poll 10
+
+yoink send agent-tests "API is done. Use query params page and limit." --submit
+yoink snapshot agent-tests
+```
+
+Why it hurts without yoink: terminal multiplexers are manual; yoink gives scripts a way to observe, steer, and reattach.
+
+## TUI Monitoring: Render The Screen, Not Escape Codes
+
+TUIs write terminal control sequences, so logs are usually unreadable. `snapshot` gives you the rendered screen.
+
+```bash
+yoink run --alias gpu "watch -n 5 nvidia-smi"
+yoink snapshot gpu
+
+yoink run --alias train "python train.py"
+yoink log train --new
+```
+
+Why it hurts without yoink: raw PTY logs are not the same thing as the screen the user sees.
+
+## Stdio Server Harness
+
+Stdio-based tools such as MCP servers need stdin/stdout held open while you send structured messages and inspect responses.
+
+```bash
+yoink run --alias mcp "node dist/mcp-server.js --stdio"
+sleep 1
+
+yoink send mcp '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
+yoink log mcp --new
+
+yoink send mcp '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{"query":"test"}}}'
+yoink log mcp --new
+```
+
+Why it hurts without yoink: you otherwise end up writing a custom harness just to keep the process alive and talk to it.
diff --git a/docs/README.md b/docs/README.md
index 5b91bcd..e8e8791 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,5 +1,12 @@
# yoink Documentation
+## Guides
+
+- [Why yoink?](WHY.md): positioning, use cases, and comparisons with common alternatives.
+- [Killer examples](KILLER_EXAMPLES.md): compact real-world CI and automation cases where yoink removes painful glue code.
+- [Command reference](COMMANDS.md): commands, aliases, flags, and behavior notes.
+- [Examples](../EXAMPLES.md): practical workflows for CI, prompts, TUIs, tunnels, AI agents, model servers, and more.
+
## Architecture
yoink uses a daemon/client architecture:
diff --git a/docs/WHY.md b/docs/WHY.md
new file mode 100644
index 0000000..0c15f2d
--- /dev/null
+++ b/docs/WHY.md
@@ -0,0 +1,65 @@
+# Why yoink?
+
+`yoink` is for commands that are too interactive for plain background jobs and too automation-heavy for a manual terminal multiplexer.
+
+It gives every managed command a real PTY, a stable name, buffered output, lifecycle state, input control, timeout-aware waiting, rendered TUI snapshots, and an escape hatch back into full interactive control.
+
+## The Problem
+
+Developers constantly run commands that do not fit neatly into one terminal session:
+
+- deploys that ask for confirmation halfway through
+- dev servers that need to stay alive while tests run
+- migrations, model pulls, builds, training jobs, and backfills that take a long time
+- `kubectl port-forward`, SSH tunnels, and local daemons that fail silently
+- TUI programs where raw logs are useless
+- AI coding agents and chat CLIs that need a real terminal
+
+The common workaround is a pile of terminal tabs, shell `&`, `nohup`, `tmux`, `screen`, `expect`, and ad hoc scripts. Those tools are useful, but the seams show when you want to script the workflow: start something, check only new output, send a key, wait with a timeout, inspect a rendered screen, then reattach if needed.
+
+## What yoink Adds
+
+`yoink` treats interactive processes as named, scriptable terminal sessions:
+
+```bash
+yoink run --alias app "npm run dev"
+yoink log app --new
+yoink send app --key ctrl+c
+yoink wait app --timeout 300
+yoink attach app
+```
+
+The important part is that the process still thinks it is attached to a terminal. That means prompts, TUIs, colored output, terminal control sequences, and REPLs behave much closer to how they behave in a real shell.
+
+## Compared To Alternatives
+
+| Tool | Great for | Where yoink fits |
+|------|-----------|------------------|
+| Shell `&` / `jobs` | Quick one-off backgrounding | Adds names, logs, process state, input, timeouts, and reattach |
+| `nohup` | Keeping non-interactive commands alive | Keeps a real PTY and supports later interaction |
+| `tmux` / `screen` | Manual long-lived terminal sessions | Provides script-friendly commands around PTY sessions |
+| `expect` | Automating known prompt sequences | Lets scripts inspect, react, send keys, and debug incrementally |
+| `timeout` | Killing commands after a deadline | Streams output while waiting and preserves process logs |
+| Process supervisors | Running services reliably | Targets developer workflows, prompts, TUIs, and temporary commands |
+| Docker Compose | Container service groups | Also manages local binaries, CLIs, tunnels, and test runners |
+
+`yoink` does not replace these tools. It gives you a small CLI vocabulary for the annoying middle ground: interactive programs that need to be backgrounded, observed, poked, waited on, and occasionally reattached.
+
+## Best Fits
+
+- CI scripts that need to run several commands and keep useful logs.
+- E2E test setup where a dev server must be started, checked, and torn down.
+- Deploys and infrastructure applies that require approval gates.
+- Long-running builds, migrations, model pulls, or ML training jobs.
+- Port-forward and tunnel management during debugging.
+- TUI monitoring with `snapshot`.
+- AI agent orchestration where each agent runs in its own PTY.
+- Demo recording with typed input via `send --type --delay`.
+
+## Mental Model
+
+Think of `yoink` as:
+
+> named, scriptable PTY sessions with logs, input, snapshots, waits, and reattach.
+
+That is the core value. It is intentionally smaller than a terminal multiplexer and more interactive than a process supervisor.