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.
98 lines
4.3 KiB
Bash
Executable File
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
|