Link the agent half of user/ on the Mac mini

Manifest lines can now name an OS. The SSH config, pwswitch and espanso are
Linux only, so link-user on the Mac links CLAUDE.md, rules, skills and the
opencode commands and nothing else. link-mcp no longer aborts under the Mac's
bash 3.2 when a server has no token.
This commit is contained in:
Gabriel Brown committed 2026-11-01 12:41:58 -05:00
1 parent 27e28b0dd1
commit d5ef335d61
5 files changed
+33 -9

No files matched your search

+5 -2
View File
@@ -157,14 +157,17 @@ while read -r name transport target token_var _rest; do
continue continue
fi fi
# ${args[@]+...} rather than "${args[@]}": the Mac's bash 3.2 treats an
# empty array as unset under set -u and aborts the whole stage.
#
# Re-registering is how a new server or a rotated token reaches the # Re-registering is how a new server or a rotated token reaches the
# runtime. Removing first avoids the CLI refusing a name it already knows. # runtime. Removing first avoids the CLI refusing a name it already knows.
# Neither call may print the token, so both are quiet unless they fail. # Neither call may print the token, so both are quiet unless they fail.
claude mcp remove "$name" -s user >/dev/null 2>&1 || true claude mcp remove "$name" -s user >/dev/null 2>&1 || true
if [[ "$transport" == stdio ]]; then if [[ "$transport" == stdio ]]; then
add=(claude mcp add "$name" -s user "${args[@]}" -- "$launcher") add=(claude mcp add "$name" -s user ${args[@]+"${args[@]}"} -- "$launcher")
else else
add=(claude mcp add --transport "$transport" "$name" "$url" "${args[@]}" -s user) add=(claude mcp add --transport "$transport" "$name" "$url" ${args[@]+"${args[@]}"} -s user)
fi fi
if "${add[@]}" >/dev/null 2>&1; then if "${add[@]}" >/dev/null 2>&1; then
log "Registered $name." log "Registered $name."
+3 -1
View File
@@ -69,10 +69,12 @@ displace() {
linked=0 linked=0
copied=0 copied=0
os="$(uname -s | tr '[:upper:]' '[:lower:]')"
while read -r kind source destination; do while read -r kind source destination only_on _rest; do
[[ -n "${kind:-}" ]] || continue [[ -n "${kind:-}" ]] || continue
[[ "$kind" == \#* ]] && continue [[ "$kind" == \#* ]] && continue
[[ -z "${only_on:-}" || "$only_on" == "$os" ]] || continue
src="$USER_DIR/$source" src="$USER_DIR/$source"
dst="${destination/#\~/$HOME}" dst="${destination/#\~/$HOME}"
+9 -1
View File
@@ -46,13 +46,15 @@ done < <(find "$repo_dir/user" -type f \( -name 'id_*' -o -name '*.pem' -o -name
# ── Every manifest line is usable ─────────────────────────────────────────── # ── Every manifest line is usable ───────────────────────────────────────────
while read -r kind source destination; do while read -r kind source destination only_on; do
[[ -e "$repo_dir/user/$source" ]] \ [[ -e "$repo_dir/user/$source" ]] \
|| note "the manifest points at $source, which is not in user/" || note "the manifest points at $source, which is not in user/"
[[ "$kind" == link || "$kind" == linkdir || "$kind" == copy ]] \ [[ "$kind" == link || "$kind" == linkdir || "$kind" == copy ]] \
|| note "the manifest uses an unknown kind: $kind" || note "the manifest uses an unknown kind: $kind"
[[ "$destination" == '~/'* ]] \ [[ "$destination" == '~/'* ]] \
|| note "the manifest destination $destination is not under the home directory" || note "the manifest destination $destination is not under the home directory"
[[ -z "$only_on" || "$only_on" == linux || "$only_on" == darwin ]] \
|| note "the manifest names an unknown os: $only_on"
done < <(grep -vE '^\s*(#|$)' "$manifest") done < <(grep -vE '^\s*(#|$)' "$manifest")
# ── The behaviour, against a fake home ────────────────────────────────────── # ── The behaviour, against a fake home ──────────────────────────────────────
@@ -77,6 +79,7 @@ linkdir agents/skills ~/.agents/skills
linkdir agents/skills ~/.claude/skills linkdir agents/skills ~/.claude/skills
copy plain.txt ~/.config/plain.txt copy plain.txt ~/.config/plain.txt
link missing.txt ~/.config/missing.txt link missing.txt ~/.config/missing.txt
copy plain.txt ~/.config/elsewhere.txt nosuchos
FIXTURE FIXTURE
home="$work/home" home="$work/home"
@@ -141,6 +144,11 @@ fi
grep -q 'missing.txt' "$work/log" \ grep -q 'missing.txt' "$work/log" \
|| note 'a skipped manifest entry was not reported' || note 'a skipped manifest entry was not reported'
# A line naming another OS is for another machine. The Mac takes the agent half
# of user/ and none of the Linux desktop half.
[[ -e "$home/.config/elsewhere.txt" ]] \
&& note 'a manifest entry for another OS was applied here'
# ── The answer sticks ─────────────────────────────────────────────────────── # ── The answer sticks ───────────────────────────────────────────────────────
# #
# So that `panama upgrade` on a configured machine relinks without an interview, # So that `panama upgrade` on a configured machine relinks without an interview,
+6
View File
@@ -86,6 +86,12 @@ Put the file under `user/`, add a line to `manifest`, run:
Anything already at the destination is moved to `config/old/` rather than Anything already at the destination is moved to `config/old/` rather than
deleted, under a name that says where it came from. deleted, under a name that says where it came from.
A line can end with `linux` or `darwin` to apply only on that OS. The Mac mini
is not a Panama desktop, but it runs agents, so it takes the agent half of this
directory and skips the SSH config, `pwswitch` and espanso. Panama's shipped
skills describe the Linux desktop, so the Mac runs `link-user` and `link-mcp`
only, never `link-skills`.
## What does not go in here ## What does not go in here
Anything secret. This repository is readable by anyone who finds it, and the Anything secret. This repository is readable by anyone who finds it, and the
+10 -5
View File
@@ -1,6 +1,6 @@
# What in user/ goes where, and in what form. # What in user/ goes where, and in what form.
# #
# <kind> <source under user/> <destination> # <kind> <source under user/> <destination> [os]
# #
# kind is one of: # kind is one of:
# link symlink the destination at the source, so editing either edits the # link symlink the destination at the source, so editing either edits the
@@ -14,6 +14,10 @@
# copy copy once, and never touch it again if the destination exists. Use # copy copy once, and never touch it again if the destination exists. Use
# this for files a machine legitimately diverges on. # this for files a machine legitimately diverges on.
# #
# os is optional: linux or darwin, matched against `uname -s`. A line naming
# one is skipped everywhere else. Leave it off for anything every machine
# wants, which is most of this file; the Mac mini only takes the agent half.
#
# Lines beginning with # are comments and blank lines are ignored. ~ in a # Lines beginning with # are comments and blank lines are ignored. ~ in a
# destination is this user's home. # destination is this user's home.
# #
@@ -43,21 +47,22 @@ link agents/rules ~/.claude/rules
# ── SSH ────────────────────────────────────────────────────────────────────── # ── SSH ──────────────────────────────────────────────────────────────────────
# Host aliases only. Keys are per-machine and are never tracked; this is the # Host aliases only. Keys are per-machine and are never tracked; this is the
# file that makes `ssh server` work the moment a machine is set up, and it is # file that makes `ssh server` work the moment a machine is set up, and it is
# what the SSH Hosts launcher command reads. # what the SSH Hosts launcher command reads. Linux only: it names id_ed25519
link ssh/config ~/.ssh/config # and the desktop keyring's agent socket, and the Mac has neither.
link ssh/config ~/.ssh/config linux
# ── Scripts ────────────────────────────────────────────────────────────────── # ── Scripts ──────────────────────────────────────────────────────────────────
# Personal commands wanted on every machine, desktop or server. ~/.local/bin is # Personal commands wanted on every machine, desktop or server. ~/.local/bin is
# on PATH in both shell configs. pwswitch toggles passwordless sudo through a # on PATH in both shell configs. pwswitch toggles passwordless sudo through a
# sudoers.d drop-in, so an agent can run sudo and the password comes back after. # sudoers.d drop-in, so an agent can run sudo and the password comes back after.
link bin/pwswitch ~/.local/bin/pwswitch link bin/pwswitch ~/.local/bin/pwswitch linux
# ── Espanso ────────────────────────────────────────────────────────────────── # ── Espanso ──────────────────────────────────────────────────────────────────
# The personal expansion triggers. Copied rather than linked, because this is a # The personal expansion triggers. Copied rather than linked, because this is a
# file each machine may add its own triggers to. It lands before setup-identity # file each machine may add its own triggers to. It lands before setup-identity
# runs, and that stage only seeds a file that does not exist yet, so what is # runs, and that stage only seeds a file that does not exist yet, so what is
# tracked here wins over the name and email the interview would have written. # tracked here wins over the name and email the interview would have written.
copy espanso/identity.yml ~/.config/espanso/match/identity.yml copy espanso/identity.yml ~/.config/espanso/match/identity.yml linux
# ── OpenCode ───────────────────────────────────────────────────────────────── # ── OpenCode ─────────────────────────────────────────────────────────────────
# Claude Code carries its commands and agents as plugins, which opencode cannot # Claude Code carries its commands and agents as plugins, which opencode cannot