文章

【笔记】npx skills 的来源解析与安装边界

【笔记】npx skills 的来源解析与安装边界

npx skills add <source> 看起来只是“从一个地址安装 Skill”,实际至少包含四个阶段:把字符串解析成来源、获取内容、发现合法 SKILL.md、再安装到目标 Agent。很多输入歧义都发生在第一阶段,但安全和可复现性问题贯穿整条链路。

本文对应 Vercel Labs skills CLI 1.5.21,核验源码提交 1164afa。解析优先级属于当前实现,可能随版本变化;使用前可通过 npx skills --version 重新确认。

1. 中心模型:解析、获取、发现、安装

flowchart LR
    I[原始 source 字符串] --> P[parseSource]
    P --> F[fetch / clone / local / download]
    F --> D[discover SKILL.md]
    D --> S[skill selector]
    S --> T[选择 Agent 与 scope]
    T --> M[copy 或 canonical + symlink]
    M --> L[skills-lock.json / update]

ParsedSource 只描述“去哪里、取哪个版本、从哪个子目录开始、是否预选某个 skill”:

1
2
3
4
5
6
7
8
interface ParsedSource {
  type: 'github' | 'gitlab' | 'git' | 'local' | 'well-known' | 'download'
  url: string
  subpath?: string
  localPath?: string
  ref?: string
  skillFilter?: string
}

它不保证目标可访问,也不保证里面存在合法 Skill。解析成功只是进入获取阶段。

2. 五个维度不要混在一段字符串里理解

2.1 Source type

type 决定后续采用本地读取、托管平台优化、通用 Git clone、well-known discovery 或直接下载。

2.2 Repository URL

url 是规范化后的仓库或资源地址。GitHub shorthand 会补成 HTTPS .git URL;local source 则把绝对路径同时放进 urllocalPath

2.3 Git ref

ref 表示 branch、tag 或其他 Git revision 选择。当前 fragment 语法为:

1
owner/repo#v1.2.0

它不是 Skill 名称。

2.4 Repository subpath

subpath 限定在仓库内哪个目录开始发现,例如:

1
owner/repo/skills/frontend

它表示目录位置,不保证目录中的 Skill namefrontend

2.5 Skill filter

skillFilter 表示发现多个 Skill 后只选择特定名称:

1
2
owner/repo@review
owner/repo#v1.2.0@review

第一种只选 Skill;第二种同时固定 ref 和 Skill。CLI 的 --skill review 是更明确的等价选择入口。

3. 为什么解析顺序本身就是语义

同一个字符串可能满足多个模式。当前 parseSource() 使用从特殊到通用的优先级,先识别强信号,再落入兜底:

1
2
3
4
5
6
7
8
9
10
11
local path
→ fragment 预解析
→ alias
→ github:/gitlab: prefix
→ hosted artifact download
→ GitHub Enterprise
→ GitHub tree/repo URL
→ GitLab tree/repo URL
→ GitHub shorthand
→ well-known HTTP(S)
→ generic git fallback

如果把 generic URL 或 owner/repo 判断放太前面,tree URL 的 ref/subpath、raw download 和 well-known endpoint 都会被错误吞掉。

4. Local path 最先识别

以下输入直接成为 local

1
2
3
4
5
6
./skills
../shared-skills
/absolute/path
.
..
C:\skills

相对路径按 CLI 当前工作目录解析成绝对路径。parser 即使发现路径不存在也会返回 local,存在性和内容校验留给后续流程。

这意味着:

  • ./owner/repo 是本地路径;
  • owner/repo 是 GitHub shorthand;
  • 工作目录不同会让同一相对输入指向不同来源。

自动化脚本应明确 cwd,或直接传绝对路径。

5. Fragment 的当前语义

5.1 只对 git-like source 生效

parser 先检查 #,但只有输入看起来像 Git source 时,fragment 才解释为 ref。普通 well-known URL 的 fragment 会保留在 URL 中,避免把 Web endpoint 自身的 anchor 错当 branch。

