Skip to main content
Glama

preimage

ci npm Node.js License Tests

The undo layer for AI agents.

Your agent is one confident rm -rf away from deleting a table, rewriting a config, or "helpfully" migrating a database. git can't save you: those changes never entered a commit, and they often aren't files at all.

preimage writes a checkpoint before the work starts, then puts everything back byte-for-byte — including database rows git has never heard of.

preimage rolling back an agent's file edits

$ preimage checkpoint "before the agent gets creative"
checkpoint 0001 created
  4 files, 893 B

$ # the agent quietly changes the port and the greeting, and leaves a stray file
│ config.js rewritten, DEBUG.md added

$ # the project's own tests notice
✖ 2 failing, 0 passing

$ preimage diff 1
diff against checkpoint 0001
  1 added
  1 modified
  0 deleted
  3 unchanged
    config.js
    DEBUG.md

--- /dev/null
+++ b/DEBUG.md
@@ -0,0 +1 @@
+# scratch notes, will delete later

--- a/config.js
+++ b/config.js
@@ -1,5 +1,5 @@
 export const config = {
 	name: "hello-service",
-	port: 3000,
-	greeting: "Hello",
+	port: Number(process.env.PORT) || 8080,
+	greeting: "Hi",
 };

$ preimage restore 1 --purge --yes
restored checkpoint 0001 (before the agent gets creative)
  1 files written
  3 already identical
  1 new files removed

$ # tests again, after the rollback
✔ 2 passing, 0 failing

$ and the stray file
│ DEBUG.md: gone

The project's own tests go from failing to passing, and the scratch file the agent left behind is gone.

Now the part git can never do:

preimage rolling back a destructive database migration

$ preimage checkpoint "before migration" --db app.db
checkpoint 0001 created
  1 files, 8.0 KB
  2 rows across 1 database(s)

$ # a migration drops a user, promotes another, invents a third
$ preimage diff 1
diff against checkpoint 0001
  0 added
  0 modified
  0 deleted
  0 unchanged
  rows: 1 missing, 1 changed, 1 extra

$ preimage restore 1 --remove-extra
restored checkpoint 0001 (before migration)
  0 files written
  0 already identical
  2 rows written
  1 rows removed

$ sqlite3 app.db "SELECT * FROM users"
[{"id":1,"email":"ada@org.org","role":"admin"},
 {"id":2,"email":"bob@org.org","role":"staff"}]

ada is an admin again. bob is back. The ghost row is gone. No commit, no git checkout, no hand-written UPDATE.

And when the agent drops a whole table, the schema comes back with it:

$ preimage checkpoint "before the schema change" --db app.db
checkpoint 0001 created
  1 files, 12 KB
  2 rows across 1 database(s)

$ # the agent decides users is no longer needed
$ preimage diff 1
diff against checkpoint 0001
  0 added
  0 modified
  0 deleted
  0 unchanged
  rows: 1 missing, 0 changed, 0 extra
  table dropped: app.db:users

$ preimage restore 1 --yes
restored checkpoint 0001 (before the schema change)
  0 files written
  0 already identical
  1 tables recreated
  2 rows written

$ sqlite3 app.db ".tables"
invoices users

DROP TABLE users is undoable. preimage stored the CREATE statement next to the rows, so recreating the table is part of the restore rather than a separate recovery ritual.

Run the whole demo yourself:

git clone https://github.com/muraa-p/preimage && cd preimage
node scripts/demo.mjs

Install

npm install -g @muraa-p/preimage

Zero dependencies. Node 22.5+ (it uses the built-in node:sqlite, so there is no native build step and no supply chain to audit).

Then wire it to your agent — see Agent integration.

If you're on Windows

Two things that are not preimage's fault, and that will otherwise look like it is:

PowerShell blocks the command. npm installs a .ps1 shim, and the default ExecutionPolicy refuses to run it:

preimage : File ...\npm\preimage.ps1 cannot be loaded because running scripts is
disabled on this system.

Either call preimage.cmd instead (preimage.cmd checkpoint), or widen the policy once:

Set-ExecutionPolicy -Scope CurrentUser RemoteSigned

Node cannot spawn the .cmd without a shell. If you are calling preimage from a script rather than a terminal, execFile("preimage", ...) fails with EINVAL. Use shell: true, or point at the entry point directly:

const bin = "C:/Users/You/AppData/Roaming/npm/node_modules/@muraa-p/preimage/bin/preimage.js";
execFileSync(process.execPath, [bin, "checkpoint", "before the refactor"]);

MCP is unaffected by all of this — the client launches the server, not your shell.

Related MCP server: undo

Why this exists

The tooling around coding agents is very good at watching them. Session logs, traces, prompt caches, token dashboards, eval harnesses — all of it observes what the agent did. Almost nothing helps you undo it.

git

preimage

Untracked files

No

Yes

.gitignored files

No

Yes

Database rows

No

Yes

Works outside a repo

No

Yes

Needs git add + commit discipline

Yes

No

Reverts agent-created junk

