Metadata-Version: 2.4
Name: snapback-cli
Version: 0.2.0
Summary: Undo for your machine: snapshot working tree, databases, and container volumes before a risky command; roll back with one command.
Author-email: Sophie Nguyễn Thu Thuỷ <sophie.nguyenthuthuy@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/sophie-nguyenthuthuy/snapback
Keywords: snapshot,undo,rollback,backup,agents,migrations
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Archiving :: Backup
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# snapback — undo for your machine

```
snapback run -- <any command>    # cheap snapshot first, then run it
snapback undo                    # didn't like it? put everything back
```

We hand filesystem access to AI agents, migration scripts, and codemods every
day — and the rollback story below "restore the whole VM" is usually *nothing*.
`git checkout` doesn't cover your SQLite file, your untracked uploads dir, or
the Postgres volume your migration just mangled.

snapback is that missing layer: **one command to checkpoint the working tree,
your databases, and your container volumes — and one command to snap back.**

## Quickstart

```bash
pip install snapback-cli

snapback run -- python migrate.py     # snapshot, then run
snapback undo                         # roll back the tree (and undo is undoable: `snapback redo`)

snapback snap -m "before letting the agent loose"   # manual checkpoint
snapback list                         # what can I go back to?
snapback diff                         # what changed since the last snapshot?
snapback gc --keep 5                  # prune old snapshots
```

No config needed — by default snapback snapshots the working tree (minus
`.snapback/` itself). Zero dependencies, Python ≥ 3.11.

## Beyond files: databases and volumes

Drop a `snapback.toml` in the project root to declare extra targets:

```toml
[snapshot]
ignore = ["node_modules", "*.log", "app.db"]   # skip in tree snapshots

[[target]]
backend = "sqlite"          # consistent even while the app holds the db open
path = "app.db"             # (uses SQLite's online backup API, not file copy)

[[target]]
backend = "docker-volume"   # tar the named volume via a helper container
volume = "pgdata"
```

Every `snapback run` / `snap` / `undo` now covers all three: tree + db +
volume, atomically bookended around your risky command.

## How it works

- Snapshots live in `.snapback/snapshots/<id>/` with a `meta.json` recording
  the command, targets, and per-backend payloads.
- `undo` restores the newest snapshot — but first it saves the *current* state
  as a `pre-undo` snapshot, so `redo` gets you back. Undo is never a trap.
- `run` passes the wrapped command's exit code through untouched, so it slots
  into scripts and CI: `snapback run -- make deploy-staging`.
- `diff [id]` shows what changed since a snapshot (`A`/`D`/`M` per path) —
  check what the agent actually did before deciding whether to undo it.
- Backends are one file each in `snapback/backends/`, discovered
  automatically. `snapback backends` lists what your install supports.

## Storage backends

| Backend | Status | Strategy |
|---|---|---|
| `copytree` | ✅ shipped | portable file copies; works everywhere, no privileges |
| `sqlite` | ✅ shipped | SQLite online backup API; consistent under live connections |
| `docker-volume` | ✅ shipped | tar via throwaway alpine container |
| `apfs` | ✅ shipped | `tmutil localsnapshot` on macOS — O(1), copy-on-write; run `sudo -v` before restore, snapshots auto-purge in ~24 h |
| `btrfs` | 🙋 help wanted | `btrfs subvolume snapshot` — O(1), copy-on-write |
| `zfs` | 🙋 help wanted | `zfs snapshot` / `zfs rollback` |
| `overlayfs` | 🙋 help wanted | run the command in an overlay, commit or discard |
| `postgres` | 🙋 help wanted | `pg_dump` / restore, or template-database clone |
| `mysql` | 🙋 help wanted | `mysqldump` / restore |
| `restic` | 🙋 help wanted | dedup'd remote snapshots via restic repo |

**A backend is one file.** It exposes `NAME`, `snapshot(spec, payload_dir,
root) -> payload`, and `restore(spec, payload_dir, root, payload)` — see
[snapback/backends/sqlite_db.py](snapback/backends/sqlite_db.py) for the
shape (60 lines). One backend or one integration per PR; see
[CONTRIBUTING.md](CONTRIBUTING.md).

## Integrations

- **[agentbox](https://github.com/sophie-nguyenthuthuy/agentbox)** — `agentbox
  run --checkpoint -- python agent.py` takes a snapback snapshot right before
  the sandboxed agent's first mutating effect and records the checkpoint id in
  agentbox's hash-chained trace; `snapback undo` reverts the whole run. Tip:
  set `ignore = ["trace.jsonl"]` in `snapback.toml` so rollback never
  truncates the audit trail.
- Your agent framework here — a pre-tool-use hook is ~30 lines (snapshot
  lazily on the first mutating tool call, surface `snapback undo` on exit);
  PRs welcome.

## What snapback is not

- Not a backup tool — snapshots are local, short-lived checkpoints, not
  disaster recovery. `gc` them freely.
- Not filesystem magic by default — the portable backend copies files. Switch
  the tree target to `apfs` (or a future `btrfs`/`zfs`) for O(1) CoW snapshots.
- Not a sandbox — the command really runs. snapback makes it *reversible*,
  not contained.

## License

MIT
