An append-only, encrypted archive of your AI conversations, from 5+ platforms, on storage you control.
Local-first: there is no server of ours in the path. Conversations are encrypted on your machine before they are stored. The archive is only ever added to. Anything the tool cannot see is reported as unknown, never quietly counted as zero.
A machine you lose is not a history you lose. What it archived stays readable from any other computer, with the destination and its key file alone. Use cases spells that out step by step, including what it cannot prove.
Quick start · Install · Support at a glance · Where the archive lives · Use cases · Security · Docs
The coding agents and web AI chats you already use, in one archive (status per platform):
- Coding agents: Claude Code, Codex CLI, opencode, Gemini CLI, Cursor, Grok CLI, Kimi Code, Copilot CLI, aider, crush, Continue, Zed
- Web chats: ChatGPT, Claude, Gemini, Grok, DeepSeek, Perplexity, Kimi
curl -fsSL https://chatstasher.com/install.sh | sh
export PATH="$HOME/.local/bin:$PATH" # the line the installer prints if this folder is not on PATH
chat-stasher doctor # read-only: is anything deleting your history?
chat-stasher init && chat-stasher run-once --stage ~/stash/chat-stasher/stageThe first command installs the CLI into ~/.local/bin (checksum-verified, no sudo); the export is the line the installer prints when that folder is not already on your PATH, and running it twice is harmless. doctor changes nothing; run-once makes your first local, encrypted archive.
The install script places a prebuilt macOS binary today. Linux and Windows builds arrive with 0.5.0; until then those platforms build from source. All of it is in Install, and backups to a remote and scheduling are in Your first archive.
Your AI tools are not archives. Several delete history on a schedule you never chose:
- Gemini CLI keeps sessions under a directory literally named
tmp. Its documented default retention is 30 days. - Claude Code deletes transcripts older than
cleanupPeriodDaysat startup, with a default of 30 days. - Web chats live in accounts you do not control, behind export features that can change without notice.
Once a conversation is gone from its tool, the archive is the only copy left. chat-stasher makes that copy continuously. It keeps every version it has made, and a deletion in a tool never reaches the archive.
It starts by answering one question, read-only: is anything on this machine deleting my history right now?
chat-stasher doctor coding-agent sessions ──(chat-stasher CLI, hourly)──┐
├─► stage ─► encrypt ─► your destination(s)
web chats ──(browser extension → local host)────────┘ (local disk · SFTP · R2)
There are two ways in, and both end in the same archive:
- The CLI reads each tool's own session files where the tool keeps them, and never modifies them. New data is sealed into a local folder called the stage, then encrypted into a repository at your destination.
- The browser extension sees web conversations as the platform's own API returns them. It hands each one to the same
chat-stasherbinary on your machine, which your browser starts as a small local host. It never sends them to a server.
A destination is a place the archive lives, such as a folder on this disk, an SFTP server or an R2 bucket. You can keep more than one. Each is a full, independently encrypted copy with its own key file.
macOS (Apple Silicon and Intel):
curl -fsSL https://chatstasher.com/install.sh | shThe script downloads a pinned release and checks its SHA-256 before installing. It puts chat-stasher in ~/.local/bin, never uses sudo, and prints the one line to add if that folder is not on your PATH.
Once the Homebrew tap formula has been merged and verified with a clean Mac install, use:
brew install dimpurr/tap/chat-stasherLinux and Windows: there is no prebuilt binary in a stable release yet. Both arrive with 0.5.0, a statically linked Linux binary for x86-64 and arm64 and an .exe for Windows. Until then, build from source with cargo build --release. docs/install.md covers each system, updating and uninstalling.
For web chats, the extension works alongside the CLI. It is not in any extension store yet: each release attaches chat-stasher-extension-X.Y.Z.zip.
-
Register the local host first. The extension delivers to it, and nothing else:
mkdir -p ~/stash/chat-stasher/stage chat-stasher install-native-host --stage ~/stash/chat-stasher/stage
The command prints every file it writes, and
--uninstallremoves exactly those. This half is per machine: one registration serves every browser and every profile on it, and--uninstalltherefore takes the delivery channel away from all of them at once. -
Download the zip from the latest release and unzip it into a folder you will keep.
-
Open
chrome://extensions, turn on Developer mode, click Load unpacked, and choose that folder. -
Reload any AI chat tab that was already open. Then open the extension's popup: it should say it can reach the host.
Do steps 3 and 4 in every browser profile you chat in. An extension belongs to one profile, so a copy in Chrome's Personal profile captures nothing in its Work profile. Each copy has its own queue and its own backfill settings; all of them deliver into the one stage above, so the archive is still one archive.
Past conversations are a separate, opt-in step: switch on backfill in the popup. Backfill works through an open, logged-in tab of that platform, and goes gently by default. Its daily cap is per install: three profiles with backfill on are three schedules, so the account can see about three times one install's rate. The full walkthrough is in docs/install.md.
With the CLI installed, this is the whole loop. docs/start.md walks through the same steps with explanations, and chat-stasher setup runs the first pass of it for you.
1. Write a config. init writes a fully commented ~/.config/chat-stasher/config.toml, and never overwrites one that exists.
chat-stasher init2. Archive once. With no remote configured yet, this archives to an encrypted repository on this disk. That already protects you from a tool deleting its own history.
mkdir -p ~/stash/chat-stasher/stage
chat-stasher run-once --stage ~/stash/chat-stasher/stageThe first run ends like this:
[push] INIT (new repository created) · masterkey created+persisted
[run-once] result: COMPLETED snapshot=created exit_code=0
Running it again when nothing changed prints result: NOOP and creates no new snapshot. That is the normal, healthy case.
Warning
Back up your key files now. The one this run created is ~/.local/share/chat-stasher/masterkey.json. If you lose it, the archive it opens can never be read again: there is no recovery code and no reset, and nobody can help. Keep a copy somewhere other than this disk, such as a password manager.
And there is one key per archive copy, not one key in total. Every destination you add gets its own file — ~/.local/share/chat-stasher/masterkey-<destination>.json — and another computer reads that copy with that file, never with the local one. Back up each of them; a backup holding only the key above recovers only this machine's local archive.
3. Keep it running. schedule writes an hourly timer file:
chat-stasher schedule --stage ~/stash/chat-stasher/stage \
--output ~/Library/LaunchAgents/com.chat-stasher.run-once.plistschedule renders the timer, but it never installs it. It prints the exact command that does, which on macOS is a launchctl bootstrap … line. Run that command. On macOS, chat-stasher schedule install runs that last step for you; on Linux, add --format systemd.
chat-stasher setup --install-schedule does that same install as the last step of the wizard, and says when the timer will next run: the scheduler's own answer where it has one — the next elapse systemd reports for the timer, or the local time a launchd calendar slot resolves to — and otherwise why there is no time to report, which is the case for a launchd interval job and for a timer systemd has not armed. A cadence is never printed as a timestamp.
4. Check on it any time.
chat-stasher statusThe first line is the verdict, for example [run-once] Healthy: last run 12 minutes ago, …, or the reason it is not healthy. status exits non-zero when the timer looks dead, and that includes "has never run".
5. Add an off-site copy. A local archive does not survive losing this disk. docs/destinations.md sets up R2, SFTP or a second local disk.
These tables are generated from the registry that ships inside the CLI and from the extension's own platform list, so they cannot drift from what the scanner reads and what the extension captures. Supported means a scanner path is documented or the extension captures that tool. Experimental means the platform is enabled in the development build only. A date in Last verified appears only where a real session was archived end to end on a maintainer's machine, with the date of that check recorded; every other row shows a dash, because these tools change their storage without notice and a permanent checkmark would be a claim nobody has re-made. For local tools, the full table scopes a verification to the OS where it was run; other OS cells keep their own confidence-based status. A date older than 90 days is shown as needs re-check (DATE) instead — the recorded run is real, but it has been long enough that nothing should rely on it being still true.
5+ platforms. Local AI tools, agent platforms, and web chats.
| Surface | Platform | Status | Last verified |
|---|---|---|---|
| Local | Claude Code | verified end-to-end (2026-09-25) | 2026-09-25 |
| Local | OpenAI Codex CLI | verified end-to-end (2026-09-25) | 2026-09-25 |
| Local | Gemini CLI | verified end-to-end (2026-09-25) | 2026-09-25 |
| Local | Google Antigravity | verified end-to-end (2026-10-03) | 2026-10-03 |
| Local | opencode | verified end-to-end (2026-09-25) | 2026-09-25 |
| Agent platform | OpenClaw | supported | - |
| Agent platform | Hermes Agent | supported | - |
| Local | Cursor | verified end-to-end (2026-09-25) | 2026-09-25 |
| Agent platform | Grok Bot (desktop) | supported | - |
| Local | Grok (xAI CLI) | verified end-to-end (2026-10-03) | 2026-10-03 |
| Local | GitHub Copilot CLI | supported | - |
| Local | aider | supported | - |
| Local | crush | supported | - |
| Local | Zed | verified end-to-end (2026-10-03) | 2026-10-03 |
| Local | Continue | supported | - |
| Local | Kimi Code | verified end-to-end (2026-10-03) | 2026-10-03 |
| Local | DeepSeek Harness | supported | - |
| Web | deepseek | verified end-to-end (2026-09-24) | 2026-09-24 |
| Web | perplexity | experimental | - |
| Web | chatgpt | verified end-to-end (2026-09-24) | 2026-09-24 |
| Web | gemini | verified end-to-end (2026-09-24) | 2026-09-24 |
| Web | claude | verified end-to-end (2026-09-24) | 2026-09-24 |
| Web | kimi | experimental | - |
| Web | grok | verified end-to-end (2026-09-24) | 2026-09-24 |
Verified means a maintainer archived a real session end to end on their own machine. Formats change, so a date is recorded instead of a permanent check; a date older than 90 days is shown as needs re-check (DATE).
Browsers. Native host registration, per browser and per OS:
| Browser | macOS | Linux | Windows |
|---|---|---|---|
| Chrome | supported | supported | supported |
| Chromium | supported | supported | supported |
| Edge | supported | supported | supported |
| Brave | supported | supported | supported |
| Arc | supported | no native build | supported |
| Chrome Beta | unverified | unverified | unverified |
| Chrome Canary | unverified | unverified | unverified |
| Opera | unverified | unverified | unverified |
| Vivaldi | unverified | unverified | unverified |
| Firefox | supported | supported | supported |
supported is the promised tier: the discovery path, and on Windows the registry key, is documented and install-native-host registers it. unverified is the best-effort tier: registration is attempted and reported, never promised. no native build means the browser itself ships no build for that OS, so there is nothing to register. Firefox is carried as supported outside these Chromium-family tiers, on paths from Mozilla's own documentation. One registration serves every profile of a browser on a machine; the extension itself still needs loading once per profile.
Two things the table cannot show:
- A tool's sessions may live somewhere else on your machine. Point
[harness_roots]in the config at the right path and the scanner looks there instead. A path that does not exist is reported as unknown, never as "0 sessions". - When backfill gets back an incomplete conversation, it records a failure. It never stores the partial copy as if it were whole. A tab that was open before the extension was installed or updated is not captured until you reload it; the popup tells you when this applies.
docs/support.md has the full tables: every operating system, the session path each tool is looked for at with the confidence behind it, and the destination each backend maps to.
| Destination | Status | Best for |
|---|---|---|
| Cloudflare R2 | ✅ Verified end to end (2026-09) | An off-site copy with a free tier (10 GB-month when the pricing page was checked, 2026-09-25) |
| SFTP (for example, a Hetzner Storage Box) | ✅ In daily use on three machines | An off-site copy on a server you rent |
| A local folder or disk | ✅ Supported | A single machine, or a second copy on an external disk |
Other backends: a rustic REST server (rest:) is accepted by the config but has not been verified, and other S3-compatible services take the same options as R2 without having been tested.
Declare a destination in ~/.config/chat-stasher/config.toml, then initialise it once:
chat-stasher dest-init --destination <name> --stage ~/stash/chat-stasher/stagedest-init seeds the new destination with this machine's history. That includes sessions your other destinations hold for this machine that it has since lost. After that, run-once --destination <name> keeps it current. Once any destination is declared, archive-reading commands need --destination <name>. There is deliberately no silent default; the one exception is ui, which opens your only destination when there is just one.
One step here needs a human. The first connection to a new SFTP server stops until you have compared its fingerprint with the one your provider publishes. chat-stasher never trusts a new host on its own.
docs/destinations.md has a section for each destination: config, credentials, the fingerprint step, and known pitfalls.
Install chat-stasher on each computer and point them all at the same destination. Each computer gets its own random identity on its first run, and writes only to its own part of the archive. Two laptops never overwrite each other, even if they share a hostname.
The dashboard shows every machine side by side, and status --destination <name> flags any machine running an older chat-stasher than the rest.
Several browser profiles on one computer are the same idea one level down, with one difference: they share that machine's identity, so their captures land in the same part of the archive. Install the extension in each profile you chat in, as above. A conversation that two profiles both captured is stored once only when the two deliveries are byte-identical; otherwise both are kept, because the archive records what was captured rather than deciding which profile was right.
Having several machines is also what makes one of them recoverable. Losing a laptop is not losing what it archived: any other machine that can reach the destination, with its key file, can read the conversations back, so Recover a lost machine's conversations is the page to read before you need it.
- Browse.
chat-stasher uiopens a dashboard on127.0.0.1, and needs no flag when the config leaves the choice unambiguous (one declared destination, or a default recorded). It shows totals, a machine × source matrix and a weekly heatmap, and you can drill into any cell. The list is sorted and paged on the server, and a conversation opens in a reader that fetches and decrypts that one session, printing the byte cost first. - Search inside conversations. The dashboard's
/searchruns a query against a local full-text index, whichchat-stasher index buildcreates from the archive into the operating-system cache directory. It matches literal substrings (including scripts without spaces) from three characters up. Each answer reports index coverage for the current view; sessions the index has not read, or could not re-read after changing, are called out rather than reported as no match.chat-stasher index cleardeletes the index. - Find.
chat-stasher searchfinds sessions by machine, tool and conversation date:--day 2026-01-15, or--since/--until. - Take out.
chat-stasher export --out <dir>writes exactly the sessionssearchfound as files in their native format, plus a checksummedmanifest.json.--dry-runshows the cost and writes nothing.--turns userfilters to the person's messages for Claude Code; for other tools it keeps every line and recordsturns_filter: "not-supported"in the manifest.--no-collapseincludes every stored shard when you need to inspect a repeated session.chat-stasher repair-duplicates --destination <name>inventories repeated shard runs without changing the archive. See the CLI guide for details.chat-stasher readprints a single session. - Recover a machine you no longer have. Everything above reads one destination and works without the computer that wrote it, so a sold or wiped laptop is a situation with a procedure rather than a loss: Recover a lost machine's conversations.
- Turn old conversations into a skill. Search inside them, export the ones that matter, and hand the files to an agent to distil a procedure or checklist out of: Turn old conversations into a skill.
There is no command yet that puts sessions back into a tool's own folder.
chat-stasher is built to be driven by scripts and agents that act on its answers:
| Exit code | search, export, ui |
status |
|---|---|---|
0 |
Answered completely | Timer healthy |
1 |
Read everything, found nothing (ui never returns this) |
Timer unhealthy, or it has never run |
3 |
Could not finish, so a "0" would be unproven | The scan itself did not complete |
2 |
Usage error | Usage error |
doctor --jsonandstatus --jsonprint exactly one JSON object. An unknown value is tagged{"kind":"unknown","why":…}, never written as0ornull.status --jsonalso carries alocallayer — whether the scheduler units are installed, the last run, and the staged sessions still waiting to upload — andoverview --json --summaryreturns aggregate totals, one record per machine and per source, and the last 30 local days, with no per-session array.statuswrites its human report to stderr, sostatus | headhides its exit code.run-onceis safe to run again at any time.- Never put credentials on the command line.
- Nothing is sent to us. There is no account, no telemetry and no server of ours. The CLI talks only to the destinations you configure. The extension talks only to the chat sites you already use and to the local host.
- Encrypted before it leaves your machine. Your storage provider sees encrypted objects. It can still see how much you back up, and when.
- You hold the only keys, and there is one per archive copy. Nobody can recover one for you, and nobody can read a copy without its key. Back up every key file: the local archive's, and each destination's.
- Append-only. Each run adds a snapshot. The only thing chat-stasher deletes is its own staged copy, and only after every destination proves it holds those bytes.
- Plaintext on your own disk. Three things sit on your disk unencrypted: captures waiting in the extension, staged shards, and the key file. Other programs running as you can read them.
- The extension has four permissions and no site host permissions. Backfill requests are made from inside your own open tabs.
Read docs/privacy-security.md before trusting chat-stasher with an archive you cannot recreate. To report a vulnerability, see SECURITY.md.
Does it change my tools' files? No. It reads session files where they are and copies what is new into its own stage. It never renames, truncates or deletes a file a tool owns.
Why not just back up the folder? A folder sync follows deletions, so when the tool deletes a session, your copy goes too. A general-purpose backup keeps old versions, but it cannot tell you which conversations exist, when they happened, or which machine they came from. It also does not cover web chats. chat-stasher keeps every version, lets you search conversations by date across tools and machines, and says plainly what it could not see.
Will backfill get my account flagged? Backfill is deliberately gentle: small batches spread through the day, under a daily cap, sent from your own logged-in tab. It is still automated traffic to a service whose terms you accepted, so leave it off if that worries you. Capturing the conversations you open costs little or nothing extra.
- No
restorecommand. Sessions come out withreadandexport, but nothing writes them back into a tool. - The extension is not in a store, and backfill is not yet verified end to end on any platform.
- No prebuilt Linux or Windows binary in a stable release yet; they arrive with 0.5.0.
- Text search uses a local plaintext index by default.
chat-stasher search --textreads the index built in the operating-system cache bychat-stasher index build; it searches indexed titles and conversation text, and can also match a session ID. Search does not compare indexed text with archive contents: after archive changes, the index reflects its last build until rebuilt. New selected sessions are reported as missing; if a changed session cannot be re-read during a build, its previous text may still match but the session is no longer counted as covered. With--scan, the CLI reads the selected conversations from the archive and searches their text directly, including queries shorter than the index's three-character minimum. Index-mode results report coverage for the selected sessions, while scan-mode results report sessions it could not read. See the CLI guide for details.
- Cloudflare R2 as the documented default remote.
- A small macOS menubar app showing how fresh each backup is.
- Platform and date facets on the search page.
- A per-platform coverage page in the extension (next extension release), and store listings.
| I want to… | Start here |
|---|---|
| Get my first archive working | docs/start.md |
| Do the first run as one wizard | docs/setup.md |
| Keep it running every hour | docs/schedule.md |
| Install on my system, update or uninstall | docs/install.md |
| Set up R2, SFTP or another disk | docs/destinations.md |
| Look up a command's flags and exit codes | docs/cli.md · chat-stasher <command> --help |
Look up a setting in config.toml |
docs/config.md |
| Fix something that looks wrong | docs/troubleshooting.md |
| Do a whole task, start to finish | docs/use-cases/ |
| See what is supported, and how sure we are | docs/support.md |
| Understand the pipeline and the archive | docs/how-it-works.md |
| Decide whether to trust it | docs/privacy-security.md |
| Work on the code | CONTRIBUTING.md · docs-dev/ |
Release notes are in CHANGELOG.md. The CLI and the extension are versioned separately.
Issues and pull requests are welcome. CONTRIBUTING.md covers the local checks and commit style. Please keep reports free of your own conversations, account names, hostnames and paths. A redacted doctor or status output is usually enough.
Licensed under the Apache License 2.0.