Bidirectional project sync between macOS and a remote dev server, with Claude Code session sync built-in.
Built for a workflow where you develop on a Mac, sometimes SSH into the server from your phone, and never work on both at the same time. Everything stays in sync automatically — no buttons, no confirmations, no friction.
OPEN LID (wake) CLOSE LID (sleep) EVERY 15 MIN
┌─────────────┐ ┌──────────────┐ ┌──────────────┐
│ Sync: │ │ Sync: │ │ Sync: │
│ mac↔server │ │ mac↔server │ │ mac↔server │
│ │ │ │ │ │
│ + dashboard │ │ caffeinate │ │ silent │
│ + restic │ │ (headless) │ │ (headless) │
│ + auto-close│ │ │ │ │
└─────────────┘ └──────────────┘ └──────────────┘
Open the lid — a Terminal window pops up showing a dashboard: server status, pending changes, last backup. If there are changes, it syncs bidirectionally and closes. If everything is up to date, it shows status for 3 seconds and closes.
Close the lid — silently syncs in both directions before the Mac sleeps. Uses caffeinate to prevent sleep until the sync completes.
Every 15 minutes — a LaunchAgent runs a silent bidirectional sync in the background, so you're never more than 15 minutes behind.
- Bidirectional rsync with
--update(newer file always wins) - Claude Code session sync — sessions and file history follow you between machines, with automatic path remapping (
-opt-project↔-Users-name-path-project) - Restic backups — encrypted, deduplicated snapshots on every interactive sync (7 daily, 4 weekly, 6 monthly)
- Lock file — prevents concurrent syncs from stepping on each other
- Smart dashboard — auto-syncs when changes exist, auto-closes when done
- sleepwatcher integration — triggers on lid open/close events
| File | Purpose |
|---|---|
sync-projects.sh |
Main sync script (dashboard, bidirectional sync, Claude sessions) |
sleep.sh |
Triggered on lid close — bidirectional sync |
wakeup.sh |
Triggered on lid open — opens dashboard |
com.n0mads.sync-projects.plist |
LaunchAgent for 15-min background sync |
brew install sleepwatcher restic rsync
brew services start sleepwatcher- Clone the repo and symlink everything:
git clone https://github.com/mad0ps/claude-code-server-syncer.git \
~/Documents/pr0j3cts/claude-code-server-syncer
# Symlink scripts
ln -s ~/Documents/pr0j3cts/claude-code-server-syncer/sync-projects.sh ~/.sync-projects.sh
ln -s ~/Documents/pr0j3cts/claude-code-server-syncer/sleep.sh ~/.sleep
ln -s ~/Documents/pr0j3cts/claude-code-server-syncer/wakeup.sh ~/.wakeup
# Add to PATH for easy access
ln -s ~/.sync-projects.sh /opt/homebrew/bin/sync-projects- Configure SSH access to your server in
~/.ssh/config:
Host projects
HostName YOUR_SERVER_IP
User YOUR_USER
IdentityFile ~/.ssh/your_key
- Create your
.envfrom the template and fill in your values:
cp .env.example .env
# Edit .env with your server IP, paths, etc.- Generate the LaunchAgent plist with your home directory:
sed "s|__HOME__|$HOME|g" com.n0mads.sync-projects.plist > ~/Library/LaunchAgents/com.n0mads.sync-projects.plist
launchctl load ~/Library/LaunchAgents/com.n0mads.sync-projects.plist- Initialize the restic repo:
restic -r ~/Backups/server-repo initsync-projects # Interactive dashboard with menu
sync-projects --force # Bidirectional sync with UI
sync-projects --sync # Silent bidirectional sync (used by automation)
sync-projects --status # Dashboard without menuClaude Code stores sessions per-project with path-encoded directory names:
- Server:
~/.claude/projects/-opt-myproject/ - Mac:
~/.claude/projects/-Users-name-Documents-projects-myproject/
The syncer remaps these paths automatically, syncs session JSONL files, and deletes sessions-index.json on the receiving side (Claude Code regenerates it on next launch). File history (~/.claude/file-history/) is UUID-based and syncs without conflicts.
MIT