# use_vala_skillhub

管理 Vala SkillHub 上的技能：推送（上传）和安装。

SkillHub 基于 Gitea，每个技能对应一个独立的 Git 仓库。推送时使用 `./tmp` 临时目录，不在 `./skills` 内创建 `.git`，避免影响 workspace 级别的 git 备份。

## 命名规则

仓库名格式：`{skill_name}.{source_name}`

- `skill_name`：技能目录名（如 `cron_job`、`web_scraper`）
- `source_name`：来源名称，即当前数字员工的 name（如 `xiaoxi`）
- 如果 `source_name` 为 `vala`，表示公司级别的官方技能

示例：
- `cron_job.xiaoxi` — xiaoxi 的定时任务技能
- `web_scraper.vala` — 公司官方的爬虫技能

## 配置

操作前需要确认以下配置（保存到 `./.vala_skillhub_config`，即当前 workspace 根目录下）：

| 配置项 | 说明 | 默认值 |
|--------|------|--------|
| `GITEA_URL` | Gitea 服务地址 | `https://git.valavala.com` |
| `GITEA_TOKEN` | Gitea API Token（需有创建仓库和推送权限） | — |
| `GITEA_OWNER` | SkillHub 组织名 | `vala_skillhub` |
| `SOURCE_NAME` | 当前数字员工的 name，用于组合仓库名 | — |

如果配置文件不存在，请询问用户获取以上信息后创建：

```bash
cat > ./.vala_skillhub_config <<EOF
GITEA_URL=https://git.valavala.com
GITEA_TOKEN=<token>
GITEA_OWNER=vala_skillhub
SOURCE_NAME=<name>
EOF
```

后续操作前先加载配置：
```bash
source ./.vala_skillhub_config
```

---

## 操作一：推送技能到 SkillHub

将本地 `./skills` 下的技能目录推送到 SkillHub。

**核心原则**：使用 `./tmp/skill_push/` 作为临时工作区，不在 `./skills` 内执行任何 git 操作，保持 workspace 干净。

### 流程

1. **确定仓库名**：`repo_name = {skill_dir_name}.{SOURCE_NAME}`

2. **检查远程仓库是否存在**：
   ```bash
   curl -s -o /dev/null -w "%{http_code}" \
     "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${repo_name}" \
     -H "Authorization: token ${GITEA_TOKEN}"
   ```
   - 返回 200 → 仓库已存在，跳到步骤 4
   - 返回 404 → 需要创建，执行步骤 3

3. **创建远程仓库**：
   ```bash
   curl -s -X POST "${GITEA_URL}/api/v1/orgs/${GITEA_OWNER}/repos" \
     -H "Authorization: token ${GITEA_TOKEN}" \
     -H "Content-Type: application/json" \
     -d '{"name": "'${repo_name}'", "private": false, "description": "技能描述", "auto_init": false}'
   ```

4. **复制到临时目录并推送**：
   ```bash
   # 清理并准备临时目录
   rm -rf ./tmp/skill_push/${repo_name}
   mkdir -p ./tmp/skill_push/${repo_name}
   
   # 复制技能内容（不含隐藏文件的 .git 等）
   cp -r ./skills/${skill_dir_name}/* ./tmp/skill_push/${repo_name}/
   cp -r ./skills/${skill_dir_name}/.[!.]* ./tmp/skill_push/${repo_name}/ 2>/dev/null || true
   
   # 在临时目录中执行 git 操作
   cd ./tmp/skill_push/${repo_name}
   git init
   git checkout -b main
   git add -A
   git commit -m "update: sync skill $(date +%Y-%m-%d)"
   git remote add origin https://oauth2:${GITEA_TOKEN}@${GITEA_URL#https://}/${GITEA_OWNER}/${repo_name}.git
   git push -u origin main --force
   ```

5. **清理临时目录**：
   ```bash
   cd -
   rm -rf ./tmp/skill_push/${repo_name}
   ```

