【笔记】npx skills 的来源解析与安装边界
npx skills add <source> 看起来只是“从一个地址安装 Skill”,实际至少包含四个阶段:把字符串解析成来源、获取内容、发现合法 SKILL.md、再安装到目标 Agent。很多输入歧义都发生在第一阶段,但安全和可复现性问题贯穿整条链路。
本文对应 Vercel Labs
skillsCLI 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 则把绝对路径同时放进 url 和 localPath。
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 name 叫 frontend。
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/path | repo + subpath |
owner/repo@skill | repo + 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():
| 输入 | type | ref | subpath | skillFilter |
|---|---|---|---|---|
./local | local | — | — | — |
vercel-labs/agent-skills | github | — | — | — |
vercel-labs/agent-skills/skills/web-design-guidelines | github | — | skills/web-design-guidelines | — |
vercel-labs/agent-skills@review | github | — | — | review |
vercel-labs/agent-skills#v1.0@review | github | v1.0 | — | review |
| GitHub tree URL | github | path 中 branch | path 中子目录 | — |
| GitLab subgroup tree URL | gitlab | path 中 branch | path 中子目录 | — |
https://example.com/skills | well-known | — | — | — |
https://example.com/repo.git | git | — | — | — |
raw GitHub SKILL.md | download | — | — | — |
Git SSH URL #dev | git | dev | — | — |
这张表只覆盖 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、至少具有 name 和 description 的 SKILL.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 过滤。这就是 subpath 与 skillFilter 不能混为一谈的原因。
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 的具体目录约定仍由其配置决定。
13. Symlink 与 copy 的真实含义
13.1 Symlink 模式
默认多目标场景推荐 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。
16.4 “默认安装总是 symlink”
单目录目标、平台限制和创建失败都可能导致 copy。应检查结果而不是只看默认选项。
16.5 “Lockfile 已经保证上游可信”
lockfile 记录 provenance 和 hash,不替代签名、来源认证与人工 review。
17. 复习索引
- source parser 只做结构化分类,获取和 Skill 校验在后续阶段;
ref、subpath、skillFilter分别表示版本、目录和 Skill 名称;- 当前
#ref@skill同时固定版本和选择 Skill,单独#value不是 Skill filter; - local、provider、clone、well-known 和 download 是不同获取路径;
- discovery 以合法
SKILL.md为入口,--full-depth会扩大搜索面; - symlink 指向 canonical copy,不是远端仓库;copy 会制造独立副本;
- 可复现安装需要同时固定 CLI 版本、source revision 和实际内容。
18. 核验入口
README.md:当前公开 source formats、add/use/update 选项和 discovery 布局;src/source-parser.ts:解析优先级、fragment、alias、download 与 subpath 校验;src/skills.ts:SKILL.md发现、深度和 shadow 规则;src/add.ts:provider、download、clone、selector 与目标选择;src/installer.ts:canonical copy、symlink/copy 与回退;src/local-lock.ts、src/update.ts:provenance、hash 与更新流程。