Git-backed sync for AI tool configuration across macOS, Linux, and Windows/WSL.
Keeps your skills, commands, hooks, settings, and tool config identical on every machine. Supports Claude Code (~/.claude), Codex (~/.codex or $CODEX_HOME), and OpenCode (~/.config/opencode/).
~/.claude is 1.6GB but only ~15MB is your actual config. Generic dotfile managers (chezmoi, yadm) don't know which files matter. ai-sync ships with an opinionated allowlist, rewrites hardcoded paths for cross-platform portability, and backs up your config before every pull.
curl -fsSL https://raw.githubusercontent.com/berlinguyinca/ai-sync/main/install.sh | bashThe installer will:
- Clone, build, and link the
ai-syncbinary - Ask which environments to sync (Claude Code, Codex, OpenCode, or any combination)
- Ask for a GitHub repo name (default:
ai-config) and visibility - Create the repo via
gh, runai-sync init, and push your config
Run it again to update an existing installation.
Requires: git, GitHub CLI (gh) for automatic repo creation. Node.js 22+ is installed automatically if missing (via fnm, nvm, Homebrew, apt, yum, or direct binary download).
git clone https://github.com/berlinguyinca/ai-sync.git
cd ai-sync
npm install
npm run build
npm link
ai-sync init
cd ~/.ai-sync && git remote add origin git@github.com:you/ai-config.git
ai-sync pushThe one-liner installer handles everything — init, repo creation, and first push. If you installed manually, see the manual steps above.
# One command — clones the repo and applies config to local directories
ai-sync bootstrap git@github.com:you/ai-config.gitDone. Your config is now identical across machines.
# After changing config on any machine
ai-sync push
# On other machines, pull the changes
ai-sync pull # backs up current state first
# Check what's changed
ai-sync statusBy default, ai-sync only syncs Claude Code. To also sync Codex (~/.codex or $CODEX_HOME):
# 1. Enable the Codex environment
ai-sync env enable codex
# 2. Push to include portable Codex config in the sync repo
ai-sync push
# 3. On other machines, enable Codex there too
ai-sync env enable codex
ai-sync pullCodex sync is intentionally narrow: it includes portable user config such as config.toml and saved automations, while excluding machine-local auth, session history, sqlite state, caches, and logs.
By default, ai-sync only syncs Claude Code. To also sync OpenCode (~/.config/opencode/):
# 1. Enable the OpenCode environment
ai-sync env enable opencode
# 2. Push to include OpenCode config in the sync repo
ai-sync push
# 3. On other machines, enable OpenCode there too
ai-sync env enable opencode
ai-sync pullClaude Code, Codex, and OpenCode configs are kept strictly isolated in per-environment subdirectories and are never mixed.
If you previously used claude-sync, the installer automatically handles the rename:
- Renames
~/.claude-sync-cli→~/.ai-sync-cli - Renames
~/.claude-sync→~/.ai-sync - Removes old
claude-syncsymlinks
Just re-run the installer:
curl -fsSL https://raw.githubusercontent.com/berlinguyinca/ai-sync/main/install.sh | bashIf you set up ai-sync before multi-environment support was added, your sync repo uses the v1 flat format where all files sit at the root. The v2 format organizes files into per-environment subdirectories (claude/, codex/, opencode/).
Check your current format:
# If this file exists, you're already on v2
cat ~/.ai-sync/.sync-versionMigrate to v2:
# Moves all root-level files into claude/ and writes .sync-version
ai-sync migrateThis is safe — it:
- Verifies the repo is clean (no uncommitted changes)
- Moves all allowlisted files into a
claude/subdirectory - Writes
.sync-versionwith content2 - Commits and pushes the change
After migrating, pull on your other machines so they pick up the new structure:
# On each other machine
ai-sync pullImportant: All machines should run the same version of ai-sync. Update all machines before or after migrating:
ai-sync updateIf you migrate on one machine, other machines need to update and pull:
# 1. Update ai-sync to the latest version
ai-sync update
# 2. Pull the migrated repo structure
ai-sync pullIf a machine was set up with an older ai-sync that doesn't understand v2, re-run the installer to update:
curl -fsSL https://raw.githubusercontent.com/berlinguyinca/ai-sync/main/install.sh | bashCreates a git-backed sync repo at ~/.ai-sync from your existing config directories.
- Scans enabled environments through their allowlist manifests
- Copies only config files (skips ephemeral data)
- Rewrites absolute paths in
settings.json,opencode.json, andconfig.tomlto portable{{HOME}}tokens - Creates
.gitattributesenforcing LF line endings - Makes an initial commit
ai-sync init # default location ~/.ai-sync
ai-sync init --repo-path ~/my-sync # custom location
ai-sync init --force # re-initialize existing repoScans config directories for changes, copies updated files to the sync repo with path rewriting, commits, and pushes to the remote.
ai-sync push
ai-sync push -v # show detailed file changesFetches remote changes and applies them to local config directories. Always creates a timestamped backup first.
ai-sync pull
ai-sync pull -v # show detailed file changesShows local modifications, remote drift, and excluded file count.
ai-sync status
ai-sync status -v # include branch, tracking info, synced file countSets up a new machine from an existing remote sync repo. Clones the repo, applies files to config directories with path expansion, backs up any existing config, and installs skills.
ai-sync bootstrap git@github.com:you/ai-config.git
ai-sync bootstrap https://github.com/you/ai-config.git
ai-sync bootstrap <url> --force # re-clone if sync repo existsSSH note: If bootstrapping via an SSH URL (
git@...) from a host you have not connected to before, SSH will prompt you to verify the host key fingerprint. This is expected — confirm it matches the server's published fingerprint before accepting.
Checks for and applies tool updates. ai-sync also checks for available updates once every 24 hours on startup and prints a notification if one is found — updates are never applied automatically. Run ai-sync update explicitly to apply them.
ai-sync update
ai-sync update --force # check even if checked recentlyInstalls slash commands (like /sync) into config directories for all enabled environments. This runs automatically during init and bootstrap, but you can run it manually after updating.
ai-sync install-skillsManage which tool environments are synced.
ai-sync env list # show all environments and their status
ai-sync env enable codex # enable Codex syncing
ai-sync env enable opencode # enable OpenCode syncing
ai-sync env disable codex # disable Codex syncing
ai-sync env disable opencode # disable OpenCode syncingMigrates a v1 (flat, Claude-only) sync repo to v2 (subdirectory, multi-environment) format. This moves all root-level files into a claude/ subdirectory and writes a .sync-version marker.
ai-sync migrateAfter installation, you can type /sync inside Claude Code or OpenCode to pull, push, and check status in one step — no need to leave the conversation.
Each tool gets its own version of the skill — they are not interchangeable. Skill files use a naming convention to target specific environments:
| Skill file | Installed as | Target |
|---|---|---|
sync.claude.md |
sync.md |
Claude Code only |
sync.opencode.md |
sync.md |
OpenCode only |
utils.md |
utils.md |
All environments |
The convention is <name>.<envId>.md for environment-specific skills, or <name>.md for skills shared across all environments.
ai-sync --no-update-check <command> # suppress the startup update notification
ai-sync --version # show version
ai-sync --help # show helpai-sync supports multiple AI tool environments:
| Environment | Config Dir | Skills Dir | Path Rewrite |
|---|---|---|---|
| Claude Code | ~/.claude |
commands/ |
settings.json |
| Codex | ~/.codex or $CODEX_HOME |
n/a | config.toml |
| OpenCode | ~/.config/opencode/ |
command/ |
opencode.json |
By default, only Claude Code is enabled. Use ai-sync env enable codex or ai-sync env enable opencode to add more environments.
v1 (legacy, flat):
~/.ai-sync/
├── CLAUDE.md
├── settings.json
├── commands/
└── ...
v2 (multi-environment):
~/.ai-sync/
├── .sync-version # contains "2"
├── claude/
│ ├── CLAUDE.md
│ ├── settings.json
│ ├── commands/
│ └── ...
├── codex/
│ ├── config.toml
│ └── automations/
└── opencode/
├── opencode.json
├── settings.json
├── command/
└── ...
Use ai-sync migrate to move from v1 to v2 format.
| Path | What it is |
|---|---|
CLAUDE.md |
Global instructions and preferences |
settings.json |
Permissions, hooks, effort level (paths auto-rewritten) |
commands/ |
Custom slash commands |
agents/ |
Agent definitions |
hooks/ |
Hook scripts |
get-shit-done/ |
GSD framework |
package.json |
Dependencies |
gsd-file-manifest.json |
Framework state |
plugins/blocklist.json |
Plugin blocklist |
plugins/known_marketplaces.json |
Marketplace registry |
plugins/marketplaces/ |
Marketplace configs |
| Path | What it is |
|---|---|
opencode.json |
Main config (paths auto-rewritten) |
settings.json |
Settings |
agents/ |
Agent definitions |
command/ |
Custom slash commands (singular!) |
hooks/ |
Hook scripts |
get-shit-done/ |
GSD framework |
package.json |
Dependencies |
gsd-file-manifest.json |
Framework state |
| Path | What it is |
|---|---|
config.toml |
Main Codex configuration (paths auto-rewritten) |
automations/ |
Saved recurring tasks and automation definitions |
auth.json, .codex-global-state.json, sessions/, session_index.jsonl, logs*.sqlite, state*.sqlite, sqlite/, tmp/, shell_snapshots/, models_cache.json, vendor_imports/
projects/, history.jsonl, debug/, telemetry/, session-env/, shell-snapshots/, statsig/, file-history/, todos/, plans/, paste-cache/, ide/, cache/, backups/, downloads/, tasks/, plugins/install-counts-cache.json
These are session data, caches, and logs that regenerate automatically and would cause constant merge conflicts.
settings.json, opencode.json, and config.toml can contain absolute paths that break on other machines. ai-sync handles this transparently:
- On push/init: Rewrites
/Users/youto{{HOME}}in the sync repo - On pull/bootstrap: Expands
{{HOME}}back to the local machine's home directory - Windows support: Handles both forward-slash and backslash path variants, including JSON-escaped
\\sequences
You never see the tokens — they exist only in the git repo.
ai-sync never applies updates without explicit user action. The startup check (every 24 hours) only prints a notification — no code is downloaded or executed. Run ai-sync update when you choose to apply an update.
The installer (install.sh) clones a specific pinned release tag (PINNED_VERSION) rather than the main branch. This means only explicitly tagged releases reach users, not every commit merged to main. The pinned version is updated as part of each release.
ai-sync bootstrap uses standard SSH host key checking (StrictHostKeyChecking=yes). If you connect to a host for the first time, SSH will prompt you to verify the fingerprint — do not accept keys you cannot verify.
Only files in the explicit allowlist are ever read or written. Credentials, session data, caches, and history are structurally excluded — they are not filtered by name matching but are simply never in scope.
- Backup before pull/bootstrap: Current config state is saved to a timestamped directory in
~/.ai-sync-backups/before any destructive operation - Line endings:
.gitattributesenforces LF everywhere — hook scripts won't break when synced from macOS to Linux - Clear errors: Every operation reports user-friendly success/failure messages. No raw stack traces for expected errors (missing remote, auth failure, etc.)
- No secrets: The allowlist excludes everything except config files. No credentials, tokens, or session data are synced.
~/.claude (1.6GB) ~/.ai-sync (git repo)
├── CLAUDE.md ──sync──► ├── .sync-version
├── settings.json ──rewrite──► ├── claude/
├── commands/ ──sync──► │ ├── CLAUDE.md
├── agents/ ──sync──► │ ├── settings.json ({{HOME}} tokens)
├── hooks/ ──sync──► │ ├── commands/
├── projects/ ✗ excluded │ └── ...
├── history.jsonl ✗ excluded ├── codex/
├── debug/ ✗ excluded │ ├── config.toml ({{HOME}} tokens)
├── telemetry/ ✗ excluded │ └── automations/
├── sessions/ ✗ excluded ├── opencode/
│ │ ├── opencode.json ({{HOME}} tokens)
│ │ ├── command/
└── ... (16 more) ✗ excluded │ └── ...
├── .gitattributes
└── .git/ → remote
The sync repo is a standard git repository. You can inspect it, view history, and resolve conflicts with normal git tools.
git clone https://github.com/berlinguyinca/ai-sync.git
cd ai-sync
npm install
# Run tests
npm test
# Type check
npm run typecheck
# Lint
npm run lint
# Build
npm run buildsrc/
├── cli/
│ ├── index.ts # Commander.js entry point
│ ├── format.ts # Colored output formatting
│ └── commands/
│ ├── init.ts # ai-sync init
│ ├── push.ts # ai-sync push
│ ├── pull.ts # ai-sync pull
│ ├── status.ts # ai-sync status
│ ├── bootstrap.ts # ai-sync bootstrap
│ ├── update.ts # ai-sync update
│ ├── install-skills.ts # ai-sync install-skills
│ ├── env.ts # ai-sync env list|enable|disable
│ └── migrate.ts # ai-sync migrate
├── core/
│ ├── manifest.ts # Allowlist of sync targets
│ ├── scanner.ts # Directory scanner filtered by manifest
│ ├── path-rewriter.ts # {{HOME}} token rewriting
│ ├── backup.ts # Timestamped backup creation
│ ├── sync-engine.ts # Push/pull/status orchestration
│ ├── updater.ts # Auto-update mechanism
│ ├── skills.ts # Skill installation (/sync command)
│ ├── environment.ts # Environment definitions (Claude, OpenCode)
│ ├── env-config.ts # Per-machine environment preferences
│ ├── env-helpers.ts # Shared helpers (allowlist, path rewrite checks)
│ └── migration.ts # v1→v2 repo migration
├── git/
│ └── repo.ts # Git operations wrapper (simple-git)
├── platform/
│ └── paths.ts # Cross-platform path resolution
└── index.ts # Library exports
skills/
├── sync.claude.md # /sync for Claude Code (installs as sync.md)
└── sync.opencode.md # /sync for OpenCode (installs as sync.md)
MIT