5.2 #ref@skill

当前格式把 fragment 按第一个 @ 分开:

1
owner/repo#release%2Fv2@audit

得到:

1
2
3
4
{
  "ref": "release/v2",
  "skillFilter": "audit"
}

ref 和 skillFilter 会做 URL decode。若 ref 内含 @,这套紧凑语法会产生歧义,应改用完整 tree URL配合 --skill

5.3 Tree URL 中的 ref 优先来自 path

1
https://github.com/acme/repo/tree/main/skills/a

解析为:

1
2
3
4
5
6
{
  "type": "github",
  "url": "https://github.com/acme/repo.git",
  "ref": "main",
  "subpath": "skills/a"
}

这里 main 是 URL path 的结构部分。对于包含 / 的 branch,普通 GitHub tree URL 的单段正则难以无歧义区分 branch 与 subpath;固定复杂 ref 时,#ref--ref 能力若存在应优先使用明确形式。

6. Alias 只是输入兼容层

当前源码内置少量 alias,例如把旧仓库名映射到新仓库名。alias 在 fragment 解析后、其他来源识别前应用。

alias 的特点是:

  • 由 CLI 版本内置,不是远端 DNS 或 Git alias;
  • 列表可能随仓库迁移改变;
  • lock/update 应保存规范化来源,不能把 alias 当长期稳定 ID。

业务自动化最好使用 canonical repository URL,而不是依赖方便输入的迁移别名。

7. GitHub 与 GitLab 解析

7.1 GitHub shorthand

当前支持:

输入结果
owner/repo整个 GitHub repo
owner/repo/pathrepo + subpath
owner/repo@skillrepo + skillFilter
github:owner/repo去掉 prefix 后递归解析

若设置 GitHub Enterprise host,shorthand 会指向该 host,并走 generic git type,因为 GitHub.com API fast path 不适用。

7.2 GitHub full URL

repo URL 规范化为 clone URL;/tree/<ref>/<path> 额外解析 ref 与 subpath。普通 /blob/... 并不是 Skill tree 入口,当前可能被宽松 repo URL 规则归一到仓库,而不是直接下载该 blob。

要安装单个远端 SKILL.md,使用 raw URL 比 GitHub blob 页面更明确。

7.3 GitLab 支持 subgroup

GitLab URL 使用 /-/tree/ 区分 repo path 与 branch/subpath,repo path 可包含多层 subgroup:

1
https://gitlab.com/group/subgroup/repo/-/tree/main/skills/a

gitlab: prefix 会转换为 https://gitlab.com/... 后重新解析。自建 GitLab tree URL 也可通过 /-/tree/ 模式识别。

8. Download、well-known 与 generic Git

8.1 Hosted artifact 直接下载

当前新增 download type,用于明确的托管产物 URL,例如:

  • raw.githubusercontent.com
  • GitHub archive/raw/release download;
  • GitLab archive/raw。

后续把它当单个 SKILL.md 或压缩包处理,而不是 clone 父仓库。

8.2 Well-known URL 先发现,再尝试下载

非 GitHub/GitLab 的普通 HTTP(S) URL、且不以 .git 结尾时,通常解析为 well-known。add 流程先尝试 well-known skills discovery;失败后可以把 URL 当直接 SKILL.md 或 archive 下载。

因此 well-known 描述的是获取策略,不保证服务端一定实现某个 manifest。

8.3 Generic Git 是最后兜底

以下常落入 git

1
2
3
4
[email protected]:owner/repo.git
ssh://[email protected]/team/repo.git
https://example.com/repo.git
自定义 Git remote 字符串

兜底宽松意味着 parseSource 很少因语法直接报错;无效字符串往往到 clone 阶段才失败。

9. 表驱动实验结果

在 1.5.21 源码上直接调用 parseSource()

