Publish skills through the agent-neutral skill home
This commit is contained in:
@@ -9,7 +9,7 @@ You are editing a running desktop, not a codebase that gets deployed later. Ever
|
|||||||
repository is symlinked into `~/.config`, so a save is live the moment it lands. That single fact
|
repository is symlinked into `~/.config`, so a save is live the moment it lands. That single fact
|
||||||
drives every rule below.
|
drives every rule below.
|
||||||
|
|
||||||
Read before large work, in this order:
|
Before large work, read these in order:
|
||||||
|
|
||||||
- `README.md` — layout, the `panama` command, how installing and updating work
|
- `README.md` — layout, the `panama` command, how installing and updating work
|
||||||
- `config/dot/hypr/README.md` — the compositor config is **Lua, not hyprlang**; read "The one
|
- `config/dot/hypr/README.md` — the compositor config is **Lua, not hyprlang**; read "The one
|
||||||
@@ -62,7 +62,8 @@ Every executable contract under `tests/` is classified in `tests/contracts.manif
|
|||||||
`PANAMA_TEST_TIMEOUT_SECONDS` value. Failures print the contract's captured stdout and stderr.
|
`PANAMA_TEST_TIMEOUT_SECONDS` value. Failures print the contract's captured stdout and stderr.
|
||||||
Successful stdout stays quiet. Successful stderr is surfaced as a warning.
|
Successful stdout stays quiet. Successful stderr is surfaced as a warning.
|
||||||
- Contracts run directly too: `tests/setup/interview-contract`.
|
- Contracts run directly too: `tests/setup/interview-contract`.
|
||||||
- After changing `PreferenceSchema.qml` or `services/SettingsRoutes.qml`, regenerate:
|
- After changing `PreferenceSchema.qml` or
|
||||||
|
`config/dot/quickshell/services/SettingsRoutes.qml`, regenerate:
|
||||||
`config/dot/quickshell/scripts/panama-settings-docs` (writes `docs/settings.md`) and
|
`config/dot/quickshell/scripts/panama-settings-docs` (writes `docs/settings.md`) and
|
||||||
`config/dot/quickshell/scripts/panama-settings-commands` (writes the launcher deep links).
|
`config/dot/quickshell/scripts/panama-settings-commands` (writes the launcher deep links).
|
||||||
Both take `--check`; `tests/quickshell/settings-docs-contract` fails when stale.
|
Both take `--check`; `tests/quickshell/settings-docs-contract` fails when stale.
|
||||||
Symlink
+1
@@ -0,0 +1 @@
|
|||||||
|
../../.agents/skills/panama
|
||||||
@@ -53,7 +53,7 @@ in order, without stopping again:
|
|||||||
| `interview` | Every prompt, before anything is installed. Answers last one run and are never written to a durable path |
|
| `interview` | Every prompt, before anything is installed. Answers last one run and are never written to a durable path |
|
||||||
| `install-packages` | Repos (RPM Fusion, Terra, Hyprland COPR), the package lists in `setup/packages/`, then whichever optional categories were chosen |
|
| `install-packages` | Repos (RPM Fusion, Terra, Hyprland COPR), the package lists in `setup/packages/`, then whichever optional categories were chosen |
|
||||||
| `link-dotfiles` | Symlinks `config/dot/<name>` → `~/.config/<name>`, and seeds the wallpaper, cursor theme and Firefox chrome |
|
| `link-dotfiles` | Symlinks `config/dot/<name>` → `~/.config/<name>`, and seeds the wallpaper, cursor theme and Firefox chrome |
|
||||||
| `link-skills` | Links the agent skills in `skills/` into `~/.claude/skills`, one per skill. Every machine gets these; personal ones link after and win a name clash |
|
| `link-skills` | Links the agent skills in `skills/` into `~/.agents/skills` and `~/.claude/skills`, one per skill. Every machine gets these; personal ones link after and win a name clash |
|
||||||
| `link-user` | Links the personal content in `user/` — agent instructions, SSH host aliases — but only on a machine that answered yes. See [user/README.md](user/README.md) |
|
| `link-user` | Links the personal content in `user/` — agent instructions, SSH host aliases — but only on a machine that answered yes. See [user/README.md](user/README.md) |
|
||||||
| `change-settings` | Copies `config/copy/` over `/`, applies gsettings, enables user services |
|
| `change-settings` | Copies `config/copy/` over `/`, applies gsettings, enables user services |
|
||||||
| `link-vicinae-scripts` | Publishes the Vicinae script commands |
|
| `link-vicinae-scripts` | Publishes the Vicinae script commands |
|
||||||
@@ -186,7 +186,7 @@ server/ The server role: compose services (one directory per
|
|||||||
service), the nightly image updater, and its units. See
|
service), the nightly image updater, and its units. See
|
||||||
server/README.md
|
server/README.md
|
||||||
skills/ Agent skills for operating this desktop, linked into
|
skills/ Agent skills for operating this desktop, linked into
|
||||||
~/.claude/skills
|
~/.agents/skills and ~/.claude/skills
|
||||||
setup/
|
setup/
|
||||||
apps/ Applications built from source, one file each
|
apps/ Applications built from source, one file each
|
||||||
keys/ Pinned signing keys, for publishers that ship no fetchable
|
keys/ Pinned signing keys, for publishers that ship no fetchable
|
||||||
|
|||||||
+21
-19
@@ -11,12 +11,11 @@
|
|||||||
# this repository, not anybody's personal content, so a stranger who clones
|
# this repository, not anybody's personal content, so a stranger who clones
|
||||||
# Panama wants it for exactly the same reason its author does.
|
# Panama wants it for exactly the same reason its author does.
|
||||||
#
|
#
|
||||||
# ~/.claude/skills was a single symlink into user/agents/skills until now, and
|
# ~/.agents/skills and ~/.claude/skills may each start as a single symlink into
|
||||||
# a directory cannot be two things at once. So the destination becomes a real
|
# user/agents/skills, but a directory cannot point at personal and shipped
|
||||||
# directory and every skill -- shipped here, personal from user/ -- is linked
|
# skills at once. Both destinations become real directories with one link per
|
||||||
# into it one at a time. link-user runs after this stage on purpose: it links
|
# skill. link-user runs after this stage on purpose, so a personal skill named
|
||||||
# last, so a personal skill named like a shipped one wins, which is the
|
# like a shipped one wins in every agent runtime.
|
||||||
# precedence Claude Code itself uses.
|
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -25,7 +24,7 @@ log() { echo -e "\033[1;34m[INFO]\033[0m $*"; }
|
|||||||
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
|
PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}"
|
||||||
SKILLS_DIR="$PANAMA_PATH/skills"
|
SKILLS_DIR="$PANAMA_PATH/skills"
|
||||||
PANAMA_OLD="$PANAMA_PATH/config/old"
|
PANAMA_OLD="$PANAMA_PATH/config/old"
|
||||||
DESTINATION="$HOME/.claude/skills"
|
DESTINATIONS=("$HOME/.agents/skills" "$HOME/.claude/skills")
|
||||||
|
|
||||||
[[ -d "$SKILLS_DIR" ]] || { log "No skills/ in this checkout; nothing to link."; exit 0; }
|
[[ -d "$SKILLS_DIR" ]] || { log "No skills/ in this checkout; nothing to link."; exit 0; }
|
||||||
|
|
||||||
@@ -51,27 +50,30 @@ displace() {
|
|||||||
log "Moved existing $destination to $backup"
|
log "Moved existing $destination to $backup"
|
||||||
}
|
}
|
||||||
|
|
||||||
# The destination itself has to be a real directory before anything can be
|
# Each destination has to be a real directory before anything can be linked
|
||||||
# linked into it. An old whole-directory symlink is removed; a regular file
|
# into it. An old whole-directory symlink is removed; a regular file somebody
|
||||||
# somebody left at this path is kept, in config/old/.
|
# left at the path is kept in config/old/.
|
||||||
mkdir -p "$(dirname "$DESTINATION")"
|
for destination in "${DESTINATIONS[@]}"; do
|
||||||
if [[ -L "$DESTINATION" ]]; then
|
mkdir -p "$(dirname "$destination")"
|
||||||
rm -f "$DESTINATION"
|
if [[ -L "$destination" ]]; then
|
||||||
log "Removed the old $DESTINATION symlink; skills are linked one by one now"
|
rm -f "$destination"
|
||||||
elif [[ -e "$DESTINATION" && ! -d "$DESTINATION" ]]; then
|
log "Removed the old $destination symlink; skills are linked one by one now"
|
||||||
displace "$DESTINATION"
|
elif [[ -e "$destination" && ! -d "$destination" ]]; then
|
||||||
|
displace "$destination"
|
||||||
fi
|
fi
|
||||||
mkdir -p "$DESTINATION"
|
mkdir -p "$destination"
|
||||||
|
done
|
||||||
|
|
||||||
linked=0
|
linked=0
|
||||||
for skill in "$SKILLS_DIR"/*; do
|
for skill in "$SKILLS_DIR"/*; do
|
||||||
[[ -e "$skill" ]] || continue
|
[[ -e "$skill" ]] || continue
|
||||||
name="$(basename "$skill")"
|
name="$(basename "$skill")"
|
||||||
target="$DESTINATION/$name"
|
for destination in "${DESTINATIONS[@]}"; do
|
||||||
|
target="$destination/$name"
|
||||||
displace "$target"
|
displace "$target"
|
||||||
ln -s "$skill" "$target"
|
ln -s "$skill" "$target"
|
||||||
log "Linked skills/$name → $target"
|
log "Linked skills/$name → $target"
|
||||||
|
done
|
||||||
linked=$(( linked + 1 ))
|
linked=$(( linked + 1 ))
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
+41
-20
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# The manual this machine hands an agent.
|
# The manual this machine hands an agent.
|
||||||
#
|
#
|
||||||
# skills/ and .claude/skills/panama exist because an agent asked to do anything
|
# skills/ and .agents/skills/panama exist because an agent asked to do anything
|
||||||
# on a Panama desktop will otherwise infer it from the source and get half of it
|
# on a Panama desktop will otherwise infer it from the source and get half of it
|
||||||
# wrong. That only helps if what the skills say is true -- and a skill is worse
|
# wrong. That only helps if what the skills say is true -- and a skill is worse
|
||||||
# than no skill when it is stale, because an agent believes it verbatim and does
|
# than no skill when it is stale, because an agent believes it verbatim and does
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
# That is a convention on the prose -- name things exactly, in backticks --
|
# That is a convention on the prose -- name things exactly, in backticks --
|
||||||
# and it is how they should be written anyway.
|
# and it is how they should be written anyway.
|
||||||
# 3. The delivery works: link-skills is a stage, in the right place, and the
|
# 3. The delivery works: link-skills is a stage, in the right place, and the
|
||||||
# personal manifest hands ~/.claude/skills over to the linkdir kind.
|
# personal manifest hands both shared skill homes to the linkdir kind.
|
||||||
#
|
#
|
||||||
# Sections 1 and 2 report clearly and keep going when a skill is not written
|
# Sections 1 and 2 report clearly and keep going when a skill is not written
|
||||||
# yet, so this contract is useful while the skills are still being authored.
|
# yet, so this contract is useful while the skills are still being authored.
|
||||||
@@ -32,9 +32,21 @@ manifest="$repo_dir/user/manifest"
|
|||||||
findings=()
|
findings=()
|
||||||
note() { findings+=("$1"); }
|
note() { findings+=("$1"); }
|
||||||
|
|
||||||
# The three, and where each is delivered from. The two under skills/ are shipped
|
# Every directory under skills/ ships to every machine. The project-level
|
||||||
# to every machine; the third is project-level and needs no delivery at all.
|
# panama skill stays in this repository and needs no home-directory delivery.
|
||||||
SKILL_DIRS=(skills/panama-desktop skills/panama-sudo .claude/skills/panama)
|
SKILL_DIRS=()
|
||||||
|
for directory in "$repo_dir"/skills/*; do
|
||||||
|
[[ -d "$directory" ]] && SKILL_DIRS+=("${directory#"$repo_dir"/}")
|
||||||
|
done
|
||||||
|
SKILL_DIRS+=(.agents/skills/panama)
|
||||||
|
|
||||||
|
# The project skill has one agent-neutral source. Claude gets a compatibility
|
||||||
|
# symlink, while Codex and other Agent Skills readers use .agents directly.
|
||||||
|
[[ -L "$repo_dir/.claude/skills/panama" ]] \
|
||||||
|
|| note '.claude/skills/panama is not a compatibility symlink to the agent-neutral source'
|
||||||
|
[[ "$(readlink -f "$repo_dir/.claude/skills/panama" 2>/dev/null)" == \
|
||||||
|
"$(readlink -f "$repo_dir/.agents/skills/panama" 2>/dev/null)" ]] \
|
||||||
|
|| note '.claude and .agents resolve the project panama skill differently'
|
||||||
|
|
||||||
# ── 1. Each skill loads ─────────────────────────────────────────────────────
|
# ── 1. Each skill loads ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -54,7 +66,7 @@ for relative in "${SKILL_DIRS[@]}"; do
|
|||||||
present+=("$directory")
|
present+=("$directory")
|
||||||
|
|
||||||
# Frontmatter is the first --- delimited block, and a skill without one is
|
# Frontmatter is the first --- delimited block, and a skill without one is
|
||||||
# not a skill: Claude Code skips the directory entirely.
|
# not a skill: agent loaders skip the directory entirely.
|
||||||
frontmatter="$(awk 'NR==1 { if ($0 != "---") exit 1; next } $0 == "---" { exit } { print }' "$file")"
|
frontmatter="$(awk 'NR==1 { if ($0 != "---") exit 1; next } $0 == "---" { exit } { print }' "$file")"
|
||||||
if [[ -z "$frontmatter" ]]; then
|
if [[ -z "$frontmatter" ]]; then
|
||||||
note "$relative/SKILL.md does not open with a --- frontmatter block"
|
note "$relative/SKILL.md does not open with a --- frontmatter block"
|
||||||
@@ -207,12 +219,12 @@ else
|
|||||||
work="$(mktemp -d)"
|
work="$(mktemp -d)"
|
||||||
trap 'rm -rf "$work"' EXIT
|
trap 'rm -rf "$work"' EXIT
|
||||||
|
|
||||||
# A checkout and a home of its own. Never the real ones: ~/.claude/skills on
|
# A checkout and a home of its own. Never the real ones: these are somebody's
|
||||||
# this machine is somebody's live agent setup, and a contract that broke it
|
# live agent setup, and a contract that broke them mid-session would be worse
|
||||||
# mid-session would be worse than the bug it was looking for.
|
# than the bug it was looking for.
|
||||||
checkout="$work/Panama"
|
checkout="$work/Panama"
|
||||||
home="$work/home"
|
home="$work/home"
|
||||||
mkdir -p "$checkout/setup/scripts" "$checkout/skills/shipped" "$home/.claude"
|
mkdir -p "$checkout/setup/scripts" "$checkout/skills/shipped" "$home/.claude" "$home/.agents"
|
||||||
cp "$linker" "$checkout/setup/scripts/link-skills"
|
cp "$linker" "$checkout/setup/scripts/link-skills"
|
||||||
printf 'a shipped skill\n' >"$checkout/skills/shipped/SKILL.md"
|
printf 'a shipped skill\n' >"$checkout/skills/shipped/SKILL.md"
|
||||||
|
|
||||||
@@ -220,6 +232,7 @@ else
|
|||||||
# symlink, which is what link-user used to leave here.
|
# symlink, which is what link-user used to leave here.
|
||||||
mkdir -p "$work/personal"
|
mkdir -p "$work/personal"
|
||||||
ln -s "$work/personal" "$home/.claude/skills"
|
ln -s "$work/personal" "$home/.claude/skills"
|
||||||
|
ln -s "$work/personal" "$home/.agents/skills"
|
||||||
|
|
||||||
run() { HOME="$home" PANAMA_PATH="$checkout" "$checkout/setup/scripts/link-skills" >"$work/log" 2>&1; }
|
run() { HOME="$home" PANAMA_PATH="$checkout" "$checkout/setup/scripts/link-skills" >"$work/log" 2>&1; }
|
||||||
|
|
||||||
@@ -231,22 +244,30 @@ else
|
|||||||
|| note 'link-skills left ~/.claude/skills a symlink, so nothing else can be linked into it'
|
|| note 'link-skills left ~/.claude/skills a symlink, so nothing else can be linked into it'
|
||||||
[[ -L "$home/.claude/skills/shipped" ]] \
|
[[ -L "$home/.claude/skills/shipped" ]] \
|
||||||
|| note 'link-skills did not link each shipped skill as a child of ~/.claude/skills'
|
|| note 'link-skills did not link each shipped skill as a child of ~/.claude/skills'
|
||||||
|
[[ -d "$home/.agents/skills" && ! -L "$home/.agents/skills" ]] \
|
||||||
|
|| note 'link-skills left ~/.agents/skills a symlink, so shipped and personal skills cannot coexist'
|
||||||
|
[[ -L "$home/.agents/skills/shipped" ]] \
|
||||||
|
|| note 'link-skills did not link each shipped skill as a child of ~/.agents/skills'
|
||||||
grep -q 'Agent skills: 1 linked' "$work/log" \
|
grep -q 'Agent skills: 1 linked' "$work/log" \
|
||||||
|| note 'link-skills does not report how many skills it linked'
|
|| note 'link-skills does not report how many skills it linked'
|
||||||
|
|
||||||
# A real directory at a shipped skill's name is somebody's work: it moves to
|
# A real directory at a shipped skill's name is somebody's work: it moves to
|
||||||
# config/old rather than being deleted, the same promise the other stages
|
# config/old rather than being deleted, the same promise the other stages
|
||||||
# make. A symlink is not, and must not accumulate there.
|
# make. A symlink is not, and must not accumulate there.
|
||||||
rm "$home/.claude/skills/shipped"
|
for skill_home in "$home/.claude/skills" "$home/.agents/skills"; do
|
||||||
mkdir -p "$home/.claude/skills/shipped"
|
rm -f "$skill_home/shipped"
|
||||||
printf 'installed by hand\n' >"$home/.claude/skills/shipped/SKILL.md"
|
mkdir -p "$skill_home/shipped"
|
||||||
mkdir -p "$home/.claude/skills/untouched"
|
printf 'installed by hand\n' >"$skill_home/shipped/SKILL.md"
|
||||||
|
mkdir -p "$skill_home/untouched"
|
||||||
|
done
|
||||||
|
|
||||||
run
|
run
|
||||||
grep -rq 'installed by hand' "$checkout/config/old" 2>/dev/null \
|
[[ "$(grep -rl 'installed by hand' "$checkout/config/old" 2>/dev/null | wc -l)" == 2 ]] \
|
||||||
|| note 'link-skills destroyed a real skill instead of moving it to config/old'
|
|| note 'link-skills did not preserve real skills from both agent homes'
|
||||||
[[ -d "$home/.claude/skills/untouched" ]] \
|
for skill_home in "$home/.claude/skills" "$home/.agents/skills"; do
|
||||||
|| note 'link-skills removed a skill it does not ship'
|
[[ -d "$skill_home/untouched" ]] \
|
||||||
|
|| note "link-skills removed an unshipped skill from $skill_home"
|
||||||
|
done
|
||||||
|
|
||||||
before="$(find "$checkout/config/old" | wc -l)"
|
before="$(find "$checkout/config/old" | wc -l)"
|
||||||
run
|
run
|
||||||
@@ -287,8 +308,8 @@ fi
|
|||||||
|
|
||||||
grep -qE '^\s*linkdir\s+agents/skills\s+~/\.claude/skills\s*$' "$manifest" \
|
grep -qE '^\s*linkdir\s+agents/skills\s+~/\.claude/skills\s*$' "$manifest" \
|
||||||
|| note 'the manifest does not use linkdir for ~/.claude/skills, so personal skills would replace the directory'
|
|| note 'the manifest does not use linkdir for ~/.claude/skills, so personal skills would replace the directory'
|
||||||
grep -qE '^\s*link\s+agents/skills\s+~/\.agents/skills\s*$' "$manifest" \
|
grep -qE '^\s*linkdir\s+agents/skills\s+~/\.agents/skills\s*$' "$manifest" \
|
||||||
|| note '~/.agents/skills is no longer a whole-directory link, and nothing else claims that path'
|
|| note 'the manifest does not use linkdir for ~/.agents/skills, so personal skills would replace shipped skills'
|
||||||
grep -q 'linkdir)' "$user_linker" \
|
grep -q 'linkdir)' "$user_linker" \
|
||||||
|| note 'link-user does not implement the linkdir kind the manifest asks for'
|
|| note 'link-user does not implement the linkdir kind the manifest asks for'
|
||||||
grep -q 'linkdir' "$repo_dir/user/README.md" \
|
grep -q 'linkdir' "$repo_dir/user/README.md" \
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ cat >"$checkout/user/manifest" <<'FIXTURE'
|
|||||||
# a comment, and a blank line follow
|
# a comment, and a blank line follow
|
||||||
|
|
||||||
link agents/AGENTS.md ~/.claude/CLAUDE.md
|
link agents/AGENTS.md ~/.claude/CLAUDE.md
|
||||||
link agents/skills ~/.agents/skills
|
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
|
||||||
@@ -102,11 +102,12 @@ PANAMA_USER_CONTENT=no run
|
|||||||
|
|
||||||
# ── 2. Saying yes links, and keeps what was there ───────────────────────────
|
# ── 2. Saying yes links, and keeps what was there ───────────────────────────
|
||||||
|
|
||||||
# ~/.claude/skills is shared now: link-skills has already made it a real
|
# Both skill homes are shared now: link-skills has already made them real
|
||||||
# directory and linked Panama's own skills into it. A linkdir entry has to land
|
# directories and linked Panama's own skills into each. Personal linkdir
|
||||||
# beside those rather than replace the directory holding them.
|
# entries land beside those rather than replacing either directory.
|
||||||
mkdir -p "$home/.claude/skills"
|
mkdir -p "$home/.claude/skills" "$home/.agents/skills"
|
||||||
ln -s "$checkout/skills/shipped" "$home/.claude/skills/shipped"
|
ln -s "$checkout/skills/shipped" "$home/.claude/skills/shipped"
|
||||||
|
ln -s "$checkout/skills/shipped" "$home/.agents/skills/shipped"
|
||||||
|
|
||||||
PANAMA_USER_CONTENT=yes run
|
PANAMA_USER_CONTENT=yes run
|
||||||
|
|
||||||
@@ -114,8 +115,12 @@ PANAMA_USER_CONTENT=yes run
|
|||||||
|| note 'CLAUDE.md was not replaced with a symlink into the checkout'
|
|| note 'CLAUDE.md was not replaced with a symlink into the checkout'
|
||||||
[[ "$(cat "$home/.claude/CLAUDE.md")" == "tracked instructions" ]] \
|
[[ "$(cat "$home/.claude/CLAUDE.md")" == "tracked instructions" ]] \
|
||||||
|| note 'the CLAUDE.md link does not resolve to the tracked file'
|
|| note 'the CLAUDE.md link does not resolve to the tracked file'
|
||||||
[[ -L "$home/.agents/skills" && -f "$home/.agents/skills/example/SKILL.md" ]] \
|
[[ -d "$home/.agents/skills" && ! -L "$home/.agents/skills" ]] \
|
||||||
|| note 'the skills directory was not linked as a directory'
|
|| note 'a linkdir entry replaced ~/.agents/skills with a symlink'
|
||||||
|
[[ -L "$home/.agents/skills/example" && -f "$home/.agents/skills/example/SKILL.md" ]] \
|
||||||
|
|| note 'a linkdir entry did not publish the personal skill for agent-neutral readers'
|
||||||
|
[[ -L "$home/.agents/skills/shipped" ]] \
|
||||||
|
|| note 'a linkdir entry removed the shipped skill from ~/.agents/skills'
|
||||||
[[ -d "$home/.claude/skills" && ! -L "$home/.claude/skills" ]] \
|
[[ -d "$home/.claude/skills" && ! -L "$home/.claude/skills" ]] \
|
||||||
|| note 'a linkdir entry replaced its destination directory with a symlink'
|
|| note 'a linkdir entry replaced its destination directory with a symlink'
|
||||||
[[ -L "$home/.claude/skills/example" && -f "$home/.claude/skills/example/SKILL.md" ]] \
|
[[ -L "$home/.claude/skills/example" && -f "$home/.claude/skills/example/SKILL.md" ]] \
|
||||||
|
|||||||
+11
-11
@@ -13,7 +13,7 @@ and one file says where each piece goes.
|
|||||||
| Path | Goes to | Why |
|
| Path | Goes to | Why |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `agents/AGENTS.md` | `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md` | Two tools, two names, one file. These were byte-identical copies before this, waiting to disagree. |
|
| `agents/AGENTS.md` | `~/.claude/CLAUDE.md`, `~/.codex/AGENTS.md` | Two tools, two names, one file. These were byte-identical copies before this, waiting to disagree. |
|
||||||
| `agents/skills/` | `~/.agents/skills`, `~/.claude/skills` | A skill installed on any machine lands in the checkout. The Claude path is `linkdir` — see below. |
|
| `agents/skills/` | `~/.agents/skills`, `~/.claude/skills` | Personal skills live once in the checkout. Both destinations use `linkdir` so Panama's shipped skills can live beside them. |
|
||||||
| `agents/rules/` | `~/.claude/rules` | |
|
| `agents/rules/` | `~/.claude/rules` | |
|
||||||
| `ssh/config` | `~/.ssh/config` | Host aliases only. Keys are per-machine and are never tracked. |
|
| `ssh/config` | `~/.ssh/config` | Host aliases only. Keys are per-machine and are never tracked. |
|
||||||
| `espanso/identity.yml` | `~/.config/espanso/match/identity.yml` | Copied, not linked, because a machine may add its own triggers. |
|
| `espanso/identity.yml` | `~/.config/espanso/match/identity.yml` | Copied, not linked, because a machine may add its own triggers. |
|
||||||
@@ -27,22 +27,22 @@ destination is empty. `linkdir` is the third, and it exists because one
|
|||||||
destination is no longer only ours.
|
destination is no longer only ours.
|
||||||
|
|
||||||
Panama ships its own agent skills now (`skills/`, linked by
|
Panama ships its own agent skills now (`skills/`, linked by
|
||||||
`setup/scripts/link-skills`), and they go to `~/.claude/skills` — the same
|
`setup/scripts/link-skills`), and they go to both skill homes. A directory
|
||||||
directory the personal ones went to as a single symlink. A directory cannot be
|
cannot be a symlink to two places, so both personal entries use `linkdir`: each
|
||||||
a symlink to two places, so that entry became `linkdir`: the destination is a
|
destination is a real directory, and each child of `user/agents/skills/` is
|
||||||
real directory, and each child of `user/agents/skills/` is linked into it
|
linked into it individually.
|
||||||
individually. `~/.agents/skills` is still a whole-directory `link`, because
|
|
||||||
nothing else claims it.
|
|
||||||
|
|
||||||
Two consequences, recorded rather than fixed:
|
Two consequences, recorded rather than fixed:
|
||||||
|
|
||||||
- **A personal skill named like a shipped one shadows it.** `link-user` runs
|
- **A personal skill named like a shipped one shadows it.** `link-user` runs
|
||||||
after `link-skills` and displaces what it finds, so the personal one wins.
|
after `link-skills` and displaces what it finds, so the personal one wins in
|
||||||
That is the intent, and it is the precedence Claude Code uses anyway.
|
both skill homes.
|
||||||
- **A new personal skill needs a re-link to appear.** The whole-directory link
|
- **A new personal skill needs a re-link to appear.** The whole-directory link
|
||||||
showed a newly created skill instantly; per-child links do not know about a
|
showed a newly created skill instantly; per-child links do not know about a
|
||||||
child that did not exist when they were made. Run `panama update` or
|
child that did not exist when they were made. Put new shared skills in
|
||||||
`setup/scripts/link-user` after adding one.
|
`user/agents/skills/`, then run `panama update` or `setup/scripts/link-user`.
|
||||||
|
A tool that installs directly into a home skill directory creates a
|
||||||
|
machine-local skill until it is moved into the checkout.
|
||||||
|
|
||||||
## It is off unless you say yes
|
## It is off unless you say yes
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ ln -s ../../.agents/skills/<name> ~/.claude/skills/<name>
|
|||||||
Writing a second copy into `~/.claude/skills/` gives you two files that drift, and the drift is
|
Writing a second copy into `~/.claude/skills/` gives you two files that drift, and the drift is
|
||||||
silent because each harness only ever reads its own. One home, one symlink per harness that needs it.
|
silent because each harness only ever reads its own. One home, one symlink per harness that needs it.
|
||||||
|
|
||||||
A skill that only makes sense inside one repo belongs in that repo, at `<repo>/.claude/skills/`.
|
A skill that only makes sense inside one repo lives once at
|
||||||
|
`<repo>/.agents/skills/<name>/`. Point Claude Code at that source with a
|
||||||
|
`<repo>/.claude/skills/<name>` symlink.
|
||||||
|
|
||||||
## Frontmatter
|
## Frontmatter
|
||||||
|
|
||||||
|
|||||||
+6
-7
@@ -28,14 +28,13 @@
|
|||||||
link agents/AGENTS.md ~/.claude/CLAUDE.md
|
link agents/AGENTS.md ~/.claude/CLAUDE.md
|
||||||
link agents/AGENTS.md ~/.codex/AGENTS.md
|
link agents/AGENTS.md ~/.codex/AGENTS.md
|
||||||
|
|
||||||
# Skills, at both paths that look for them. Linked so that a skill installed by
|
# Skills, at both paths that look for them. Each tracked skill is linked from
|
||||||
# any tool lands in the checkout and `panama update` offers to commit it.
|
# this checkout so edits stay shared and `panama update` offers to commit them.
|
||||||
#
|
#
|
||||||
# ~/.agents/skills is this directory and nothing else, so it stays one link.
|
# Both destinations also hold skills Panama itself ships, put there by the
|
||||||
# ~/.claude/skills also holds the skills Panama itself ships, put there by the
|
# link-skills stage, so both are real directories with one link per skill. This
|
||||||
# link-skills stage, so it is a real directory with one link per skill -- and
|
# stage runs after that one, so a personal skill wins a name collision.
|
||||||
# this stage runs after that one, so a personal skill wins a name collision.
|
linkdir agents/skills ~/.agents/skills
|
||||||
link agents/skills ~/.agents/skills
|
|
||||||
linkdir agents/skills ~/.claude/skills
|
linkdir agents/skills ~/.claude/skills
|
||||||
|
|
||||||
link agents/rules ~/.claude/rules
|
link agents/rules ~/.claude/rules
|
||||||
|
|||||||
Reference in New Issue
Block a user