{"id":48777181,"url":"https://github.com/coredipper/enclaude","last_synced_at":"2026-04-17T14:02:19.391Z","repository":{"id":350752948,"uuid":"1203128023","full_name":"coredipper/enclaude","owner":"coredipper","description":"Encrypted git-like sync for ~/.claude/ session data","archived":false,"fork":false,"pushed_at":"2026-04-11T22:03:24.000Z","size":91,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-11T23:25:48.566Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coredipper.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-06T18:44:48.000Z","updated_at":"2026-04-11T21:46:10.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/coredipper/enclaude","commit_stats":null,"previous_names":["coredipper/enclaude"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/coredipper/enclaude","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coredipper%2Fenclaude","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coredipper%2Fenclaude/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coredipper%2Fenclaude/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coredipper%2Fenclaude/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coredipper","download_url":"https://codeload.github.com/coredipper/enclaude/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coredipper%2Fenclaude/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31753601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-13T09:16:15.125Z","status":"ssl_error","status_checked_at":"2026-04-13T09:16:05.023Z","response_time":93,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-04-13T13:07:19.320Z","updated_at":"2026-04-13T13:07:19.831Z","avatar_url":"https://github.com/coredipper.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# enclaude\n\nEncrypted, git-backed, cross-device sync for `~/.claude/`.\n\n## The Problem\n\nClaude Code stores everything in plaintext at `~/.claude/`:\n\n- **`history.jsonl`** — every prompt you've ever typed, timestamped\n- **Session JSONL files** — full conversation transcripts including tool calls, tool results, and any file content Claude read during the session\n- **Memory files** — project-specific context Claude remembers between sessions\n- **Settings and stats** — your configuration, usage patterns, plugin list\n\nThis means your `~/.claude/` directory contains a detailed record of your work: code snippets, error messages, file paths, environment variables, and anything else that appeared in a session. It sits on disk as readable text with no encryption, no signatures, and no tamper detection.\n\nThis isn't a bug — it's how every AI coding assistant works today (Cursor, Copilot, Windsurf all store history in plaintext too). But it means:\n\n1. **Anyone with access to your disk can read your full Claude history.** If your laptop is lost, stolen, or accessed by another user, all session data is exposed.\n2. **There's no way to sync sessions across devices.** Your history on your work laptop and your personal machine are completely separate.\n3. **There's no version history.** If a session file is corrupted or a memory file is overwritten, there's no way to recover a previous state.\n\n`enclaude` addresses all three.\n\n## What It Does\n\n`enclaude` sits between Claude Code and your filesystem. It doesn't modify Claude Code — it works alongside it using a two-directory architecture:\n\n```\n~/.claude/              plaintext (what Claude Code reads/writes)\n     |\n     |  seal (encrypt)\n     v\n~/.enclaude/         encrypted git repo\n  manifest.json         file index: path -\u003e SHA-256 hash, merge strategy\n  seal.toml             config: include/exclude patterns, device ID\n  key.age.backup        passphrase-encrypted key backup\n  objects/              content-addressed age-encrypted blobs\n     |\n     |  git push/pull\n     v\n  remote repo           synced across devices\n```\n\n**Seal** encrypts changed files from `~/.claude/` into content-addressed objects using [age](https://age-encryption.org/) (ChaCha20-Poly1305 + X25519). Each file is hashed (SHA-256) and encrypted individually. Only changed files are re-encrypted — unchanged files are skipped by comparing hashes.\n\n**Unseal** decrypts the objects back to `~/.claude/` so Claude Code can use them.\n\n**Git** provides the transport layer. The encrypted objects are committed to a git repository, giving you full version history, branching, and remote sync — all on encrypted data. Your plaintext never leaves your machine; only encrypted blobs are pushed.\n\n### Why This Works Well for Claude Data\n\nClaude Code's session files have a property that makes sync trivial: **they're immutable after completion**. Once a session ends, its JSONL file is never modified again. This means:\n\n- Two devices that both ran sessions produce different files with different hashes — no conflicts, just union both sides\n- `history.jsonl` is append-only — merging two diverged copies means deduplicating lines and sorting by timestamp\n- Memory files are small markdown — standard 3-way text merge handles them\n\nThe only files that need real merge logic are `settings.json` (last-write-wins) and `history.jsonl` (line-level dedup). Everything else is either immutable or trivially mergeable.\n\n## Quick Start\n\n```bash\n# Install\ngo install github.com/coredipper/enclaude@latest\n\n# Initialize — generates an age key, stores it in your OS keychain,\n# encrypts all ~/.claude/ data into ~/.enclaude/\nenclaude init\n\n# See what's changed since last seal\nenclaude status\n\n# Encrypt changes\nenclaude seal\n\n# Decrypt back to ~/.claude/\nenclaude unseal\n```\n\n### Set Up Cross-Device Sync\n\n```bash\n# Create a private repo for your encrypted data\n# (only encrypted blobs are pushed — your plaintext never leaves your machine)\nenclaude remote add origin git@github.com:you/enclaude-data.git\nenclaude push\n\n# On another device — clone the encrypted repo and import your key\ngit clone git@github.com:you/enclaude-data.git ~/.enclaude\nenclaude key import --from-backup   # or: enclaude key import keyfile.txt\nenclaude unseal\nenclaude hooks install\n```\n\n### Auto-Sync with Hooks\n\n```bash\nenclaude hooks install\n```\n\nThis adds `SessionStart` and `SessionEnd` hooks to `~/.claude/settings.json`. When a session starts, `enclaude` unseals the latest sealed data. When it ends, it seals changes locally. To enable automatic remote sync, set `auto_push = true` and `auto_pull = true` in `~/.enclaude/seal.toml`. Your existing hooks (peon-ping, notchi, etc.) are preserved — the installer appends to the hooks array, never overwrites.\n\n## Commands\n\n### Core\n| Command | Description |\n|---------|-------------|\n| `init` | Generate age keypair, store in OS keychain, initial seal |\n| `seal` | Encrypt changed files, commit to seal store |\n| `unseal` | Decrypt seal store to `~/.claude/` |\n| `status` | Show changes since last seal |\n| `sync` | Seal + pull + push (the daily driver) |\n| `push` | Seal + git push |\n| `pull` | Git pull + merge + unseal |\n\n### History \u0026 Recovery\n| Command | Description |\n|---------|-------------|\n| `log` | Show seal history with commit messages |\n| `diff [ref]` | Decrypt and diff between current state and a previous commit |\n| `rollback \u003cref\u003e` | Restore `~/.claude/` to a previous commit (creates a safety seal first, so you can always undo) |\n\n### Key Management\n| Command | Description |\n|---------|-------------|\n| `key show` | Display public key and source |\n| `key export` | Print private key to stdout (pipe to a password manager) |\n| `key import \u003cfile\u003e` | Import key from file, stdin (`-`), or `--from-backup` |\n| `key rotate` | Generate new key, re-encrypt all objects, update keychain |\n\n### Maintenance\n| Command | Description |\n|---------|-------------|\n| `repair` | Verify integrity and fix missing objects by re-sealing from plaintext |\n| `repair --check` | Verify-only mode (exit code 1 if issues found, useful for CI) |\n| `repair --delete-orphans` | Also remove unreferenced object files |\n| `hooks install` | Add auto-sync hooks to Claude Code settings |\n| `hooks remove` | Remove auto-sync hooks |\n| `hooks status` | Check if hooks are installed |\n\n## Merge Strategies\n\nWhen pulling from a remote, two devices may have diverged. `enclaude` uses a custom git merge driver that applies different strategies depending on the file type:\n\n| Strategy | Used for | How it works |\n|----------|----------|-------------|\n| `immutable` | Session JSONL files | Union both sides — sessions never change after completion, so there are no conflicts |\n| `jsonl_dedup` | `history.jsonl` | Parse each line as JSON, SHA-256 hash for dedup, sort by timestamp |\n| `sessions_index` | `sessions-index.json` | Deduplicate entries by `sessionId`, preserving unique sessions from both sides |\n| `last_write_wins` | `settings.json`, `stats-cache.json` | Keep whichever version has the later modification time |\n| `text_merge` | Memory files (`.md`) | Standard 3-way text merge; conflict markers if both sides changed |\n\nThese strategies are configurable per path pattern in `seal.toml`.\n\n## Configuration\n\n`~/.enclaude/seal.toml` controls what gets synced and how:\n\n```toml\n[seal]\nclaude_dir = \"~/.claude\"\nseal_dir = \"~/.enclaude\"\ndevice_id = \"macbook-ab12cd34\"\n\n[sync]\nauto_seal_on_session_end = true\nauto_unseal_on_session_start = true\nauto_push = false\nauto_pull = false\n\n[include]\npatterns = [\n  \"history.jsonl\",\n  \"settings.json\",\n  \"projects/*/*.jsonl\",\n  \"projects/*/memory/**\",\n  \"projects/*/subagents/**\",\n]\n\n[exclude]\npatterns = [\n  \"statsig/**\",       # feature flag caches — regenerated automatically\n  \"plugins/**\",       # 200+ MB of cached plugin data — regenerated\n  \"debug/**\",         # debug logs\n  \"hooks/**\",         # your hook scripts — version these separately\n  \"settings.local.json\",  # device-specific paths and permissions\n]\n\n[merge_strategies]\n\"history.jsonl\" = \"jsonl_dedup\"\n\"projects/*/*.jsonl\" = \"immutable\"\n\"settings.json\" = \"last_write_wins\"\n\"projects/*/memory/**\" = \"text_merge\"\n```\n\n## Security Model\n\n| Property | Status |\n|----------|--------|\n| Encrypted at rest (between sessions) | Yes — completed session plaintext can be shredded after seal |\n| Encrypted at rest (during active session) | No — Claude Code requires plaintext to function |\n| Encrypted in transit (git push/pull) | Yes — only age-encrypted blobs are pushed |\n| Key storage | OS keychain (macOS Keychain, Linux secret-service, Windows Credential Manager) |\n| Key backup | Passphrase-encrypted `key.age.backup` travels with the repo |\n| Tamper detection | SHA-256 content hashes in manifest; `repair --check` verifies integrity |\n| Key never in git | Correct — only the passphrase-encrypted backup is committed |\n\n### Honest Limitations\n\n- **During an active Claude Code session, plaintext exists on disk.** This is unavoidable — Claude Code reads `~/.claude/` directly and cannot be modified to read encrypted data. Use OS-level disk encryption (FileVault, BitLocker, LUKS) for protection during sessions.\n- **Application-level encryption does not protect against a malicious process running as your user.** If an attacker has code execution as your user, they can read decrypted files in memory or extract the key from the keychain. OS-level protections are the right defense layer here.\n- **The encryption key must be shared across devices.** This is inherent to any cross-device sync scheme. Use `key export` to save your key in a password manager, or rely on the passphrase-encrypted `key.age.backup` that travels with the repo.\n\n## How It Works Under the Hood\n\n### Content-Addressed Storage\n\nLike git itself, `enclaude` stores objects by their content hash. When you seal a file:\n\n1. Read the plaintext from `~/.claude/`\n2. Compute SHA-256 of the plaintext → this becomes the content address\n3. Encrypt the plaintext with your age public key\n4. Store the encrypted blob at `objects/\u003chash[0:2]\u003e/\u003chash[2:]\u003e.age`\n5. Record the mapping in `manifest.json`\n\nOn the next seal, unchanged files produce the same hash and are skipped entirely. Only new or modified files are encrypted. This makes incremental seals fast — typically under 1 second after a normal session.\n\n### Session Lifecycle\n\nWith hooks installed, the flow is:\n\n```\nSession starts → hook fires → pull latest + unseal\n    ↓\nClaude Code runs (reads/writes ~/.claude/ as normal)\n    ↓\nSession ends → hook fires → seal changes + push\n```\n\nThe hook handler acquires a file lock (`~/.enclaude/.seal.lock`) to prevent concurrent seal/unseal operations. If the lock can't be acquired within 5 seconds, the hook exits silently — it never blocks Claude Code.\n\n### New Device Onboarding\n\n```\n1. Install enclaude\n2. Clone your encrypted data repo\n3. Import your key (from password manager, file, or backup passphrase)\n4. Pull + unseal\n5. Install hooks\n```\n\nYour full Claude Code history, memory, and settings appear on the new device, encrypted in transit and at rest.\n\n## License\n\nMIT\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoredipper%2Fenclaude","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoredipper%2Fenclaude","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoredipper%2Fenclaude/lists"}