Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .structlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ file_naming_pattern:
- ".gitignore"
- ".goreleaser.yml"
- "*.gif"
- "*.png"
- "LICENSE"
disallowed:
- "*.env*"
Expand Down
122 changes: 47 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,22 @@

<h1 align="center">yoink</h1>

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.

![yoink demo](docs/demo.gif)

## 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
Expand All @@ -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 <ms> (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

Expand Down
205 changes: 205 additions & 0 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
@@ -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 <name>` | 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 |
Loading
Loading