### 批量推送

遍历 `./skills/` 下所有子目录，对每个目录重复以上流程。注意跳过 `use_vala_skillhub` 目录本身。

---

## 操作二：安装 / 更新技能

从 SkillHub 下载技能到本地 `./skills` 目录。若本地已存在同名目录，则**清空后重新下载**，确保与远程版本一致。

**注意**：不使用 `git clone`，而是下载归档解压，避免在 `./skills` 下产生 `.git` 目录。

### 流程

1. **确定要安装的仓库名**（完整名，如 `cron_job.xiaoxi`）

2. **下载并解压**（已存在则先清空再覆盖）：
   ```bash
   repo_name="cron_job.xiaoxi"
   target_dir="./skills/${repo_name}"
   
   # 如果已存在，清空目录内容以确保与远程一致（删除远程已移除的文件）
   rm -rf "${target_dir}"
   mkdir -p "${target_dir}"
   
   # 通过 Gitea API 下载 tar.gz 归档并解压（自动尝试 main/master）
   curl -sL "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${repo_name}/archive/main.tar.gz" \
     -H "Authorization: token ${GITEA_TOKEN}" \
     | tar xz --strip-components=1 -C "${target_dir}" 2>/dev/null \
   || curl -sL "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${repo_name}/archive/master.tar.gz" \
     -H "Authorization: token ${GITEA_TOKEN}" \
     | tar xz --strip-components=1 -C "${target_dir}"
   ```

---

## 操作三：列出 SkillHub 上的技能

```bash
curl -s "${GITEA_URL}/api/v1/orgs/${GITEA_OWNER}/repos?page=1&limit=50&sort=updated" \
  -H "Authorization: token ${GITEA_TOKEN}"
```

返回 JSON 数组，每个元素包含 `name`、`description`、`updated_at` 等字段。如结果满 50 条，继续翻页 `page=2`。

---

## 操作四：自动同步（变更检测 + 自动推送）

当 skill 发生新增或更新时，自动检测变更并仅推送有变化的 skill。

### 哈希状态文件

使用 `./.vala_skill_hashes`（workspace 根目录下）记录每个 skill 上次推送时的内容哈希，格式为每行一条记录：

```
skill_dir_name hash
```

示例：
```
cron_job a3f2b8c9...
web_scraper 7d1e4f6a...
```

### 计算 skill 目录哈希

对 skill 目录内所有文件（含文件路径）计算综合哈希，确保文件新增、删除、重命名、内容修改都能被检测到：

```bash
compute_skill_hash() {
  local skill_dir="$1"
  (cd "${skill_dir}" && find . -type f -not -path '*/\.*' | LC_ALL=C sort | while read f; do echo "FILE:$f"; cat "$f"; done | sha256sum | awk '{print $1}')
}
```

### check_and_push 完整流程

1. **加载配置和哈希状态**：
   ```bash
   source ./.vala_skillhub_config
   HASH_FILE=./.vala_skill_hashes
   touch "${HASH_FILE}"
   ```

