refactor: 重构 SKILL.md
- 所有涉及 .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,不混入其他来源
This commit is contained in:
@@ -6,10 +6,14 @@ allowed-tools: Read, Grep, Edit, Write, Bash
|
|||||||
|
|
||||||
# PineSound 内部 Skills 安装器
|
# 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 统一使用以下路径:
|
整个 skill 统一使用以下路径:
|
||||||
|
|
||||||
| 含义 | Unix | Windows |
|
| 含义 | Unix | Windows |
|
||||||
@@ -23,13 +27,17 @@ allowed-tools: Read, Grep, Edit, Write, Bash
|
|||||||
|
|
||||||
### 1.1 获取令牌(首次/令牌无效时)
|
### 1.1 获取令牌(首次/令牌无效时)
|
||||||
|
|
||||||
**流程**: agent 通过 `AskUserQuestion` 工具向用户索要令牌 → 用户输入 → 写入 `.env` → 验证。
|
**流程**: agent 向用户索要令牌 → 用户输入 → 先 escalate → 写入 `.env` → 验证。
|
||||||
|
|
||||||
|
> **需 escalate**: 以下命令涉及文件系统写入。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. 检查 .env 是否存在且非空
|
# 1. 确保目录存在,再检查 .env 中是否包含 GITEA_TOKEN
|
||||||
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
|
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
|
||||||
|
mkdir -p "$(dirname "$TOKEN_FILE")"
|
||||||
|
|
||||||
if [ ! -f "$TOKEN_FILE" ] || [ ! -s "$TOKEN_FILE" ]; then
|
# KEY=VALUE 格式:检查是否存在且包含 GITEA_TOKEN 键
|
||||||
|
if [ ! -f "$TOKEN_FILE" ] || ! grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
|
||||||
echo "TOKEN_MISSING"
|
echo "TOKEN_MISSING"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -39,18 +47,18 @@ fi
|
|||||||
索要到令牌后,执行存储:
|
索要到令牌后,执行存储:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 2. 创建目录并写入令牌
|
# 2. 创建目录并写入令牌(KEY=VALUE 格式)
|
||||||
mkdir -p ~/.codex/skills/PineSoundSkills
|
mkdir -p ~/.codex/skills/PineSoundSkills
|
||||||
|
|
||||||
# 用 printf 写入,避免 echo 对特殊字符的转义问题
|
# 用 KEY=VALUE 格式写入,避免 echo 对特殊字符的转义问题
|
||||||
printf '%s' "$GITEA_TOKEN" > ~/.codex/skills/PineSoundSkills/.env
|
printf "GITEA_TOKEN=%s\n" "$GITEA_TOKEN" > ~/.codex/skills/PineSoundSkills/.env
|
||||||
|
|
||||||
# 3. Unix: 设置权限仅 owner 可读写
|
# 3. Unix: 设置权限仅 owner 可读写
|
||||||
chmod 600 ~/.codex/skills/PineSoundSkills/.env
|
chmod 600 ~/.codex/skills/PineSoundSkills/.env
|
||||||
|
|
||||||
# 4. 验证写入成功
|
# 4. 验证写入成功
|
||||||
if [ -s ~/.codex/skills/PineSoundSkills/.env ]; then
|
if grep -q "^GITEA_TOKEN=" ~/.codex/skills/PineSoundSkills/.env 2>/dev/null; then
|
||||||
echo "令牌已持久化存储"
|
echo "令牌已持久化存储(KEY=VALUE 格式)"
|
||||||
else
|
else
|
||||||
echo "错误: 令牌写入失败"
|
echo "错误: 令牌写入失败"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -58,12 +66,12 @@ fi
|
|||||||
```
|
```
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Windows 版
|
# Windows 版(KEY=VALUE 格式)
|
||||||
New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex\skills\PineSoundSkills"
|
New-Item -ItemType Directory -Force "$env:USERPROFILE\.codex\skills\PineSoundSkills"
|
||||||
$env:GITEA_TOKEN | Out-File -FilePath "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -NoNewline -Encoding ASCII
|
"GITEA_TOKEN=$env:GITEA_TOKEN" | Out-File -FilePath "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Encoding ASCII
|
||||||
# 验证
|
# 验证
|
||||||
if ((Get-Item "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env").Length -gt 0) {
|
if (Select-String -Path "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Pattern "^GITEA_TOKEN=" -Quiet) {
|
||||||
Write-Host "令牌已持久化存储"
|
Write-Host "令牌已持久化存储(KEY=VALUE 格式)"
|
||||||
} else {
|
} else {
|
||||||
Write-Host "错误: 令牌写入失败"
|
Write-Host "错误: 令牌写入失败"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -73,20 +81,24 @@ if ((Get-Item "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env").Length -gt
|
|||||||
### 1.2 读取令牌(后续使用)
|
### 1.2 读取令牌(后续使用)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Unix — 去除首尾空白,避免换行符污染 API 请求
|
# Unix — 确保目录存在后再读取令牌(按 KEY 提取)
|
||||||
GITEA_TOKEN=$(cat ~/.codex/skills/PineSoundSkills/.env 2>/dev/null | tr -d '\n\r' | xargs)
|
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
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
echo "错误: 令牌文件缺失或为空,请先设置令牌"
|
echo "错误: .env 中缺少 GITEA_TOKEN,请先设置令牌"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Windows
|
# Windows — 确保目录存在后再读取令牌(按 KEY 提取)
|
||||||
$GITEA_TOKEN = (Get-Content "$env:USERPROFILE\.codex\skills\PineSoundSkills\.env" -Raw).Trim()
|
$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)) {
|
if ([string]::IsNullOrEmpty($GITEA_TOKEN)) {
|
||||||
Write-Host "错误: 令牌文件缺失或为空,请先设置令牌"
|
Write-Host "错误: .env 中缺少 GITEA_TOKEN,请先设置令牌"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -95,7 +107,7 @@ if ([string]::IsNullOrEmpty($GITEA_TOKEN)) {
|
|||||||
|
|
||||||
| 场景 | 行为 |
|
| 场景 | 行为 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 首次运行 | agent 用 `AskUserQuestion` 索要 → 写入 `.env` |
|
| 首次运行 | agent 索要 → escalate(fs 写入)→ 写入 `.env` |
|
||||||
| 再次运行 | 直接从 `.env` 读取,跳过询问 |
|
| 再次运行 | 直接从 `.env` 读取,跳过询问 |
|
||||||
| 令牌失效(API 返回 401) | 提示用户令牌可能已过期,重新执行 1.1 流程 |
|
| 令牌失效(API 返回 401) | 提示用户令牌可能已过期,重新执行 1.1 流程 |
|
||||||
| 用户明确要求重新设置 | 覆盖 `.env`,执行 1.1 流程 |
|
| 用户明确要求重新设置 | 覆盖 `.env`,执行 1.1 流程 |
|
||||||
@@ -121,22 +133,27 @@ echo "$PLATFORM"
|
|||||||
|
|
||||||
## 三、查看 Gitea 仓库中的 Skills 列表
|
## 三、查看 Gitea 仓库中的 Skills 列表
|
||||||
|
|
||||||
|
> **需 escalate**: 以下步骤需要同时 escalate 文件系统写入(mkdir/读取 .env)和网络访问(curl 请求内网 Gitea)。
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
|
TOKEN_FILE=~/.codex/skills/PineSoundSkills/.env
|
||||||
|
|
||||||
# 读取并清理令牌
|
# 确保目录存在
|
||||||
if [ ! -f "$TOKEN_FILE" ] || [ ! -s "$TOKEN_FILE" ]; then
|
mkdir -p "$(dirname "$TOKEN_FILE")"
|
||||||
echo "错误: 令牌文件缺失或为空。请先执行令牌获取流程(见第一章)"
|
|
||||||
|
# 按 KEY 读取 GITEA_TOKEN
|
||||||
|
if [ ! -f "$TOKEN_FILE" ] || ! grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
|
||||||
|
echo "错误: .env 中缺少 GITEA_TOKEN。请先执行令牌获取流程(见第一章)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
GITEA_TOKEN=$(cat "$TOKEN_FILE" 2>/dev/null | tr -d '\n\r' | xargs)
|
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
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
echo "错误: 令牌为空"
|
echo "错误: 令牌为空"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 请求 API
|
# 请求 API(内网 Gitea,需 escalate 网络权限)
|
||||||
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||||
"http://192.168.1.3:10082/api/v1/orgs/PineSoundSkills/repos" \
|
"http://192.168.1.3:10082/api/v1/orgs/PineSoundSkills/repos" \
|
||||||
| python3 -c "
|
| python3 -c "
|
||||||
@@ -153,10 +170,62 @@ for r in repos:
|
|||||||
|
|
||||||
> 注意:`cat` 读取的令牌可能包含尾部换行符,通过 `tr -d '\n\r'` 和 `xargs` 做双重清理。如果 API 返回 401,说明令牌无效或已过期。
|
> 注意:`cat` 读取的令牌可能包含尾部换行符,通过 `tr -d '\n\r'` 和 `xargs` 做双重清理。如果 API 返回 401,说明令牌无效或已过期。
|
||||||
|
|
||||||
|
### 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 内部技能)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 四、安装 Skills
|
## 四、安装 Skills
|
||||||
|
|
||||||
|
> **需 escalate**: 以下涉及文件系统写入和 git 克隆(网络访问)。
|
||||||
|
|
||||||
### 4.1 确保目录存在
|
### 4.1 确保目录存在
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -527,28 +596,32 @@ GITEA_API="http://192.168.1.3:10082/api/v1"
|
|||||||
|
|
||||||
# ========== 令牌管理 ==========
|
# ========== 令牌管理 ==========
|
||||||
|
|
||||||
# 检查令牌文件是否存在且非空
|
# 确保目录存在
|
||||||
if [ ! -f "$TOKEN_FILE" ] || [ ! -s "$TOKEN_FILE" ]; then
|
mkdir -p "$(dirname "$TOKEN_FILE")"
|
||||||
echo "令牌文件不存在或为空。"
|
|
||||||
|
# 检查 .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 令牌。"
|
echo "请通过 AskUserQuestion 向用户索要 Gitea 令牌。"
|
||||||
|
|
||||||
# [agent 操作] 用 AskUserQuestion 获取用户输入的令牌
|
# [agent 操作] 用 AskUserQuestion 获取用户输入的令牌
|
||||||
# 假设用户提供了令牌,存入变量 USER_TOKEN
|
# 假设用户提供了令牌,存入变量 USER_TOKEN
|
||||||
|
|
||||||
mkdir -p "$SKILL_DIR"
|
mkdir -p "$SKILL_DIR"
|
||||||
printf '%s' "$USER_TOKEN" > "$TOKEN_FILE"
|
printf "GITEA_TOKEN=%s\n" "$USER_TOKEN" > "$TOKEN_FILE"
|
||||||
chmod 600 "$TOKEN_FILE"
|
chmod 600 "$TOKEN_FILE"
|
||||||
|
|
||||||
# 写入后验证
|
# 写入后验证
|
||||||
if [ ! -s "$TOKEN_FILE" ]; then
|
if grep -q "^GITEA_TOKEN=" "$TOKEN_FILE" 2>/dev/null; then
|
||||||
|
echo "令牌已持久化存储到 $TOKEN_FILE(KEY=VALUE 格式)"
|
||||||
|
else
|
||||||
echo "错误: 令牌写入失败!"
|
echo "错误: 令牌写入失败!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "令牌已持久化存储到 $TOKEN_FILE"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 读取令牌(清理空白字符)
|
# 按 KEY 读取令牌(清理空白字符)
|
||||||
GITEA_TOKEN=$(cat "$TOKEN_FILE" 2>/dev/null | tr -d '\n\r' | xargs)
|
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
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
echo "错误: 令牌为空"
|
echo "错误: 令牌为空"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -563,6 +636,21 @@ HTTP_CODE=$(curl -s -o /tmp/pinesound_repos.json -w "%{http_code}" \
|
|||||||
if [ "$HTTP_CODE" != "200" ]; then
|
if [ "$HTTP_CODE" != "200" ]; then
|
||||||
echo "API 请求失败 (HTTP $HTTP_CODE),令牌可能无效或已过期"
|
echo "API 请求失败 (HTTP $HTTP_CODE),令牌可能无效或已过期"
|
||||||
echo "请重新执行令牌获取流程"
|
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
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user