Files
dots/common/.config/claude/link.sh
T
coja 1e39e0cb4b [Sync] replace the flat layout with the unified stow tree
Supersedes the old flat .config/ layout (last published 2026-06-28) with the
private repo's structure: one shared base plus per-host overlays.

- packages: common/ gui/ lw/ fl/ wm/ plus install.sh and bin/ tooling
  (dotsync, reconcile-hyde.sh)
- new README covering the layout, deploy order and the HyDE dependency
- current HyDE waybar rig (layouts/, cava), pi agent extensions, claude/
  config, tmux, presenterm, aichat roles
- fish: kp (keepassxc-cli + fzf picker, db path from $KP_DB) and
  bind_M_n_history (alt+1..9 recalls the nth history entry)
- drops cruft that should never have been tracked: the duplicate top-level
  .pi/ copy, btop.log, zellij config.kdl.bak, fish_variables
- .pi/agent/auth.json is gitignored; auth.json.example ships instead

Host-specific work sessions and the personal backlog stay in the private
tree. Endpoint locators in the llamacpp/whisper guides are placeholders
($SERVER, <own-domain>) — the guides themselves stay, since they are the
useful part.
2026-08-13 02:53:06 +02:00

98 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# Surface this repo's Claude Code config into ~/.claude via symlinks.
#
# `stow` deploys the repo to ~/.config/claude; Claude itself reads from ~/.claude,
# so we link the individual files/dirs across. Idempotent and self-healing: re-run
# any time. It also flags any file that has broken out of stow into a plain file —
# which silently stops Claude's edits from reaching the repo.
# (New skills/hooks added to the repo appear after a re-stow: `./install.sh` or `dotsync`.)
#
# bash ~/.config/claude/link.sh
set -eu
SRC="$HOME/.config/claude"
DST="$HOME/.claude"
if [ ! -e "$SRC" ]; then
echo "error: $SRC not found — run 'cd ~/.dots && ./install.sh' first" >&2
exit 1
fi
mkdir -p "$DST"
# This repo's claude dir, resolved via link.sh's own symlink (location-independent).
repo=$(dirname "$(readlink -f "$SRC/link.sh")")
# If link.sh itself has broken out of stow into a plain file, $repo resolves to $SRC and
# the drift heal below would relink each drifted file onto ITSELF — destroying the only
# copy of its content. Refuse to continue.
if [ "$repo" = "$SRC" ]; then
echo "error: link.sh itself is a plain file (broken out of stow) — re-stow first:" >&2
echo " cd ~/.dots && ./install.sh" >&2
exit 1
fi
# Tracked items to expose in ~/.claude. Add new ones (e.g. commands agents) here.
items="settings.json statusline.py keybindings.json CLAUDE.md hooks skills"
# Expose each item in ~/.claude (where Claude actually reads).
for item in $items; do
[ -e "$SRC/$item" ] || continue
if [ -d "$DST/$item" ] && [ ! -L "$DST/$item" ]; then
echo "skip ~/.claude/$item (real directory in the way — move it aside first)" >&2
continue
fi
# A plain FILE here means an atomic write replaced the link (Claude writes e.g.
# ~/.claude/settings.json) — it may hold newer edits, so never clobber it silently:
# heal only when provably lossless, warn otherwise (mirrors the SRC drift guard below).
if [ -f "$DST/$item" ] && [ ! -L "$DST/$item" ]; then
same=0
case "$item" in
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$DST/$item" "$SRC/$item" 2>/dev/null && same=1 ;;
*)
cmp -s "$DST/$item" "$SRC/$item" && same=1 ;;
esac
if [ "$same" != 1 ]; then
echo "DRIFT ~/.claude/$item is a real file that differs from the repo copy —" >&2
echo " it may hold newer edits. Review, then re-link:" >&2
echo " diff '$DST/$item' '$SRC/$item' # compare; keep whichever is right" >&2
echo " ln -sfn '$SRC/$item' '$DST/$item' # re-link" >&2
continue
fi
echo "healed ~/.claude/$item (plain copy identical to repo — re-linking)"
fi
ln -sfn "$SRC/$item" "$DST/$item"
echo "linked ~/.claude/$item -> ~/.config/claude/$item"
done
# Stow-layer drift guard: each SRC *file* should be a symlink into the repo, so edits
# Claude writes (e.g. via /config) flow back and stay tracked. If an atomic write
# replaced one with a plain file, it has silently broken out of stow. Auto-heal only
# when provably lossless — the plain file is identical to the repo copy (JSON compared
# ignoring key order, since /config often just reorders keys). Otherwise warn and stop:
# the file may hold newer edits worth diffing in before re-linking.
for item in $items; do
[ -f "$SRC/$item" ] && [ ! -L "$SRC/$item" ] || continue # a real file where a link belongs
same=0
if [ -f "$repo/$item" ]; then
case "$item" in
*.json)
python3 -c 'import json,sys; sys.exit(0 if json.load(open(sys.argv[1]))==json.load(open(sys.argv[2])) else 1)' \
"$SRC/$item" "$repo/$item" 2>/dev/null && same=1 ;;
*)
cmp -s "$SRC/$item" "$repo/$item" && same=1 ;;
esac
fi
if [ "$same" = 1 ]; then
ln -sfrn "$repo/$item" "$SRC/$item" # -r: relative link, so stow keeps owning it
echo "healed ~/.config/claude/$item (plain copy identical to repo — re-linked)"
continue
fi
echo "DRIFT ~/.config/claude/$item is a real file that differs from the repo copy —" >&2
echo " it may hold edits not yet tracked. Review, then re-link:" >&2
echo " diff '$SRC/$item' '$repo/$item' # compare; keep whichever is right" >&2
echo " ln -sfrn '$repo/$item' '$SRC/$item' # re-link (relative, stow-owned)" >&2
done