输入typerefsubpathskillFilter
./locallocal
vercel-labs/agent-skillsgithub
vercel-labs/agent-skills/skills/web-design-guidelinesgithubskills/web-design-guidelines
vercel-labs/agent-skills@reviewgithubreview
vercel-labs/agent-skills#v1.0@reviewgithubv1.0review
GitHub tree URLgithubpath 中 branchpath 中子目录
GitLab subgroup tree URLgitlabpath 中 branchpath 中子目录
https://example.com/skillswell-known
https://example.com/repo.gitgit
raw GitHub SKILL.mddownload
Git SSH URL #devgitdev

这张表只覆盖 parser 输出,不包含网络访问、private repo 认证或 Skill 校验。

10. 获取阶段不是只有 git clone

10.1 GitHub fast path

GitHub source 且未要求 --full-depth 时,当前 add 流程可以通过 provider/API 发现并拉取必要 blobs,失败再回退 clone。故不要从 CLI 行为假定本地一定出现完整 Git checkout。

10.2 Generic Git 与 GitLab clone

需要完整仓库或 provider fast path 不适用时,CLI clone 到临时目录,并按 ref checkout。private source 的认证依赖本机 Git、SSH agent、token 或 host 配置。

10.3 Download 有资源上限

直接 URL 可以是单个合法 SKILL.md 或 zip/tar archive。CLI 对下载字节、解压后大小和文件数设置上限,降低 zip bomb 和无限资源消耗风险。

能下载不等于可信。Skill 本身包含给 Agent 的指令和脚本,安装前仍需审查来源。

11. Skill discovery 如何工作

11.1 合法入口是 SKILL.md

目录只有包含可解析 Front Matter、至少具有 namedescriptionSKILL.md,才会成为候选 Skill。普通 Markdown 文件不会自动安装。

11.2 默认搜索有结构偏好

当前 discovery 优先:

  • source 根目录自身的 SKILL.md
  • 常见 skills/<name>/SKILL.md
  • catalog 式额外一层目录;
  • plugin manifest 声明的 Skill 容器。

发现浅层 SKILL.md 后通常不继续向下递归,避免把 example、fixture 或 Skill 自带参考目录误识别成另一个 Skill。

11.3 --full-depth 扩大搜索范围

--full-depth 会搜索更深层目录,即使根目录已经有 Skill。它适合不标准的 monorepo 布局,但也会扩大候选和审计范围。

使用 subpath 比全仓库 full-depth 更精确:前者先缩小根目录,后者在更大范围递归发现。

11.4 Filter 在发现后选择

owner/repo@skill--skill 不直接映射到某个硬编码目录。CLI 先获取并发现 Skill,再按 Skill name 过滤。这就是 subpathskillFilter 不能混为一谈的原因。

12. 安装目标与 scope

12.1 Agent 决定目标目录

CLI 维护多种 Agent 配置,每个 Agent 声明 project/global skills directory 和是否已安装。--agent claude-code 等参数选择目标;未指定时可能进入交互选择。

12.2 Project 与 global

  • project scope 把 Skill 关联到当前项目;
  • --global 安装到用户级目录,跨项目可见。

是否提交 project Skill 和 skills-lock.json 是团队所有权决策。全局安装不应被误认为仓库依赖已固定。

12.3 Universal directory 与 Agent-specific directory

当前 CLI 使用 canonical/universal skill 目录减少重复,再为不同 Agent 创建 symlink。不同 Agent 的具体目录约定仍由其配置决定。

默认多目标场景推荐 symlink:CLI 先把 Skill 内容复制到 canonical directory,再让各 Agent 目录链接到同一份 canonical copy。

1
2
source snapshot → canonical copy ← agent A symlink
                               ← agent B symlink

它不是直接链接远端 repo,也不是让 Agent 目录链接用户最初传入的 local source。

13.2 Copy 模式

--copy 把 Skill 分别复制到各 Agent 目录。副本可以独立修改,但多 Agent 之间容易漂移,更新时也没有单一文件事实源。

13.3 单目标目录可能直接 copy

