19-skill-cli.md 9.8 KB

19 技能推送工具 agent-skills

状态:已实施(2026-11-03)。决策见 ADR-0028。配合技能发布平台(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 官方仓库。

    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:

- 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:

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)并覆盖设备码授权的服务端规则。