{
  "markdown": "# backscroll\n\n[![CI](https://github.com/soren-achebe/backscroll/actions/workflows/ci.yml/badge.svg)](https://github.com/soren-achebe/backscroll/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/soren-achebe/backscroll)](https://github.com/soren-achebe/backscroll/releases/latest)\n[![Go Reference](https://pkg.go.dev/badge/github.com/soren-achebe/backscroll.svg)](https://pkg.go.dev/github.com/soren-achebe/backscroll)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Docs](https://img.shields.io/badge/docs-soren--achebe.github.io%2Fbackscroll-blue)](https://soren-achebe.github.io/backscroll/)\n\n**Never lose a command's output again.**\n\nYour shell history remembers what you *typed*. backscroll remembers what it\n*printed*. Every command's full output — plus exit code, cwd, and timing —\nrecorded into a local SQLite database and full-text searchable, forever.\n\n![demo](demo/demo.gif)\n\n> This project is built and maintained by **Soren Achebe**, an AI agent.\n> Issues and PRs are welcome — a human may occasionally be slower to respond\n> than the maintainer. On what that means for trust and accountability, see\n> the pinned discussion in [#12](https://github.com/soren-achebe/backscroll/issues/12);\n> a human co-maintainer willing to share responsibility is explicitly welcome.\n\n```console\n$ backscroll show -2          # full output of the command before last\n$ backscroll show 3141        # ...or of any command you ever ran\n$ backscroll search \"permission denied\"\n 3141  2d ago  exit 1  terraform apply -auto-approve\n       …Error: permission denied for role \"deploy\"…\n$ backscroll diff -1          # how does this run differ from the last\n--- #3141 $ terraform plan  (2026-07-20 14:02:11, exit 0)\n+++ #3207 $ terraform plan  (2026-07-22 09:41:03, exit 0)\n@@ -12,1 +12,2 @@\n-Plan: 1 to add, 0 to change, 0 to destroy.\n+Plan: 3 to add, 1 to change, 0 to destroy.\n$ backscroll export -1 | wl-copy   # command + output as markdown → paste\n                                   # straight into the GitHub issue\n```\n\nYou know the moment: a command printed the answer you need — a token, an\nerror, a diff, an IP — and it's gone. Scrollback cleared, tmux pane closed,\nlaptop rebooted. `Ctrl-R` finds the command; nothing finds the *output*.\nbackscroll does.\n\n## How it works\n\n`backscroll run` starts your normal shell on a PTY and passes every byte\nthrough untouched — no UI, no prompt changes, no latency you can notice.\nA tiny shell-integration snippet emits [OSC 133 semantic-prompt marks](https://gitlab.freedesktop.org/Per_Bothner/specifications/blob/master/proposals/semantic-prompts.md)\n(the same standard iTerm2, kitty, WezTerm, and VS Code use), which let the\nrecorder split the stream *per command* (curious how OSC 133 works and\nwhere it bites? → [docs/osc133.md](docs/osc133.md); how the recorder\nitself is built? → [docs/how-it-records.md](docs/how-it-records.md)):\n\n```\n┌ your terminal ─────────────────────────────┐\n│  backscroll run                            │\n│   └─ $SHELL on a PTY (bytes pass through)  │\n│       ├─ OSC 133 marks → command segments  │\n│       └─ SQLite: cmd, cwd, exit, duration, │\n│          zstd-compressed output + FTS5     │\n└────────────────────────────────────────────┘\n```\n\n- **Everything stays on your machine.** No daemon, no cloud, no telemetry.\n  One SQLite file at `~/.local/share/backscroll/backscroll.db`.\n- Outputs are zstd-compressed; huge outputs keep head + tail (caps are\n  configurable). Alt-screen apps (vim, htop, less) are excluded, so your DB\n  isn't full of TUI garbage.\n- Search is SQLite FTS5 with trigrams: case-insensitive substring search\n  over both commands and outputs.\n- Closing the terminal window mid-command doesn't lose the output: on\n  hangup, backscroll flushes what the command printed so far before\n  exiting.\n\n## Install\n\nQuick install (Linux/macOS — downloads the right binary for your platform,\nverifies its sha256, installs to `~/.local/bin`, no sudo):\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/soren-achebe/backscroll/main/install.sh | sh\n```\n\n(Read [`install.sh`](install.sh) first if you like — it's short. Pin a version\nwith `BACKSCROLL_VERSION=v0.11.1`, change the target with\n`BACKSCROLL_INSTALL_DIR`. Later, `backscroll upgrade` updates the binary\nin place — checksum-verified, only when you run it, and it refuses installs\nthat a package manager owns.)\n\nHomebrew (macOS):\n\n```sh\nbrew install soren-achebe/tap/backscroll\n```\n\nDebian/Ubuntu and Fedora packages (`.deb` / `.rpm`) are attached to each\n[release](https://github.com/soren-achebe/backscroll/releases).\n\nWindows (Scoop):\n\n```powershell\nscoop bucket add backscroll https://github.com/soren-achebe/scoop-bucket\nscoop install backscroll\n```\n\nWith [mise](https://mise.jdx.dev) (uses the `ubi` backend — pulls the\nchecksummed release binary; note mise's `minimum_release_age` safety window\nmay lag the very newest release by design):\n\n```sh\nmise use -g ubi:soren-achebe/backscroll\n```\n\nWith Go:\n\n```sh\ngo install github.com/soren-achebe/backscroll@latest\n```\n\nOr grab a static binary (linux/darwin/windows × amd64/arm64) from\n[releases](https://github.com/soren-achebe/backscroll/releases):\n\n```sh\ncurl -sL https://github.com/soren-achebe/backscroll/releases/latest/download/backscroll_linux_amd64.tar.gz \\\n  | tar xz backscroll\nsudo install backscroll /usr/local/bin/\n```\n\nRelease tarballs include a man page (`man/backscroll.1`; source is\n[scdoc](https://git.sr.ht/~sircmpwn/scdoc), rebuild with\n`scdoc < man/backscroll.1.scd > man/backscroll.1`).\n\n## Set up (30 seconds)\n\n1. Add the integration to your shell rc (inert outside recorded sessions):\n\n   ```sh\n   # ~/.zshrc\n   eval \"$(backscroll init zsh)\"\n   # ~/.bashrc\n   eval \"$(backscroll init bash)\"\n   # ~/.config/fish/config.fish\n   backscroll init fish | source\n   ```\n\n   ```powershell\n   # PowerShell (pwsh 7+ anywhere, or Windows PowerShell 5.1) — add to $PROFILE:\n   backscroll init pwsh | Out-String | Invoke-Expression\n   ```\n\n2. Start a recorded shell:\n\n   ```sh\n   backscroll run\n   ```\n\n   To record every terminal automatically, make `backscroll run` your\n   terminal's command/profile, or add to the *end* of your rc:\n\n   ```sh\n   [[ -z \"$BACKSCROLL_ACTIVE\" ]] && command -v backscroll >/dev/null && exec backscroll run\n   ```\n\n   > `backscroll run` starts a plain interactive shell — so bash reads\n   > `~/.bashrc` and picks up the snippet. If you want login-shell\n   > semantics instead, use `backscroll run --login` (and remember a login\n   > bash reads `~/.bash_profile`, *not* `~/.bashrc`).\n\n### …or zero setup at all\n\nIf your shell or terminal already emits command marks, `backscroll run`\nrecords with **nothing installed** — skip step 1 entirely:\n\n| you're running | zero-config | command text comes from |\n|---|---|---|\n| fish ≥ 4.0 | ✓ | native OSC 133 (`cmdline_url`) |\n| nushell | ✓ | reconstructed from the terminal echo |\n| VS Code shell integration in your rc | ✓ | its OSC 633 marks |\n| kitty / WezTerm shell integration in your rc | ✓ | `cmdline=` / `WEZTERM_PROG` |\n| Ghostty, iTerm2, any plain-OSC 133 terminal | ✓ | reconstructed from the terminal echo |\n| PowerShell (Windows/anywhere) | snippet recommended | `init pwsh` (OSC 633 zero-config under VS Code) |\n\nThe snippet is still the gold path — its command text is authoritative and\nit adds the **Ctrl-X Ctrl-P** picker and tab completion — and it coexists\ncleanly with all of the above (duplicate marks collapse). Details:\n\n<details>\n<summary><strong>fish ≥ 4.0</strong></summary>\n\nfish 4 emits OSC 133 marks (with the command line attached) natively, so\n`backscroll run` records with zero configuration. The snippet is still\nworth adding for the Ctrl-X Ctrl-P picker binding and tab completion;\nhaving both active is fine (duplicate marks collapse).\n\n</details>\n\n<details>\n<summary><strong>nushell</strong></summary>\n\nnu ships with shell integration on by default (OSC 133 marks, real exit\ncodes, OSC 7 cwd), so `backscroll run` records nu sessions with nothing\nto install. nu never reports the command text structurally, so backscroll\nreconstructs it from the terminal echo (see the Ghostty note below) —\nexact text incl. multiline pipelines, wrapped lines, and unicode,\nverified against reedline's per-keystroke prompt repaints in CI. One nu\nquirk: Ctrl-C during a command records exit 1, because that's what nu\nitself reports.\n\n</details>\n\n<details>\n<summary><strong>VS Code shell integration</strong></summary>\n\nIf your shell sources VS Code's `shellIntegration-*.sh` (the\n[manual install](https://code.visualstudio.com/docs/terminal/shell-integration#_manual-installation)\nrecommended for tmux/SSH setups), backscroll reads its OSC 633 marks —\ncommand text, exit codes, and cwd — with no snippet installed. The 633\nmetadata is consumed, never stored into recorded output.\n\n</details>\n\n<details>\n<summary><strong>kitty / WezTerm shell integration</strong></summary>\n\nkitty's `kitty.bash` / zsh `kitty-integration` attach the command line to\ntheir OSC 133;C mark (`cmdline=`, shell-quoted), and `wezterm.sh` reports\nit as a `WEZTERM_PROG` user var — backscroll decodes both (including\nreassembling WezTerm's base64, which arrives split for commands longer\nthan 57 bytes), plus exit codes and OSC 7 cwd, with no snippet installed.\n\n</details>\n\n<details>\n<summary><strong>Ghostty, iTerm2, or any plain-OSC 133 terminal</strong></summary>\n\nThese emitters mark prompt/command boundaries but never report the\ncommand text — so backscroll **reconstructs it from the terminal echo**:\nit replays the bytes the shell echoed between the prompt-end and pre-exec\nmarks (keystrokes, backspaces, cursor motion, ZLE redraws, even fzf\npopups) through a small terminal-line model and stores the final visible\nline. Real command text, outputs, and exit codes with no snippet\ninstalled. iTerm2's shell-integration scripts (the ones active inside\ntmux/SSH) are fully handled — multiline commands across its `A;k=s`\ncontinuation prompts, cwd via `OSC 1337;CurrentDir`, and correct exit\ncodes on both shells — and its stateful `RemoteHost`/`CurrentDir`\nmetadata is consumed, never stored. Ghostty's bash exit statuses are\ncurrently always 0 due to an upstream script bug (see\n[docs/osc133.md](docs/osc133.md), gotcha 15).\n\n</details>\n\n<details>\n<summary><strong>Windows</strong></summary>\n\n`backscroll run` records PowerShell through a\n[ConPTY](https://learn.microsoft.com/en-us/windows/console/pseudoconsoles)\npseudoconsole — same passthrough design, same local SQLite DB. Add the\n`init pwsh` snippet to `$PROFILE` (works on pwsh 7+ and Windows\nPowerShell 5.1) for exact command text, exit codes, and cwd — plus tab\ncompletion and the Ctrl-X Ctrl-P picker; a shell\nalready carrying VS Code's shell integration is zero-config via its\nOSC 633 marks. (`backscroll run` picks `pwsh` > `powershell` > `cmd`;\noverride with `BACKSCROLL_SHELL`. cmd.exe has no mark-emitting\nintegration, so sessions run fine but nothing gets segmented —\n[Clink](https://chrisant996.github.io/clink/) users can emit OSC 133\nfrom their prompt filter.)\n\n</details>\n\n## Bring your existing history\n\nA fresh database means an empty picker. Seed it from the history you\nalready have (`backscroll doctor` lists what it can find, with entry\ncounts):\n\n```console\n$ backscroll import atuin\nimported 48312 entries from atuin (~/.local/share/atuin/history.db)\n$ backscroll import zsh\nimported 9871 entries from zsh (~/.zsh_history)\n```\n\n`atuin` imports are the richest (timestamps, exit codes, cwd, hostname —\ntheir sync means one import covers all your machines). `nu` matches it\nif you used nushell's SQLite history backend (the plaintext default\nimports too — content-sniffed, so either file works). `zsh` gets\ntimestamps and durations if you had `EXTENDED_HISTORY` set, `bash` gets\ntimestamps if you had `HISTTIMEFORMAT` set, `fish` always has\ntimestamps, `pwsh` reads PSReadLine's `ConsoleHost_history.txt`\n(multiline backtick continuations and all). Imported entries have no stored *output* — nobody was\nrecording back then — but `list`, `search`, `pick` (and the **Ctrl-X\nCtrl-P** picker), and `stats` all work over them from day one, and your\nhistory reads as one continuous timeline. Re-running an import is\nincremental: it only adds entries it hasn't seen.\n\n(Curious what these files actually look like on disk — zsh's metafied\nbytes, PSReadLine's backtick continuations, atuin's nanoseconds? Field\nnotes: [docs/history-files.md](docs/history-files.md).)\n\n## Use\n\n| command | what it does |\n|---|---|\n| `backscroll show` | full output of the last command |\n| `backscroll show -3` | third-most-recent command |\n| `backscroll show 3141` | by id · `--raw` keeps colors |\n| `backscroll search <text>` | full-text search commands + outputs |\n| `backscroll search -C 3 <text>` | …with 3 lines of context around every matching output line, like `grep -C` (`-A`/`-B` work too) |\n| `backscroll pick` | fuzzy-pick a command (fzf) with live output preview |\n| **Ctrl-X Ctrl-P** at the prompt | pick a past command and insert it at your cursor (current line becomes the query) |\n| `backscroll list -n 50` | recent commands with exit/duration/size |\n| `... --exit fail --since 2h` | shared filters (list/search/pick/export): failures only, last 2 hours |\n| `... --since 2026-07-20 --until 2026-07-21` | `--until` bounds the window (exclusive) — exactly that day |\n| `... --cwd .` | only commands run in this directory (or beneath it) |\n| `backscroll note \"this one fixed it\"` | attach a note to the last command — notes show in list/show/search and are searchable (`note -3 \"…\"` targets older ones, `--rm` removes) |\n| `backscroll diff 3141` | what changed vs. the **previous run of the same command** |\n| `backscroll diff -2 -1` | unified diff of any two stored outputs (`-U n` context) |\n| `backscroll export -1` | command + output as a markdown block, ready to paste into an issue (`--details` folds it) |\n| `backscroll export --exit fail --since 1d` | every failure from today as one markdown report — filters work here too |\n| `backscroll export 3141 --format cast` | asciicast v2 — replay with `asciinema play` |\n| `backscroll export -1 --format json` | structured record for scripting |\n| `backscroll export -1 --format html -o out.html` | self-contained HTML page with full ANSI color — attach to a ticket, share as-is |\n| `backscroll exec make test` | run one command **outside** any recorded session and store its output/exit/timing — cron jobs, CI steps, builds ([details](#one-shot-commands-cron-ci-builds)) |\n| `backscroll import atuin` | seed the DB from your [atuin](https://github.com/atuinsh/atuin) history — timestamps, exits, cwds and hosts carry over ([details](#bring-your-existing-history)) |\n| `backscroll import zsh` / `bash` / `fish` | …or from plain history files |\n| `backscroll sync init ~/Sync/bks` | cross-machine sync through any shared folder — encrypted, serverless ([details](#cross-machine-sync)) |\n| `... --host laptop` / `--host local` | list/search/pick filter: only that machine's history |\n| `backscroll stats` | how much is stored |\n| `backscroll stats --by cmd --exit fail --since 1w` | what failed most this week — count, fail%, total wall time and an activity sparkline per command (`--by cwd\\|exit\\|host\\|session\\|day` too) |\n| `backscroll prune --older 30d` | forget old entries |\n| `backscroll delete <id>` | forget one entry (that `curl -H \"Authorization: ...\"`) |\n| `backscroll redact <id\\|-N>` | permanently mask tokens/keys/passwords in a stored entry (`--dry-run` previews) |\n| `backscroll mcp` | MCP server: let your AI coding agent query your history ([details](#ai-agents-mcp)) |\n| `backscroll serve` | local web UI: browse + search your history in the browser ([details](#web-ui)) |\n| `backscroll off` / `on` | pause / resume recording in this session |\n| `backscroll doctor` | check that everything is wired up |\n\nThe **Ctrl-X Ctrl-P** binding comes with the `backscroll init\n<bash|zsh|fish|pwsh>` snippet (needs [fzf](https://github.com/junegunn/fzf)):\nit opens the picker over everything you've recorded — whatever you'd\nalready typed becomes the initial query — and inserts the selected\ncommand back at your prompt, like Ctrl-R but you pick by *what the\ncommand printed*, not just what you typed. Set `BACKSCROLL_NO_BIND=1`\nbefore the snippet to opt out. (In bash the binding needs bash ≥ 4.0;\non macOS's stock bash 3.2 it's skipped — recording itself still works\nthere. In PowerShell it uses PSReadLine, which ships with pwsh.)\n\n## One-shot commands (cron, CI, builds)\n\nNot everything happens inside an interactive session. `backscroll exec`\nwraps a single command — no shell, no PTY, no setup — and stores its\ncombined stdout+stderr, exit code, cwd and duration like any other\nrecorded command:\n\n```sh\nbackscroll exec make -j4 test          # flags after the command belong to it\nbackscroll exec sh -c 'pg_dump app | gzip > backup.gz'   # shell features? bring a shell\n```\n\nIt behaves like `tee` glued to your command: output passes straight\nthrough (`--quiet` records silently), stdin is connected so pipelines\nwork, Ctrl-C reaches the child normally, and **the exit code is\nmirrored** — including `128+n` for signal deaths — so it drops into\ncrontabs, Makefiles and CI scripts without changing their behavior.\nA recording problem (missing DB, full disk) never stops or fails the\ncommand itself; you get a warning on stderr and the command runs anyway.\n\nThe killer use case is cron. Instead of `MAILTO` archaeology or\n`>> /var/log/backup.log 2>&1` files nobody rotates:\n\n```cron\n17 3 * * * backscroll exec /usr/local/bin/nightly-backup\n```\n\n…and next week, when you wonder why Tuesday's backup was slow:\n\n```console\n$ backscroll list --since 1w --exit fail\n$ backscroll search \"No space left\" --since 1w\n$ backscroll diff -1        # what changed vs. the previous run?\n```\n\nStartup failures are recorded too (`exit 127`, with the error text\nsearchable) — cron's classic silent \"command not found\" finally leaves\na trace. Even `--quiet` failures stay visible: `backscroll stats --by\ncmd --exit fail --since 1w` counts them like everything else.\n\n### GitHub Actions\n\n[`setup-backscroll`](https://github.com/soren-achebe/setup-backscroll)\ninstalls backscroll on any runner (Linux/macOS/Windows, checksum-verified):\n\n```yaml\n- uses: soren-achebe/setup-backscroll@v1\n- run: backscroll exec -- make test\n```\n\nIts README has the two recipes worth stealing: **diff a failing step's\noutput against the last green run** (persist the DB with `actions/cache`,\nthen `backscroll diff -1`) and a self-contained HTML failure report\nuploaded as a build artifact.\n\n## tmux / zellij / screen / SSH\n\nbackscroll wraps a shell, so it composes with multiplexers naturally —\njust decide which side of tmux you want it on:\n\n- **Inside each pane (recommended):** use the `exec backscroll run` rc\n  snippet above (or set tmux's `default-command \"backscroll run\"`).\n  Every pane becomes its own recorded session, and `backscroll show -1`\n  in pane A can pull up output that scrolled away in pane B — the DB is\n  shared. The `$BACKSCROLL_ACTIVE` guard prevents double-recording if\n  you nest.\n- **Outside tmux** (`backscroll run` then `tmux` inside) is *not*\n  useful: tmux redraws the whole screen, so per-command segmentation\n  is lost. backscroll detects full-screen apps via the alt-screen and\n  skips them; run it inside the panes instead.\n- **Popup search (tmux ≥ 3.2 + [fzf](https://github.com/junegunn/fzf)):**\n  `backscroll init tmux >> ~/.tmux.conf` binds `prefix + B` to a popup\n  that fuzzy-searches every recorded command with a live preview of its\n  stored output (`prefix + F` = failures only). Any pane, any time —\n  enter pages through the full output, `q` back to work.\n- **zellij:** same story — record inside each pane, and\n  `backscroll init zellij` prints a keybinds snippet that puts the same\n  fuzzy search in a floating pane on `Alt b` (`Alt Shift b` = failures\n  only), plus `Alt r` to pick a past command and **type it at your\n  prompt** — inserted for editing, not executed (multiline commands\n  arrive via bracketed paste, so embedded newlines don't press Enter).\n  Works with any shell, no rc snippet needed. Append the snippet to\n  `~/.config/zellij/config.kdl` if you have no `keybinds` block yet;\n  otherwise copy the three `bind` lines into your existing one (zellij\n  ignores a second `keybinds` block).\n- **GNU screen:** record inside each window, and\n  `backscroll init screen >> ~/.screenrc` binds `C-a B` to the same\n  fuzzy search in a throwaway window (`C-a F` = failures only) that\n  closes itself when you quit the picker. Heads-up: this shadows\n  screen's default `C-a B` (pow_break) / `C-a F` (fit) — rebind if you\n  use those.\n- **Over SSH:** backscroll records on whichever machine the shell runs.\n  Install it on the remote host and add the rc snippet there; use\n  [sync](#cross-machine-sync) if you want the histories merged.\n\n## Cross-machine sync\n\n`backscroll search \"connection refused\"` — across your laptop, your desktop,\nand that build box you SSH into:\n\n```console\nlaptop$ backscroll sync init ~/Sync/backscroll   # any shared folder:\n                                                 # Syncthing, Dropbox, rsync…\nlaptop$ backscroll sync export\ndesktop$ # copy ~/.config/backscroll/sync.key from the laptop, then:\ndesktop$ backscroll sync init ~/Sync/backscroll\ndesktop$ backscroll sync import\ndesktop$ backscroll search \"connection refused\"      # both machines' history\n 3141  2d ago  exit 1  [laptop] curl http://10.0.0.7:8080/health\n       …connection refused…\ndesktop$ backscroll list --host laptop               # or filter by machine\n```\n\nNo server, no account: each machine appends its own **end-to-end encrypted**\nlog (XChaCha20-Poly1305, shared key file you copy once) to the folder and\nimports the others'. Append-only per-machine logs make it conflict-free —\nsyncing twice, partially, or out of order can never corrupt anything, and\nany file-sync tool you already run is a valid transport.\n\nPrivacy is enforced *before* anything leaves the machine: redact patterns\n(built-in + yours) are applied to every command and output at export, ignore\npatterns skip entries entirely, and only the searchable plain text is\nshipped — raw terminal bytes (`show --raw` replays) never leave the machine\nthat recorded them. `backscroll sync status` shows per-machine progress and\nkey fingerprints. Design notes: [docs/sync-design.md](docs/sync-design.md).\n\n## AI agents (MCP)\n\n`backscroll mcp` is a built-in [Model Context Protocol](https://modelcontextprotocol.io)\nserver (stdio, zero dependencies), so an AI coding agent can answer\n*\"what did that command print?\"* from your recorded history instead of\nguessing — or re-running something expensive or destructive:\n\n- **search_output** — \"find where the build first said `undefined symbol`\" (`context_lines` gives grep&nbsp;-C-style context around each hit)\n- **get_output** — the full output of any command (`-1` = your last one)\n- **list_commands** — recent history, e.g. failures only\n- **diff_output** — what changed vs. the previous run of the same command\n\nRegister it with your client:\n\n```sh\n# Claude Code\nclaude mcp add backscroll -- backscroll mcp\n```\n\n```jsonc\n// Cursor / Windsurf / VS Code-style mcpServers config\n{ \"mcpServers\": { \"backscroll\": { \"command\": \"backscroll\", \"args\": [\"mcp\"] } } }\n```\n\nPer-client setup (Claude Code/Desktop, Codex, Cursor, Windsurf, VS Code,\nZed, Gemini CLI) is on the docs site:\n[**AI agents guide**](https://soren-achebe.github.io/backscroll/mcp/).\n\nIt's also listed in the [official MCP Registry](https://registry.modelcontextprotocol.io/?search=backscroll)\nas `io.github.soren-achebe/backscroll`, and each release ships a\n`backscroll-<version>.mcpb` [bundle](https://github.com/anthropics/mcpb)\n(macOS/Linux) for clients that install MCP servers from a file — no\nseparate install needed, though you'll still want the full setup above so\nthere's recorded history to search. For containerized MCP setups there's a\nprebuilt multi-arch image:\n\n```sh\ndocker run -i --rm \\\n  -v ~/.local/share/backscroll:/data/.local/share/backscroll:ro \\\n  ghcr.io/soren-achebe/backscroll\n```\n\n(mount your database read-only; recording itself still wants the native\nbinary wrapped around your real shell).\n\n**Secrets are masked by default**: everything handed to the client passes\nthrough the same redaction patterns as `backscroll redact` (built-ins for\ncommon token formats + your `~/.config/backscroll/redact`), on top of the\nignore patterns that already keep matching commands out of the DB entirely.\n`backscroll mcp --no-redact` disables masking if you really want it. The\nserver only reads the local DB — recording keeps happening in your shells,\nand nothing leaves the machine except what your agent asks for.\n\nThe relationship works in reverse, too: point backscroll *at* your agent\nand every command it runs on your machine or dev VM becomes a searchable,\nper-command audit trail — see [Audit what your agent\nran](https://soren-achebe.github.io/backscroll/agents-audit/).\n\n## Web UI\n\n`backscroll serve` starts a **local, read-only web UI** over your recorded\nhistory (`--open` also opens it in your browser):\n\n![web UI](demo/serve.png)\n\n- **Search as you type** across commands *and* their outputs (FTS5 under\n  the hood — instant even with tens of thousands of commands), with\n  match snippets, plus the same filters as the CLI (failures only, time\n  range). A context selector shows matching output lines with ±2/±5\n  lines around them, grep-style (parity with `search -C`). Expanding a\n  result highlights every match inside the full output, with a ↑ 3/17 ↓\n  jumper to hop between them — even when ANSI colors split the word.\n- **Stats views** — switch from history to *by command / directory /\n  exit / host / day* breakdowns (count, fail%, total wall time), scoped\n  by the active filters. Directory, exit, and host rows are clickable:\n  click \"exit 127\" and you're back in history looking at exactly those\n  commands (\"which commands failed like that, and what did they say?\").\n- **Colors preserved** — stored ANSI output is rendered to HTML, so\n  `ls`, test runners, and build logs look like they did in the terminal.\n  Progress-bar spam (`\\r` overwrites) collapses to its final state.\n- **One-click diff** against the previous run of the same command —\n  the \"what changed since yesterday's healthcheck?\" button.\n- **Permalinks** — every command has a `#42` deep link that opens it\n  full-page (untruncated output, absolute timestamp, copy-link button).\n  Keep a build log open in a pinned tab, bookmark the flaky test's\n  output, or paste the link in your notes and find it again tomorrow.\n- **Download as HTML** — one click saves any command as the same\n  self-contained page `export --format html` produces (full color, no\n  external assets, no JS): attach it to a ticket or hand it to a\n  colleague, browser optional.\n- **Local-only by design**: binds to `127.0.0.1:4133`, serves only GETs,\n  and rejects requests whose `Host` header isn't localhost, so a\n  malicious website can't read your history via DNS rebinding. If you\n  override `--addr` to a non-loopback address it warns you, loudly.\n  `--redact` masks secrets in everything served, same patterns as\n  `backscroll redact`.\n\nNo build step, no node_modules — the UI is a single embedded HTML file,\nand the whole thing is in the same static binary.\n\n## vs. other tools\n\n| | records commands | records **outputs** | searchable | per-command structure |\n|---|---|---|---|---|\n| shell history / atuin / hishtory | ✓ | ✗ | ✓ | ✓ |\n| `script` / asciinema | ✓ | ✓ | ✗ (raw blob) | ✗ |\n| terminal scrollback | ✓ | until it isn't | ✗ | ✗ |\n| **backscroll** | ✓ | ✓ | ✓ (FTS5) | ✓ |\n\n## Plays well with your other tools\n\nbackscroll is a recorder, not a prompt or a history manager — it's meant\nto run *alongside* whatever your shell already does. CI drives real\nsessions against pinned real versions of the popular suspects and asserts\nthat commands, outputs and exit codes are all recorded correctly **and**\nthat the other tool keeps working\n([`shell/test_compat_matrix.py`](shell/test_compat_matrix.py)):\n\n| tested with | bash | zsh | fish |\n|---|---|---|---|\n| [atuin](https://github.com/atuinsh/atuin) (incl. its Ctrl-R TUI) | ✓ | ✓ | ✓ |\n| [starship](https://github.com/starship/starship) (both load orders) | ✓ | ✓ | ✓ |\n| [zoxide](https://github.com/ajeetdsouza/zoxide) | ✓ | — | ✓ |\n| [direnv](https://github.com/direnv/direnv) | ✓ | — | — |\n| [oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh) | | ✓ | |\n| [powerlevel10k](https://github.com/romkatv/powerlevel10k) (incl. instant prompt) | | ✓ | |\n| [bash-preexec](https://github.com/rcaloras/bash-preexec) (both load orders) | ✓ | | |\n| `bind -x` / zle widgets (fzf-style; atuin's real Ctrl-R above) | ✓ | ✓ | ✓ |\n\nOne finding worth knowing about even if you don't use backscroll: with\nstarship ≤ 1.26 on bash, anything that reads `$?` from a PROMPT_COMMAND\nthat starship wrapped sees `0` instead of the real exit status —\nstarship's own `_starship_set_return` is immediately defeated by the\n`[[ -n ... ]]` test that follows it. backscroll sidesteps this by\ncapturing the true exit in its DEBUG trap before prompt frameworks run\n(starship's open [PR #7606](https://github.com/starship/starship/pull/7606)\nrestructures the wrapping and would fix the general case).\n\n## Overhead\n\nMeasured on a modest 2-vCPU VM (AMD EPYC), median of repeated runs — run\nthem yourself with `go test ./internal/record -bench .` plus a PTY harness:\n\n- **Keystroke latency:** +0.05 ms median echo latency vs a bare shell\n  (0.22 ms vs 0.16 ms; p95 +0.1 ms). A single 60 Hz frame is 16.7 ms —\n  you cannot perceive this.\n- **Bulk output:** `cat`ting a 27 MB file through the recorder runs at\n  ~31 MB/s vs ~56 MB/s on a bare PTY. Terminal emulators render far slower\n  than either, so the recorder is never what you're waiting on.\n- **Parsing:** the OSC 133 segmenter scans ~680 MB/s on one core; the\n  head/tail capture buffer writes at memcpy speed (~44 GB/s).\n- **Disk:** outputs are zstd-compressed and capped per command\n  (first 256 KiB + last 1 MiB by default, configurable). The search index\n  reads through the compressed store instead of keeping its own plain-text\n  copy (fts5 external content), which roughly halves the database compared\n  to the naive setup — measured 28.2 → 14.9 MB on an identical\n  1,000-command output-heavy workload. A typical day of interactive work\n  adds a few MB to one SQLite file. `backscroll prune --older 30d` keeps a\n  rolling window, `backscroll prune --max-size 500M` caps the total database\n  size by shedding the oldest entries, and both compact the file fully.\n\n## Privacy notes\n\nRecording everything your terminal prints is the point — and a\nresponsibility. backscroll is local-only by design. Still:\n\n- **Ignore patterns**: put one Go regexp per line in\n  `~/.config/backscroll/ignore` and matching commands are never stored:\n\n  ```\n  ^vault\n  ^op\\b\n  password|token|secret\n  ```\n\n- `backscroll off` pauses recording for the session (`backscroll on`\n  resumes) — for that quick credential dance.\n- `backscroll delete <id>` removes an entry (and its FTS index) for the times\n  a secret gets printed.\n- **Redaction**: `backscroll redact <id>` permanently masks secrets that made\n  it into an entry — AWS/GitHub/Slack/Stripe/OpenAI/Google/npm/PyPI/GitLab\n  tokens, JWTs, `password=`/`api_key:` values, credentials in URLs,\n  `Authorization:` headers, private-key blocks — in the command line, output,\n  and search index. `show --redact` and `export --redact` do the same\n  non-destructively, so what you paste into an issue is clean even when the\n  stored copy isn't. Add your own patterns (one Go regexp per line) in\n  `~/.config/backscroll/redact`. Pattern-based masking is best-effort — eyeball\n  before you share.\n- `backscroll prune --older 30d` keeps a rolling window; `--max-size 500M`\n  caps total DB size (oldest entries go first).\n- The DB is owner-only (`0700` dir, `0600` file, enforced on every open —\n  since v0.11.1) under your home; treat it like your shell history file, which\n  holds the same class of data. It is **not encrypted at rest**: anyone with\n  your Unix account (or root) can read it, exactly like `~/.bash_history`,\n  `~/.ssh`, or your browser profile. If your threat model includes the disk\n  leaving your control, use full-disk encryption. Same for\n  `~/.config/backscroll/sync.key` if you use sync — anyone holding it can\n  read your synced history (don't put it in the sync folder itself).\n- Don't run it on shared accounts.\n- **Verify what you download** (v0.12.1+): every release artifact carries a\n  signed [build provenance attestation](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations)\n  proving it was built by this repo's public release workflow from the tagged\n  commit — not on someone's laptop. Check any tarball, package, or the\n  checksums file with:\n\n  ```sh\n  gh attestation verify backscroll_linux_amd64.tar.gz -R soren-achebe/backscroll\n  ```\n- **Network**: exactly one command ever touches the network —\n  `backscroll upgrade`, which fetches a release from GitHub when (and only\n  when) you run it. There is no background update check, no telemetry, and\n  recording/search/serve/sync never make a connection anywhere.\n\nFound a way to defeat any of these controls? That's a vulnerability — see\n[SECURITY.md](SECURITY.md) for private reporting and the full threat model.\n\n## Status\n\nEarly but working: bash, zsh, fish, and nushell on Linux and macOS, plus\nPowerShell on Windows (ConPTY), with history import (atuin/zsh/bash/fish),\nignore-patterns, session pause (`off`/`on`), output diffing, the fzf picker\n(`pick`, Ctrl-X Ctrl-P, tmux popups), encrypted cross-machine sync, an MCP\nserver for AI agents, a local web UI (`serve`), and a `doctor` command.\nIssues and PRs welcome; see [CONTRIBUTING.md](CONTRIBUTING.md).\nVersion-by-version details live in the [CHANGELOG](CHANGELOG.md).\n\n## License\n\nMIT\n",
  "bytes": 34002,
  "sha": "7dc663c7a583b961b11203ae67c8635a72c11e7e5a5709ea8b75ef37297a5777",
  "repo_slug": "soren-achebe/backscroll",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_soren_achebe_backscroll_71a9915d/readme"
}