# 19 技能推送工具 agent-skills > 状态:**已实施(2026-11-03)**。决策见 ADR-0028。配合技能发布平台([18-skill-registry.md](18-skill-registry.md))使用。 ## 1. 目标 让项目(开发机或 CI)用一条命令把技能推送到平台: - 不用手动打包、不用复制密钥(开发机在浏览器中批准登录); - 推送前在本地发现问题(名称、SKILL.md、限额、疑似密钥),而不是等审核时才发现; - CI 中按退出码判断结果,可以选择等待审核。 ## 2. 形态与分发 - Node 命令行工具(Node 18+),包名 `@agent-server/skills-cli`,源码 `packages/skills-cli`,用 tsup 打包成一个不带依赖的文件(fflate 一起打包,约 50 KB)。 - 由 `scripts/build-sdk.mjs` 放进 `dist-sdk`,部署到 `app/sdk`(保留旧版本)。服务在 `/npm/` 提供只读的 npm 仓库(`http/npm-registry.ts`,与 SDK 的三个包共用):`GET /npm/<包名>` 返回全部版本与 `dist-tags.latest`,`GET /npm/-/<文件>.tgz` 下载(永久缓存)。不发布到 npm 官方仓库。 ```bash npx -y --registry https://agent.zenpower.cc/npm/ @agent-server/skills-cli@latest <命令> # 不安装,每次都是最新版本 npm i -g --registry https://agent.zenpower.cc/npm/ @agent-server/skills-cli@latest # 或全局安装,之后用 agent-skills ``` 在项目或用户目录的 `.npmrc` 中加一行 `@agent-server:registry=https://agent.zenpower.cc/npm/` 后可以省略 `--registry`。 ### 2.1 更新 - `@latest`:npx 每次向仓库查询包信息(不缓存),有新版本时下载安装,否则直接运行已有的安装。最初用固定地址的 tgz(`/sdk/agent-skills.tgz`),实测 npx 按地址缓存安装目录,包更新后仍运行旧版本,因此改为仓库;`/sdk/` 已不再提供。 - 全局安装或锁定了版本时:每次执行命令前(`init`、`--help`、`--version` 除外)查询仓库中推送工具的最新版本(超时 3 秒)。比当前版本新时,提示后执行 `npx -y --registry <仓库> @agent-server/skills-cli@<新版本> <原参数>` 并返回它的退出码;子进程设置 `AGENT_SKILLS_NO_UPDATE_CHECK=1`,不会循环。全局安装的另外提示 `npm i -g` 更新命令。 - 查询失败或 npx 无法执行时继续用当前版本;设置 `AGENT_SKILLS_NO_UPDATE_CHECK=1` 关闭检查。 修改推送工具后必须提高 `packages/skills-cli/package.json` 的版本号:仓库按版本号区分,版本号不变时客户端不会更新。 ### 2.2 后台用法页 后台 `/admin/skill-cli`("技能与插件 › 自定义技能"中"推送的技能"标题旁的书本图标、"发布密钥"下方与登录审批页都有入口):本机推送、项目配置、CI 示例、命令、退出码、密钥与更新,命令中的服务器地址取当前地址,可以把页面地址转发给项目方。 ## 3. 命令 | 命令 | 说明 | | --- | --- | | `login [--server 地址] [--no-browser]` | 浏览器登录(§5),密钥保存在用户目录 | | `login --with-token` | 从标准输入读取后台创建的密钥,校验后保存 | | `logout` | 在服务器上停用当前密钥,并删除本机保存的密钥 | | `whoami` | 当前密钥的名称、来源(浏览器登录 / 后台创建)、到期时间、来自环境变量还是本机登录 | | `init` | 在当前目录生成 `.agent-skills.json`(服务器地址、技能目录、排除规则;不含密钥) | | `push [目录…]` | 推送(§4) | | `status [提交ID]` | 最近 10 次提交,或某次提交的审核结果 | | `withdraw <提交ID>` | 撤回仍在待审核的技能 | 通用选项:`--server`、`--json`、`-h/--help`、`-v/--version`。 ## 4. push 1. **找技能**:参数中的目录,或项目配置的 `paths`(相对配置文件,在子目录中执行也能找到),或当前目录。识别规则与服务端一致:单个技能、`<名称>/SKILL.md`、`skills/<名称>/SKILL.md`。`--only` 只推送指定的技能;同名技能报错。 2. **本地预检**:技能名规则、SKILL.md 的 name / description、文件数与大小限额(与服务端相同);疑似密钥给出警告,`--strict` 时作为错误。有错误时不推送。 3. **排除**:跳过符号链接;默认排除 `.git`、`node_modules`、`__pycache__`、`.venv`、`.DS_Store`、`*.pyc`、`.env`、`.env.*`(保留 `.env.example`),项目配置的 `exclude` 可以追加(`*` 不跨目录、`**` 跨目录、`!` 开头表示不排除)。 4. **打包上传**:在内存中按 `<名称>/<文件>` 打成 zip。提交说明默认取最近一次 git 提交的标题(`-m` 指定);来源信息自动取自 git(仓库地址去掉用户名和密码、分支、提交号、工作区有未提交改动时 `dirty`)与工具版本。 5. **输出**:每个技能的状态、与平台已有版本相比变化的文件数、需要注意的检查结果、后台审核链接(`/admin/plugins?tab=custom&review=<提交项>`)。`--dry-run` 只校验;`--json` 输出接口返回的 JSON。 ### 4.1 --wait 推送后每 5 秒查询一次,直到全部处理或超时(`--timeout` 秒,默认 600)。默认不等待: - 本机开发推荐 `push --wait`:在浏览器中批准后终端立即显示结果; - CI 推荐不等待,只推送:审核依赖人,等待会占用运行器、超时会被当作失败。确实需要等待时,退出码 5 表示"仍在待审核",可以与失败区分。 ### 4.2 退出码 | 退出码 | 含义 | | --- | --- | | 0 | 成功(待审核、无变化;`--wait` 时全部批准或撤回) | | 1 | 校验不通过(本地预检、服务端 `invalid`、参数错误) | | 2 | 鉴权失败(没有密钥、密钥无效 / 停用 / 过期、登录被拒绝或过期) | | 3 | 限额、网络或服务器错误 | | 4 | `--wait`:有技能被拒绝 | | 5 | `--wait`:超时,仍有技能在待审核 | ## 5. 浏览器登录(设备码授权,RFC 8628) ``` 终端 agent-skills login ──POST /device/code(机器名、系统用户、系统、工具版本)──▶ 平台 ◀── 设备码(只给终端)、验证码 WDJB-MJHT、审批页地址、10 分钟有效、询问间隔 5 秒 ── 显示验证码与地址(有图形界面时自动打开浏览器;SSH 远程终端在任意电脑的浏览器中打开即可) 管理员打开 /admin/skill-login?code=…(未登录先登录) 核对验证码、机器、系统用户、来源 IP 后批准(可改密钥名称、有效期) 终端每 5 秒 POST /device/token ──▶ 未批准:authorization_pending;过快:slow_down(间隔加 5 秒) 拒绝:access_denied;过期或已用:expired_token 已批准:此时才签发发布密钥,返回明文一次,申请作废 保存到用户目录(权限 600) ``` - 签发的是普通发布密钥(`origin: device`),名称默认"系统用户@机器名 · agent-skills",在后台"发布密钥"中标注"推送工具登录",可以停用、删除。 - 平台不保存密钥明文:批准时只记录状态,终端下一次询问时才生成。 - 防冒用:审批页显示来源 IP、机器名、系统用户,并提示只在本人刚执行 login 且验证码一致时批准;即使误批,对方得到的也只是只能产生待审核提交的密钥。 - 防滥用:每个来源 IP 10 分钟内最多申请 10 次,待批准的申请总数有上限;设备码 32 字节随机、只能换取一次;过期一天以上的申请记录删除。 - CI 不走浏览器登录,使用后台创建的密钥(环境变量 `AGENT_SKILLS_TOKEN`)。 ## 6. 配置 读取顺序:命令行参数 > 环境变量(`AGENT_SKILLS_TOKEN`、`AGENT_SKILLS_SERVER`)> 项目的 `.agent-skills.json`(只含服务器地址、`paths`、`exclude`,从当前目录向上查找)> 用户目录的登录信息。 用户目录:`$XDG_CONFIG_HOME/agent-skills/credentials.json`(默认 `~/.config/agent-skills/`;Windows 为 `%APPDATA%\agent-skills\`;可用 `AGENT_SKILLS_CONFIG_DIR` 指定),按服务器地址分别保存密钥,记录最近一次登录的服务器作为默认值。 ## 7. CI 示例 GitHub Actions: ```yaml - name: 推送技能 if: github.event_name == 'push' run: npx -y --registry https://agent.zenpower.cc/npm/ @agent-server/skills-cli@latest push env: AGENT_SKILLS_TOKEN: ${{ secrets.AGENT_SKILLS_TOKEN }} AGENT_SKILLS_SERVER: https://agent.zenpower.cc ``` GitLab CI: ```yaml push-skills: image: node:22 rules: [{ changes: ["skills/**/*"] }] script: npx -y --registry https://agent.zenpower.cc/npm/ @agent-server/skills-cli@latest push variables: { AGENT_SKILLS_SERVER: https://agent.zenpower.cc } # AGENT_SKILLS_TOKEN 设为受保护的 CI 变量 ``` ## 8. 实施记录(2026-11-03) - 服务端:`custom-skills/device-login.ts`(设备码授权),发布接口增加 `device/code`、`device/token`、`GET / DELETE /me`;后台审批页 `SkillLoginView.vue`(`/admin/skill-login`),"发布密钥"中提示待批准的登录申请;`skill_publish_keys.origin` 区分后台创建与浏览器登录。迁移 `0021_skill_device_login`。 - 推送工具:`packages/skills-cli`(`main.ts` 命令、`pack.ts` 识别与预检、`config.ts` 配置、`api.ts` 接口、`git.ts` 来源信息)。 - 后台"API 协议 › 技能发布 API"增加"登录与密钥"一节。 - 2026-11-04:分发改为平台的 npm 仓库 `/npm/`(`http/npm-registry.ts`,SDK 三个包一起迁移,`/sdk/` 不再提供);自动更新 `update.ts`(版本 0.3.0);后台用法页 `SkillCliView.vue`。 - 测试:`test/skills-cli.test.ts` 对测试服务端到端运行命令(登录、init、试运行、预检、推送、--wait 的三种结果、撤回、CI 环境变量、--strict、logout)并覆盖设备码授权的服务端规则。