- 所有涉及 .env 的代码块均添加 mkdir -p(或 New-Item)确保目录存在 - .env 改用标准 KEY=VALUE 格式(GITEA_TOKEN=<值>) - 写入:printf '%s' -> printf 'GITEA_TOKEN=...' - 读取:cat -> grep + cut 按 key 提取 - 检查:-s 大小检查 -> grep -q 按 key 检查 - 所有无标签代码块标注了语言(bash/powershell) - 零、路径定义首部添加 escalate 权限说明 - 每处需文件写入/网络访问的操作前标注需 escalate - 三、添加 3.1 fallback 网络不可用时的降级处理 - 限定范围:仅返回 PineSound 内部 Skills,不混入其他来源
22 KiB
name, description, allowed-tools
| name | description | allowed-tools |
|---|---|---|
| pinesound-skills-installer | PineSound 内部 Skills 安装器。通过 Gitea API 获取技能列表、安装/卸载/查询已安装的内部技能。当用户需要管理 PineSound 内部技能时使用。 | Read, Grep, Edit, Write, Bash |
PineSound 内部 Skills 安装器
范围说明:本 Skill 仅关注 PineSound 内部 Skills(
PineSoundSkills组织下的仓库)。当用户查询或管理技能时,只返回 PineSound 内部 Skills,不涉及其他来源的技能。如果 Gitea 不可达,fallback 也只列出本地已安装的 PineSound 内部技能。
零、路径定义
⚠️ 需要请求 escalate 权限:本 Skill 需要在
~/.codex/skills/PineSoundSkills/下创建目录和.env令牌文件,以及通过curl访问内网 Gitea 服务。该路径不在沙箱默认writable_roots中,内网地址不再默认网络允许列表。所有涉及mkdir、printf(写入 .env)、curl(请求 Gitea API)的命令,都必须通过sandbox_permissions: "require_escalated"请求 escalate 权限后执行。
整个 skill 统一使用以下路径:
| 含义 | Unix | Windows |
|---|---|---|
| Skills 根目录 | ~/.codex/skills/PineSoundSkills |
$env:USERPROFILE\.codex\skills\PineSoundSkills |
| 令牌文件 | ~/.codex/skills/PineSoundSkills/.env |
$env:USERPROFILE\.codex\skills\PineSoundSkills\.env |
一、令牌管理
1.1 获取令牌(首次/令牌无效时)
流程: agent 向用户索要令牌 → 用户输入 → 先 escalate → 写入 .env → 验证。
需 escalate: 以下命令涉及文件系统写入。
# 1. 确保目录存在,再检查 .env 中是否包含 GITEA_TOKEN
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
mkdir -p "$(dirname "$TOKEN_FILE")"
# KEY=VALUE 格式:检查是否存在且包含 GITEA_TOKEN 键
if [ ! -f "$TOKEN_FILE" ] || ! grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
echo "TOKEN_MISSING"
fi
# 如果输出 TOKEN_MISSING,则需要向用户索要令牌
索要到令牌后,执行存储:
# 2. 创建目录并写入令牌(KEY=VALUE 格式)
mkdir -p ~/.codex/skills/PineSoundSkills
# 用 KEY=VALUE 格式写入,避免 echo 对特殊字符的转义问题
printf "GITEA_TOKEN=%s\n" "$GITEA_TOKEN" > ~/.codex/skills/PineSoundSkills/.env
# 3. Unix: 设置权限仅 owner 可读写
chmod 600 ~/.codex/skills/PineSoundSkills/.env
# 4. 验证写入成功
if grep -q "^GITEA_TOKEN=" ~/.codex/skills/PineSoundSkills/.env 2>/dev/null; then
echo "令牌已持久化存储(KEY=VALUE 格式)"
else
echo "错误: 令牌写入失败"
exit 1
fi
# Windows 版(KEY=VALUE 格式)
New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex\skills\PineSoundSkills"
"GITEA_TOKEN=$env:GITEA_TOKEN" | Out-File -FilePath "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Encoding ASCII
# 验证
if (Select-String -Path "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Pattern "^GITEA_TOKEN=" -Quiet) {
Write-Host "令牌已持久化存储(KEY=VALUE 格式)"
} else {
Write-Host "错误: 令牌写入失败"
exit 1
}
1.2 读取令牌(后续使用)
# Unix — 确保目录存在后再读取令牌(按 KEY 提取)
mkdir -p ~/.codex/skills/PineSoundSkills
# 从 .env 中按 KEY 提取 GITEA_TOKEN(KEY=VALUE 格式)
GITEA_TOKEN=$(grep "^GITEA_TOKEN=" ~/.codex/skills/PineSoundSkills/.env 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\n\r' | xargs)
if [ -z "$GITEA_TOKEN" ]; then
echo "错误: .env 中缺少 GITEA_TOKEN,请先设置令牌"
exit 1
fi
# Windows — 确保目录存在后再读取令牌(按 KEY 提取)
$null = New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex\skills\PineSoundSkills" 2>$null
$GITEA_TOKEN = (Select-String -Path "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Pattern "^GITEA_TOKEN=" | ForEach-Object { $_.Line.Split("=", 2)[1].Trim() })
if ([string]::IsNullOrEmpty($GITEA_TOKEN)) {
Write-Host "错误: .env 中缺少 GITEA_TOKEN,请先设置令牌"
exit 1
}
1.3 令牌生命周期
| 场景 | 行为 |
|---|---|
| 首次运行 | agent 索要 → escalate(fs 写入)→ 写入 .env |
| 再次运行 | 直接从 .env 读取,跳过询问 |
| 令牌失效(API 返回 401) | 提示用户令牌可能已过期,重新执行 1.1 流程 |
| 用户明确要求重新设置 | 覆盖 .env,执行 1.1 流程 |
二、平台检测
OS=$(uname -s)
case "$OS" in
Darwin) PLATFORM="macOS" ;;
Linux) PLATFORM="Linux" ;;
MINGW*|MSYS*|CYGWIN*) PLATFORM="Windows" ;;
*) PLATFORM="Unknown" ;;
esac
echo "$PLATFORM"
根据 PLATFORM 选择对应平台的命令执行。
三、查看 Gitea 仓库中的 Skills 列表
需 escalate: 以下步骤需要同时 escalate 文件系统写入(mkdir/读取 .env)和网络访问(curl 请求内网 Gitea)。
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
# 确保目录存在
mkdir -p "$(dirname "$TOKEN_FILE")"
# 按 KEY 读取 GITEA_TOKEN
if [ ! -f "$TOKEN_FILE" ] || ! grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
echo "错误: .env 中缺少 GITEA_TOKEN。请先执行令牌获取流程(见第一章)"
exit 1
fi
GITEA_TOKEN=$(grep "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\n\r' | xargs)
if [ -z "$GITEA_TOKEN" ]; then
echo "错误: 令牌为空"
exit 1
fi
# 请求 API(内网 Gitea,需 escalate 网络权限)
curl -s -H "Authorization: token $GITEA_TOKEN" \
"http://192.168.1.3:10082/api/v1/orgs/PineSoundSkills/repos" \
| python3 -c "
import json, sys
repos = json.load(sys.stdin)
if not repos:
print('(空列表 — 检查令牌或组织名称是否正确)')
for r in repos:
print(f\" {r['name']:30s} {r.get('description', '')}\")
" 2>/dev/null \
|| curl -s -H "Authorization: token $GITEA_TOKEN" \
"http://192.168.1.3:10082/api/v1/orgs/PineSoundSkills/repos"
注意:
cat读取的令牌可能包含尾部换行符,通过tr -d '\n\r'和xargs做双重清理。如果 API 返回 401,说明令牌无效或已过期。
3.1 网络不可用时的 fallback(仅列出 PineSound 内部技能)
如果 curl 返回 exit code 7(连接失败)、exit code 6(DNS 解析失败)或 HTTP 状态码非 200,按以下方式处理:
- 先检查
~/.codex/skills/PineSoundSkills/下是否有已安装的 PineSound 技能 - 向用户报告 Gitea 连不通,仅列出本地已安装的 PineSound 内部技能
- 询问用户是否要重试连接或手动操作
注意:不要在输出中混入其他来源的技能(如系统 skills、社区 skills 等),只显示 PineSound 内部的。
# 当 Gitea API 不可达时的 fallback:列出本地已安装的 PineSound 内部技能
echo "Gitea 服务 (192.168.1.3:10082) 当前不可达。"
echo ""
echo "=== 已安装的 PineSound 内部 Skills ==="
SKILL_DIR=~/.codex/skills/PineSoundSkills
if [ -d "$SKILL_DIR" ]; then
for d in "$SKILL_DIR"/*/; do
[ -d "$d" ] || continue
name=$(basename "$d")
[ -f "${d}SKILL.md" ] && desc=$(grep '^description:' "${d}SKILL.md" | head -1 | sed 's/^description:\s*//i' | sed 's/"//g') || desc=""
echo " $name${desc:+ — $desc}"
done
else
echo "(暂未安装任何 PineSound 内部技能)"
fi
# Windows fallback — 仅列出 PineSound 内部技能
Write-Host "Gitea 服务 (192.168.1.3:10082) 当前不可达。"
Write-Host ""
Write-Host "=== 已安装的 PineSound 内部 Skills ==="
$skillDir = "$env:USERPROFILE\.codex\skills\PineSoundSkills"
if (Test-Path $skillDir) {
foreach ($d in Get-ChildItem -Directory $skillDir) {
$name = $d.Name
$sf = "$($d.FullName)\SKILL.md"
$desc = ""
if (Test-Path $sf) {
$match = Select-String -Path $sf -Pattern '(?im)^description:\s*(.+)'
if ($match) { $desc = $match.Matches.Value -replace '(?im)^description:\s*', '' }
}
Write-Host " $name$(if ($desc) { " -- $desc" })"
}
} else {
Write-Host "(暂未安装任何 PineSound 内部技能)"
}
四、安装 Skills
需 escalate: 以下涉及文件系统写入和 git 克隆(网络访问)。
4.1 确保目录存在
mkdir -p ~/.codex/skills/PineSoundSkills
New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex\skills\PineSoundSkills"
4.2 克隆仓库
cd ~/.codex/skills/PineSoundSkills && git clone <仓库地址>
cd "$env:USERPROFILE\.codex\skills\PineSoundSkills"; git clone <仓库地址>
<仓库地址> 格式示例: http://192.168.1.3:10082/PineSoundSkills/<技能仓库名称>.git
4.3 完成后提示用户
技能已安装。请执行刷新技能列表操作以使新技能生效(如
/reload或重新加载会话)。
五、查看已安装的 Skills
核心要求: 只读取每个 SKILL.md 的 frontmatter(--- 到 --- 之间的元数据),不加载整个文件内容。
Unix (macOS / Linux)
SKILL_DIR=~/.codex/skills/PineSoundSkills
if [ ! -d "$SKILL_DIR" ]; then
echo "技能目录不存在: $SKILL_DIR"
echo "请先安装技能(见第四章)"
exit 0
fi
echo "=== 已安装的 PineSound Skills ==="
for dir in "$SKILL_DIR"/*/; do
[ -d "$dir" ] || continue
name=$(basename "$dir")
skill_file=""
[ -f "${dir}SKILL.md" ] && skill_file="${dir}SKILL.md"
[ -z "$skill_file" ] && [ -f "${dir}skill.md" ] && skill_file="${dir}skill.md"
if [ -n "$skill_file" ]; then
# 仅提取 frontmatter(第一个 --- 到第二个 --- 之间的内容)
meta=$(awk 'BEGIN{c=0} /^---/{c++; next} c==1{print} c==2{exit}' "$skill_file")
skill_name=$(echo "$meta" | grep -i '^name:' | head -1 | sed 's/^name:\s*//i' | sed 's/"//g')
skill_desc=$(echo "$meta" | grep -i '^description:' | head -1 | sed 's/^description:\s*//i' | sed 's/"//g')
echo " 📦 $name"
echo " 名称: ${skill_name:-未设置}"
echo " 说明: ${skill_desc:-无描述}"
else
echo " 📁 $name (无 SKILL.md)"
fi
echo ""
done
Windows (PowerShell)
$skillDir = "$env:USERPROFILE\.codex\skills\PineSoundSkills"
if (-not (Test-Path $skillDir)) {
Write-Host "技能目录不存在: $skillDir" -ForegroundColor Yellow
Write-Host "请先安装技能(见第四章)"
exit 0
}
Write-Host "=== 已安装的 PineSound Skills ===" -ForegroundColor Cyan
Get-ChildItem -Directory $skillDir | ForEach-Object {
$folder = $_.FullName
$name = $_.Name
$skillFile = $null
if (Test-Path "$folder\SKILL.md") { $skillFile = "$folder\SKILL.md" }
elseif (Test-Path "$folder\skill.md") { $skillFile = "$folder\skill.md" }
if ($skillFile) {
# 只读取 frontmatter(第一个 --- 到第二个 --- 之间)
$content = Get-Content $skillFile -Raw
if ($content -match '(?s)^---\s*\n(.*?)\n---') {
$frontmatter = $matches[1]
$skillName = ($frontmatter | Select-String '(?im)^name:\s*(.+)').Matches.Value -replace '(?im)^name:\s*', ''
$skillDesc = ($frontmatter | Select-String '(?im)^description:\s*(.+)').Matches.Value -replace '(?im)^description:\s*', ''
}
Write-Host " 📦 $name"
Write-Host " 名称: $(if ($skillName) { $skillName.Trim('" ') } else { '未设置' })"
Write-Host " 说明: $(if ($skillDesc) { $skillDesc.Trim('" ') } else { '无描述' })"
} else {
Write-Host " 📁 $name (无 SKILL.md)" -ForegroundColor DarkYellow
}
Write-Host ""
}
六、卸载 Skills
# Unix
SKILL_DIR=~/.codex/skills/PineSoundSkills
if [ ! -d "$SKILL_DIR/<文件夹名称>" ]; then
echo "错误: 技能目录不存在: $SKILL_DIR/<文件夹名称>"
exit 1
fi
rm -rf "$SKILL_DIR/<文件夹名称>"
echo "已卸载: <文件夹名称>"
# Windows
$path = "$env:USERPROFILE\.codex\skills\PineSoundSkills\<文件夹名称>"
if (-not (Test-Path $path)) {
Write-Host "错误: 技能目录不存在: $path"
exit 1
}
Remove-Item -Recurse -Force $path
Write-Host "已卸载: <文件夹名称>"
卸载后提示用户刷新技能列表。
七、技能更新
检查本地已安装的技能仓库与远程是否一致,如有更新则拉取最新代码。
7.1 更新指定技能
# Unix
SKILL_DIR=~/.codex/skills/PineSoundSkills
SKILL_NAME="$1" # 要更新的技能文件夹名称
if [ ! -d "$SKILL_DIR/$SKILL_NAME" ]; then
echo "错误: 技能 '$SKILL_NAME' 未安装"
exit 1
fi
cd "$SKILL_DIR/$SKILL_NAME"
if [ ! -d ".git" ]; then
echo "跳过: '$SKILL_NAME' 不是 git 仓库,无法更新"
exit 0
fi
# 获取远程最新信息
git fetch origin
# 检查本地与远程的差异
BEHIND=$(git rev-list --count HEAD..origin/$(git rev-parse --abbrev-ref HEAD) 2>/dev/null)
if [ "$BEHIND" -gt 0 ]; then
echo "检测到 '$SKILL_NAME' 落后远程 $BEHIND 个提交,正在更新..."
git pull
if [ $? -eq 0 ]; then
echo "✅ '$SKILL_NAME' 已更新到最新版本"
echo "提示: 可能需要刷新技能列表使更新生效(如 /reload)"
else
echo "❌ '$SKILL_NAME' 更新失败,请检查网络或手动处理"
exit 1
fi
else
echo "'$SKILL_NAME' 已是最新"
fi
# Windows
$skillDir = "$env:USERPROFILE\.codex\skills\PineSoundSkills"
$skillName = $args[0]
if (-not (Test-Path "$skillDir\$skillName")) {
Write-Host "错误: 技能 '$skillName' 未安装"
exit 1
}
Push-Location "$skillDir\$skillName"
if (-not (Test-Path ".git")) {
Write-Host "跳过: '$skillName' 不是 git 仓库,无法更新"
Pop-Location
exit 0
}
git fetch origin
$branch = git rev-parse --abbrev-ref HEAD
$behind = git rev-list --count HEAD..origin/$branch
if ([int]$behind -gt 0) {
Write-Host "检测到 '$skillName' 落后远程 $behind 个提交,正在更新..."
git pull
if ($LASTEXITCODE -eq 0) {
Write-Host "✅ '$skillName' 已更新到最新版本"
Write-Host "提示: 可能需要刷新技能列表使更新生效(如 /reload)"
} else {
Write-Host "❌ '$skillName' 更新失败,请检查网络或手动处理"
exit 1
}
} else {
Write-Host "'$skillName' 已是最新"
}
Pop-Location
7.2 更新所有已安装的技能
# Unix — 遍历每个技能目录执行更新
SKILL_DIR=~/.codex/skills/PineSoundSkills
if [ ! -d "$SKILL_DIR" ]; then
echo "没有已安装的技能"
exit 0
fi
echo "=== 检查所有技能更新 ==="
echo ""
UPDATED=0
CURRENT=0
FAILED=0
for dir in "$SKILL_DIR"/*/; do
[ -d "$dir" ] || continue
name=$(basename "$dir")
if [ ! -d "${dir}.git" ]; then
echo " ⏭️ $name (非 git 仓库,跳过)"
continue
fi
cd "$dir"
# 保存当前分支名
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -z "$BRANCH" ]; then
echo " ⏭️ $name (无法获取分支信息,跳过)"
continue
fi
# 获取远程信息
git fetch origin 2>/dev/null
BEHIND=$(git rev-list --count HEAD..origin/$BRANCH 2>/dev/null)
if [ "$BEHIND" -gt 0 ]; then
echo " 🔄 $name (落后 $BEHIND 提交,正在更新...)"
if git pull 2>/dev/null; then
echo " ✅ 更新完成"
UPDATED=$((UPDATED + 1))
else
echo " ❌ 更新失败"
FAILED=$((FAILED + 1))
fi
else
echo " ✅ $name (已是最新)"
CURRENT=$((CURRENT + 1))
fi
done
echo ""
echo "=== 更新总结: $CURRENT 已最新, $UPDATED 已更新, $FAILED 失败 ==="
if [ "$UPDATED" -gt 0 ]; then
echo "提示: 如有技能已更新,建议刷新技能列表(如 /reload)"
fi
# Windows — 遍历每个技能目录执行更新
$skillDir = "$env:USERPROFILE\.codex\skills\PineSoundSkills"
if (-not (Test-Path $skillDir)) {
Write-Host "没有已安装的技能"
exit 0
}
Write-Host "=== 检查所有技能更新 ==="
Write-Host ""
$updated = 0
$current = 0
$failed = 0
Get-ChildItem -Directory $skillDir | ForEach-Object {
$dir = $_.FullName
$name = $_.Name
if (-not (Test-Path "$dir\.git")) {
Write-Host " ⏭️ $name (非 git 仓库,跳过)"
return
}
Push-Location $dir
$branch = git rev-parse --abbrev-ref HEAD
if ([string]::IsNullOrEmpty($branch)) {
Write-Host " ⏭️ $name (无法获取分支信息,跳过)"
Pop-Location
return
}
git fetch origin 2>$null
$behind = git rev-list --count HEAD..origin/$branch
if ([int]$behind -gt 0) {
Write-Host " 🔄 $name (落后 $behind 提交,正在更新...)"
git pull 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Host " ✅ 更新完成"
$updated++
} else {
Write-Host " ❌ 更新失败"
$failed++
}
} else {
Write-Host " ✅ $name (已是最新)"
$current++
}
Pop-Location
}
Write-Host ""
Write-Host "=== 更新总结: $current 已最新, $updated 已更新, $failed 失败 ==="
if ($updated -gt 0) {
Write-Host "提示: 如有技能已更新,建议刷新技能列表(如 /reload)"
}
7.3 自动跳过非 git 目录
更新时自动检测每个技能目录是否为 git 仓库(检查 .git 目录是否存在):
- 是 git 仓库 → 执行
git fetch+git rev-list --count检查差异 → 按需git pull - 不是 git 仓库 → 跳过并提示"非 git 仓库"
- git 命令失败 → 跳过并提示错误,不中断其余技能更新
注意: 如果本地有未提交的修改,
git pull可能失败或产生合并冲突。如有冲突,agent 应手动解决或提示用户处理。
八、完整工作流示例
以下端到端脚本(Unix)涵盖令牌获取 → 验证 → 列出 → 安装 → 更新 → 查看全过程:
#!/bin/bash
SKILL_DIR=~/.codex/skills/PineSoundSkills
TOKEN_FILE="$SKILL_DIR/.env"
GITEA_API="http://192.168.1.3:10082/api/v1"
# ========== 令牌管理 ==========
# 确保目录存在
mkdir -p "$(dirname "$TOKEN_FILE")"
# 检查 .env 是否存在且包含 GITEA_TOKEN(KEY=VALUE 格式)
if [ ! -f "$TOKEN_FILE" ] || ! grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
echo "令牌文件不存在或未设置 GITEA_TOKEN。"
echo "请通过 AskUserQuestion 向用户索要 Gitea 令牌。"
# [agent 操作] 用 AskUserQuestion 获取用户输入的令牌
# 假设用户提供了令牌,存入变量 USER_TOKEN
mkdir -p "$SKILL_DIR"
printf "GITEA_TOKEN=%s\n" "$USER_TOKEN" > "$TOKEN_FILE"
chmod 600 "$TOKEN_FILE"
# 写入后验证
if grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
echo "令牌已持久化存储到 $TOKEN_FILE(KEY=VALUE 格式)"
else
echo "错误: 令牌写入失败!"
exit 1
fi
fi
# 按 KEY 读取令牌(清理空白字符)
GITEA_TOKEN=$(grep "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\n\r' | xargs)
if [ -z "$GITEA_TOKEN" ]; then
echo "错误: 令牌为空"
exit 1
fi
# ========== 列出可安装 Skills ==========
echo ""
echo "=== 可安装的 Skills ==="
HTTP_CODE=$(curl -s -o /tmp/pinesound_repos.json -w "%{http_code}" \
-H "Authorization: token $GITEA_TOKEN" "$GITEA_API/orgs/PineSoundSkills/repos")
if [ "$HTTP_CODE" != "200" ]; then
echo "API 请求失败 (HTTP $HTTP_CODE),令牌可能无效或已过期"
echo "请重新执行令牌获取流程"
# fallback:列出本地已安装的 PineSound 内部技能
echo ""
echo "=== 已安装的 PineSound 内部 Skills ==="
SKILL_DIR=~/.codex/skills/PineSoundSkills
if [ -d "$SKILL_DIR" ]; then
for d in "$SKILL_DIR"/*/; do
[ -d "$d" ] || continue
name=$(basename "$d")
[ -f "${d}SKILL.md" ] && desc=$(grep '^description:' "${d}SKILL.md" | head -1 | sed 's/^description:\s*//i' | sed 's/"//g') || desc=""
echo " $name${desc:+ — $desc}"
done
else
echo "(暂未安装任何 PineSound 内部技能)"
fi
exit 1
fi
python3 -c "
import json
with open('/tmp/pinesound_repos.json') as f:
repos = json.load(f)
if not repos:
print('(空列表)')
for r in repos:
print(f\" {r['name']:30s} {r.get('description', '')}\")
"
rm -f /tmp/pinesound_repos.json
# ========== 已安装 Skills 处理(更新 + 查看)==========
echo ""
if [ ! -d "$SKILL_DIR" ]; then
echo "(暂无已安装的技能)"
exit 0
fi
# 更新所有已安装的技能
echo "=== 更新已安装的 Skills ==="
UPDATED=0
for dir in "$SKILL_DIR"/*/; do
[ -d "$dir" ] || continue
name=$(basename "$dir")
if [ ! -d "${dir}.git" ]; then
echo " ⏭️ $name (非 git)" && continue
fi
cd "$dir"
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
[ -z "$BRANCH" ] && continue
git fetch origin 2>/dev/null
BEHIND=$(git rev-list --count HEAD..origin/$BRANCH 2>/dev/null)
if [ "$BEHIND" -gt 0 ]; then
echo " 🔄 $name (落后 $BEHIND 提交,更新中...)"
git pull 2>/dev/null && echo " ✅ 完成" && UPDATED=$((UPDATED + 1)) || echo " ❌ 失败"
else
echo " ✅ $name (已最新)"
fi
done
[ "$UPDATED" -gt 0 ] && echo "提示: 建议刷新技能列表(如 /reload)" || true
# 查看已安装的技能
echo ""
echo "=== 已安装的 Skills ==="
for dir in "$SKILL_DIR"/*/; do
[ -d "$dir" ] || continue
name=$(basename "$dir")
sf="${dir}SKILL.md"; [ -f "$sf" ] || sf="${dir}skill.md"; [ -f "$sf" ] || sf=""
if [ -n "$sf" ]; then
meta=$(awk 'BEGIN{c=0} /^---/{c++; next} c==1{print} c==2{exit}' "$sf")
sname=$(echo "$meta" | grep -i '^name:' | head -1 | sed 's/^name:\s*//i' | sed 's/"//g')
sdesc=$(echo "$meta" | grep -i '^description:' | head -1 | sed 's/^description:\s*//i' | sed 's/"//g')
echo " $name: ${sname:-$name} — ${sdesc:-无描述}"
else
echo " $name (无 SKILL.md)"
fi
done