#!/usr/bin/env bash # Agent skills, shipped to every machine. # # Panama is a desktop that an agent is frequently asked to operate: open the # settings, check what is running, take a screenshot, run something as root. An # agent that has to infer all of that from the source reliably invents half of # it, so the machine carries its own manual and skills/ is where it lives. # # This stage is un-gated, unlike link-user. What it links is documentation of # this repository, not anybody's personal content, so a stranger who clones # Panama wants it for exactly the same reason its author does. # # ~/.agents/skills and ~/.claude/skills may each start as a single symlink into # user/agents/skills, but a directory cannot point at personal and shipped # skills at once. Both destinations become real directories with one link per # skill. link-user runs after this stage on purpose, so a personal skill named # like a shipped one wins in every agent runtime. set -euo pipefail log() { echo -e "\033[1;34m[INFO]\033[0m $*"; } PANAMA_PATH="${PANAMA_PATH:-$HOME/.local/share/Panama}" SKILLS_DIR="$PANAMA_PATH/skills" PANAMA_OLD="$PANAMA_PATH/config/old" DESTINATIONS=("$HOME/.agents/skills" "$HOME/.claude/skills") [[ -d "$SKILLS_DIR" ]] || { log "No skills/ in this checkout; nothing to link."; exit 0; } mkdir -p "$PANAMA_OLD" # Moves whatever is already at a destination out of the way, once -- the same # promise link-dotfiles and link-user make. A symlink is removed rather than # backed up: it is a pointer this installer made, and keeping copies of it # would grow config/old/ by one entry on every upgrade. displace() { local destination="$1" backup if [[ -L "$destination" ]]; then rm -f "$destination" return 0 fi [[ -e "$destination" ]] || return 0 backup="$PANAMA_OLD/skills-$(printf '%s' "${destination#"$HOME"/}" | tr '/' '-')" if [[ -e "$backup" ]]; then backup="$backup.$(date +%s)" fi mv "$destination" "$backup" log "Moved existing $destination to $backup" } # Each destination has to be a real directory before anything can be linked # into it. An old whole-directory symlink is removed; a regular file somebody # left at the path is kept in config/old/. for destination in "${DESTINATIONS[@]}"; do mkdir -p "$(dirname "$destination")" if [[ -L "$destination" ]]; then rm -f "$destination" log "Removed the old $destination symlink; skills are linked one by one now" elif [[ -e "$destination" && ! -d "$destination" ]]; then displace "$destination" fi mkdir -p "$destination" done linked=0 for skill in "$SKILLS_DIR"/*; do [[ -e "$skill" ]] || continue name="$(basename "$skill")" for destination in "${DESTINATIONS[@]}"; do target="$destination/$name" displace "$target" ln -s "$skill" "$target" log "Linked skills/$name → $target" done linked=$(( linked + 1 )) done log "Agent skills: $linked linked."