Mac Sync

LGJ Exclusive Skill v1.0 7 files

Link two Macs over your own Tailscale network and reconcile them after a trip. Guided setup mints a dedicated key, installs it password-only, and writes the SSH config; then a read-only drift report compares every git repo, unpushed branch, uncommitted change and Claude Code session across both machines. Never merges, resets or copies secrets on its own.

macsshtailscaleremotegitworktreeclaude-codesync

Mac Sync is a Claude Code skill. Link two Macs over your own Tailscale network and reconcile them after a trip. It works well with cmux-setup.

Categorieslgj-exclusivedeveloper-toolsproductivity
Works withcmux-setup

Documentation

--- name: mac-sync description: "Link two Macs over your own Tailscale network and keep them reconciled. Guided setup mints a dedicated SSH key, installs it password-only, writes the ~/.ssh/config block, and verifies the link; then a read-only drift report compares every git repo, unpushed branch, uncommitted change and Claude Code session across both machines so nothing is lost after a trip. Ships a link health-check, a reconcile reporter, and a dry-run-by-default push helper. Use when the user says \"mac-sync\", \"connect my Macs\", \"link my laptop and desktop\", \"work on my other Mac\", \"remote Mac\", \"what changed on my laptop\", \"sync after a trip\", or asks to read or run something on their second Mac." invoke: user --- # Mac Sync Two Macs means two sources of truth. You commit on the laptop, forget to push, and rebuild the same fix on the desktop a week later. Or you reset a branch to tidy it and take real work with it. This skill closes that gap in two halves: 1. **Link** the two machines once, over Tailscale, with a dedicated key. 2. **Reconcile** them whenever you come back — a read-only report of everything that drifted, so you decide per project instead of guessing. It reads by default. It never merges, resets, force-pushes, or copies secrets on its own. --- ## Step 0 — Prerequisites (BLOCKING) Before any other operation, verify these. If any are missing, **STOP** and tell the user where to get each one. Do NOT generate placeholder bash and do NOT proceed with broken state. | # | Requirement | Check | If missing | |---|---|---|---| | 1 | Tailscale installed and logged in, on **both** Macs | `tailscale status` lists both machines | https://tailscale.com/download — install and sign in with the same account on both | | 2 | `ssh` client (ships with macOS) | `ssh -V` | Reinstall Xcode command line tools: `xcode-select --install` | | 3 | **Remote Login enabled on the target Mac** | On the target: System Settings → General → Sharing → Remote Login = on | GUI-only. Ask the user to switch it on; it cannot be enabled over the network. | | 4 | The target account is an **admin** on that Mac | On the target: `id -Gn <user> \| tr ' ' '\n' \| grep -x admin` | macOS gates Remote Login behind the `com.apple.access_ssh` group, which nests to `admin`. A standard account is refused even with a correct password AND a valid key. Either grant admin, or add the account to Remote Login's allowed-users list in the Sharing pane. | | 5 | `git` ≥ 2.30 (only for the reconcile half) | `git --version` | `brew install git` | If anything is missing, STOP. Two things worth saying out loud before you start, because they surprise people: - Access rides Tailscale, so **the target Mac is never exposed to the public internet**. If both nodes are owned by the same Tailscale account, the default tailnet policy already permits this and **no ACL edit is needed** — do not add one. If access breaks later, check `tailscale status` before touching policy. - **Leave password auth enabled.** It is tempting to harden it off once keys work. Don't: Tailscale is already the network perimeter, and keys-only means a lost key costs you a physical trip to the machine. Password auth is the recovery path. --- ## Step 1 — Interview Ask before writing anything. Use `AskUserQuestion` when it is available. Nothing in this skill is hardcoded; every value below comes from these answers. 1. **What should the connection be called?** A short lowercase alias the user will type — `laptop`, `desktop`, `studio`. This becomes the `Host` name, so `ssh laptop` is the whole command. 2. **Which machine is the target?** Take the Tailscale IP or the MagicDNS name from `tailscale status`. Prefer the **MagicDNS name** if the tailnet has it enabled — it survives an IP change. 3. **What is the account name on that Mac?** Usually the short username; confirm with `whoami` on the target rather than guessing from the machine name. 4. **Is this a peer Mac or an always-on box?** A laptop that sleeps is fine as a peer. An always-on machine you expect to reach at any hour needs Step 4. Confirm the four answers back to the user before writing to `~/.ssh/config`. --- ## Step 2 — Mint a key and install it Make a key **dedicated to this link**, not a reused general-purpose key. A dedicated key is revocable on its own: you can cut this one connection without breaking every other thing that key opens. ```bash ssh-keygen -t ed25519 -f ~/.ssh/<alias>_key -C "<this-machine>-><alias>" -N "" ``` Install it. The flags are not optional: ```bash ssh-copy-id -o PubkeyAuthentication=no \ -o PreferredAuthentications=password,keyboard-interactive \ -i ~/.ssh/<alias>_key.pub <user>@<host> ``` **Why the flags:** `ssh-copy-id` bypasses your `Host` alias, so `IdentitiesOnly` does not apply and ssh offers every key in `~/.ssh` first. That exhausts the server's auth-attempt budget and disconnects you after one or two password tries — it looks like a wrong password when it is not. Forcing password-only auth for this one command avoids it. If the password is not to hand, install the key directly in Terminal **on the target Mac** instead: ```bash mkdir -p ~/.ssh && chmod 700 ~/.ssh echo '<paste the .pub contents>' >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys ``` Then write the config block (append to `~/.ssh/config`, create it `chmod 600` if absent): ``` Host <alias> HostName <tailscale-ip-or-magicdns-name> User <user> IdentityFile ~/.ssh/<alias>_key IdentitiesOnly yes StrictHostKeyChecking accept-new ServerAliveInterval 30 ``` `IdentitiesOnly yes` stops ssh offering unrelated keys on every connect. `accept-new` trusts the key the first time and then pins it — so a **changed** host key later is a real warning. If that happens, stop and tell the user; do not clear `known_hosts` to make it go away. --- ## Step 3 — Verify the link ```bash ~/.claude/skills/mac-sync/scripts/mac-link-check.sh <alias> ``` That script is read-only. It checks reachability, confirms a login shell finds the user's real toolchain, reports the power settings, and flags the two macOS permission traps. Run it now, and any time the link misbehaves later. The one check to understand, because you will hit it constantly: ```bash ssh -o BatchMode=yes <alias> 'bash -lc "node -v"' # works ssh -o BatchMode=yes <alias> 'node -v' # command not found ``` Always pass `-o BatchMode=yes` so a broken link fails fast instead of hanging on an interactive prompt. --- ## Step 4 — Keep an always-on Mac reachable Skip for a laptop. For a machine you expect to reach at any hour: ```bash ssh -o BatchMode=yes <alias> 'pmset -g | egrep "sleep|womp|autorestart"' ``` Want `sleep 0` (never fully sleeps), `womp 1` (wakes on network), and `autorestart 1` (comes back after a power blip), on AC power. `disksleep` is harmless — disk sleep does not drop the tailnet. Only if those regress: ```bash ssh -o BatchMode=yes <alias> 'sudo pmset -a sleep 0 womp 1 autorestart 1' ``` A sleeping Mac is an unreachable Mac, and it reads exactly like a broken connection. Check power before debugging ssh. --- ## Step 5 — Reconcile after a trip This is the half that earns the skill. Full playbook, including the decision table for every repo state: **`references/post-trip-reconcile.md`** — read it before acting on a report. ```bash # same project root on both Macs ~/.claude/skills/mac-sync/scripts/mac-reconcile.sh <alias> ~/path/to/projects # different roots (common) — pass both, or one side reports nothing ~/.claude/skills/mac-sync/scripts/mac-reconcile.sh <alias> ~/code --remote-dir '~/Projects' # add --sessions to also compare Claude Code transcripts ``` Read-only. For every git repo on both machines it reports the branch, how far ahead or behind `origin` it is, uncommitted and staged files, stashes, and registered worktrees — then diffs the two lists so you can see what exists on one machine and not the other. It also lists Claude Code session transcripts (`~/.claude/projects/`) present on only one side. Then work the report **one project at a time**. The rules that matter: - **A divergence gets a rescue branch, never a reset.** If both machines have commits the other lacks, push the far side to a throwaway branch and merge deliberately. `git reset --hard` on a diverged branch is how the work disappears. - **Uncommitted work is the fragile thing.** Commit it (even to a WIP branch) or stash it before anything else moves. It is the only state with no remote copy. - **Never sync `.env*`, credentials, key material, `node_modules` or build caches.** This is a rule, not a default — say so when you propose a copy. To actually move files once the user has chosen: ```bash ~/.claude/skills/mac-sync/scripts/mac-push.sh <alias> <local-src> <remote-dest> # preview ~/.claude/skills/mac-sync/scripts/mac-push.sh <alias> <local-src> <remote-dest> --go # apply ``` Dry run is the default; `--go` writes. `--delete` is opt-in, never implied, and requires `--go`. The script refuses an empty destination, `/`, `~`, and `.`. **Trailing slash on the source matters** (rsync semantics): `foo` copies the folder to `dest/foo/`; `foo/` copies its *contents* into `dest/`. --- ## Operating protocol Once the link works, these are the things that will otherwise cost you an afternoon each. Symptom, cause and fix for all six: **`references/gotchas.md`**. The short version: - **Wrap anything needing real tooling in a login shell.** A plain `ssh <alias> '<cmd>'` runs with a stunted PATH — no Homebrew, no node, no `gh`. Use `bash -lc "..."`. - **Use SINGLE quotes around remote commands.** `ssh <alias> "~/foo"` expands `~` on *your* machine and sends an absolute path that does not exist on the other one. Single quotes let `~` expand remotely. This bites every `~` path, not just the obvious ones. - **Quote paths with spaces INSIDE the command string.** The remote shell word-splits them even when your local side is quoted: `ssh <alias> 'ls -d ~/"My Projects"'`. - **Environment set through the macOS GUI layer is invisible over SSH.** That is a macOS boundary, not something to debug. - **A locked login keychain fakes a logged-out CLI.** `gh auth status` and similar can report missing credentials over SSH while being perfectly healthy locally. **Do not re-authenticate** — you will clobber working credentials. - **`launchctl` needs the GUI domain to act.** A bare `launchctl list` works over SSH, but `print`, `kickstart`, `bootout` and `bootstrap` need `gui/$(id -u)/<label>`. And never use `kickstart` as a connectivity test — it fires the real job. ### Safety rules - **Confirm before writing, deleting, killing processes, or running installers on the other Mac.** It is a machine someone uses; reading and reporting is free, mutation is not. - **Never print secrets.** `.env` files and credential stores are readable over this link. Read a specific key with `grep` if you must; never `cat` a whole env file into output, and never echo a value into a summary, a log, a commit, or a chat message. - **Check free disk space on the target before anything that creates large files.** - **If the remote copy of a file is newer than yours, say so and stop.** The other machine may have been used since. A blind push silently destroys work. --- ## Revoking access Remove the key line from the target's `authorized_keys`, then delete the local key: ```bash ssh <alias> "grep -v '<the key comment>' ~/.ssh/authorized_keys > /tmp/ak && mv /tmp/ak ~/.ssh/authorized_keys" rm ~/.ssh/<alias>_key ~/.ssh/<alias>_key.pub ``` Password auth remains as the way back in — which is exactly why Step 0 said to leave it enabled. **The private key never leaves `~/.ssh/` and is never committed to any repo.** --- ## Composes well `cmux-setup` — if you run agents in parallel across both machines, set up the workspace side there and the link here.
JF
Built by Jay Feldman
Founder, Lead Gen Jay · Inc. 5000 · 84K+ YouTube subs

Install

curl -sL 'https://leadgenjay.com/api/skills/install.sh?items=mac-sync' | bash
Installs in ~3 seconds
Verified by Jay
Files included
SKILL.md
manifest.yaml
references/gotchas.md
references/post-trip-reconcile.md
scripts/mac-link-check.sh
scripts/mac-reconcile.sh
scripts/mac-push.sh
AI Automation Insiders · Cohort #14

Unlock all exclusive skills now

Get every Claude Code skill, command, and agent Jay uses to run an Inc. 5000 company — plus live coaching, the private community, and lifetime access.

  • Claude Code Zero to ProFastest way to learn Claude
    $2,997
  • Claude Remote AgentBuild your own 24/7 Bob
    $3,497
  • 100+ Exclusive Skills & AutomationsClaude Code & n8n
    $1,997
  • Unlimited Live CoachingOn-demand + Tuesdays with Jay
    $1,497
  • Top 1% Skool Community3,000+ founders
    $997
Total real-world value$13,479
You pay today$1,497
Encrypted 14-day refund Cancel anytime