feat(update-skills): dual install target — ~/.claude/skills + ~/.agents/skills (agent-neutral); pi settings hack removed (v0.2.1)

This commit is contained in:
2026-08-12 21:43:00 +03:00
parent e431e9e142
commit 348f6f3108
7 changed files with 155 additions and 94 deletions

View File

@@ -1,6 +1,10 @@
# Install skills/<name>/ into ~\.claude\skills\<name>\ (or $env:CLAUDE_SKILLS_DIR)
# Install skills/<name>/ into BOTH agent skill dirs:
# ~\.claude\skills\<name>\ (Claude Code — native, not configurable)
# ~\.agents\skills\<name>\ (pi — native default scan path, agent-neutral namespace)
# $env:CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI).
#
# Usage: install.ps1 [-Names <name1>,<name2>] [-Prune]
# no args = install all skills/* into target
# no args = install all skills/* into both targets
# -Prune = after install, remove target/<name>/ dirs that are NOT in skills/*
# (prune always scans full target, ignores -Names filter — it's a global cleanup)
#
@@ -17,45 +21,54 @@ $ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$src = Join-Path $root 'skills'
# Dual install targets — the "canon" is the git repo (skills/); both dirs are installs.
$targets = @()
if ($env:CLAUDE_SKILLS_DIR) {
$target = $env:CLAUDE_SKILLS_DIR
$targets += $env:CLAUDE_SKILLS_DIR
} else {
$target = Join-Path $env:USERPROFILE '.claude\skills'
$targets += (Join-Path $env:USERPROFILE '.claude\skills')
}
$targets += (Join-Path $env:USERPROFILE '.agents\skills')
if (-not (Test-Path $target)) {
New-Item -ItemType Directory -Force -Path $target | Out-Null
foreach ($target in $targets) {
if (-not (Test-Path $target)) {
New-Item -ItemType Directory -Force -Path $target | Out-Null
}
}
if ($Names.Count -eq 0) {
$Names = Get-ChildItem -Path $src -Directory | Sort-Object Name | Select-Object -ExpandProperty Name
}
foreach ($name in $Names) {
$srcDir = Join-Path $src $name
$dstDir = Join-Path $target $name
if (-not (Test-Path $srcDir -PathType Container)) {
Write-Warning "skip: $name (not found in skills/)"
continue
foreach ($target in $targets) {
foreach ($name in $Names) {
$srcDir = Join-Path $src $name
$dstDir = Join-Path $target $name
if (-not (Test-Path $srcDir -PathType Container)) {
Write-Warning "skip: $name (not found in skills/)"
continue
}
if (-not (Test-Path (Join-Path $srcDir 'SKILL.md'))) {
Write-Warning "skip: $name (missing SKILL.md)"
continue
}
if (Test-Path $dstDir) {
Remove-Item -Recurse -Force $dstDir
}
Copy-Item -Recurse $srcDir $dstDir
Write-Host "installed: $name -> $dstDir"
}
if (-not (Test-Path (Join-Path $srcDir 'SKILL.md'))) {
Write-Warning "skip: $name (missing SKILL.md)"
continue
}
if (Test-Path $dstDir) {
Remove-Item -Recurse -Force $dstDir
}
Copy-Item -Recurse $srcDir $dstDir
Write-Host "installed: $name -> $dstDir"
}
if ($Prune) {
$sourceNames = @(Get-ChildItem -Path $src -Directory | Select-Object -ExpandProperty Name)
$installedDirs = Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue
foreach ($dir in $installedDirs) {
if ($sourceNames -notcontains $dir.Name) {
Write-Host "pruning: $($dir.Name) (not in skills/) -> $($dir.FullName)"
Remove-Item -Recurse -Force $dir.FullName
foreach ($target in $targets) {
$installedDirs = Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue
foreach ($dir in $installedDirs) {
if ($sourceNames -notcontains $dir.Name) {
Write-Host "pruning: $($dir.Name) (not in skills/) -> $($dir.FullName)"
Remove-Item -Recurse -Force $dir.FullName
}
}
}
}

View File

@@ -1,14 +1,20 @@
#!/usr/bin/env bash
# Install skills/<name>/ into ~/.claude/skills/<name>/ (or $CLAUDE_SKILLS_DIR)
# Install skills/<name>/ into BOTH agent skill dirs:
# ~/.claude/skills/<name>/ (Claude Code — native, not configurable)
# ~/.agents/skills/<name>/ (pi — native default scan path, agent-neutral namespace)
# $CLAUDE_SKILLS_DIR overrides ONLY the claude target (for testing/CI).
#
# Usage: install.sh [--prune] [name...]
# no args = install all skills/* into target
# no args = install all skills/* into both targets
# --prune = after install, remove target/<name>/ dirs that are NOT in skills/*
# (prune always scans full target, ignores name filter — global cleanup)
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SRC="$ROOT/skills"
TARGET="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}"
# Dual install targets — the "canon" is the git repo (skills/); both dirs are installs.
TARGETS=("${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "$HOME/.agents/skills")
prune=0
positional=()
@@ -33,31 +39,37 @@ else
names=("${positional[@]}")
fi
mkdir -p "$TARGET"
for target in "${TARGETS[@]}"; do
mkdir -p "$target"
done
for name in "${names[@]}"; do
src_dir="$SRC/$name"
dst_dir="$TARGET/$name"
if [ ! -d "$src_dir" ]; then
echo "skip: $name (not found in skills/)" >&2
continue
fi
if [ ! -f "$src_dir/SKILL.md" ]; then
echo "skip: $name (missing SKILL.md)" >&2
continue
fi
rm -rf "$dst_dir"
cp -R "$src_dir" "$dst_dir"
echo "installed: $name$dst_dir"
for target in "${TARGETS[@]}"; do
for name in "${names[@]}"; do
src_dir="$SRC/$name"
dst_dir="$target/$name"
if [ ! -d "$src_dir" ]; then
echo "skip: $name (not found in skills/)" >&2
continue
fi
if [ ! -f "$src_dir/SKILL.md" ]; then
echo "skip: $name (missing SKILL.md)" >&2
continue
fi
rm -rf "$dst_dir"
cp -R "$src_dir" "$dst_dir"
echo "installed: $name$dst_dir"
done
done
if [ "$prune" -eq 1 ]; then
for d in "$TARGET"/*/; do
[ -d "$d" ] || continue
name="$(basename "$d")"
if [ ! -d "$SRC/$name" ]; then
echo "pruning: $name (not in skills/) → $d"
rm -rf "$d"
fi
for target in "${TARGETS[@]}"; do
for d in "$target"/*/; do
[ -d "$d" ] || continue
name="$(basename "$d")"
if [ ! -d "$SRC/$name" ]; then
echo "pruning: $name (not in skills/) → $d"
rm -rf "$d"
fi
done
done
fi

View File

@@ -22,7 +22,10 @@ $ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$skillsSrc = Join-Path $root 'skills'
$target = if ($env:CLAUDE_SKILLS_DIR) { $env:CLAUDE_SKILLS_DIR } else { Join-Path $env:USERPROFILE '.claude\skills' }
# Dual install targets — the "canon" is the git repo (skills/); both dirs are installs.
$targets = @()
if ($env:CLAUDE_SKILLS_DIR) { $targets += $env:CLAUDE_SKILLS_DIR } else { $targets += (Join-Path $env:USERPROFILE '.claude\skills') }
$targets += (Join-Path $env:USERPROFILE '.agents\skills')
$commonRoot = if ($env:COMMON_ROOT) { $env:COMMON_ROOT } else { Join-Path $env:USERPROFILE 'projects\.common' }
$metaMcp = Join-Path $commonRoot 'lib\projects-meta-mcp'
@@ -31,13 +34,15 @@ $internsMcp = Join-Path $commonRoot 'lib\interns-mcp'
# -- Collect before-versions --------------------------------------------------
$beforeVersions = @{}
if (Test-Path $target) {
Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$vf = Join-Path $_.FullName 'SKILL.md'
if (Test-Path $vf) {
$match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1
$ver = if ($match) { $match.Matches.Groups[1].Value } else { '?' }
$beforeVersions[$_.Name] = $ver
foreach ($target in $targets) {
if (Test-Path $target) {
Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$vf = Join-Path $_.FullName 'SKILL.md'
if (Test-Path $vf) {
$match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1
$ver = if ($match) { $match.Matches.Groups[1].Value } else { '?' }
$beforeVersions[$_.Name] = $ver
}
}
}
}
@@ -144,16 +149,18 @@ Write-Host "[ok] Skills installed." -ForegroundColor Green
Write-Host "[update] Version diff:" -ForegroundColor Cyan
$changes = $false
Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$vf = Join-Path $_.FullName 'SKILL.md'
if (Test-Path $vf) {
$match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1
$after = if ($match) { $match.Matches.Groups[1].Value } else { '?' }
$before = $beforeVersions[$_.Name]
if (-not $before) { $before = 'new' }
if ($before -ne $after) {
Write-Host " $before -> $after ($($_.Name))"
$changes = $true
foreach ($target in $targets) {
Get-ChildItem -Path $target -Directory -ErrorAction SilentlyContinue | ForEach-Object {
$vf = Join-Path $_.FullName 'SKILL.md'
if (Test-Path $vf) {
$match = Select-String -Path $vf -Pattern '^version:\s*(\d+\.\d+\.\d+)' -ErrorAction SilentlyContinue | Select-Object -First 1
$after = if ($match) { $match.Matches.Groups[1].Value } else { '?' }
$before = $beforeVersions[$_.Name]
if (-not $before) { $before = 'new' }
if ($before -ne $after) {
Write-Host " $before -> $after ($($_.Name))"
$changes = $true
}
}
}
}
@@ -166,9 +173,11 @@ if (-not $changes) {
$newSetup = @()
Get-ChildItem -Path $skillsSrc -Directory | ForEach-Object {
$name = $_.Name
if ($name -like 'setup-*' -and -not (Test-Path (Join-Path $target $name))) {
$newSetup += $name
$missing = $false
foreach ($target in $targets) {
if ($name -like 'setup-*' -and -not (Test-Path (Join-Path $target $name))) { $missing = $true }
}
if ($missing) { $newSetup += $name }
}
if ($newSetup.Count -gt 0) {

View File

@@ -20,7 +20,8 @@ YES_MODE=false
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
SKILLS_SRC="$ROOT/skills"
TARGET="${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}"
# Dual install targets — the "canon" is the git repo (skills/); both dirs are installs.
TARGETS=("${CLAUDE_SKILLS_DIR:-$HOME/.claude/skills}" "$HOME/.agents/skills")
COMMON_ROOT="${COMMON_ROOT:-$HOME/projects/.common}"
META_MCP="$COMMON_ROOT/lib/projects-meta-mcp"
@@ -43,8 +44,9 @@ confirm() {
# ── Collect before-versions ────────────────────────────────────────────────
declare -A BEFORE_VERSIONS=()
if [[ -d "$TARGET" ]]; then
for skill_dir in "$TARGET"/*/; do
for target in "${TARGETS[@]}"; do
[[ -d "$target" ]] || continue
for skill_dir in "$target"/*/; do
name="$(basename "$skill_dir")"
vf="$skill_dir/SKILL.md"
if [[ -f "$vf" ]]; then
@@ -52,7 +54,7 @@ if [[ -d "$TARGET" ]]; then
BEFORE_VERSIONS["$name"]="$ver"
fi
done
fi
done
# ── Step 1: git pull ────────────────────────────────────────────────────────
@@ -133,17 +135,19 @@ ok "Skills installed."
info "Version diff:"
CHANGES=false
for skill_dir in "$TARGET"/*/; do
name="$(basename "$skill_dir")"
vf="$skill_dir/SKILL.md"
if [[ -f "$vf" ]]; then
after="$(grep -oP '^version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$vf" 2>/dev/null || echo '?')"
before="${BEFORE_VERSIONS[$name]:-new}"
if [[ "$before" != "$after" ]]; then
echo " $before$after ($name)"
CHANGES=true
for target in "${TARGETS[@]}"; do
for skill_dir in "$target"/*/; do
name="$(basename "$skill_dir")"
vf="$skill_dir/SKILL.md"
if [[ -f "$vf" ]]; then
after="$(grep -oP '^version:\s*\K[0-9]+\.[0-9]+\.[0-9]+' "$vf" 2>/dev/null || echo '?')"
before="${BEFORE_VERSIONS[$name]:-new}"
if [[ "$before" != "$after" ]]; then
echo " $before$after ($name)"
CHANGES=true
fi
fi
fi
done
done
if ! $CHANGES; then
echo " (no version changes)"
@@ -154,8 +158,12 @@ fi
NEW_SETUP=()
for skill_dir in "$SKILLS_SRC"/*/; do
name="$(basename "$skill_dir")"
if [[ "$name" == setup-* ]] && [[ ! -d "$TARGET/$name" ]]; then
NEW_SETUP+=("$name")
if [[ "$name" == setup-* ]]; then
missing=false
for target in "${TARGETS[@]}"; do
[[ ! -d "$target/$name" ]] && missing=true
done
$missing && NEW_SETUP+=("$name")
fi
done