2. **遍历 `./skills/` 下所有 skill 目录**，对每个 skill：
   ```bash
   for skill_dir in ./skills/*/; do
     skill_name=$(basename "${skill_dir}")

     # 跳过 use_vala_skillhub 自身
     if [ "${skill_name}" = "use_vala_skillhub" ]; then
       continue
     fi

     # 计算当前哈希
     current_hash=$(compute_skill_hash "${skill_dir}")

     # 读取上次推送的哈希
     stored_hash=$(grep "^${skill_name} " "${HASH_FILE}" | awk '{print $2}')

     if [ "${current_hash}" = "${stored_hash}" ]; then
       echo "[skip] ${skill_name} — 无变更"
       continue
     fi

     echo "[sync] ${skill_name} — 检测到变更，开始推送..."

     # 执行推送（同操作一的流程）
     repo_name="${skill_name}.${SOURCE_NAME}"

     # 检查远程仓库是否存在
     http_code=$(curl -s -o /dev/null -w "%{http_code}" \
       "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${repo_name}" \
       -H "Authorization: token ${GITEA_TOKEN}")

     # 如果不存在则创建
     if [ "${http_code}" = "404" ]; then
       # 从 skill.json 读取描述（如果有）
       desc=""
       if [ -f "${skill_dir}/skill.json" ]; then
         desc=$(cat "${skill_dir}/skill.json" | grep '"description"' | head -1 | sed 's/.*"description"[[:space:]]*:[[:space:]]*"\(.*\)".*/\1/')
       fi
       curl -s -X POST "${GITEA_URL}/api/v1/orgs/${GITEA_OWNER}/repos" \
         -H "Authorization: token ${GITEA_TOKEN}" \
         -H "Content-Type: application/json" \
         -d '{"name": "'"${repo_name}"'", "private": false, "description": "'"${desc}"'", "auto_init": false}'
     fi

     # 复制到临时目录并推送
     rm -rf ./tmp/skill_push/${repo_name}
     mkdir -p ./tmp/skill_push/${repo_name}
     cp -r ${skill_dir}* ./tmp/skill_push/${repo_name}/
     cp -r ${skill_dir}.[!.]* ./tmp/skill_push/${repo_name}/ 2>/dev/null || true

     cd ./tmp/skill_push/${repo_name}
     git init
     git checkout -b main
     git add -A
     git commit -m "auto-sync: ${skill_name} $(date +%Y-%m-%d_%H:%M)"
     git remote add origin "https://oauth2:${GITEA_TOKEN}@${GITEA_URL#https://}/${GITEA_OWNER}/${repo_name}.git"
     git push -u origin main --force
     cd -

     # 清理临时目录
     rm -rf ./tmp/skill_push/${repo_name}

     # 更新哈希状态文件
     # 先移除旧记录，再追加新记录
     grep -v "^${skill_name} " "${HASH_FILE}" > "${HASH_FILE}.tmp" || true
     echo "${skill_name} ${current_hash}" >> "${HASH_FILE}.tmp"
     mv "${HASH_FILE}.tmp" "${HASH_FILE}"

     echo "[done] ${skill_name} — 推送完成"
   done
   ```

3. **输出同步摘要**：推送完成后，汇总本次同步了哪些 skill（跳过了哪些）。

### 单个 skill 的快速同步

当你明确知道哪个 skill 发生了变更时，可以仅对该 skill 执行 check_and_push，无需遍历所有目录：

```bash
skill_name="目标skill名"
skill_dir="./skills/${skill_name}"
# 然后执行上面循环体中的逻辑
```

---

## 数字员工行为规范（重要）

作为数字员工，你**必须**在以下场景中自动执行 `check_and_push`：

1. **创建新 skill 后** — 在 `./skills/` 下新建了 skill 目录并完成编写后，立即执行同步
2. **修改现有 skill 后** — 对 `./skills/` 下任何 skill 的文件做了修改后，立即执行同步
3. **删除 skill 文件后** — 如果删除了 skill 中的某些文件（重构等），同步以保持远程一致
4. **定时兜底**（可选） — 如果你拥有 `cron_job` 技能，建议设置每日一次的全量 check_and_push 作为安全网

### 推送时机建议

- **立即推送**：skill 的功能性变更完成且验证通过后
- **不要推送**：正在编辑中间态、尚未完成的 skill
- **批量场景**：如果一次性修改了多个 skill，在全部完成后执行一次全量 check_and_push 即可

### 自动推送日志

每次推送后，建议在控制台输出简短日志，方便追溯：
```
[auto-sync] 2025-04-01 23:00 — 推送 2 个 skill: cron_job, web_scraper | 跳过 3 个（无变更）
```
