Files

718 lines
22 KiB
Markdown
Raw Permalink Normal View History

2026-06-08 13:18:18 +08:00
---
name: pinesound-skills-installer
description: PineSound 内部 Skills 安装器。通过 Gitea API 获取技能列表、安装/卸载/查询已安装的内部技能。当用户需要管理 PineSound 内部技能时使用。
allowed-tools: Read, Grep, Edit, Write, Bash
---
# PineSound 内部 Skills 安装器
2026-06-08 13:55:15 +08:00
> **范围说明**:本 Skill 仅关注 PineSound 内部 Skills`PineSoundSkills` 组织下的仓库)。当用户查询或管理技能时,**只返回 PineSound 内部 Skills**,不涉及其他来源的技能。如果 Gitea 不可达,fallback 也只列出本地已安装的 PineSound 内部技能。
2026-06-08 13:18:18 +08:00
---
## 零、路径定义
2026-06-08 13:55:15 +08:00
> **⚠️ 需要请求 escalate 权限**:本 Skill 需要在 `~/.codex/skills/PineSoundSkills/` 下创建目录和 `.env` 令牌文件,以及通过 `curl` 访问内网 Gitea 服务。该路径不在沙箱默认 `writable_roots` 中,内网地址不再默认网络允许列表。**所有涉及 `mkdir`、`printf`(写入 .env)、`curl`(请求 Gitea API)的命令,都必须通过 `sandbox_permissions: "require_escalated"` 请求 escalate 权限后执行。**
2026-06-08 13:18:18 +08:00
整个 skill 统一使用以下路径:
| 含义 | Unix | Windows |
|------|------|---------|
| Skills 根目录 | `~/.codex/skills/PineSoundSkills` | `$env:USERPROFILE\.codex\skills\PineSoundSkills` |
| 令牌文件 | `~/.codex/skills/PineSoundSkills/.env` | `$env:USERPROFILE\.codex\skills\PineSoundSkills\.env` |
---
## 一、令牌管理
### 1.1 获取令牌(首次/令牌无效时)
2026-06-08 13:55:15 +08:00
**流程**: agent 向用户索要令牌 → 用户输入 → 先 escalate → 写入 `.env` → 验证。
> **需 escalate**: 以下命令涉及文件系统写入。
2026-06-08 13:18:18 +08:00
```bash
2026-06-08 13:55:15 +08:00
# 1. 确保目录存在,再检查 .env 中是否包含 GITEA_TOKEN
2026-06-08 13:18:18 +08:00
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
2026-06-08 13:55:15 +08:00
mkdir -p "$(dirname "$TOKEN_FILE")"
2026-06-08 13:18:18 +08:00
2026-06-08 13:55:15 +08:00
# KEY=VALUE 格式:检查是否存在且包含 GITEA_TOKEN 键
if [ ! -f "$TOKEN_FILE" ] || ! grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
2026-06-08 13:18:18 +08:00
echo "TOKEN_MISSING"
fi
# 如果输出 TOKEN_MISSING,则需要向用户索要令牌
```
索要到令牌后,执行存储:
```bash
2026-06-08 13:55:15 +08:00
# 2. 创建目录并写入令牌(KEY=VALUE 格式)
2026-06-08 13:18:18 +08:00
mkdir -p ~/.codex/skills/PineSoundSkills
2026-06-08 13:55:15 +08:00
# 用 KEY=VALUE 格式写入,避免 echo 对特殊字符的转义问题
printf "GITEA_TOKEN=%s\n" "$GITEA_TOKEN" > ~/.codex/skills/PineSoundSkills/.env
2026-06-08 13:18:18 +08:00
# 3. Unix: 设置权限仅 owner 可读写
chmod 600 ~/.codex/skills/PineSoundSkills/.env
# 4. 验证写入成功
2026-06-08 13:55:15 +08:00
if grep -q "^GITEA_TOKEN=" ~/.codex/skills/PineSoundSkills/.env 2>/dev/null; then
echo "令牌已持久化存储(KEY=VALUE 格式)"
2026-06-08 13:18:18 +08:00
else
echo "错误: 令牌写入失败"
exit 1
fi
```
```powershell
2026-06-08 13:55:15 +08:00
# Windows 版(KEY=VALUE 格式)
2026-06-08 13:18:18 +08:00
New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex\skills\PineSoundSkills"
2026-06-08 13:55:15 +08:00
"GITEA_TOKEN=$env:GITEA_TOKEN" | Out-File -FilePath "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Encoding ASCII
2026-06-08 13:18:18 +08:00
# 验证
2026-06-08 13:55:15 +08:00
if (Select-String -Path "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Pattern "^GITEA_TOKEN=" -Quiet) {
Write-Host "令牌已持久化存储(KEY=VALUE 格式)"
2026-06-08 13:18:18 +08:00
} else {
Write-Host "错误: 令牌写入失败"
exit 1
}
```
### 1.2 读取令牌(后续使用)
```bash
2026-06-08 13:55:15 +08:00
# Unix — 确保目录存在后再读取令牌(按 KEY 提取)
mkdir -p ~/.codex/skills/PineSoundSkills
# 从 .env 中按 KEY 提取 GITEA_TOKENKEY=VALUE 格式)
GITEA_TOKEN=$(grep "^GITEA_TOKEN=" ~/.codex/skills/PineSoundSkills/.env 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\n\r' | xargs)
2026-06-08 13:18:18 +08:00
if [ -z "$GITEA_TOKEN" ]; then
2026-06-08 13:55:15 +08:00
echo "错误: .env 中缺少 GITEA_TOKEN,请先设置令牌"
2026-06-08 13:18:18 +08:00
exit 1
fi
```
```powershell
2026-06-08 13:55:15 +08:00
# 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() })
2026-06-08 13:18:18 +08:00
if ([string]::IsNullOrEmpty($GITEA_TOKEN)) {
2026-06-08 13:55:15 +08:00
Write-Host "错误: .env 中缺少 GITEA_TOKEN,请先设置令牌"
2026-06-08 13:18:18 +08:00
exit 1
}
```
### 1.3 令牌生命周期
| 场景 | 行为 |
|------|------|
2026-06-08 13:55:15 +08:00
| 首次运行 | agent 索要 → escalatefs 写入)→ 写入 `.env` |
2026-06-08 13:18:18 +08:00
| 再次运行 | 直接从 `.env` 读取,跳过询问 |
| 令牌失效(API 返回 401) | 提示用户令牌可能已过期,重新执行 1.1 流程 |
| 用户明确要求重新设置 | 覆盖 `.env`,执行 1.1 流程 |
---
## 二、平台检测
```bash
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 列表
2026-06-08 13:55:15 +08:00
> **需 escalate**: 以下步骤需要同时 escalate 文件系统写入(mkdir/读取 .env)和网络访问(curl 请求内网 Gitea)。
2026-06-08 13:18:18 +08:00
```bash
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
2026-06-08 13:55:15 +08:00
# 确保目录存在
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。请先执行令牌获取流程(见第一章)"
2026-06-08 13:18:18 +08:00
exit 1
fi
2026-06-08 13:55:15 +08:00
GITEA_TOKEN=$(grep "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\n\r' | xargs)
2026-06-08 13:18:18 +08:00
if [ -z "$GITEA_TOKEN" ]; then
echo "错误: 令牌为空"
exit 1
fi
2026-06-08 13:55:15 +08:00
# 请求 API(内网 Gitea,需 escalate 网络权限)
2026-06-08 13:18:18 +08:00
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,说明令牌无效或已过期。
2026-06-08 13:55:15 +08:00
### 3.1 网络不可用时的 fallback(仅列出 PineSound 内部技能)
如果 `curl` 返回 `exit code 7`(连接失败)、`exit code 6`(DNS 解析失败)或 HTTP 状态码非 200,按以下方式处理:
1. 先检查 `~/.codex/skills/PineSoundSkills/` 下是否有已安装的 PineSound 技能
2. 向用户报告 Gitea 连不通,仅列出本地已安装的 PineSound 内部技能
3. 询问用户是否要重试连接或手动操作
**注意**:不要在输出中混入其他来源的技能(如系统 skills、社区 skills 等),只显示 PineSound 内部的。
```bash
# 当 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
```
```powershell
# 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 内部技能)"
}
```
2026-06-08 13:18:18 +08:00
---
## 四、安装 Skills
2026-06-08 13:55:15 +08:00
> **需 escalate**: 以下涉及文件系统写入和 git 克隆(网络访问)。
2026-06-08 13:18:18 +08:00
### 4.1 确保目录存在
```bash
mkdir -p ~/.codex/skills/PineSoundSkills
```
```powershell
New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex\skills\PineSoundSkills"
```
### 4.2 克隆仓库
```bash
cd ~/.codex/skills/PineSoundSkills && git clone <仓库地址>
```
```powershell
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)
```bash
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)
```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
```bash
# Unix
SKILL_DIR=~/.codex/skills/PineSoundSkills
if [ ! -d "$SKILL_DIR/<文件夹名称>" ]; then
echo "错误: 技能目录不存在: $SKILL_DIR/<文件夹名称>"
exit 1
fi
rm -rf "$SKILL_DIR/<文件夹名称>"
echo "已卸载: <文件夹名称>"
```
```powershell
# 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 更新指定技能
```bash
# 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
```
```powershell
# 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 更新所有已安装的技能
```bash
# 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
```
```powershell
# 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)涵盖令牌获取 → 验证 → 列出 → 安装 → 更新 → 查看全过程:
```bash
#!/bin/bash
SKILL_DIR=~/.codex/skills/PineSoundSkills
TOKEN_FILE="$SKILL_DIR/.env"
GITEA_API="http://192.168.1.3:10082/api/v1"
# ========== 令牌管理 ==========
2026-06-08 13:55:15 +08:00
# 确保目录存在
mkdir -p "$(dirname "$TOKEN_FILE")"
# 检查 .env 是否存在且包含 GITEA_TOKENKEY=VALUE 格式)
if [ ! -f "$TOKEN_FILE" ] || ! grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
echo "令牌文件不存在或未设置 GITEA_TOKEN。"
2026-06-08 13:18:18 +08:00
echo "请通过 AskUserQuestion 向用户索要 Gitea 令牌。"
# [agent 操作] 用 AskUserQuestion 获取用户输入的令牌
# 假设用户提供了令牌,存入变量 USER_TOKEN
mkdir -p "$SKILL_DIR"
2026-06-08 13:55:15 +08:00
printf "GITEA_TOKEN=%s\n" "$USER_TOKEN" > "$TOKEN_FILE"
2026-06-08 13:18:18 +08:00
chmod 600 "$TOKEN_FILE"
# 写入后验证
2026-06-08 13:55:15 +08:00
if grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
echo "令牌已持久化存储到 $TOKEN_FILEKEY=VALUE 格式)"
else
2026-06-08 13:18:18 +08:00
echo "错误: 令牌写入失败!"
exit 1
fi
fi
2026-06-08 13:55:15 +08:00
# 按 KEY 读取令牌(清理空白字符)
GITEA_TOKEN=$(grep "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null | head -1 | cut -d= -f2- | tr -d '\n\r' | xargs)
2026-06-08 13:18:18 +08:00
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 "请重新执行令牌获取流程"
2026-06-08 13:55:15 +08:00
# 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
2026-06-08 13:18:18 +08:00
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
```