Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

opencode-snapshot

Automatic code snapshots and optional offsite backups for opencode workflows.

Why?

With agentic coding, changes pile up faster than you can commit them. You can't review and commit after every prompt, and you shouldn't have to. opencode-snapshot plugin gives you automatic undo, revert to any previous state without manual commits or fear of losing work.

How does it work?

On every prompt you submit, the plugin:

  1. Local — records a full-tree git snapshot into a dedicated ref (default refs/opencode/snapshots). Never touches HEAD, your branches, your index, or the working tree. Skips silently outside git repos.
  2. External — pushes source + .git to external repositories on a target path (any mounted location), throttled (default: max one push per 2 min) and serialized so rapid prompts never stack restic processes. Repos are auto-created on first use.
         YOU SUBMIT A PROMPT

                  │
                  ▼
      ┌──────────────────────┐
      │  chat.message hook   │
      │  (fires each prompt) │
      └──────────────────────┘
                  │
             ┌────┴────┐
             ▼         ▼
┌──────────────┐   ┌──────────────────────┐
│ LOCAL        │   │ EXTERNAL             │
│ snapshot     │   │ backup               │
│ (sync/await) │   │ (async, throttled)   │
└──────────────┘   └──────────────────────┘
        │                │
        ▼                ▼
┌────────────────────────────────────────┐
│ VERBOSE LOG (.opencode/snapshot.log)   │
│   + on failure: Desktop error + toast  │
└────────────────────────────────────────┘

Install

Copy index.ts into your global plugins directory, then restart opencode:

mkdir -p ~/.config/opencode/plugins
cp index.ts ~/.config/opencode/plugins/opencode-snapshot.ts

To enable it for a single project instead, copy it to that project's .opencode/plugins/opencode-snapshot.ts.

Usage

Local snapshots

git log --oneline refs/opencode/snapshots
git checkout <sha> -- path/to/file        # restore a file
git checkout <sha> -- .                   # restore everything

Snapshots live in each repo's own .git, so git push origin main never publishes them — they stay on your machine.

Target backups

restic -r <externalBackup>/<project>/src --insecure-no-password snapshots
restic -r <externalBackup>/<project>/src --insecure-no-password restore latest --target /tmp/out
# default externalBackup is /mnt/pendrive/opencode-backup; replace with your path

Version history & recovery

Every prompt becomes one snapshot in the local chain, so you can walk, diff, and restore any point-in-time state:

git log --oneline refs/opencode/snapshots      # per prompt: one commit each
git diff <sha>~1 <sha>                          # what changed at a given prompt
git log -p --all -- path/to/file                # file's full history across snapshots
git show <sha>:path/to/file                     # a file as it was at a prompt
git checkout <sha> -- path/to/file              # restore one file
git checkout <sha> -- .                         # restore everything to that prompt
  • Each snapshot commit records the full working tree including untracked files (only excluded paths and .gitignored files are left out).
  • Snapshots are per-project, isolated in that repo's .git, and never pushed by git push origin main.
  • Because each snapshot is a full tree, you can always restore exactly what the code looked like before a given prompt — no partial/broken state.
  • Offsite copies live in the restic repos (src / git); restore the latest of either via the restic restore ... --target commands above.

Configuration

Global config at ~/.config/opencode/snapshot.json — every key is optional and has a sensible default. Only include what you want to change. Per-project overrides in <project>/.opencode/snapshot.json.

All keys are optional. The defaults below are what you get with zero config — localSnapshots on, externalBackup off, restic throttled every 2 minutes, 50 source / 20 git snapshots retained, prompts and verbose log enabled. Only add a key when you want something different.

{
  // ── Local snapshots ──────────────────────────────────────────────
  "localSnapshots": true,            // false = disable entirely
  "refName": "refs/opencode/snapshots", // git ref for the chain
  "skipIfUnchanged": true,           // skip when tree == last snapshot

  // ── External backups ─────────────────────────────────────────────
  // Needs restic installed and target path mounted; omit all to stay local-only.
  "externalBackup": "/mnt/pendrive/opencode-backup", // target path; omit = disabled
  "minIntervalSec": 120,             // max pushes per 2 min; 0 = every prompt
  "exclude": ["node_modules", ".cache", "dist", "target"],

  // ── Layout ───────────────────────────────────────────────────────
  "separateRepos": true,             // true: <root>/<project>/{src,git}
                                     // false: <root>/<project>/all
  "backupGit": true,                 // false = skip .git (separateRepos only)

  // ── Retention ────────────────────────────────────────────────────
  // Old snapshots pruned automatically. Only the keys matching your layout apply.
  "keepLast": 50,                    // mixed repo (separateRepos: false)
  "keepLastSource": 50,              // src repo (separateRepos: true)
  "keepLastGit": 20,                 // git repo (separateRepos: true)

  // ── Logging ──────────────────────────────────────────────────────
  // Error log: one file per failure on Desktop. Verbose log: always in project and included in restic src backup.
  //   [ok local] <iso> | opencode pre-prompt | <sha> | <prompt>   (prompt pipe-escaped to ¦, empty → trailing |)
  //   [ok src|git]   (or src fail|git fail + [error] detail lines + Desktop file + toast on failure)
  // Path variables: {project}, {timestamp}, {date}, {hour}, ~/
  "logErrorPath": "~/Desktop/snapshot-{project}-ERROR-{timestamp}.txt",
  "logVerbosePath": "<project>/.opencode/snapshot.log",
  "logIncludePrompt": true,          // include prompt text (no token cost)
  "notifyOnError": true,             // TUI toast on failure

  // ── Backend commands (restic by default) ─────────────────────────
  // All four use {repo}/{what}/{excludes}/{keep} placeholders where applicable.
  // Omit a key for the restic default; set to "none" to disable that step.
  // exclude[] only feeds the default backupCommand — custom commands own their excludes.
  // "initCommand": "restic -r {repo} --insecure-no-password init",
  // "checkCommand": "restic -r {repo} --insecure-no-password cat config",
  // "backupCommand": "restic -r {repo} --insecure-no-password backup {what} --tag opencode-pre-prompt {excludes}",
  // "retentionCommand": "restic -r {repo} --insecure-no-password forget --keep-last {keep} --prune"
}

Omit a key entirely to use its default.

Custom backends

Replace any step with a different tool by overriding its command string. Placeholders: {repo} (quoted target path), {what} (source: . or .git), {excludes} (only in default backupCommand), {keep} (retention count). Set a command to "none" to skip it.

{
  // borg example — same shape, different tool
  "checkCommand": "borg info {repo}",
  "initCommand": "borg init --encryption=none {repo}",
  "backupCommand": "borg create {repo}::opencode-{now} {what}",
  "retentionCommand": "borg prune --keep-last {keep} {repo}"
  // rsync example — needs no init/check/retention
  // "checkCommand": "none",
  // "initCommand": "none",
  // "backupCommand": "rsync -a --delete {what}/ {repo}/",
  // "retentionCommand": "none"
}

Requirements

  • git (for local snapshots)
  • restic by default when externalBackup is set; swap via command strings above for other tools
  • Target path must be a mounted filesystem; missing mounts are skipped with a warning

License

MIT

About

OpenCode plugin for automatic project snapshots at every prompt, optional offsite backup.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages