0096addb23
Codex 0.148 discovers skills under $CODEX_HOME/skills only, so every skill in this repo was invisible to codex sessions — review sessions run by a codex reviewer reported review-pr as an unavailable skill and fell back to improvising a review. Both installers now link each skill into ~/.codex/skills as well. The nix module links one symlink per skill dir instead of recursive=true: codex skips any skill whose SKILL.md is itself a symlink, which is what a per-file link produces.
155 lines
6.0 KiB
Bash
Executable File
155 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Symlink centralized skills/commands/hooks/context into each agent's config dir.
|
|
# For non-Nix machines (NixOS boxes use nix/home.nix instead).
|
|
# Idempotent. Backs up any pre-existing real dir once to <name>.bak.
|
|
set -euo pipefail
|
|
|
|
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
|
|
|
# targets: agent config skill roots. Claude Code reads ~/.claude/skills,
|
|
# Pi reads ~/.agents/skills, opencode auto-loads both dirs, and Codex reads
|
|
# $CODEX_HOME/skills and nothing else -- ~/.agents/skills is invisible to it,
|
|
# which is how review sessions ended up reporting an unavailable review-pr
|
|
# skill. All four consume the same SKILL.md dirs.
|
|
CLAUDE_SKILLS="$HOME/.claude/skills"
|
|
AGENTS_SKILLS="$HOME/.agents/skills"
|
|
CODEX_SKILLS="$HOME/.codex/skills"
|
|
CLAUDE_CMDS="$HOME/.claude/commands" # commands are Claude-only; Codex ignores
|
|
OPENCODE_CMDS="${XDG_CONFIG_HOME:-$HOME/.config}/opencode/commands"
|
|
CLAUDE_HOOKS="$HOME/.claude/hooks" # hooks are Claude-only
|
|
CLAUDE_SCRIPTS="$HOME/.claude/scripts" # referenced by hook commands in settings.json
|
|
CLAUDE_HOME="$HOME/.claude" # CLAUDE.md fragments, pulled in via @name.md
|
|
CLAUDE_RULES="$HOME/.claude/rules" # path-scoped rules
|
|
CODEX_HOME="$HOME/.codex" # Codex global config root
|
|
PI_HOME="$HOME/.pi/agent" # Pi global config root
|
|
OPENCODE_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/opencode"
|
|
|
|
# Which claude-md/machines/<name>.md to bake into Pi/opencode AGENTS.md.
|
|
MACHINE="${MACHINE:-default}"
|
|
|
|
# Per-FILE links, never a whole-dir link: ~/.claude/hooks and ~/.claude itself hold
|
|
# machine-local files this repo does not own, and a dir symlink would hide them.
|
|
|
|
# backups go OUTSIDE the skill dirs — a *.bak left inside gets discovered as a
|
|
# duplicate skill by both Claude Code and Codex.
|
|
BACKUP="$HOME/.agent-skills-backup"
|
|
|
|
link() { # link <src> <dst>
|
|
local src="$1" dst="$2"
|
|
if [ -e "$dst" ] && [ ! -L "$dst" ]; then
|
|
mkdir -p "$BACKUP"
|
|
mv "$dst" "$BACKUP/$(basename "$(dirname "$dst")")-$(basename "$dst").bak"
|
|
echo "backed up existing $dst -> $BACKUP/"
|
|
fi
|
|
ln -sfn "$src" "$dst"
|
|
echo "linked $dst -> $src"
|
|
}
|
|
|
|
gen() { # gen <dst> <fragment...> — writes a generated (concatenated) file
|
|
local dst="$1"
|
|
shift
|
|
if [ -e "$dst" ] && ! grep -q "$GEN_MARK" "$dst" 2>/dev/null; then
|
|
mkdir -p "$BACKUP"
|
|
mv "$dst" "$BACKUP/$(basename "$(dirname "$dst")")-$(basename "$dst").bak"
|
|
echo "backed up existing $dst -> $BACKUP/"
|
|
fi
|
|
{
|
|
echo "$GEN_MARK"
|
|
for f in "$@"; do
|
|
echo
|
|
cat "$f"
|
|
done
|
|
} >"$dst"
|
|
echo "wrote $dst (generated from fragments)"
|
|
}
|
|
|
|
GEN_MARK="<!-- generated by agent-skills/bin/link.sh — edit fragments, re-run -->"
|
|
|
|
mkdir -p "$CLAUDE_SKILLS" "$AGENTS_SKILLS" "$CODEX_SKILLS" "$CLAUDE_CMDS" "$CLAUDE_HOOKS" "$CLAUDE_SCRIPTS" "$CLAUDE_RULES" "$CODEX_HOME" "$PI_HOME" "$OPENCODE_CMDS"
|
|
|
|
for d in "$REPO"/skills/*/; do
|
|
name="$(basename "$d")"
|
|
link "$d" "$CLAUDE_SKILLS/$name"
|
|
link "$d" "$AGENTS_SKILLS/$name"
|
|
link "$d" "$CODEX_SKILLS/$name"
|
|
done
|
|
|
|
for f in "$REPO"/commands/*.md; do
|
|
[ -e "$f" ] || continue
|
|
link "$f" "$CLAUDE_CMDS/$(basename "$f")"
|
|
link "$f" "$OPENCODE_CMDS/$(basename "$f")"
|
|
done
|
|
|
|
for f in "$REPO"/hooks/*.py "$REPO"/hooks/*.sh; do
|
|
[ -e "$f" ] || continue
|
|
link "$f" "$CLAUDE_HOOKS/$(basename "$f")"
|
|
done
|
|
|
|
# Hook commands in settings.json call these by absolute path, so they have to
|
|
# exist under ~/.claude/scripts on every machine.
|
|
for f in "$REPO"/scripts/*; do
|
|
[ -e "$f" ] || continue
|
|
link "$f" "$CLAUDE_SCRIPTS/$(basename "$f")"
|
|
done
|
|
|
|
# code-comments.md is path-scoped and belongs in rules/, not here — linking it
|
|
# into ~/.claude/ as well would load it unconditionally and defeat the scoping.
|
|
# opencode-header.md is opencode-only (baked into its generated AGENTS.md).
|
|
for f in "$REPO"/claude-md/*.md; do
|
|
[ -e "$f" ] || continue
|
|
[ "$(basename "$f")" = "code-comments.md" ] && continue
|
|
[ "$(basename "$f")" = "opencode-header.md" ] && continue
|
|
link "$f" "$CLAUDE_HOME/$(basename "$f")"
|
|
done
|
|
|
|
# Per-machine section. entry/CLAUDE.md @imports it unconditionally, so it has
|
|
# to resolve even on a box with no profile of its own (MACHINE=default).
|
|
link "$REPO/claude-md/machines/$MACHINE.md" "$CLAUDE_HOME/machine.md"
|
|
|
|
# Path-scoped rules load only when Claude reads a matching file.
|
|
link "$REPO/claude-md/code-comments.md" "$CLAUDE_RULES/code-comments.md"
|
|
rm -f "$CLAUDE_HOME/code-comments.md" "$HOME/.agents/AGENTS.md"
|
|
|
|
# Entry files: machine-local sections plus @imports of the shared fragments.
|
|
# Codex supports @path the same as CLAUDE.md, so neither tool needs a copy.
|
|
mkdir -p "$CODEX_HOME"
|
|
link "$REPO/entry/CLAUDE.md" "$CLAUDE_HOME/CLAUDE.md"
|
|
link "$REPO/entry/codex-AGENTS.md" "$CODEX_HOME/AGENTS.md"
|
|
rm -f "$CODEX_HOME/RTK.md"
|
|
|
|
# Pi and opencode have no @file imports: their AGENTS.md is generated by
|
|
# concatenating the fragments (machine profile first). opencode auto-loads
|
|
# skills from ~/.claude/skills and ~/.agents/skills, so it needs no skill links.
|
|
gen "$PI_HOME/AGENTS.md" \
|
|
"$REPO/claude-md/machines/$MACHINE.md" \
|
|
"$REPO/claude-md/operating.md" \
|
|
"$REPO/claude-md/writing.md" \
|
|
"$REPO/claude-md/code-comments.md" \
|
|
"$REPO/claude-md/intercomms.md" \
|
|
"$REPO/claude-md/RTK.md"
|
|
|
|
gen "$OPENCODE_HOME/AGENTS.md" \
|
|
"$REPO/claude-md/opencode-header.md" \
|
|
"$REPO/claude-md/machines/$MACHINE.md" \
|
|
"$REPO/claude-md/operating.md" \
|
|
"$REPO/claude-md/writing.md" \
|
|
"$REPO/claude-md/code-comments.md" \
|
|
"$REPO/claude-md/intercomms.md" \
|
|
"$REPO/claude-md/RTK.md"
|
|
|
|
# Linked but never enabled: enabling on every machine would spawn one review
|
|
# session per box for the same week.
|
|
if [ -d /run/systemd/system ]; then
|
|
mkdir -p "$HOME/.config/systemd/user"
|
|
for f in "$REPO"/systemd/*.service "$REPO"/systemd/*.timer; do
|
|
[ -e "$f" ] || continue
|
|
link "$f" "$HOME/.config/systemd/user/$(basename "$f")"
|
|
done
|
|
fi
|
|
|
|
echo "done. (MACHINE=$MACHINE for generated Pi/opencode AGENTS.md)"
|
|
echo "hooks still need wiring in ~/.claude/settings.json — see hooks/README.md"
|
|
echo "timers (one machine only):"
|
|
echo " systemctl --user daemon-reload"
|
|
echo " systemctl --user enable --now week-review.timer hourlog.timer"
|