Publish skills through the agent-neutral skill home

This commit is contained in:
Gabriel Brown
2026-08-27 16:39:16 -04:00
parent cb305f6662
commit c71d6c8799
9 changed files with 104 additions and 73 deletions
@@ -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.
+1
View File
@@ -0,0 +1 @@
../../.agents/skills/panama
+2 -2
View File
@@ -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
+22 -20
View File
@@ -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
fi displace "$destination"
mkdir -p "$DESTINATION" fi
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
View File
@@ -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" \
+12 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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