BullseyeSync
Syncs your editor and tool configuration across machines through a private GitHub repo you own — no third-party sync service, no encryption keys to carry, and every change is a real git commit you can diff, revert or roll back to.
It covers what VS Code's built-in Settings Sync covers (settings, keybindings, extensions list, snippets, tasks, MCP servers, UI state, per profile) and the configuration that lives outside VS Code: Claude, Copilot Chat, Cursor, Windsurf, Zed, Continue, Aider, plus shell, git, prompt and package-manager dotfiles.
It does not fight VS Code Settings Sync
BullseyeSync runs in one of two relationships with Settings Sync, chosen by GitHub Sync mode (auto by default, forceable to coexist or takeover):
- Coexist — Settings Sync is on and keeps ownership of VS Code's own resources. BullseyeSync observes those files, commits each burst of writes to git for history and point-in-time restore, and actively syncs everything outside VS Code. It never writes editor resources back on pull, so the two never overwrite each other.
- Takeover — Settings Sync is off. BullseyeSync becomes the sole sync and delivers editor resources to the live config on every pull, on top of the external files.
In auto, the mode follows Settings Sync's own cache: no sync activity for 14 days is read as off. The window is deliberately generous — an idle-but-enabled machine must not be flipped into takeover and start overwriting.
What it syncs
VS Code, per profile — settings.json, keybindings.json, tasks.json, mcp.json, user snippets, the installed-extensions list, and UI state (globalState.json). You pick which profiles participate.
AI and editor tools — Claude CLI (~/.claude: settings.json, CLAUDE.md, keybindings.json, skills/, agents/, rules/), Claude Desktop, GitHub Copilot Chat agent files, Continue, Aider, Cursor and Windsurf rules and settings, Zed settings.
Dotfiles — .gitconfig, global gitignore, lazygit, .bashrc, .bash_profile, .zshrc, .zprofile, fish, nushell, PowerShell profile, starship, WezTerm, Ghostty, Alacritty, .tmux.conf, .vimrc, Neovim init.lua, ~/.ssh/config (config only, never keys), .npmrc, pnpm, pip, Poetry.
Anything else — add your own files or folders from the wizard or BullseyeSync: Edit custom items.
Every built-in preset is an allowlist: it names the specific files to sync and nothing else. Pointing a preset at ~/.claude does not drag along caches, transcripts or credentials — only the listed files travel. The directory serializer additionally refuses credential-shaped files and *.backup* variants.
Setup
- Install the extension and click BullseyeSync in the Activity Bar.
- Sign in with GitHub — the same OS-native consent dialog Copilot uses. No token to paste. A private
vscode-bullseye-sync repo is created for you if it does not exist.
- Pick what to sync — VS Code resources plus every tool config the extension actually found on this machine. The step also offers to turn Settings Sync off and let BullseyeSync take over.
- Done — the first push runs immediately.
On a second machine, install and sign in with the same account; the wizard finds the existing repo and pulls. If you already ran setup in another VS Code variant or profile on the same box, this one adopts that setup silently rather than asking again.
Configuration lives in the repo, not in settings.json
The synced set is one fleet-global file, manifest.yml, at the root of your sync repo. Change what is synced on any machine and every other machine adopts it on its next pull. The VS Code Settings UI therefore shows a single read-only pointer rather than a wall of knobs.
manifest.yml carries the synced profiles, the synced resources, your custom directories, and two fleet settings: GitHub Sync mode and the default conflict action (prompt / local-wins / remote-wins). Edit it by hand with BullseyeSync: Edit manifest, or through the panel buttons.
How syncing works
Push — chokidar watches every synced path. A change debounces for 5 seconds, then the affected paths are re-serialized, diffed against HEAD, and pushed. A snapshot that produces no git-level change never becomes a commit. If more than 200 events arrive within 10 seconds, an anti-thrash brake trips instead of hammering the remote. When the native watcher cannot work — network drives, some containers — the path falls back to 5-second polling and the panel says so.
Pull — the remote is checked every 60 seconds, and on boot and on demand. A watchdog sweep every 2 minutes restarts a dead watcher or a stalled remote-check loop. Three consecutive failures trip a circuit breaker; a hard misconfiguration (no repo URL, 404) stops the daemon rather than retrying forever.
Merge — three-way, against the commit this machine last synced (tracked per machine at refs/bullseye-sync/machines/<id>/last-synced, so a merge base is never guessed). JSON and JSONC files merge per key with comments preserved; everything else merges by line with diff3. Same key with the same value is a no-op; different keys union. Only a genuine same-key divergence reaches you, and it opens in VS Code's native merge editor. extensions.yml resolves by capture time — the newest capture wins, so an uninstall on one machine is never resurrected by an older peer that still has it.
Extension sync asks first
A pulled extension list is turned into a plan — install these, uninstall those — and shown to you before anything is installed or removed, because applying it forces a window reload that kills whatever that window was running. Every entry is independently deniable. A refusal is remembered, and forgotten again the moment the repo stops asking, so a later flip re-asks. An empty captured list means "extensions were never captured", never "uninstall everything". BullseyeSync never proposes uninstalling itself.
Fleet operations
The panel keeps a roster of every machine that has pushed, with its last sync time. From there:
- Download — overwrite this machine from the remote. Upload — overwrite every other machine with this one.
- Revert to old version — pick any of the last 60 commits and restore your config to that point.
- Reconfigure all machines — change the synced set once and propagate it.
- Leave on this machine — forget membership here, keep the editor files.
- Factory reset everywhere — wipe BullseyeSync state on every machine, optionally deleting the remote repo. Deleting the repo asks for the extra GitHub scope only at that moment.
- Remove machine — tombstone a retired machine so it stops appearing.
Fleet commands travel as a control file in the repo (.bullseye-control/control.json) and are honoured on the next pull, plus a marker on disk so every VS Code variant and profile on the same box reacts too, not just the window you clicked in.
Where things are
- Activity Bar — the BullseyeSync dashboard: status, sync state, watcher list, machine roster, and every action.
BullseyeSync: Open status as a tab is the fallback if the view ever fails to paint.
- Status bar — one chip on the right; clicking it opens the dashboard.
- Output panel —
BullseyeSync for the plain-English activity log, BullseyeSync (Debug) for raw library errors.
- On disk — a rotated log at
<globalStorage>/logs/bullseye-sync.log, 1 MB per file, 3 generations, opened by BullseyeSync: Open log file.
Repo layout
manifest.yml
editor/profiles/<profile>/settings.json
editor/profiles/<profile>/keybindings.json
editor/profiles/<profile>/tasks.json
editor/profiles/<profile>/mcp.json
editor/profiles/<profile>/globalState.json
editor/profiles/<profile>/snippets/
editor/profiles/<profile>/extensions.yml
dirs/<id>/...
.bullseye-control/control.json
.machines/<machineId>.removed
Commands
| Command |
What it does |
Set up |
Run the wizard. Re-runnable any time to change what syncs. |
Clone setup from repo |
Restore a machine from an existing sync repo. |
Sync now |
Pull, then push. |
Pause / Resume |
Halt auto-sync on this machine. |
Edit synced components |
Everything that syncs — the known catalog plus your own items. |
Edit known components |
Toggle the built-in catalog entries. |
Edit custom items |
Add or remove your own files and directories. |
Edit synced profiles |
Choose which VS Code profiles participate. |
Edit manifest |
Open manifest.yml directly. |
Add path to sync / Remove path from sync |
Single-path shortcuts. |
Resolve conflicts |
Open queued conflicts in the merge editor. |
Restore from history |
Point-in-time rollback to any recent commit. |
Download / Upload |
One-way overwrite: local from remote, or remote from local. |
Reconfigure all machines |
Change the synced set fleet-wide. |
Leave on this machine |
Forget membership here, keep the files. |
Factory reset everywhere |
Wipe every machine, optionally delete the remote. |
Remove machine from sync repo |
Tombstone a retired machine. |
Open remote repo / Open local repo folder |
Jump to either copy. |
Status / Open status as a tab |
The dashboard. |
Show output log / Show debug log / Open log file |
Activity log, raw errors, on-disk file. |
Install latest from GitHub / Install latest from source repo |
Update peer machines from a published build. |
Publish to Marketplace, Stamp version, Reinstall latest and Clear reset marker are maintainer commands; they appear in the palette but are only useful from a source checkout.
Auth and privacy
Authentication goes through VS Code's built-in GitHub provider — the same one Copilot uses. The token is fetched fresh for each git operation and is never written to settings or to the repo. The repo is private and yours; there is no BullseyeSync server and nothing is sent anywhere else. That private repo is the whole security boundary: its contents are not encrypted, so do not add paths that hold secrets.
License
MIT.