当前实现发现所有选中 Agent 实际共享同一 skills directory 时,symlink 没有收益,会转为 copy。Windows symlink 创建失败时也会回退 copy。

所以不能仅凭未传 --copy 就断言安装结果一定是 symlink,应看 CLI 输出或 filesystem 状态。

14. Lock 与 update

project install 可以记录 skills-lock.json,包括 source、ref、Skill path 和内容 hash 等 provenance。它支持:

  • 列出 Skill 来源;
  • 检测本地内容与记录是否变化;
  • skills update 重新获取来源;
  • experimental install 从 lock 恢复 project Skills。

但 lockfile 不是包管理器意义上的完整供应链保证:

  • 未固定 ref 时,update 仍可能追随上游变化;
  • Git branch 可被移动;
  • hash 用于检测内容,不等于签名或作者认证;
  • local source 的可重复性取决于本地文件。

需要强复现时,应固定不可变 commit,并在 code review 中审查 Skill 目录内容。

15. 安全边界

15.1 Subpath 防目录穿越

parser 拒绝包含 .. segment 的 subpath,discovery 还会验证 resolve 后路径仍在 base directory 内。两层校验防止仓库路径逃逸。

15.2 Skill 是可执行影响面

SKILL.md 会影响 Agent 行为,目录还可能包含 scripts、hooks、references 和 assets。copy 逻辑会递归复制大部分内容,不能因为它“只是 Markdown”就跳过审计。

15.3 Source 分类不代表信任等级

GitHub、well-known 或 direct download 都只是传输来源。官方 host 上也可能有恶意仓库,自定义域名也可能是内部可信服务。信任应建立在 owner、revision、内容 review 和最小权限上。

15.4 npx 还包含 CLI 自身供应链

执行 npx skills 会解析和运行 npm package。生产或企业自动化应固定 CLI 版本:

1
npx [email protected] add ...

否则 source parser 和安装行为可能在没有修改脚本的情况下变化。

16. 常见误区

16.1 “#foo 表示选择 foo Skill”

当前 1.5.21 中 #foo 是 Git ref。选择 Skill 使用 @foo--skill foo

16.2 “owner/repo/path 一定安装 path 这个 Skill”

它先把 path 作为 subpath,再从其中发现合法 SKILL.md。Skill name 来自 Front Matter。

16.3 “任意 HTTPS URL 都会 git clone”

hosted artifact 走 download,普通非 Git host URL 走 well-known/下载回退,.git URL 才更明确地走 generic Git。

单目录目标、平台限制和创建失败都可能导致 copy。应检查结果而不是只看默认选项。

16.5 “Lockfile 已经保证上游可信”

lockfile 记录 provenance 和 hash,不替代签名、来源认证与人工 review。

17. 复习索引

  1. source parser 只做结构化分类,获取和 Skill 校验在后续阶段;
  2. refsubpathskillFilter 分别表示版本、目录和 Skill 名称;
  3. 当前 #ref@skill 同时固定版本和选择 Skill,单独 #value 不是 Skill filter;
  4. local、provider、clone、well-known 和 download 是不同获取路径;
  5. discovery 以合法 SKILL.md 为入口,--full-depth 会扩大搜索面;
  6. symlink 指向 canonical copy,不是远端仓库;copy 会制造独立副本;
  7. 可复现安装需要同时固定 CLI 版本、source revision 和实际内容。

18. 核验入口

  • README.md:当前公开 source formats、add/use/update 选项和 discovery 布局;
  • src/source-parser.ts:解析优先级、fragment、alias、download 与 subpath 校验;
  • src/skills.tsSKILL.md 发现、深度和 shadow 规则;
  • src/add.ts:provider、download、clone、selector 与目标选择;
  • src/installer.ts:canonical copy、symlink/copy 与回退;
  • src/local-lock.tssrc/update.ts:provenance、hash 与更新流程。
本文由作者按照 CC BY 4.0 进行授权