No

Yes, with --purge

Preimage is not a backup tool and not a VCS. It is the write-ahead log for the one window where a non-human is editing your machine.

Usage

preimage init                          # create the journal
preimage checkpoint "before refactor"  # snapshot now
preimage list                          # see checkpoints
preimage diff                          # what changed since the latest
preimage diff 3                        # ...since a specific one
preimage restore 3                     # put it back

diff doesn't just name the files. It prints a real unified diff, byte for byte what git diff would print, so you can read the damage instead of opening each file to find it:

$ preimage diff
diff against checkpoint 0001
  1 added
  1 modified
  0 deleted
  2 unchanged
    app.js
    SCRATCH.js

--- a/app.js
+++ b/app.js
@@ -1,4 +1,8 @@
-export function greet(name) {
-  return `hello ${name}`;
+export function greet(name, greeting = 'hi') {
+  const msg = `${greeting} ${name}`;
+  console.log(msg);
+  return msg;
+}
+
+export function farewell(name) {
+  return 'bye';
 }

--no-hunks gives you just the file list. Binary files and files too large to diff line-by-line are reported with the reason rather than skipped silently.

diff and show default to the most recent checkpoint. restore always wants an explicit id, because guessing wrong there is expensive.

Every command takes --json for machine-readable output. diff --json puts the patches in a patches array, each with path, added, removed and the diff -u text.

Options that matter

--root <dir>       Project root (default: cwd)
--db <path>        Capture a SQLite database too (repeatable)
--table <name>     Limit to specific tables (repeatable)
--max-bytes <n>    Skip files larger than this (default 10 MB)
--no-hunks         diff: file names only, no line-level patches
--dry-run          Report what restore would do, change nothing
--purge            Also delete files created after the checkpoint
--remove-extra     Also delete rows created after the checkpoint
--yes              Skip the confirmation prompt

Agent integration

MCP — this is the path, and it is agent-agnostic

preimage mcp speaks the Model Context Protocol over stdio, so it works with every MCP client, not just Claude. That includes OpenAI Codex and ChatGPT, Cursor, VS Code and Copilot, Windsurf, Gemini CLI, Zed, Cline, Roo Code, Kilo Code, Amazon Q and Claude Desktop. If your agent speaks MCP, preimage plugs in.

Most clients take the same JSON:

{
  "mcpServers": {
    "preimage": { "command": "preimage", "args": ["mcp"] }
  }
}

Where each one wants it:

Agent

How to add it

Claude Code

claude mcp add preimage -- preimage mcp

OpenAI Codex

codex mcp add preimage -- preimage mcp, or a [mcp_servers.preimage] table in ~/.codex/config.toml

Gemini CLI

gemini mcp add preimage preimage mcp, or mcpServers in ~/.gemini/settings.json

Cursor

.cursor/mcp.json, using the mcpServers key above

VS Code / Copilot

.vscode/mcp.json — the key is servers, not mcpServers

Windsurf

~/.codeium/windsurf/mcp_config.json

Zed

.zed/settings.json, under context_servers

Claude Desktop

claude_desktop_config.json

Clients that launch the server in the wrong working directory will snapshot the wrong tree, so pass the path explicitly if that happens:

"preimage": { "command": "preimage", "args": ["mcp", "--root", "/path/to/project"] }

Four tools:

  • preimage_checkpoint — snapshot before risky work

  • preimage_diff — what changed since a checkpoint, with a unified diff per file

  • preimage_restore — roll back

  • preimage_list — what's recoverable

preimage_restore requires an explicit confirm: true, and refuses otherwise. An agent mid-task should not be able to undo your working state by accident; it has to ask.

preimage_diff returns the patches, so the agent sees the lines it changed rather than being told a file changed and having to go read it. The patch text is capped at a budget; if a refactor exceeds it, the response says how many files were left out rather than letting the agent conclude they were untouched. Pass includePatches: false when you only want the file names.

Hooks (automatic)

Snapshotting before every single edit is correct but wasteful, so the bundled hooks debounce: one checkpoint per 120 seconds. The first write in a burst captures the state you actually want to return to.

preimage hook install claude-code    # prints a settings.json fragment
preimage hook install opencode       # prints a plugin command

Merge the printed fragment into ~/.claude/settings.json. The hook never fails your session — if the checkpoint can't be taken, the edit proceeds anyway.

Set PREIMAGE_SESSION_WINDOW=0 to snapshot on literally every write.

This is the one part that is not agent-agnostic: hooks are wired for Claude Code and OpenCode only. Every other agent is covered by MCP above — the difference is that a hook snapshots without the model asking, so on the other agents you either rely on the model calling preimage_checkpoint or wrap your own command chain.

The bundled hook scripts are bash. On Windows they need WSL or Git Bash on your PATH; MCP has no such requirement and is the path to use there. If you want hook behaviour on Windows, the script is four lines — call preimage checkpoint --root <dir> from whatever your harness runs before a write.

The GIFs above were recorded before diff printed line-level hunks, so they show the file list without the patch. The transcripts below them are regenerated from real runs and are current. Re-recording the GIFs needs vhs, ffmpeg and ttyd; the tapes are in tape/.

Safety design

This tool deletes things, so the defaults are conservative.

  • Nothing destructive without a flag. restore only rewrites files it recorded. Deleting agent-created files requires --purge; deleting agent-created rows requires --remove-extra.

  • Confirmation before you lose data. Interactive restores prompt first. Agent restores need confirm: true.

  • Big files are skipped, never blanked. Files over --max-bytes are recorded as existing but their content is not stored. On restore preimage leaves them completely alone rather than overwriting them with nothing.

  • Writes are atomic. Every restore goes through a temp file and a rename, so a crash mid-restore cannot leave a half-written file.

  • A checkpoint is all-or-nothing. The row, the file entries and the database rows are written in one transaction. A checkpoint that dies partway through leaves nothing behind rather than an empty row that looks restorable. Anything unfinished is refused outright, because a restore that reports success without restoring anything is the one failure this tool cannot have.

  • Parallel agents do not collide. Several preimage checkpoint processes can run at once; they queue on the journal rather than failing on a lock.

  • Path traversal is blocked. A tampered journal record cannot write outside the project root.

  • Table scope is explicit. preimage never snapshots an entire database silently. You name the tables, or you opt in per database. And a database you captured with --db is restored table by table, never as a file — that's the only way --table users can leave audit_log alone.

  • A dropped table comes back. The CREATE statement is stored alongside the rows, so DROP TABLE users is undoable, schema included.

How it works

A checkpoint is a content-addressed snapshot in a SQLite journal at <root>/.preimage/journal.db.

checkpoints   id, created_at, label, source, status
files         checkpoint_id, path, kind, mode, size, sha
blobs         sha, bytes            -- deduplicated by content hash
db_tables     checkpoint_id, db_path, table_name, ddl
db_rows       checkpoint_id, db_path, table_name, pk, row_json

Blobs are keyed by sha256, so a file that appears in twenty checkpoints is stored once. Restore looks up the blob and writes it back; identical files are skipped, which makes restore idempotent and fast. preimage gc drops blobs no longer referenced.

Database rows are stored as JSON keyed by primary key (falling back to rowid), with BigInt and BLOB columns tagged so they round-trip exactly. Each table's CREATE statement is stored too, which is what lets restore rebuild a table the agent dropped. Restores run inside a transaction per table.

A database captured with --db is handed to the table adapter and removed from the file layer's work list. Without that, restoring --table users would rewrite the whole .db file and silently revert every table you never asked about.

Add .preimage/ to .gitignore.

Limitations

Worth knowing before you rely on it:

  • SQLite only for database capture. Postgres and MySQL adapters are the obvious next step; the dbadapter.js interface is small.

  • Not a backup. The journal lives next to your project. It protects you from the agent's mistakes, not from your disk failing.

  • Restore is a snapshot, not a merge. You get the state at the checkpoint, not a three-way diff. If you changed something by hand after the checkpoint and want to keep it, restoring loses it.

  • First 10 MB per file. Larger files are tracked but not restorable.

  • Single-writer. The journal is not designed for concurrent restores from several processes at once.

Development

npm test          # 153 tests, node:test
npm run check     # syntax check every entrypoint
node scripts/demo.mjs             # all three stories, instant
node scripts/demo.mjs --story=1   # just one story

CI runs the suite on Linux, macOS and Windows across Node 22 and 24, and additionally installs the packed tarball into a clean project to check that the real binary works from a real install.

Re-recording the demos

The GIFs in this README come from tape/, recorded with vhs. Needs vhs, ffmpeg and ttyd on PATH.

vhs tape/files.tape        # -> docs/demo-files.gif
vhs tape/database.tape     # -> docs/demo-database.gif

Both record scripts/demo.mjs with --paced, so the text in the GIFs is generated by the same code path quoted above. If you change the demo, re-record and update the transcripts here in the same commit.

Contributing

Issues and PRs welcome. Keep it dependency-free — that constraint is the whole security argument for a tool that sits in the agent's hot path. Destructive behaviour stays behind a flag, and every bug fix ships with the test that fails without it.

See CONTRIBUTING.md. Vulnerabilities go to SECURITY.md, not the issue tracker.

License

MIT

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    C
    maintenance
    Auto-snapshot tool for AI agents. Reduces the risk of AI agents breaking your code by automatically saving a snapshot of every file change locally. Rewind sessions, audit code changes, and inspect diffs directly from your AI chat or the built-in dashboard. Free and safe no cloud run local.
    1
    -
  • A
    license
    A
    quality
    C
    maintenance
    Provides checkpoint and rollback capabilities for AI agents, reversing file system changes and recording network mutations.
    16
    10 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI coding agents to checkpoint workspace state, branch parallel attempts, inspect diffs, and roll back to known-good states mid-task through MCP tools, with automatic safety checkpoints to prevent data loss.
    5 npm
    MIT