diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index c66482d..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,29 +0,0 @@
-name: CI
-
-on:
- push:
- branches: [main]
-
-jobs:
- test:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - uses: actions/setup-go@v5
- with:
- go-version: "1.25"
- cache: true
-
- - name: Run tests
- run: go test ./... -v -race -timeout 120s
-
- - name: Build
- run: go build -o yoink .
-
- - name: Vulnerability check
- run: |
- go install golang.org/x/vuln/cmd/govulncheck@latest
- govulncheck ./...
diff --git a/.structlint.yaml b/.structlint.yaml
index f4f79ca..a935e05 100644
--- a/.structlint.yaml
+++ b/.structlint.yaml
@@ -37,6 +37,7 @@ file_naming_pattern:
- "Makefile"
- ".gitignore"
- ".goreleaser.yml"
+ - "*.gif"
- "LICENSE"
disallowed:
- "*.env*"
diff --git a/EXAMPLES.md b/EXAMPLES.md
new file mode 100644
index 0000000..58063e6
--- /dev/null
+++ b/EXAMPLES.md
@@ -0,0 +1,646 @@
+# Examples & Real-World Use Cases
+
+Practical scenarios where yoink shines — from CI pipelines to local development workflows.
+
+---
+
+## Real-World Use Cases
+
+
+CI/CD Pipeline Orchestration
+
+### Problem
+You need to run multiple build steps in parallel, wait for all of them, and fail fast if any step breaks — without losing output.
+
+### Solution
+```bash
+# Kick off parallel builds
+yoink run --alias build-api "make build-api"
+yoink run --alias build-web "make build-web"
+yoink run --alias build-worker "make build-worker"
+
+# Wait for all three, streaming output as it arrives
+yoink wait build-api build-web build-worker --timeout 300 --poll 2
+
+# If any build fails, wait exits with its exit code
+# If all succeed, exit code is 0
+```
+
+### Why yoink?
+Standard `&` + `wait` loses output ordering, doesn't stream logs, and makes it hard to debug which step failed. yoink labels output per-process and propagates exit codes.
+
+
+
+
+Answering Interactive Prompts in Scripts
+
+### Problem
+A CLI tool (installer, scaffolder, migration) requires interactive input but you need to automate it in a script or CI environment.
+
+### Solution
+```bash
+yoink run --alias setup "npx create-next-app@latest my-app"
+sleep 2
+
+# Check what it's asking
+yoink log setup --new
+
+# Answer the prompts as they appear
+yoink send setup "Yes" # TypeScript?
+sleep 1
+yoink send setup "Yes" # ESLint?
+sleep 1
+yoink send setup "Yes" # Tailwind?
+sleep 1
+yoink send setup "No" # src/ directory?
+sleep 1
+yoink send setup "Yes" # App Router?
+
+# Wait for it to finish
+yoink wait setup --timeout 120
+```
+
+### Why yoink?
+Tools like `expect` are brittle and hard to debug. yoink lets you inspect output between sends, making it easy to adapt to changing prompts.
+
+
+
+
+Dev Server + Test Runner Workflow
+
+### Problem
+You need a dev server running in the background while you iterate on tests, and you want to see server logs when something fails.
+
+### Solution
+```bash
+# Start the dev server
+yoink run --alias server "npm run dev"
+sleep 3
+
+# Verify it's up
+yoink log server --new | grep "ready on"
+
+# Run tests against it
+yoink run --alias tests "npm test -- --watch"
+
+# Check test output as you work
+yoink log tests --new
+
+# When a test fails, check what the server logged
+yoink log server --new
+
+# Done for the day
+yoink kill server
+yoink kill tests
+yoink clean
+```
+
+### Why yoink?
+Running servers in terminal tabs loses context when you switch. yoink keeps everything in one terminal session with on-demand log access.
+
+
+
+
+Secure Token/Password Entry in Automation
+
+### Problem
+An automated script needs to enter credentials (API keys, passwords) but you don't want them showing up in logs, CI output, or shell history.
+
+### Solution
+```bash
+yoink run --alias deploy "kubectl exec -it pod -- /bin/sh"
+sleep 2
+
+# Send the password — it's masked as ******** in yoink's logs
+yoink send-redacted deploy "$DB_PASSWORD"
+
+# Verify it worked without exposing the secret
+yoink log deploy --new
+# Output shows: [in] ********
+# [out] Authentication successful
+```
+
+### Why yoink?
+`echo $SECRET | command` leaks to process lists. Piping stdin doesn't work with PTY-aware prompts. `send-redacted` writes directly to the PTY and masks the value everywhere.
+
+
+
+
+Monitoring TUI Applications
+
+### Problem
+You're running a TUI app (htop, k9s, vim, Claude) in the background and need to check its current state programmatically — but `log` gives you raw escape sequences.
+
+### Solution
+```bash
+# Start a TUI process
+yoink run --alias dash "htop"
+
+# Wrong way — escape garbage:
+# yoink log dash
+
+# Right way — rendered screen:
+yoink snapshot dash
+
+# Use snapshot in scripts for health checks
+if yoink snapshot dash | grep -q "load average: [5-9]"; then
+ echo "High load detected!"
+fi
+
+# Navigate the TUI with named keys
+yoink send dash --key f6 # Sort by column
+yoink send dash --key down
+yoink send dash --key enter
+yoink snapshot dash # See updated view
+```
+
+### Why yoink?
+No other background process manager gives you a rendered screen capture of TUI apps. yoink replays VT100 escape sequences through a terminal emulator and returns clean text.
+
+
+
+
+Long-Running Migrations with Timeout Guards
+
+### Problem
+Database migrations or data backfills can hang silently. You need a timeout that kills the process and alerts, plus a way to check progress mid-flight.
+
+### Solution
+```bash
+yoink run --alias migrate "python manage.py migrate --run-syncdb"
+
+# Check progress periodically
+yoink log migrate --new
+
+# Set a hard timeout with a custom error message
+yoink wait migrate \
+ --timeout 600 \
+ --poll 10 \
+ --message "Migration exceeded 10min budget — check for locks"
+
+# Exit code 124 = timeout, process exit code = failure, 0 = success
+echo "Migration exit code: $?"
+```
+
+### Why yoink?
+Raw `timeout` command doesn't stream output while waiting. yoink streams progress on each poll cycle so you can watch the migration in real time.
+
+
+
+
+Driving AI Agents and Chat CLIs
+
+### Problem
+You want to script interactions with a conversational CLI (Claude, ChatGPT CLI, database REPLs) — send prompts, read responses, and react to output.
+
+### Solution
+```bash
+yoink run --alias claude "claude"
+sleep 3
+
+# Send a prompt with realistic typing
+yoink send claude "Explain this error: ConnectionRefused on port 5432" --type --delay 30
+
+# Wait for response to appear, then read it
+sleep 10
+yoink snapshot claude
+
+# Send follow-up
+yoink send claude "How do I fix it?" --type
+
+# Navigate through output
+yoink send claude --key pageup
+yoink snapshot claude
+```
+
+### Why yoink?
+Chat CLIs need a real PTY to function properly. yoink provides one, plus `snapshot` gives you the rendered conversation instead of raw terminal codes.
+
+
+
+
+Multi-Service Integration Testing
+
+### Problem
+Integration tests need multiple services (API, worker, database) running simultaneously. You need to start them, verify they're healthy, run tests, and tear everything down.
+
+### Solution
+```bash
+# Start all services
+yoink run --alias db "docker compose up postgres redis"
+yoink run --alias api "cargo run --bin api"
+yoink run --alias worker "cargo run --bin worker"
+
+# Wait for readiness (check logs for markers)
+sleep 5
+yoink log db --new | grep -q "ready to accept connections"
+yoink log api --new | grep -q "listening on :8080"
+yoink log worker --new | grep -q "connected to queue"
+
+# Run the integration suite
+yoink run --alias tests "cargo test --test integration"
+yoink wait tests --timeout 300
+
+# If tests fail, grab all service logs for debugging
+if [ $? -ne 0 ]; then
+ echo "=== API logs ===" && yoink log api
+ echo "=== Worker logs ===" && yoink log worker
+ echo "=== DB logs ===" && yoink log db
+fi
+
+# Tear down
+yoink kill worker
+yoink kill api
+yoink kill db
+yoink clean
+```
+
+### Why yoink?
+Docker Compose handles containers, but what about local binaries? yoink manages any process — containerized or not — with consistent log access and lifecycle control.
+
+
+
+
+Asciinema Recording with Simulated Typing
+
+### Problem
+You're recording a terminal demo with asciinema and want realistic-looking typed input without actually typing it yourself.
+
+### Solution
+```bash
+# Start recording in a yoink-managed shell
+yoink run --alias demo "asciinema rec demo.cast"
+sleep 2
+
+# Simulate typing with natural delay
+yoink send demo "yoink run --alias server 'python -m http.server'" --type --delay 80
+sleep 2
+
+yoink send demo "yoink ls" --type --delay 60
+sleep 2
+
+yoink send demo "curl localhost:8000" --type --delay 70
+sleep 3
+
+yoink send demo "yoink kill server" --type --delay 60
+sleep 1
+
+# End the recording
+yoink send demo "exit" --type
+```
+
+### Why yoink?
+`--type --delay` sends characters one at a time with configurable timing, producing natural-looking recordings without manual typing or complex scripting.
+
+
+
+
+Local LLM Serving & Inference
+
+### Problem
+You want to run a local model server (Ollama, vLLM, llama.cpp) in the background, wait for it to load, and then hit it from scripts or other tools — without blocking your terminal.
+
+### Solution
+```bash
+# Start Ollama in the background
+yoink run --alias ollama "ollama serve"
+sleep 3
+
+# Pull a model — this takes a while
+yoink run --alias pull "ollama pull qwen3:32b"
+yoink wait pull --timeout 600 --poll 10
+
+# Verify model is loaded
+yoink log pull --new | grep "success"
+
+# Run inference in a conversational session
+yoink run --alias chat "ollama run qwen3:32b"
+sleep 2
+
+yoink send chat "Summarize this error log in one sentence: $(cat /tmp/crash.log)"
+sleep 15
+yoink log chat --new
+
+# Send follow-up
+yoink send chat "What's the root cause?"
+sleep 10
+yoink log chat --new
+
+# Kill the session but keep the server
+yoink kill chat
+```
+
+### Why yoink?
+Model servers take time to load and need to stay alive across workflows. yoink lets you start the server once, confirm it's ready, and interact with it from anywhere — scripts, CI, or other yoink processes.
+
+
+
+
+Kubernetes Port-Forward Juggling
+
+### Problem
+Debugging microservices locally means managing a pile of `kubectl port-forward` processes. They crash silently, and you lose track of which ports map to which service.
+
+### Solution
+```bash
+# Forward all the services you need
+yoink run --alias pf-api "kubectl port-forward svc/api 8080:80"
+yoink run --alias pf-redis "kubectl port-forward svc/redis 6379:6379"
+yoink run --alias pf-postgres "kubectl port-forward svc/postgres 5432:5432"
+yoink run --alias pf-grafana "kubectl port-forward svc/grafana 3000:3000"
+
+# Quick health check — are they all still alive?
+yoink ls
+
+# When a forward dies (it always does), check why and restart
+yoink log pf-api --new
+# "error: lost connection to pod"
+yoink kill pf-api
+yoink run --alias pf-api "kubectl port-forward svc/api 8080:80"
+
+# Done debugging — tear down all forwards
+yoink kill pf-api pf-redis pf-postgres pf-grafana
+yoink clean
+```
+
+### Why yoink?
+Port-forwards in background shells are invisible when they break. yoink gives each one a name, tracks its state, and shows you the error when the connection drops.
+
+
+
+
+Infrastructure Applies with Approval Gates
+
+### Problem
+Running `pulumi up` or `terraform apply` in automation requires reviewing the plan and then confirming — an interactive step that doesn't work in headless scripts.
+
+### Solution
+```bash
+# Start the apply
+yoink run --alias infra "pulumi up --stack prod"
+sleep 10
+
+# Review the plan output
+yoink log infra --new
+# Shows: "Do you want to perform this update? [yes/no/details]"
+
+# Inspect details first
+yoink send infra "details"
+sleep 5
+yoink log infra --new
+
+# Looks good — approve it
+yoink send infra "yes"
+
+# Wait for the apply with a generous timeout
+yoink wait infra --timeout 900 --poll 15 \
+ --message "Pulumi apply exceeded 15min — possible stuck resource"
+
+echo "Apply exit code: $?"
+```
+
+### Why yoink?
+IaC tools need interactive confirmation for production changes. yoink bridges the gap between "review before applying" and "run it in a script" — you get both.
+
+
+
+
+ML Training Run Monitoring
+
+### Problem
+Training runs take hours or days. You need to launch them, check loss/metrics periodically, and catch hangs or NaN losses early — without babysitting a terminal.
+
+### Solution
+```bash
+# Start training
+yoink run --alias train "python train.py --epochs 100 --lr 3e-4 --wandb"
+
+# Periodically check progress
+yoink log train --new
+# "Epoch 12/100 | loss: 0.342 | val_acc: 0.891 | lr: 2.7e-4"
+
+# Watch GPU utilization alongside
+yoink run --alias gpu "watch -n 5 nvidia-smi"
+yoink snapshot gpu
+# See real GPU memory/utilization as a rendered table
+
+# Set a timeout guard for the full run
+yoink wait train --timeout 86400 --poll 60 \
+ --message "Training exceeded 24h budget — possible hang"
+
+# Check final metrics
+yoink log train --lines 20
+```
+
+### Why yoink?
+Training scripts in tmux sessions get lost. `nvidia-smi` in another tab requires switching. yoink keeps both in one session — `log` for training output, `snapshot` for the GPU dashboard.
+
+
+
+
+MCP Server Lifecycle
+
+### Problem
+You're developing or testing MCP (Model Context Protocol) servers and need to start them, verify they initialize, test tool calls, and restart on code changes.
+
+### Solution
+```bash
+# Start your MCP server
+yoink run --alias mcp "node dist/mcp-server.js --stdio"
+sleep 2
+
+# Check it initialized properly
+yoink log mcp --new
+# "MCP server started, tools: [search, fetch, analyze]"
+
+# Test a tool call by sending JSON-RPC over stdin
+yoink send mcp '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
+sleep 1
+yoink log mcp --new
+
+# Send a tool invocation
+yoink send mcp '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search","arguments":{"query":"test"}}}'
+sleep 2
+yoink log mcp --new
+
+# Code changed — restart the server
+yoink kill mcp
+yoink run --alias mcp "node dist/mcp-server.js --stdio"
+```
+
+### Why yoink?
+MCP servers using stdio transport need a process to hold their stdin/stdout open. yoink acts as the host process, letting you send JSON-RPC messages and read responses without writing a test harness.
+
+
+
+
+SSH Tunnel Management
+
+### Problem
+You need multiple SSH tunnels open to reach databases, internal APIs, or dashboards behind a bastion — and they randomly drop.
+
+### Solution
+```bash
+# Tunnel to production database through bastion
+yoink run --alias tunnel-db \
+ "ssh -N -L 5433:prod-db.internal:5432 bastion.example.com"
+
+# Tunnel to internal monitoring
+yoink run --alias tunnel-grafana \
+ "ssh -N -L 3001:grafana.internal:3000 bastion.example.com"
+
+# Verify tunnels are alive
+yoink ls
+# ID ALIAS STATE PID
+# 1 tunnel-db running 48291
+# 2 tunnel-grafana running 48305
+
+# Connect to the tunneled database
+psql -h localhost -p 5433 -U readonly prod
+
+# If a tunnel drops, yoink shows the exit state
+yoink ls
+# ID ALIAS STATE EXIT
+# 1 tunnel-db failed 255
+
+# Check why and restart
+yoink log tunnel-db --new
+yoink kill tunnel-db
+yoink run --alias tunnel-db \
+ "ssh -N -L 5433:prod-db.internal:5432 bastion.example.com"
+```
+
+### Why yoink?
+SSH tunnels in `&` background jobs give no feedback when they die. yoink tracks their state, shows exit codes, and preserves error output so you know exactly what broke.
+
+
+
+
+E2E Tests with Dev Server Lifecycle
+
+### Problem
+Playwright/Cypress E2E tests need a fully booted dev server. CI scripts use hacky `wait-on` or `sleep 30` to guess when the server is ready.
+
+### Solution
+```bash
+# Start the dev server
+yoink run --alias app "npm run dev"
+
+# Poll until the server is actually ready (not just started)
+for i in $(seq 1 30); do
+ if yoink log app --new | grep -q "Local:.*http://localhost:3000"; then
+ echo "Server ready"
+ break
+ fi
+ sleep 1
+done
+
+# Run Playwright
+yoink run --alias e2e "npx playwright test --reporter=list"
+yoink wait e2e --timeout 300 --poll 5
+
+# On failure, grab server-side errors for the test report
+if [ $? -ne 0 ]; then
+ echo "--- Server logs during test run ---"
+ yoink log app --new
+fi
+
+yoink kill app
+yoink clean
+```
+
+### Why yoink?
+The server and tests are correlated — when a test fails, you need the server logs from that exact window. yoink's `--new` flag gives you just the logs from during the test run, not the entire server history.
+
+
+
+
+Multi-Agent AI Orchestration
+
+### Problem
+You're coordinating multiple AI coding agents (Claude Code, Aider, Codex) working on different parts of a codebase, and need to monitor their progress and feed them results from each other.
+
+### Solution
+```bash
+# Start agents on different tasks in separate worktrees
+yoink run --alias agent-api \
+ "cd worktree-api && claude -p 'Add pagination to the /users endpoint'"
+
+yoink run --alias agent-tests \
+ "cd worktree-tests && claude -p 'Write integration tests for the users API'"
+
+yoink run --alias agent-docs \
+ "cd worktree-docs && claude -p 'Update API docs for the users endpoint'"
+
+# Monitor progress with snapshots (Claude is a TUI)
+yoink snapshot agent-api
+yoink snapshot agent-tests
+
+# Wait for the API agent to finish first — tests depend on its output
+yoink wait agent-api --timeout 600 --poll 10
+
+# Feed the API changes to the test agent
+yoink send agent-tests "The API agent is done. The new endpoint is GET /users?page=N&limit=N. Update tests accordingly."
+
+# Wait for remaining agents
+yoink wait agent-tests agent-docs --timeout 600
+```
+
+### Why yoink?
+AI agents are long-running, interactive, and need to be coordinated. yoink gives each agent a named PTY, lets you monitor via `snapshot`, and pipe context between them with `send`.
+
+
+
+
+Docker Build + Push Matrix
+
+### Problem
+You're building multi-arch container images or pushing to multiple registries, and want to parallelize while tracking which builds succeed.
+
+### Solution
+```bash
+# Parallel multi-arch builds
+yoink run --alias build-amd64 \
+ "docker build --platform linux/amd64 -t myapp:amd64 ."
+yoink run --alias build-arm64 \
+ "docker build --platform linux/arm64 -t myapp:arm64 ."
+
+# Wait for both
+yoink wait build-amd64 build-arm64 --timeout 600
+
+# Create and push manifest
+yoink run --alias push "docker manifest create myapp:latest \
+ myapp:amd64 myapp:arm64 && docker manifest push myapp:latest"
+yoink wait push --timeout 120
+
+# Check what happened
+yoink log build-amd64 --lines 5
+yoink log build-arm64 --lines 5
+yoink log push --lines 5
+
+yoink clean
+```
+
+### Why yoink?
+`docker build &` runs blind — you can't tell which arch failed or see its output. yoink labels each build, streams logs, and propagates the first failure via `wait`.
+
+
+
+---
+
+## Quick Patterns
+
+| Pattern | Command |
+|---------|---------|
+| Run in background | `yoink run --alias name "command"` |
+| Check new output | `yoink log name --new` |
+| Send input | `yoink send name "text"` |
+| Send secret input | `yoink send-redacted name "$SECRET"` |
+| Send key combo | `yoink send name --key ctrl+c` |
+| Wait with timeout | `yoink wait name --timeout 60` |
+| Wait for multiple | `yoink wait a b c --timeout 120` |
+| View TUI screen | `yoink snapshot name` |
+| Interactive reattach | `yoink attach name` (Ctrl+] to detach) |
+| Kill gracefully | `yoink kill name` |
+| Force kill | `yoink kill name --force` |
+| Cleanup finished | `yoink clean` |
diff --git a/README.md b/README.md
index d5c954e..030de76 100644
--- a/README.md
+++ b/README.md
@@ -83,6 +83,8 @@ yoink kill 1 --force # SIGKILL
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.