Carry your dev hearth between machines.
hearth is a cross-platform CLI that keeps the configuration of your developer tools in sync across every machine you use — Mac, Linux, Windows — through a private git repo that you own. No sync server, no third-party cloud, no telemetry. Just you, your machines, and GitHub.
It was built because setting up a new laptop — restoring your Claude Code agents, your VSCode settings, your shell aliases, your Gemini CLI config, your git preferences — is a day of friction that nobody should have to do twice.
Project status: Phase 1 (local CLI loop works for Claude Code; additional profiles, 3-way merges, automatic scheduling, and the VSCode extension are on the roadmap — see Roadmap below).
- Why hearth
- What it does
- What it explicitly does NOT do
- How it works
- Install
- First-time setup
- Daily use
- Configuration
- Security model
- Command reference
- Troubleshooting
- FAQ
- Roadmap
- Contributing
- License
If you use modern developer tools (Claude Code, VSCode, Gemini CLI, a customized shell, etc.), your personal setup is now a meaningful artifact:
- Custom Claude Code subagents you've hand-crafted over weeks.
- Skills, memories, commands in
~/.claude/. - VSCode settings, keybindings, snippets tuned to your workflow.
- A shell RC full of aliases and functions.
- Git aliases,
ghCLI config, tool-specific preferences.
Today all of this lives in one place: the laptop in front of you. When you get a new machine, wipe a drive, or switch between work and home, you lose it — or you spend hours manually reassembling it, guessing at what's missing.
Existing solutions don't fit:
- Dotfile managers (chezmoi, yadm, rcm) are great for shell rc files but assume your full home directory is version-controlled. They don't handle secrets scrubbing, don't understand JSON structural merges, don't re-inject API keys from a keychain, and have no story for tools like Claude Code whose config includes machine-generated artifacts.
- VSCode Settings Sync is VSCode-only.
- Cloud-based config sync tools require you to trust a vendor with the contents of your
.zshrc.
hearth is a single, opinionated tool that covers the whole surface: AI CLI tools, editor, shell, git — via a git repo that you own and can delete at any time.
Out of the box, hearth is aware of these tools and knows the right files to sync for each:
| Tool | What gets synced | What stays local |
|---|---|---|
| Claude Code | CLAUDE.md, agents/, skills/, team-memory/, commands/, installed-plugins list, settings.json (scrubbed) |
.credentials.json, session data, cache, project history, telemetry |
| VSCode (Phase 3) | settings.json, keybindings.json (per-host), snippets/, installed-extension list |
Extension binaries (reinstalled from the list), per-workspace state |
| Gemini CLI (Phase 3) | Config directory | Cached auth tokens |
| Shell (Phase 3) | .zshrc / .bashrc — shared core + per-host fragment |
Secret-bearing aliases, private env-var blocks |
| Git (Phase 3) | Aliases and shared [core] config |
Per-host user.email, signing keys |
| GitHub CLI (Phase 3) | ~/.config/gh/config.yml |
Auth tokens |
You can extend this: every synced file is declared in a manifest.yaml inside your data repo. Adding a new tool is a matter of editing that file.
hearth will never sync:
- SSH private keys (
~/.ssh/id_*). If your data repo is ever exposed, you don't want your SSH keys in it. Regenerate per machine. - GPG keys (
~/.gnupg/). Same reason. - Claude's
.credentials.json. Re-runclaude loginon a new machine. .env,.env.local. Secrets in files live with the project.*.pem,*.p12,*.pfx,*.derand any path containingprivate/. Hard-coded never-sync list.- API keys inside config files. Declared secrets are scrubbed at push, replaced with
<<HEARTH:…>>placeholders, and re-injected on apply from your OS keychain. They never enter git history.
This list is hard-coded in the tool and cannot be overridden by the user's manifest. Even if you try, hearth will block it.
┌────────────┐ push (scrubbed) ┌────────────────────┐
│ Your local │ ────────────────────────▶ │ Your private git │
│ configs │ │ repo on GitHub │
│ │ ◀──────────────────────── │ (ONLY you have │
└────────────┘ pull + apply │ access) │
│ (re-inject secrets) └────────────────────┘
│ ▲
OS keychain │
(secrets vault) │ pull + apply
┌──────┴──────┐
│ Another │
│ machine │
└─────────────┘
- You keep your configs on each machine as normal.
hearth syncreads them, scrubs declared secrets, writes them into a local clone of your private data repo, and pushes.- On another machine,
hearth syncpulls, writes the configs atomically to disk, and re-injects your secrets from that machine's keychain. - GitHub is just a durable filesystem and an audit log — it has no idea what
hearthis.
There is no hearth server. The only external service involved is git talking to your private repo.
hearthrequires Node.js 20 or newer. Check withnode --version.
- Node.js 20+ — nodejs.org or
nvm/fnm. - git — comes preinstalled on most systems.
- GitHub account with permission to create a private repo.
- (Recommended) GitHub CLI (
gh) — makes auth and repo creation one command. cli.github.com.
# via Homebrew (recommended)
brew install node git
brew install gh # optional but recommended
# install hearth — published to npm once v0.1.0 releases
npm install -g hearth
# Today (building from source)
git clone https://github.com/yetesfa/hearth ~/Documents/hearth
cd ~/Documents/hearth
npm install && npm run build && npm linkAfter npm link, the hearth binary is on your PATH.
If npm link complains about permissions, see Troubleshooting.
# Debian/Ubuntu
sudo apt install -y nodejs npm git
# or, for an up-to-date Node
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Fedora/RHEL
sudo dnf install -y nodejs git
# Arch
sudo pacman -S nodejs npm git
# GitHub CLI (optional)
# Follow https://cli.github.com/manual/installation
# install hearth
npm install -g hearth # once published
# or, from source:
git clone https://github.com/yetesfa/hearth ~/Documents/hearth
cd ~/Documents/hearth
npm install && npm run build && npm linkLinux-only note: for the automatic scheduler (Phase 4 — hearth install) to run when you're logged out, hearth will run loginctl enable-linger $USER. This requires one-time sudo access and is opt-in.
Windows supports are in two flavors:
Option A — Native PowerShell (recommended for most users):
# Install Node.js 20+ from https://nodejs.org or:
winget install OpenJS.NodeJS.LTS
winget install Git.Git
winget install GitHub.cli # optional
# install hearth
npm install -g hearth
# or from source:
git clone https://github.com/yetesfa/hearth $HOME\Documents\hearth
cd $HOME\Documents\hearth
npm install; npm run build; npm linkOption B — WSL (if you live in a Linux shell on Windows): follow the Linux instructions inside your WSL distro. hearth will then sync the Linux side of your machine, not the Windows side — choose one or the other depending on where your configs actually live.
Windows-only notes:
- Paths use
%APPDATA%\Code\User\for VSCode instead of~/.config/Code/User/—hearthhandles this translation automatically. - The scheduler uses Windows Task Scheduler at user-level (no admin elevation). Registered at logon.
Two different flows depending on whether this is the first machine you're setting hearth up on, or you're adding another machine to an existing setup.
This is for your first machine with hearth.
# Authenticate to GitHub once (if you installed gh)
gh auth login
# Run the guided wizard
hearth initThe wizard will:
- Briefly explain what
hearthdoes and what it won't sync. - Offer to create a private data repo for you (via
gh repo create) — pick a name likepersonal-configs. You can also point at an existing empty private repo you already made. - Detect which tools are installed on this machine and let you opt-in per profile.
- Write a
manifest.yamlinto the data repo describing what will be synced. - Show a dry-run: every file that would be pushed, with any secret redactions preview.
- On your confirmation, do the real first push.
- Offer to register the automatic scheduler (Phase 4).
After this, your data repo contains your current configs (scrubbed) and you're ready to set up your second machine.
Note: in the current Phase-1 build the interactive wizard is a stub. For now, pass
--from <url>(see Flow B) with a repo you created on GitHub.
This is for every machine after the first.
# Authenticate first
gh auth login
# or provide a GitHub PAT when hearth asks
# Clone the existing data repo into hearth's workspace
hearth init --from [email protected]:<you>/<your-data-repo>.git
# Preview what would be written to disk, then apply for real
hearth apply --dry-run
hearth applyhearth will write your Claude Code agents, VSCode settings, etc. into the expected OS-specific paths (~/.config/Code/User/ on Linux, ~/Library/Application Support/Code/User/ on Mac, %APPDATA%\Code\User\ on Windows).
Then finish the per-machine manual steps that hearth prints:
Finish setup manually:
• Run `claude login` to reauth Claude Code
• Run `gh auth login` if not already done
• Generate a fresh SSH key: ssh-keygen -t ed25519
• Install VSCode extensions: hearth apply-extensions (Phase 3)
• Register scheduler: hearth install (Phase 4)
These are deliberately not synced. Syncing SSH private keys would turn your data repo into a single point of compromise forever, and reauthenticating Claude takes about thirty seconds.
Once set up, the loop is simple.
# Most of the time, do nothing — Phase 4 watcher + scheduler handles sync automatically.
# Before a long trip / before switching machines, manually force a sync:
hearth sync
# After returning to a machine you haven't used in a while:
hearth sync
# Preview what would happen before doing it:
hearth sync --dry-run
# See the current state
hearth status
# See the last 20 audit entries
hearth log # (Phase 4)The single most important command is hearth sync. It pulls from the remote, pushes your local changes, and writes applicable remote updates to disk — all as one atomic operation.
The contents of your data repo are governed by a single file — manifest.yaml at the repo root. It looks like this:
version: 1
entries:
- source: "~/.claude/CLAUDE.md"
repo: "shared/claude/CLAUDE.md"
scope: shared
merge: text3way
- source: "~/.claude/agents/"
repo: "shared/claude/agents/"
scope: shared
merge: text3way
recursive: true
- source: "~/.claude/settings.json"
repo: "shared/claude/settings.json"
scope: shared
merge: json3way
scrub:
- jsonPath: "$.env.ANTHROPIC_API_KEY"
- jsonPath: "$.env.OPENAI_API_KEY"
- source: "~/.config/Code/User/keybindings.json"
repo: "hosts/{host}/vscode/keybindings.json"
scope: host
merge: json3wayEach entry has:
source— the file or directory on your machine. Use~/for home. OS-specific paths are handled automatically for well-known tools.repo— where in the data repo this lives.{host}is replaced with your hostname whenscope: host.scope—shared(same content on every machine),host(per-machine, never conflicts), ortemplate(generated from shared + host fragments; good for.zshrc).merge—text3way,json3way,setunion(for extension lists), orlww(last-writer-wins). Phase 2 implements the merge strategies; Phase 1 is fast-forward only.scrub— optional list of secret-redaction rules. Supports JSON-path ($.env.KEY) and regex.recursive— for directories, include all files under it.
Edit manifest.yaml in the data repo, commit, push — all other machines pick up the change on their next sync.
hearth distinguishes three situations:
| Scope | Example | How it behaves |
|---|---|---|
| Shared | ~/.claude/CLAUDE.md, custom agents |
One canonical copy in the repo. Edits on any machine propagate to all. |
| Host | VSCode keybindings.json (keys differ on Mac vs PC), user.email in .gitconfig |
Each machine has its own copy under hosts/<hostname>/. Can never conflict with another machine. |
| Template | .zshrc (mostly shared, with machine-specific bits) |
The real file is assembled from a shared fragment + per-host fragment. Phase 3 feature. |
If you're not sure which scope to use, start with shared. When a file starts diverging between machines, promote it to host.
Planned for Phase 4. Once hearth install is implemented, it will register the following on each OS:
| OS | Mechanism | What runs |
|---|---|---|
| macOS | launchd LaunchAgents (~/Library/LaunchAgents/com.hearth.*.plist) |
A watcher process (KeepAlive=true) and a scheduled sync (StartCalendarInterval, every 6h). |
| Linux | systemd user units (`~/.config/systemd/user/hearth-*.service | .timer) + loginctl enable-linger` |
| Windows | Task Scheduler (user-level, no admin) | Watcher registered at logon, restart-on-fail. Sync task every 6h, "run ASAP if missed". |
You'll be able to adjust or disable either layer. Until Phase 4 ships, run hearth sync manually (or wire it into your own cron / launchd / Task Scheduler).
Short version:
- Your data repo must be private.
hearth initwill check and refuse to point at a public repo. - Secrets never enter git. Declared secrets (per
scrubrules in the manifest) are replaced with<<HEARTH:…>>placeholders before any commit. - A safety-net leak scan runs on every push. If any of ~10 common secret patterns (Anthropic keys, OpenAI, GitHub PATs, AWS keys, Stripe keys, JWTs, etc.) survive scrubbing, the push aborts — no git commit is created.
- Secrets live in your OS keychain. When
hearth applywrites configs to disk, it pulls the real values from Keychain Access (Mac) / Secret Service / GNOME Keyring (Linux) / Credential Manager (Windows) and injects them into the file. - Hard-coded never-sync list. Regardless of what your manifest says, paths matching
.credentials.json,id_*(SSH keys),*.pem,.env*, etc., are silently dropped from every snapshot. - No telemetry.
hearthphones home to nobody.
The threat model and full list of scrubbed patterns will live in SECURITY.md (written during Phase 5.5, OSS launch).
What this does not protect against:
- A compromised GitHub account. If someone steals your GitHub credentials they can read the repo. Use 2FA.
- A compromised machine. If someone has root on your laptop, they can read your keychain too.
- A user who manually commits a secret into the data repo bypassing
hearth. Don't do that — the scrubber only runs whenhearthcreates the commit.
| Command | What it does | Status |
|---|---|---|
hearth init |
First-time setup wizard: choose / create data repo, detect profiles, generate manifest. | Phase 5 (today: pass --from <url>). |
hearth init --from <url> |
Clone an existing data repo into the workspace. | Working. |
hearth sync [--dry-run] |
Pull → merge → push → apply. The normal daily command. | Working (fast-forward only). |
hearth apply [--dry-run] |
Write workspace state to local filesystem. Does not touch the remote. | Working. |
hearth push |
Local → workspace → remote only. | Phase 2. |
hearth pull |
Remote → workspace only. | Phase 2. |
hearth status |
Show last sync, workspace state, pending changes. | Working. |
hearth doctor |
Verify auth, scheduler, watcher, keychain, lingering. | Phase 4. |
hearth install |
Register OS scheduler + file watcher. | Phase 4. |
hearth uninstall |
Remove scheduler + watcher, keep data. | Phase 4. |
hearth rollback --to <ts> |
Restore a pre-apply backup. | Phase 5. |
hearth log |
Tail the audit log. | Phase 4. |
hearth secret set <account> <value> |
Store a secret in the OS keychain. | Phase 2. |
hearth secret get <account> |
Print a secret (in a TTY, with confirmation). | Phase 2. |
hearth secret list |
List keychain entry names (values never shown in bulk). | Phase 2. |
Run hearth --help or hearth <command> --help for flag details.
npm's global prefix is owned by root. Either fix the prefix or use a version manager:
# Option 1 — move npm's global prefix under your home
mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
# add to your shell rc:
export PATH="$HOME/.npm-global/bin:$PATH"
# Option 2 — use fnm / nvm so npm lives under your home automaticallykeytar talks to the OS keychain via a native module. On Linux it needs libsecret:
# Debian/Ubuntu
sudo apt install libsecret-1-dev
# Fedora
sudo dnf install libsecret-devel
# Arch
sudo pacman -S libsecretThen npm rebuild keytar. On macOS and Windows, keytar uses the OS keychain directly — no extra install needed.
In Phase 1, hearth only handles fast-forward pulls and pushes. If both your remote and your local machine have changes, pick a side:
# Prefer local (discard remote changes since last sync)
hearth push # Phase 2
# Prefer remote (discard local changes)
hearth pull && hearth apply # Phase 2Until Phase 2 ships, resolve manually inside ~/.hearth/repo using plain git, then push.
The safety-net scanner found a secret-looking pattern in content you're about to push. Two fixes:
- Add a scrub rule for the file in
manifest.yaml:- source: "~/.somefile.json" repo: "shared/somefile.json" scope: shared merge: json3way scrub: - jsonPath: "$.secretKey"
- Remove the secret from the source and put it in an environment variable or the keychain instead.
Once scrubbing is in place, hearth sync --dry-run will show the redacted output before you push for real.
GitHub repo history is permanent even after deletion. The safest recovery:
- Rotate the leaked credential immediately (revoke the API key, regenerate the token).
- Delete the data repo entirely and create a new one.
- Run
hearth init --from <new-url> --forceon each machine.
Future hearth versions will add a hearth rotate-repo command to automate steps 2 and 3.
Why a git repo and not a sync server?
Because git is already a perfectly good sync protocol, GitHub is a credibly durable store, and adding a server is one more piece to secure, pay for, and maintain. hearth sticks to the "private plumbing, no bespoke infrastructure" principle.
Can I use a GitLab / Bitbucket / self-hosted git?
Yes — pass any URL that git can clone to hearth init --from. The gh-based convenience flows won't work, but manual setup does.
What if I don't want to sync everything on the default list?
Edit your data repo's manifest.yaml. Remove entries you don't want, add entries for tools we don't support yet. On the next sync, your changes take effect.
Can multiple people share one hearth repo (team / family)?
Not recommended. hearth is designed for one person with multiple machines. For team defaults, put shared conventions in a chezmoi-style base and layer hearth on top for personal preferences.
Will hearth ever support encryption-at-rest?
Phase 7. Using age for files marked encrypted: true in the manifest, with the key in your OS keychain. Useful for configs that can't be fully scrubbed (for example, private MCP server URLs or Gemini project IDs).
Can I extend hearth to sync my own tool?
Yes — add an entry to manifest.yaml. For built-in detection / auto-add via hearth init, contribute a profile under src/profiles/. Each profile is ~30 lines; see src/profiles/claude-code.ts as a reference.
Does this work offline?
Local commits happen offline. Push/pull need network. hearth apply (materialize workspace → disk) is fully offline.
I'm not a developer. Should I use this?
Probably not — hearth assumes you're comfortable with the CLI, git, and a text editor. If those aren't routine, chezmoi or a setup script are simpler starts.
- Phase 1 — CLI scaffold, Claude Code profile, fast-forward sync, scrubbing, vault, atomic writes, backups.
- Phase 2 — 3-way JSON merge, git 3-way text merge, set-union for extension lists, secret keychain CLI commands.
- Phase 3 — VSCode, Gemini, shell (template+fragments), git, GitHub CLI profiles.
- Phase 4 — Watcher daemon + OS scheduler (systemd / launchd / Task Scheduler),
install/uninstall/doctor. - Phase 5 — Cross-OS installers, guided
initwizard withgh repo create, rollback, log tailer. - Phase 5.5 — OSS launch: SECURITY.md, CONTRIBUTING.md, CI matrix, release automation, npm publish,
hearth-startertemplate. - Phase 6 — VSCode extension with 3-way merge editor integration.
- Phase 7 — Encryption-at-rest via
age.
Contributions are very welcome — especially new profiles for tools we don't yet cover (Neovim, tmux, Starship, Zed, JetBrains IDEs, etc.). Each profile is a single TypeScript file under src/profiles/ — see src/profiles/claude-code.ts as the canonical example.
Before committing:
npm run typecheck
npm run test # tests land in Phase 2Detailed contributor docs land in CONTRIBUTING.md during Phase 5.5.
MIT. See LICENSE.