00 The round trip
State lives in the repo, not on any one machine. A snapshot on Machine A becomes the source of truth that Machine B restores from.
01 Quick start — restore a machine
On the new machine: clone, then run the installer. It is idempotent — safe to re-run any time.
git clone https://github.com/<you>/claude-skills ~/projects/claude-skillsbash ~/projects/claude-skills/setup/setup.shsetup.sh --dry-run to print every action with zero mutations. Add --verbose to trace each shell command.Prerequisites the preflight step checks (and installs where it can): Python ≥ 3.11, git, and — via Homebrew if missing — bats, shellcheck, and uv. gh is only needed to contribute back.
02 What setup.sh does — 9 steps
Each step is reached in order; any one can be isolated with --only <step> or excluded with --skip-<step>.
preflight re-checks
Ensure $HOME/.local/bin is on PATH, verify Python ≥ 3.11, install bats/shellcheck/uv via brew if absent.
claude re-checks
Install the Claude Code CLI from the official installer if not present, then run claude update.
marketplaces idempotent
Read [[marketplaces]] from the TOML; for each, add if new or update if already registered (checked against claude plugin marketplace list).
plugins idempotent
Read [[plugins]]; install or update each name@marketplace. Version pins are surfaced as warnings — claude plugin install has no version flag.
skills idempotent
Run npx -y skills add <source>@<name> -g -y for each [[skills]] entry, then a global update pass.
dotfiles writes + backup
Copy templates/home-CLAUDE.md → $HOME/CLAUDE.md and templates/user-settings.json → $HOME/.claude/settings.json. Existing files are backed up to *.bak.<timestamp> first.
guidelines additive merge
Run merge_guidelines.py: extract the region between the claude-skills:guidelines markers in CLAUDE.md, and append only the ## sections not already present (heading match is case- and number-insensitive). Targets $HOME/CLAUDE.md, the current repo, and any --merge-claude-md <path>.
local_plugins idempotent
Register this repo itself as a marketplace (.claude-plugin/marketplace.json) and install/update its own four plugins.
symlinks safe
Remove stale links into the repo from the pre-plugin layout, then link $HOME/.local/bin/claude-skills-contribute and $HOME/.local/bin/wind. Never clobbers a foreign symlink or real file.
0 clean · 1 a step failed · 2 warnings only (e.g. an unsupported version pin). The loop never aborts early — failures are tallied and reported by the summary step.03 The manifest — claude-setup.toml
One declarative file is the source of truth. parse_toml.py reads it for setup.sh; write_toml.py rewrites it (preserving comments) for capture.sh.
name + marketplacesource + namehome_claude_md, user_settings → template pathsschema_version — rejected if unknown, future-proofs the formatsetup.sh. The installer reconciles the machine to whatever the TOML says.04 Flags
| Flag | Effect |
|---|---|
| --dry-run | Print every action, mutate nothing. Always your first run on a new box. |
| --verbose | set -x — echo each shell command as it runs. |
| --only <step> | Run a single step in isolation (e.g. --only guidelines). |
| --skip-<step> | Exclude one step; stackable (e.g. --skip-skills --skip-symlinks). |
| --merge-claude-md <path> | In the guidelines step, also merge the behavioral guidelines into any other CLAUDE.md. |
05 Snapshot a machine — capture.sh
The reverse direction. Run it on a well-configured machine to fold its live state back into the TOML and templates, then commit.
Four sources it reads
- 1
$HOME/CLAUDE.md+settings.json→templates/&[dotfiles] - 2
~/.claude/plugins/known_marketplaces.json→[[marketplaces]] - 3
~/.claude/plugins/installed_plugins.json→[[plugins]] - 4
~/.agents/.skill-lock.json→[[skills]]
How it writes
- Each source is piped as JSON into
write_toml.py tomlkitpreserves comments & formatting — no destructive rewrite- Only the named section is replaced; the rest of the file is untouched
- Result: a clean diff you can review before committing
06 Round-trip a change — contribute.sh
Available anywhere as claude-skills-contribute after setup. It branches, validates, commits, and opens a PR so a change made on one machine reaches the others.
claude-skills-contribute --message 'capture: new plugin + guidelines'claude-skills-contribute --skill my-skill --message 'add my-skill'Under the hood: gh auth check → switch to main and pull → new contrib/<slug> branch → capture state or scaffold a skill → run bats + pytest + shellcheck → commit → push → gh pr create. If validation fails, the branch is left for you to fix.
07 Long runs survive the move — Second Wind
setup.sh links wind onto your PATH. It orchestrates long Claude Code runs across the 5-hour usage limit so a relocated workflow keeps going.
bash tools/second-wind/install.shwind init # pick repos, set permissionswind up # start a tmux session per repowind watch # auto-resume when the limit resetswind dash # live dashboard on 127.0.0.1:8787Config search order: ./second-wind.json → ~/.wind/config.json. State (paused sessions, reset time) lives in ~/.wind/state.json. The dashboard is localhost-only and gates writes behind a per-run token.
08 Troubleshooting
| Symptom | Fix |
|---|---|
python3 too old | Need ≥ 3.11. On macOS: brew install python@3.11, then re-run. |
| symlink target is "foreign" | A link at $HOME/.local/bin/* or ~/.claude/* points elsewhere. Setup refuses to clobber it — remove/rename it, re-run. |
bats: command not found | Re-run setup.sh (preflight installs it) or brew install bats-core manually. |
uv: command not found | Re-run setup, or curl -LsSf https://astral.sh/uv/install.sh | sh. |
| a plugin "version pin" warning | Expected — claude plugin install can't pin versions, so latest is installed and the pin is surfaced as a warning. |