@anov/gis-skills-cli 0.3.0 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,30 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.4] - 2026-11-02
8
+
9
+ - feat: setup 按检测到的智能体安装 skill 与 MCP,修 Codex 装错目录 (`1379891`)
10
+
11
+
12
+ ## [0.3.3] - 2026-11-02
13
+
14
+ - fix: README 文档链接改绝对 URL,修 npm 包页面点不开的问题 (`a118366`)
15
+ - feat: npm run release 自动打 tag 并推送 (`018027d`)
16
+ - feat: npm run release 自动提交并推送发版文件 (`e6c5bb1`)
17
+
18
+
19
+ ## [0.3.2] - 2026-10-30
20
+
21
+ - chore: 新增 npm run release 一键发版脚本 (`e147f1d`)
22
+ - fix: setup 只装已检测智能体,MCP 默认写 Claude Code + Codex (`ee704ee`)
23
+
24
+
25
+ ## [0.3.1] - 2026-10-30
26
+
27
+ - chore: 新增 npm run release 一键发版脚本 (`e147f1d`)
28
+ - fix: setup 只装已检测智能体,MCP 默认写 Claude Code + Codex (`ee704ee`)
29
+
30
+
7
31
  ## [0.3.0] - 2026-10-30
8
32
 
9
33
  分发体系改造:npm 包内置 Cesium skill(vendor 15 个,Apache-2.0)、CLI 命令族重写为 setup/mcp/doctor/uninstall、MCP 配置自动写入各智能体
package/INSTALL.md CHANGED
@@ -44,10 +44,10 @@ anov-gis-skills setup
44
44
 
45
45
  ```bash
46
46
  # 装到哪(默认用户级,任何项目都能用)
47
- npx @anov/gis-skills-cli setup # → ~/.claude/skills/ 等
47
+ npx @anov/gis-skills-cli setup # → 探测到的智能体各自的 skill 目录
48
48
  npx @anov/gis-skills-cli setup --project # → ./.claude/skills/,可提交进项目仓库
49
49
 
50
- # 装给谁(默认全部检测到的智能体)
50
+ # 装给谁(默认:探测到的所有智能体)
51
51
  npx @anov/gis-skills-cli setup --agent claude-code
52
52
  npx @anov/gis-skills-cli setup --agent claude-code --agent codex
53
53
 
@@ -60,13 +60,35 @@ npx @anov/gis-skills-cli setup --print
60
60
 
61
61
  ### A.3 各智能体的落点
62
62
 
63
- | 智能体 | skill 装到 | MCP 配置写到 |
64
- |---|---|---|
65
- | Claude Code | `~/.claude/skills/` | `~/.claude.json` 的 `mcpServers`(项目级 → `./.mcp.json`) |
66
- | Codex | `~/.codex/skills/` | `~/.codex/config.toml` 的 `[mcp_servers.anov-gis]` |
67
- | Cursor | `~/.agents/skills/` | `~/.cursor/mcp.json`(项目级 → `./.cursor/mcp.json`) |
68
- | VS Code | `~/.agents/skills/` | `./.vscode/mcp.json` 的 `servers` |
69
- | Cline / Windsurf / Continue 等 | `~/.agents/skills/` | 不支持(打印片段自行粘贴) |
63
+ `setup` 探测本机装了哪些智能体 —— **判定标准是「配置目录存在且非空」**(空目录不算),只装给探测到的那些:
64
+
65
+ | 智能体 | 探测目录 | skill 装到 | MCP 配置写到 |
66
+ |---|---|---|---|
67
+ | Claude Code | `~/.claude` | `~/.claude/skills/` | `~/.claude.json` 的 `mcpServers`(项目级 → `./.mcp.json`) |
68
+ | Codex | `~/.codex` | `~/.codex/skills/` | `~/.codex/config.toml` 的 `[mcp_servers.anov-gis]` |
69
+ | Cursor | `~/.cursor` | `~/.cursor/skills/` | `~/.cursor/mcp.json`(项目级 → `./.cursor/mcp.json`) |
70
+ | OpenClaw | `~/.openclaw` | `~/.openclaw/skills/` | 不支持 |
71
+ | Hermes Agent | `~/.hermes` | `~/.hermes/skills/` | 不支持 |
72
+ | Trae | `~/.trae` | `~/.trae/skills/` | 不支持 |
73
+ | Trae CN | `~/.trae-cn` | `~/.trae-cn/skills/` | 不支持 |
74
+ | Gemini CLI | `~/.gemini` | `~/.gemini/skills/` | 不支持 |
75
+ | Antigravity | `~/.gemini/config` | `~/.gemini/config/skills/` | 不支持 |
76
+ | GitHub Copilot | `~/.copilot` | `~/.copilot/skills/` | 不支持 |
77
+ | VS Code | `./.vscode` | 不支持 | `./.vscode/mcp.json` 的 `servers`(仅项目级) |
78
+ | Claude 桌面端 | `%APPDATA%/Claude` | 不支持(只能在 UI 里上传) | `claude_desktop_config.json` 的 `mcpServers` |
79
+ | 共享目录 | `~/.kimi-code` / `~/.cline` / `~/.dexto` / `~/.loaf` / `~/.warp` 等任一 | `~/.agents/skills/` | 不支持 |
80
+
81
+ > **落点为什么不全信上游。** 底层 `skills` CLI 把所有「项目级 skill 目录是 `.agents/skills`」的智能体归成一族,
82
+ > 用户级一律装到 `~/.agents/skills`。这对 Codex / Cursor / Gemini CLI / Antigravity / Copilot 都是错的 ——
83
+ > 它们各有自己的用户级目录(如 Codex 只读 `~/.codex/skills`)。这几个由本 CLI 直接复制到正确位置,
84
+ > 装完还会核对一遍落点,上游算错就兜底。
85
+
86
+ > **几个容易搞混的读法。**
87
+ > - Cursor 同时读 `~/.agents/skills/` 和 `~/.cursor/skills/`,但**只有 `~/.cursor/skills/` 会同步到 Cloud Agents**。
88
+ > - Codex 只读 `~/.codex/skills`(`~/.agents/skills` 是项目级路径)。
89
+ > - Antigravity 的 App / CLI / IDE 现在共用 `~/.gemini/config/skills/`,**App 不读 `~/.gemini/skills/`**。
90
+ > - Claude Code 桌面版(Code 标签页)与 CLI 共享 `~/.claude/skills`,装一次两边都有。
91
+ > - 「Claude 桌面端」指 claude.ai 桌面聊天端,它的 skill 只能在 UI 里上传,本 CLI 只能帮它写 MCP。
70
92
 
71
93
  底层 `skills` CLI 版本**锁定在 `skills@1.5.18`**(不是 latest):1.6.0+ 的 `engines` 要求 `node >= 22.20.0`,而团队环境实测是 node 22.19.0,锁低版本才能在 node ≥ 18 全覆盖。
72
94
 
@@ -87,12 +109,12 @@ npx @anov/gis-skills-cli setup --print
87
109
 
88
110
  ### A.5 MCP 接入
89
111
 
90
- `setup` 默认会写一条 MCP 配置(stdio 直连 `anov-gis-mcp`)。单独管理:
112
+ `setup` 默认给**探测到的**每个智能体各写一条 MCP 配置(stdio 直连 `anov-gis-mcp`)—— 没装的智能体不会留配置,避免「MCP 配好了但一个 skill 都没有」的半装状态。单独管理:
91
113
 
92
114
  ```bash
93
115
  npx @anov/gis-skills-cli mcp show # 看当前状态
94
- npx @anov/gis-skills-cli mcp add # 补上(默认 Claude Code + 用户级)
95
- npx @anov/gis-skills-cli mcp add --mcp-agent codex
116
+ npx @anov/gis-skills-cli mcp add # 补上(默认写给检测到的智能体 + 用户级)
117
+ npx @anov/gis-skills-cli mcp add --mcp-agent cursor
96
118
  npx @anov/gis-skills-cli mcp remove # 拿掉
97
119
  ```
98
120
 
@@ -100,7 +122,7 @@ npx @anov/gis-skills-cli mcp remove # 拿掉
100
122
 
101
123
  | 参数 | 说明 |
102
124
  |---|---|
103
- | `--mcp-agent <名字>` | `claude-code`(默认)/ `codex` / `cursor` / `vscode` |
125
+ | `--mcp-agent <名字...>` | `claude-code` / `codex` / `cursor` / `vscode` / `claude-desktop`,可指定多个;不指定就只写检测到的 |
104
126
  | `--mcp-mode stdio\|http` | 默认 `stdio`(本地起进程);`http` 连服务器 |
105
127
  | `--mcp-url <地址>` | HTTP 模式必填,例如 `http://192.168.11.164:3000/mcp` |
106
128
  | `--mcp-command` / `--mcp-arg` | 自定义 stdio 启动命令(默认 `npx -y anov-gis-mcp --stdio`) |
@@ -121,17 +143,28 @@ npx @anov/gis-skills-cli mcp remove # 拿掉
121
143
  npx @anov/gis-skills-cli doctor
122
144
  ```
123
145
 
124
- 逐个目录报:
146
+ 逐个落点报:
125
147
 
126
148
  ```
127
149
  包内 skill 数:27
128
150
 
129
- ── Claude Code ── C:\Users\你\.claude\skills
130
- ✓ 27 个 skill 齐全
151
+ ── skill 落点 ──
152
+ ✓ Claude Code C:\Users\你\.claude\skills —— 27/27 齐全
153
+ ✓ frontmatter 全部可被严格 YAML 解析
154
+ ✓ Codex C:\Users\你\.codex\skills —— 27/27 齐全
131
155
  ✓ frontmatter 全部可被严格 YAML 解析
156
+ · Cursor C:\Users\你\.cursor\skills —— 未安装该智能体
157
+
158
+ ── MCP ──
132
159
 
133
- ── MCP · Claude Code(用户级) ── C:\Users\你\.claude.json
160
+ Claude Code(用户级) —— C:\Users\你\.claude.json
134
161
  ✓ 已配置(stdio)
162
+
163
+ Codex(用户级) —— C:\Users\你\.codex\config.toml
164
+ ✓ 已配置(stdio)
165
+
166
+ ── 一致性 ──
167
+ ✓ 没有「MCP 配了但 skill 缺失」的半装状态
135
168
  ```
136
169
 
137
170
  | 检查项 | 不通过意味着 |
@@ -139,6 +172,7 @@ npx @anov/gis-skills-cli doctor
139
172
  | skill 齐全 | 装漏了,或只装了一部分(单独装部分 skill 时跨 skill 链接会失效) |
140
173
  | frontmatter 可被严格 YAML 解析 | 某些 agent 的 skill 加载器会**整个跳过**这个 skill(v0.2.17 踩过:只发现 10/12) |
141
174
  | MCP 已配置 | 少了条目 —— 跑 `mcp add` |
175
+ | 一致性 | 该智能体 MCP 配了但一个 skill 都没有 —— 跑 `setup` 补上 |
142
176
 
143
177
  有问题会逐条列出并以退出码 1 结束。
144
178
 
@@ -241,7 +275,7 @@ Claude Code 走 marketplace,多两个好处:**发版后自动升级**、**
241
275
 
242
276
  > ⚠️ **版本约束只对 `enabledPlugins` 生效**。`/plugin install` 与 `claude plugin install` 的参数只接受 `插件名` 或 `插件名@市场名`(第三段会被丢弃),所以**命令行装的时候锁不住版本** —— 要锁版本就靠 settings 里的 `enabledPlugins`。
243
277
 
244
- **完全锁死(不推荐)**:把约束写成精确版本 `anov-gis-skills@anov-gis-skills@0.3.0`。精确锁定后**不会自动升级**,每次发版都要人工改这一行 —— 这正是 v0.2.15 之前"发完版还要挨个催"的原因。
278
+ **完全锁死(不推荐)**:把约束写成精确版本 `anov-gis-skills@anov-gis-skills@0.3.4`。精确锁定后**不会自动升级**,每次发版都要人工改这一行 —— 这正是 v0.2.15 之前"发完版还要挨个催"的原因。
245
279
 
246
280
  ### B.4 插件缓存位置
247
281
 
@@ -294,7 +328,8 @@ npx skills add <repo-url> -l
294
328
  npx skills update
295
329
  ```
296
330
 
297
- - 装到 `~/.claude/skills/`(Claude Code)、`~/.codex/skills/`(Codex)等标准位置
331
+ - 装到 `~/.claude/skills/`(Claude Code)、`~/.agents/skills/`(Codex / Cursor / Cline 等共用一个目录)等位置
332
+ —— 上游把「项目级目录是 `.agents/skills`」的一族都归到共享目录,Codex 的 `~/.codex/skills`、Cursor 的 `~/.cursor/skills` 不会被填;要装对请用 §方式 A
298
333
  - 默认**拷贝**(`--copy` 是显式开关,默认行为随版本变化);加 `--copy` 强制拷贝
299
334
  - **不带 cesiumjs-skills** —— 这条路没有 marketplace 的 `dependencies` 机制,Cesium 原生 skill 要单独装(见 §方式 B.5)
300
335
 
@@ -448,9 +483,9 @@ git clone https://github.com/CesiumGS/cesiumjs-skills.git ~/.codex/skills/cesium
448
483
  | 场景 | 说明 |
449
484
  |---|---|
450
485
  | 首次开启 `autoUpdate` | 2026-10 之前装的,`known_marketplaces.json` 里 `autoUpdate` 默认是 `false`(第三方 marketplace 的默认值)。改一次 §方式 B.2 的配置或跑一次 `/plugin marketplace update` 即可 |
451
- | 用了精确锁版本 | `anov-gis-skills@anov-gis-skills@0.3.0` 这种精确锁定**不会自动升级**,必须改这一行 |
486
+ | 用了精确锁版本 | `anov-gis-skills@anov-gis-skills@0.3.4` 这种精确锁定**不会自动升级**,必须改这一行 |
452
487
  | 提示 restart required | 重开 Claude Code 会话 |
453
- | 想立刻跳到指定版本 | `/plugin install anov-gis-skills@anov-gis-skills@0.3.0`(降级同理) |
488
+ | 想立刻跳到指定版本 | `/plugin install anov-gis-skills@anov-gis-skills@0.3.4`(降级同理) |
454
489
 
455
490
  ### 查看当前版本
456
491
 
@@ -461,7 +496,7 @@ git clone https://github.com/CesiumGS/cesiumjs-skills.git ~/.codex/skills/cesium
461
496
 
462
497
  ### 破坏性变更
463
498
 
464
- **从 v0.1.1 升级到 v0.3.0 的破坏性变更**:无。所有 v0.1.1 skill 在 v0.3.0 仍然可用;v0.2.0 新增 3 个 skill + DOMAINS 强化;v0.2.1–v0.3.0 为修复性版本(skill 名称与目录结构未变)。
499
+ **从 v0.1.1 升级到 v0.3.4 的破坏性变更**:无。所有 v0.1.1 skill 在 v0.3.4 仍然可用;v0.2.0 新增 3 个 skill + DOMAINS 强化;v0.2.1–v0.3.4 为修复性版本(skill 名称与目录结构未变)。
465
500
 
466
501
  ---
467
502
 
@@ -474,5 +509,7 @@ git clone https://github.com/CesiumGS/cesiumjs-skills.git ~/.codex/skills/cesium
474
509
  | 插件装了但 skill 一个都没出现 | frontmatter 没被严格 YAML 解析 | 跑 `npx @anov/gis-skills-cli doctor` 看哪几条断了 |
475
510
  | skill 只有一部分能用 | 单独装了部分 skill,指向兄弟 skill 的相对链接失效 | 全量装(12 个同级),别只装一个 |
476
511
  | `doctor` 报 skill 不齐 | 只装了一部分 | 重跑 `setup`;仓库侧跑 `node scripts/build-npm.mjs` 复查 |
512
+ | `doctor` 报「MCP 配了但 skill 缺失」 | MCP 写了但 skill 没装上(0.3.3 的 bug:只装 Claude Code,MCP 却写了 Codex) | 重跑 `setup`(当前版本起按检测结果装齐) |
513
+ | Codex 里看不到 skill | skill 被装到了 `~/.agents/skills`,而 Codex 只读 `~/.codex/skills` | 重跑 `setup`(当前版本起直装到 `~/.codex/skills`) |
477
514
  | 已装过官方 `cesiumjs-skills` 导致触发混乱 | 官方版与内网镜像版注册了同名 skill | 见 §方式 B.5 的卸载步骤二选一 |
478
515
  | Chrome DevTools MCP 起不来 | 内网访问不了 npm registry | 不影响 skill 本身,可忽略 |
package/README.md CHANGED
@@ -23,8 +23,8 @@ npx @anov/gis-skills-cli setup # 27 个 skill + MCP 接入
23
23
 
24
24
  `setup` 做两件事:
25
25
 
26
- 1. 把 **27 个 skill** 落到各智能体的标准目录(`~/.claude/skills/`、`~/.agents/skills/` 等)
27
- 2. 写一条 **MCP 配置**(默认 Claude Code,stdio 直连 `anov-gis-mcp`)—— 不想要就加 `--no-mcp`
26
+ 1. 探测本机装了哪些智能体(配置目录存在且非空),把 **27 个 skill** 装到它们各自读 skill 的目录
27
+ 2. 写 **MCP 配置**(只写探测到的智能体,stdio 直连 `anov-gis-mcp`)—— 不想要就加 `--no-mcp`
28
28
 
29
29
  不需要预先 `npm i -g`,也不需要 clone 本仓库。
30
30
 
@@ -32,13 +32,14 @@ npx @anov/gis-skills-cli setup # 27 个 skill + MCP 接入
32
32
 
33
33
  ```bash
34
34
  npx @anov/gis-skills-cli setup --project # 装到当前项目(默认用户级)
35
- npx @anov/gis-skills-cli setup --agent cursor # 只装给某个智能体
35
+ npx @anov/gis-skills-cli setup --agent cursor # 只装给某个智能体(跳过探测)
36
+ npx @anov/gis-skills-cli setup --agent codex --agent cursor # 装给多个智能体
36
37
  npx @anov/gis-skills-cli setup --no-mcp # 只装 skill,不碰 MCP
37
- npx @anov/gis-skills-cli mcp add --mcp-agent codex # MCP 单独接到 Codex
38
+ npx @anov/gis-skills-cli mcp add --mcp-agent cursor # MCP 接到 Cursor(默认写给检测到的智能体)
38
39
  npx @anov/gis-skills-cli uninstall # 清理(只删自己的)
39
40
  ```
40
41
 
41
- 完整参数见 [INSTALL.md](INSTALL.md)。
42
+ 完整参数见 [INSTALL.md](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.4/INSTALL.md)。
42
43
 
43
44
 
44
45
  ### 验证
@@ -47,11 +48,11 @@ npx @anov/gis-skills-cli uninstall # 清理(只删自己的)
47
48
  npx @anov/gis-skills-cli doctor
48
49
  ```
49
50
 
50
- 逐个目录报:27 个 skill 是否齐全、frontmatter 能否被严格 YAML 解析、MCP 条目写没写对。
51
+ 逐个落点报:27 个 skill 是否齐全、frontmatter 能否被严格 YAML 解析、MCP 条目写没写对,以及有没有「MCP 配了但 skill 缺失」的半装状态。
51
52
 
52
53
  新开一个会话提问"flowPolyline 颜色怎么改",应自动激活 `anovgis-materials` 并给出溯源 snippet。
53
54
 
54
- 安装排错见 [INSTALL.md](INSTALL.md#排错);地图黑屏 / Workers 404 / 材质不生效等问题看下面的「报错排查」。
55
+ 安装排错见 [INSTALL.md](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.4/INSTALL.md) 的「排错」一节;地图黑屏 / Workers 404 / 材质不生效等问题看下面的「报错排查」。
55
56
 
56
57
  ---
57
58
 
@@ -77,7 +78,7 @@ npx @anov/gis-skills-cli doctor
77
78
 
78
79
  ### 改 SDK 自带 demo
79
80
 
80
- 团队最常见的场景是「复制 SDK 自带 demo → 改成自己的业务」。137 个 demo 的路由表在 [`skills/using-anovgis/DEMO-SKILL-MAP.md`](skills/using-anovgis/DEMO-SKILL-MAP.md)。
81
+ 团队最常见的场景是「复制 SDK 自带 demo → 改成自己的业务」。137 个 demo 的路由表在 [`skills/using-anovgis/DEMO-SKILL-MAP.md`](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.4/skills/using-anovgis/DEMO-SKILL-MAP.md)。
81
82
 
82
83
  直接跟 AI 说文件名 + 想改什么,它会自动路由:
83
84
 
@@ -143,7 +144,7 @@ npx @anov/gis-skills-cli mcp remove # 拿掉
143
144
  | 11 | `anovgis-effects` | 天气 / 漫游 / 动画 / 后期处理(Bloom / 夜视 / 景深 / 黑白)+ 天空盒 | weather、roaming、AroundPoint、FlyManager、bloom、skybox |
144
145
  | 12 | `anovgis-pitfalls` | Cesium 1.143 + v1.8.6 典型陷阱(P0–P12)+ 自定义图元报错 | 黑屏、白屏、不生效、报错、Workers 404、AMD、CustomPrimitive |
145
146
 
146
- 完整路由(用户意图 → skill)见 `skills/using-anovgis/SKILL.md`;完整 demo 路由(demo 文件名 → skill)见 [`skills/using-anovgis/DEMO-SKILL-MAP.md`](skills/using-anovgis/DEMO-SKILL-MAP.md)。
147
+ 完整路由(用户意图 → skill)见 `skills/using-anovgis/SKILL.md`;完整 demo 路由(demo 文件名 → skill)见 [`skills/using-anovgis/DEMO-SKILL-MAP.md`](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.4/skills/using-anovgis/DEMO-SKILL-MAP.md)。
147
148
 
148
149
  另外 15 个 Cesium skill 由官方 `cesiumjs-skills` 提供(`cesiumjs-3d-tiles` / `cesiumjs-camera` / `cesiumjs-primitives` …),
149
150
  `using-cesiumjs-skills` 是它们的入口 —— 由 `using-anovgis` 自动转过去,你一般不用直接调用。
package/RELEASING.md CHANGED
@@ -79,18 +79,20 @@ node scripts/bump.mjs --check # 校验所有版本位一致(发版前自检
79
79
  10. **结构自检**:`node scripts/check-structure.mjs` —— 相对链接 / 锚点 / frontmatter / 章节完整性,不一致就 exit 1
80
80
  11. **npm 包自检**:`node scripts/build-npm.mjs` —— 校验两棵 skill 树的相对链接、无本机绝对路径、vendor 齐全(15 个 skill + LICENSE + NOTICE),不一致就 exit 1
81
81
  11b. **vendor 一致性**(改动过 Cesium skill 时):`node scripts/sync-cesium.mjs --check` —— vendor 与内网镜像不一致就 exit 1;需要更新时去掉 `--check` 重新同步,并核对 `vendor/cesiumjs-skills/NOTICE` 里的版本号
82
- 12. `git commit -am "feat: v<X.Y.Z> — ..."` + `git push`
83
- 13. **手工补 GitLab 侧产物**(本仓库不跑 CI,这步要人工做):打 `anov-gis-skills--v<版本>` tag,见 §四
82
+ 12. **提交推送**:用 `npm run release` 时自动完成 —— 只 `git add` bump 改写的发版文件后 commit + push(`--no-commit` 可只改文件不提交)。手工流程则 `git add` 发版文件 + `git commit -m "chore: release v<X.Y.Z>"` + `git push`
83
+ 13. **打 tag**:用 `npm run release` 时自动打 `anov-gis-skills--v<版本>` 注解 tag 并推送;不走 release 时按 §四 手工打
84
84
  14. **发 npm 包**(方式 A 的分发源):`npm publish` —— 发到哪由仓库根 `.npmrc` 决定(内网 / 公网),见 §五
85
85
  15. **验证已发布的包**:`node scripts/verify-published.mjs` —— 确认可安装、内容与源码一致、CLI 能发现全部 skill、链接全解析,不一致就 exit 1
86
86
 
87
87
  > v0.2.15 起,第 8 步之前的手工改版本号(各 manifest + DOMAINS.md + DEMO-SKILL-MAP.md)全部由 `scripts/bump.mjs` 代劳,避免版本漂移。
88
+ >
89
+ > 用 `npm run release` 时,第 8–13 步(bump · 版本校验 · 结构校验 · npm 包校验 · 提交推送 · 打 tag)会一并自动执行;两道结构校验在写文件之前跑,失败则什么都不会改。
88
90
 
89
91
  ---
90
92
 
91
- ## 四、手工打的 tag
93
+ ## 四、tag
92
94
 
93
- 本仓库**不跑 CI**(内网 GitLab 上没有 runner),下面这步要人工执行。
95
+ 本仓库**不跑 CI**(内网 GitLab 上没有 runner),没有流水线帮你打 tag —— `npm run release` 会自动打并推送,手工发版时用下面的命令。
94
96
 
95
97
  **打 tag** —— 带版本约束的安装串(`"anov-gis-skills@anov-gis-skills@^0.3.0"`)靠这个 tag 解析版本,没有匹配 tag 会报 `no-matching-tag`:
96
98
 
@@ -5,12 +5,18 @@
5
5
  // anov-gis-skills setup 装全部 skill(+ 写 MCP 配置)
6
6
  // anov-gis-skills mcp add 只写 MCP 配置
7
7
  // anov-gis-skills mcp remove 删掉 MCP 配置
8
- // anov-gis-skills mcp show 看当前配置状态
9
- // anov-gis-skills doctor 自检
10
- // anov-gis-skills uninstall 清理
8
+ // anov-gis-skills mcp show 看当前配置状态
9
+ // anov-gis-skills doctor 自检
10
+ // anov-gis-skills uninstall 清理
11
11
  //
12
- // 底层用 vercel-labs 的 skills CLI(npm 包 `skills`)搬 skill —— 它管着 30+ 智能体的
13
- // skills 目录映射。锁 1.5.18 而不是 latest:1.6.0+ 的 engines 要求 node >= 22.20.0,
12
+ // 装到哪、写到哪,都由 lib/agents.mjs 的一张表决定:探测本机装了哪些智能体
13
+ // (配置目录存在且非空),只装给它们,MCP 也只写给它们。
14
+ //
15
+ // 底层用 vercel-labs 的 skills CLI(npm 包 `skills`)搬 skill —— 但只用于它能
16
+ // 算对落点的那些 agent。它把所有 skillsDir === ".agents/skills" 的 agent 归进
17
+ // universal 一族、一律装到 ~/.agents/skills,会把 codex / cursor / gemini-cli
18
+ // 这些另有独立目录的 agent 装错地方,那些由我们自己 copy(见 lib/agents.mjs)。
19
+ // 锁 1.5.18 而不是 latest:1.6.0+ 的 engines 要求 node >= 22.20.0,
14
20
  // 而团队环境实测是 node 22.19.0,锁低版本才能在 node >= 18 全覆盖。
15
21
  //
16
22
  // MCP 配置由我们自己写(lib/mcp-config.mjs),不引第三方库 —— 只覆盖我们真正常用的
@@ -20,7 +26,6 @@ import { spawnSync } from "node:child_process";
20
26
  import { readdirSync, existsSync, readFileSync } from "node:fs";
21
27
  import { fileURLToPath } from "node:url";
22
28
  import { dirname, join, resolve } from "node:path";
23
- import { homedir } from "node:os";
24
29
 
25
30
  import {
26
31
  MCP_SERVER_NAME,
@@ -33,17 +38,30 @@ import {
33
38
  writeServer,
34
39
  } from "../lib/mcp-config.mjs";
35
40
 
41
+ import {
42
+ SKILL_TARGETS,
43
+ detectMcpAgents,
44
+ detectSkillTargets,
45
+ resolveSkillDir,
46
+ installSelf,
47
+ uninstallSelf,
48
+ } from "../lib/agents.mjs";
49
+
36
50
  const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
37
51
  const SKILLS_DIR = join(PKG_ROOT, "skills");
38
52
  const CESIUM_DIR = join(PKG_ROOT, "vendor", "cesiumjs-skills", "skills");
39
53
  const SKILLS_CLI = "skills@1.5.18";
40
54
 
41
- const MCP_PACKAGE = "anov-gis-mcp";
55
+ // 用 npx 跑时脚本在 npm 缓存的 _npx 目录里,此时 `anov-gis-skills` 不在 PATH 上,
56
+ // 提示语必须给回 npx 形式,否则用户会撞上「不是内部或外部命令」。
57
+ const CLI_CMD = /[\\/]_npx[\\/]/.test(process.argv[1] ?? "")
58
+ ? "npx @anov/gis-skills-cli"
59
+ : "anov-gis-skills";
42
60
 
43
61
  // ---------------------------------------------------------------- 参数解析
44
62
 
45
63
  function parseArgs(argv) {
46
- const out = { _: [], agents: [], mcpArgs: [], unknown: [] };
64
+ const out = { _: [], agents: [], mcpAgents: [], mcpArgs: [], unknown: [] };
47
65
  for (let i = 0; i < argv.length; i++) {
48
66
  const a = argv[i];
49
67
  if (a === "-g" || a === "--global") out.scope = "global";
@@ -54,13 +72,18 @@ function parseArgs(argv) {
54
72
  else if (a === "--no-mcp") out.noMcp = true;
55
73
  else if (a === "--mcp") out.mcp = true;
56
74
  else if (a === "-a" || a === "--agent") {
57
- while (argv[++i] && !argv[i].startsWith("-")) out.agents.push(argv[i]);
58
- } else if (a === "--mcp-agent") out.mcpAgent = argv[++i];
59
- else if (a === "--mcp-mode") out.mcpMode = argv[++i];
75
+ // 只在真的吃到值时才推进 i —— 否则下一个 "-" 开头的 flag 会被连同位置
76
+ // 一起跳过(`--agent codex --no-mcp` 会把 --no-mcp 吞掉)
77
+ while (i + 1 < argv.length && !argv[i + 1].startsWith("-")) out.agents.push(argv[++i]);
78
+ } else if (a === "--mcp-agent") {
79
+ while (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
80
+ for (const v of argv[++i].split(",")) if (v.trim()) out.mcpAgents.push(v.trim());
81
+ }
82
+ } else if (a === "--mcp-mode") out.mcpMode = argv[++i];
60
83
  else if (a === "--mcp-url") out.mcpUrl = argv[++i];
61
84
  else if (a === "--mcp-command") out.mcpCommand = argv[++i];
62
85
  else if (a === "--mcp-arg") {
63
- while (argv[++i] && !argv[i].startsWith("-")) out.mcpArgs.push(argv[i]);
86
+ while (i + 1 < argv.length && !argv[i + 1].startsWith("-")) out.mcpArgs.push(argv[++i]);
64
87
  } else if (a === "-h" || a === "--help") out.help = true;
65
88
  else if (a.startsWith("-")) out.unknown.push(a);
66
89
  else out._.push(a);
@@ -68,26 +91,26 @@ function parseArgs(argv) {
68
91
  return out;
69
92
  }
70
93
 
71
- const HELP = `anov-gis-skills —— 装 skill + 接 MCP
94
+ const HELP = `${CLI_CMD} —— 装 skill + 接 MCP
72
95
 
73
96
  用法
74
- anov-gis-skills setup 装全部 skill(默认顺带写 MCP 配置)
75
- anov-gis-skills mcp add 只写 MCP 配置
76
- anov-gis-skills mcp remove 删掉 MCP 配置
77
- anov-gis-skills mcp show 看 MCP 配置状态
78
- anov-gis-skills doctor 自检(skill 完整性 + MCP 配置)
79
- anov-gis-skills uninstall 清理(只删本包与本 MCP 的东西)
97
+ ${CLI_CMD} setup 装全部 skill(默认顺带写 MCP 配置)
98
+ ${CLI_CMD} mcp add 只写 MCP 配置
99
+ ${CLI_CMD} mcp remove 删掉 MCP 配置
100
+ ${CLI_CMD} mcp show 看 MCP 配置状态
101
+ ${CLI_CMD} doctor 自检(skill 完整性 + MCP 配置)
102
+ ${CLI_CMD} uninstall 清理(只删本包与本 MCP 的东西)
80
103
 
81
104
  通用选项
82
105
  --scope global|project 装到哪(默认 global = 用户级,任何项目都能用)
83
106
  -g, --global 等价于 --scope global
84
107
  --project 等价于 --scope project
85
- -a, --agent <名字...> 只装给指定智能体(默认全部检测到的)
108
+ -a, --agent <名字...> 只装给指定智能体(默认:本机检测到的智能体)
86
109
  -y, --yes 跳过所有交互
87
110
  --print 只打印要写的内容,不落盘
88
111
 
89
112
  MCP 选项
90
- --mcp-agent <名字> 写到哪个智能体(${SUPPORTED_AGENTS.join(" / ")},默认 claude-code)
113
+ --mcp-agent <名字...> 写到哪个智能体(${SUPPORTED_AGENTS.join(" / ")};默认:检测到的)
91
114
  --mcp-mode stdio|http 连接方式(默认 stdio)
92
115
  --mcp-url <地址> HTTP 模式的服务地址(stdio 模式不需要)
93
116
  --mcp-command <命令> stdio 模式的启动命令(默认 npx)
@@ -97,17 +120,18 @@ MCP 选项
97
120
 
98
121
  // ---------------------------------------------------------------- 基础工具
99
122
 
100
- function skillNames(dir) {
123
+ /** 一个 skill 的来源:名字 + 它在包里的目录。 */
124
+ function skillSources(dir) {
101
125
  if (!existsSync(dir)) return [];
102
126
  return readdirSync(dir, { withFileTypes: true })
103
127
  .filter((d) => d.isDirectory() && existsSync(join(dir, d.name, "SKILL.md")))
104
- .map((d) => d.name)
105
- .sort();
128
+ .map((d) => ({ name: d.name, srcDir: dir }))
129
+ .sort((a, b) => a.name.localeCompare(b.name));
106
130
  }
107
131
 
108
132
  /** 本包提供的全部 skill:anov-gis 12 个 + Cesium 15 个。 */
109
133
  function allSkillNames() {
110
- return [...skillNames(SKILLS_DIR), ...skillNames(CESIUM_DIR)];
134
+ return [...skillSources(SKILLS_DIR), ...skillSources(CESIUM_DIR)].map((s) => s.name);
111
135
  }
112
136
 
113
137
  function runSkillsCLI(extra, { quiet = false } = {}) {
@@ -129,16 +153,32 @@ function scopeFlags(scope) {
129
153
  return scope === "global" ? ["--global"] : [];
130
154
  }
131
155
 
132
- function targetFlags(agents) {
133
- return agents.length ? ["--agent", ...agents] : ["--all"];
156
+ /** 目标落点。scope=project 时相对当前目录,global 时按表里的用户级落点。 */
157
+ function targetDir(target, scope = "global") {
158
+ return resolveSkillDir(target, scope);
159
+ }
160
+
161
+ /** --agent 的名字 → 安装目标。同时接受我们的 id 和上游的 agent 名。 */
162
+ function targetsByName(names) {
163
+ const out = [];
164
+ for (const name of names) {
165
+ const t = SKILL_TARGETS.find((x) => x.id === name || x.upstream === name);
166
+ if (!t) {
167
+ console.error(`✗ 不支持的智能体:${name}`);
168
+ console.error(` 可用:${SKILL_TARGETS.map((x) => x.id).join(" / ")}`);
169
+ process.exit(2);
170
+ }
171
+ if (!out.includes(t)) out.push(t);
172
+ }
173
+ return out;
134
174
  }
135
175
 
136
176
  // ---------------------------------------------------------------- setup
137
177
 
138
178
  function cmdSetup(opts) {
139
179
  const scope = opts.scope ?? "global";
140
- const anov = skillNames(SKILLS_DIR);
141
- const cesium = skillNames(CESIUM_DIR);
180
+ const anov = skillSources(SKILLS_DIR);
181
+ const cesium = skillSources(CESIUM_DIR);
142
182
 
143
183
  if (!anov.length) {
144
184
  console.error("✗ 包内找不到 skill(skills/ 目录缺失或为空)");
@@ -149,23 +189,58 @@ function cmdSetup(opts) {
149
189
  process.exit(1);
150
190
  }
151
191
 
192
+ const detected = detectSkillTargets();
193
+ const targets = opts.agents.length ? targetsByName(opts.agents) : detected;
194
+
152
195
  console.log(`\n安装 ${anov.length + cesium.length} 个 skill`);
153
196
  console.log(` anov-gis ${anov.length} 个`);
154
197
  console.log(` cesiumjs ${cesium.length} 个`);
155
198
  console.log(` 范围 ${scope === "global" ? "用户级(任何项目可用)" : "项目级(当前目录)"}`);
199
+ if (targets.length) {
200
+ console.log(` 智能体 ${opts.agents.length ? "(指定)" : "(检测到)"}`);
201
+ for (const t of targets) console.log(` · ${t.label.padEnd(16)} ${targetDir(t, scope)}`);
202
+ } else {
203
+ console.log(" 智能体 未检测到任何智能体 —— 用 --agent 指定");
204
+ }
156
205
 
157
- for (const [label, dir] of [["anov-gis", SKILLS_DIR], ["cesiumjs", CESIUM_DIR]]) {
158
- console.log(`\n── ${label} ──`);
159
- const code = runSkillsCLI(["add", dir, ...targetFlags(opts.agents), ...scopeFlags(scope), "-y"]);
160
- if (code !== 0) {
161
- console.error(`✗ ${label} 安装失败(退出码 ${code})`);
162
- process.exit(code);
206
+ if (!targets.length) {
207
+ console.error("\n✗ 没有目标智能体可装");
208
+ process.exit(1);
209
+ }
210
+
211
+ // 走上游的:它能算对这些 agent 的落点
212
+ const upstreamTargets = targets.filter((t) => t.upstream);
213
+ if (upstreamTargets.length) {
214
+ const agentNames = upstreamTargets.map((t) => t.upstream);
215
+ for (const [label, dir] of [["anov-gis", SKILLS_DIR], ["cesiumjs", CESIUM_DIR]]) {
216
+ console.log(`\n── ${label} ──`);
217
+ const code = runSkillsCLI(["add", dir, "--agent", ...agentNames, ...scopeFlags(scope), "-y"]);
218
+ if (code !== 0) {
219
+ console.error(`✗ ${label} 安装失败(退出码 ${code})`);
220
+ process.exit(code);
221
+ }
222
+ }
223
+ }
224
+
225
+ // 自己 copy 的:上游会把它们装错目录
226
+ const skills = [...anov, ...cesium];
227
+ const fallback = targets.filter((t) => !t.upstream);
228
+ for (const t of upstreamTargets) {
229
+ // 上游装完核对落点 —— 它要是算错了目录,这里兜住
230
+ const dir = targetDir(t, scope);
231
+ if (!skills.every((s) => existsSync(join(dir, s.name, "SKILL.md")))) fallback.push(t);
232
+ }
233
+ if (fallback.length) {
234
+ console.log(`\n── 直装(上游落点有误,改为直接复制)──`);
235
+ for (const t of fallback) {
236
+ const dir = targetDir(t, scope);
237
+ installSelf({ skillDir: dir }, skills);
238
+ console.log(` ✓ ${t.label}:${skills.length} 个 → ${dir}`);
163
239
  }
164
240
  }
165
241
 
166
242
  if (!opts.noMcp) {
167
243
  const mode = opts.mcpMode ?? "stdio";
168
- const agent = opts.mcpAgent ?? "claude-code";
169
244
  const config = buildServerConfig({
170
245
  mode,
171
246
  url: opts.mcpUrl,
@@ -173,32 +248,79 @@ function cmdSetup(opts) {
173
248
  args: opts.mcpArgs.length ? opts.mcpArgs : undefined,
174
249
  env: collectEnv(),
175
250
  });
176
- writeMcp(agent, scope, config, opts.print);
251
+ writeMcp(mcpAgents(opts, scope), scope, config, {
252
+ printOnly: opts.print,
253
+ strict: opts.mcpAgents.length > 0,
254
+ });
177
255
  }
178
256
 
179
257
  console.log("\n✓ 完成");
180
258
  if (!opts.noMcp) {
181
- console.log(" 验证:anov-gis-skills doctor");
259
+ console.log(` 验证:${CLI_CMD} doctor`);
182
260
  }
183
261
  }
184
262
 
185
263
  // ---------------------------------------------------------------- MCP
186
264
 
187
- function mcpTargets(agent, scope) {
188
- const targets = agentTargets(agent, scope);
189
- if (!targets.length) {
190
- console.error(`✗ ${agent} 不支持 ${scope} 级配置`);
191
- console.error(` 支持的智能体:${SUPPORTED_AGENTS.join(" / ")}`);
192
- process.exit(1);
265
+ /** 默认只写探测到的智能体;--mcp-agent 显式点名时用点名的那些。 */
266
+ function mcpAgents(opts, scope = "global") {
267
+ if (opts.mcpAgents.length) return opts.mcpAgents;
268
+ const detected = new Set(detectMcpAgents(scope).map((a) => a.id));
269
+ return SUPPORTED_AGENTS.filter((a) => detected.has(a) && agentTargets(a, scope).length);
270
+ }
271
+
272
+ /**
273
+ * strict = 用户显式点名了智能体:不支持当前 scope 就直接报错。
274
+ * 默认集合则静默跳过(如 Codex 没有项目级配置),不算失败。
275
+ */
276
+ function mcpTargets(agents, scope, strict) {
277
+ const out = [];
278
+ for (const agent of agents) {
279
+ if (!SUPPORTED_AGENTS.includes(agent)) {
280
+ console.error(`✗ 不支持的智能体:${agent}(可用:${SUPPORTED_AGENTS.join(" / ")})`);
281
+ process.exit(2);
282
+ }
283
+ const targets = agentTargets(agent, scope);
284
+ if (!targets.length) {
285
+ if (strict) {
286
+ console.error(`✗ ${agent} 不支持 ${scope === "global" ? "用户级" : "项目级"}配置`);
287
+ process.exit(1);
288
+ }
289
+ continue;
290
+ }
291
+ out.push(...targets);
292
+ }
293
+ return out;
294
+ }
295
+
296
+ /** --print 的片段按目标格式输出:Codex 是 TOML,其余是 JSON。 */
297
+ function printTarget(target, config) {
298
+ console.log(`\n# ${target.label} → ${target.file}`);
299
+ if (target.key !== null) {
300
+ console.log(JSON.stringify({ [target.key]: { [MCP_SERVER_NAME]: config } }, null, 2));
301
+ return;
302
+ }
303
+ const toml = (v) =>
304
+ typeof v === "string"
305
+ ? `"${v.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`
306
+ : Array.isArray(v)
307
+ ? `[${v.map(toml).join(", ")}]`
308
+ : String(v);
309
+ const lines = [`[mcp_servers.${MCP_SERVER_NAME}]`];
310
+ for (const [k, v] of Object.entries(config)) {
311
+ if (k !== "env") lines.push(`${k} = ${toml(v)}`);
193
312
  }
194
- return targets;
313
+ if (config.env) {
314
+ lines.push("", `[mcp_servers.${MCP_SERVER_NAME}.env]`);
315
+ for (const [k, v] of Object.entries(config.env)) lines.push(`${k} = ${toml(v)}`);
316
+ }
317
+ console.log(lines.join("\n"));
195
318
  }
196
319
 
197
- function writeMcp(agent, scope, config, printOnly) {
198
- for (const target of mcpTargets(agent, scope)) {
320
+ function writeMcp(agents, scope, config, { printOnly = false, strict = false } = {}) {
321
+ for (const target of mcpTargets(agents, scope, strict)) {
199
322
  if (printOnly) {
200
- console.log(`\n# ${target.label} → ${target.file}`);
201
- console.log(JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: config } }, null, 2));
323
+ printTarget(target, config);
202
324
  continue;
203
325
  }
204
326
  writeServer(target, MCP_SERVER_NAME, config);
@@ -209,10 +331,11 @@ function writeMcp(agent, scope, config, printOnly) {
209
331
  function cmdMcp(opts) {
210
332
  const action = opts._[0] ?? "show";
211
333
  const scope = opts.scope ?? "global";
212
- const agent = opts.mcpAgent ?? "claude-code";
334
+ const agents = mcpAgents(opts, scope);
335
+ const strict = opts.mcpAgents.length > 0;
213
336
 
214
337
  if (action === "show") {
215
- for (const target of mcpTargets(agent, scope)) {
338
+ for (const target of mcpTargets(agents, scope, strict)) {
216
339
  const existing = readServer(target, MCP_SERVER_NAME);
217
340
  console.log(`\n${target.label} —— ${target.file}`);
218
341
  if (!existing) {
@@ -225,7 +348,7 @@ function cmdMcp(opts) {
225
348
  }
226
349
 
227
350
  if (action === "remove") {
228
- for (const target of mcpTargets(agent, scope)) {
351
+ for (const target of mcpTargets(agents, scope, strict)) {
229
352
  const r = removeServer(target, MCP_SERVER_NAME);
230
353
  console.log(r.removed ? ` ✓ 已删除:${target.file}` : ` · 本来就没有:${target.file}`);
231
354
  }
@@ -246,7 +369,7 @@ function cmdMcp(opts) {
246
369
  env: collectEnv(),
247
370
  });
248
371
  console.log(`\n接入 MCP(${mode})`);
249
- writeMcp(agent, scope, config, opts.print);
372
+ writeMcp(agents, scope, config, { printOnly: opts.print, strict });
250
373
  if (!opts.print) console.log("\n✓ 完成。重启智能体生效。");
251
374
  }
252
375
 
@@ -267,43 +390,32 @@ function frontmatterOk(text) {
267
390
  return { ok: true, name: name[1] };
268
391
  }
269
392
 
270
- /** 智能体的 skills 目录(用户级 / 项目级),与 skills CLI 的落点一致。 */
271
- function installedSkillDirs(scope) {
272
- const home = homedir();
273
- const bases =
274
- scope === "global"
275
- ? [
276
- ["Claude Code", join(home, ".claude", "skills")],
277
- ["Codex", join(home, ".codex", "skills")],
278
- ["通用 / Cursor", join(home, ".agents", "skills")],
279
- ]
280
- : [
281
- ["Claude Code", join(process.cwd(), ".claude", "skills")],
282
- ["Codex / 通用", join(process.cwd(), ".agents", "skills")],
283
- ];
284
- return bases.filter(([, dir]) => existsSync(dir));
285
- }
286
-
287
393
  function cmdDoctor(opts) {
288
394
  const scope = opts.scope ?? "global";
289
395
  const expected = allSkillNames();
396
+ const detected = detectSkillTargets();
290
397
  let problems = 0;
291
398
 
292
399
  console.log(`\n检查 @anov/gis-skills-cli\n`);
293
400
  console.log(`包内 skill 数:${expected.length}`);
294
401
 
295
- for (const [label, dir] of installedSkillDirs(scope)) {
402
+ console.log(`\n── skill 落点 ──`);
403
+ const skillStatus = new Map();
404
+ for (const target of SKILL_TARGETS) {
405
+ const dir = targetDir(target, scope);
406
+ const installed = detected.some((d) => d.id === target.id);
296
407
  const present = expected.filter((n) => existsSync(join(dir, n, "SKILL.md")));
297
- console.log(`\n── ${label} ── ${dir}`);
298
- if (!present.length) {
299
- console.log(" · 该目录没装本包的 skill(跳过)");
408
+ skillStatus.set(target.id, { dir, installed, present: present.length });
409
+
410
+ if (!installed) {
411
+ console.log(` · ${target.label.padEnd(16)} ${dir} —— 未安装该智能体`);
300
412
  continue;
301
413
  }
302
414
  if (present.length === expected.length) {
303
- console.log(` ✓ ${present.length} 个 skill 齐全`);
415
+ console.log(` ✓ ${target.label.padEnd(16)} ${dir} —— ${present.length}/${expected.length} 齐全`);
304
416
  } else {
305
417
  const missing = expected.filter((n) => !present.includes(n));
306
- console.log(` ! 只装了 ${present.length}/${expected.length} 个,缺:${missing.join(", ")}`);
418
+ console.log(` ! ${target.label.padEnd(16)} ${dir} —— 只装了 ${present.length}/${expected.length},缺:${missing.join(", ")}`);
307
419
  problems++;
308
420
  }
309
421
 
@@ -319,21 +431,36 @@ function cmdDoctor(opts) {
319
431
  if (bad) problems++;
320
432
  }
321
433
 
434
+ console.log(`\n── MCP ──`);
435
+ const mcpConfigured = new Set();
322
436
  for (const agent of SUPPORTED_AGENTS) {
323
437
  for (const target of agentTargets(agent, scope)) {
324
438
  const existing = readServer(target, MCP_SERVER_NAME);
325
- console.log(`\n── MCP · ${target.label} ── ${target.file}`);
439
+ console.log(`\n ${target.label} —— ${target.file}`);
326
440
  if (existing) {
441
+ mcpConfigured.add(agent);
327
442
  console.log(` ✓ 已配置(${existing.type ?? "unknown"})`);
328
443
  } else {
329
- console.log(" · 未配置 —— anov-gis-skills mcp add");
444
+ console.log(` · 未配置 —— ${CLI_CMD} mcp add`);
330
445
  }
331
446
  }
332
447
  }
333
448
 
449
+ // 半装检查:MCP 配了但对应智能体一个 skill 都没有
450
+ console.log(`\n── 一致性 ──`);
451
+ let half = 0;
452
+ for (const agent of mcpConfigured) {
453
+ const st = skillStatus.get(agent);
454
+ if (!st || !st.installed || st.present > 0) continue;
455
+ console.log(` ! ${agent}:MCP 已配置但 ${st.dir} 下没有本包的 skill —— 跑 ${CLI_CMD} setup 补上`);
456
+ half++;
457
+ }
458
+ if (!half) console.log(" ✓ 没有「MCP 配了但 skill 缺失」的半装状态");
459
+ problems += half;
460
+
334
461
  console.log("");
335
462
  if (problems) {
336
- console.error(`✗ 发现 ${problems} 处问题 —— 重装:anov-gis-skills setup\n`);
463
+ console.error(`✗ 发现 ${problems} 处问题 —— 重装:${CLI_CMD} setup\n`);
337
464
  process.exit(1);
338
465
  }
339
466
  console.log("✓ 检查通过\n");
@@ -350,15 +477,21 @@ function cmdUninstall(opts) {
350
477
  }
351
478
 
352
479
  console.log(`\n移除 ${names.length} 个 skill(${scope === "global" ? "用户级" : "项目级"})`);
480
+ // 不传 --agent:上游的语义是从所有智能体删,正好清掉历史上 --all 铺出去的残留。
353
481
  // 只按名字删本包提供的 skill —— skills remove --skill '*' 会连别的来源的一起删掉
354
- const code = runSkillsCLI(["remove", ...names, ...targetFlags(opts.agents), ...scopeFlags(scope), "-y"]);
482
+ const code = runSkillsCLI(["remove", ...names, ...scopeFlags(scope), "-y"]);
355
483
  if (code !== 0) {
356
484
  console.error(`✗ 移除 skill 失败(退出码 ${code})`);
357
485
  process.exit(code);
358
486
  }
359
487
 
360
- const agent = opts.mcpAgent ?? "claude-code";
361
- for (const target of mcpTargets(agent, scope)) {
488
+ // 上游不认识 Antigravity 的新落点,这几个自己删
489
+ for (const target of SKILL_TARGETS.filter((t) => !t.upstream)) {
490
+ uninstallSelf({ skillDir: targetDir(target, scope) }, names);
491
+ }
492
+
493
+ const agents = mcpAgents(opts, scope);
494
+ for (const target of mcpTargets(agents, scope, opts.mcpAgents.length > 0)) {
362
495
  const r = removeServer(target, MCP_SERVER_NAME);
363
496
  console.log(r.removed ? ` ✓ 已删除 MCP 条目:${target.file}` : ` · 没有 MCP 条目:${target.file}`);
364
497
  }
@@ -378,8 +511,8 @@ function main() {
378
511
  return;
379
512
  }
380
513
  if (opts.unknown.length) {
381
- console.error(`✗ 未知选项:${opts.unknown.join(", ")}`);
382
- console.error(" 用 anov-gis-skills --help 看用法");
514
+ console.error(`✗ 未知选项:${opts.unknown.join(" ")}`);
515
+ console.error(` 用 anov-gis-skills --help 看用法`);
383
516
  process.exit(2);
384
517
  }
385
518
 
package/lib/agents.mjs ADDED
@@ -0,0 +1,236 @@
1
+ // 各智能体的 skill 落点、检测规则、以及「MCP 配置写到哪」。
2
+ //
3
+ // 落点一律按各家官方文档定,不抄上游 skills CLI 的 --agent 映射 ——
4
+ // 上游把所有 skillsDir === ".agents/skills" 的 agent 归进 universal 一族,
5
+ // 装的时候一律落到 ~/.agents/skills,会把 codex / cursor / gemini-cli
6
+ // 这些另有独立目录的 agent 装错地方:
7
+ //
8
+ // codex 上游装到 ~/.agents/skills,但 Codex 用户级只读 ~/.codex/skills
9
+ // cursor 上游装到 ~/.agents/skills,但 Cursor 只有 ~/.cursor/skills 才同步云端
10
+ // gemini-cli 上游装到 ~/.agents/skills,但 Gemini CLI 读 ~/.gemini/skills
11
+ //
12
+ // 所以「自己装」= 绕开上游、直接把 skill 目录 copy 到正确位置。
13
+ //
14
+ // 判定「已安装」= 探测目录存在且非空。上游用的是纯 existsSync,家目录里
15
+ // 躺个同名空壳文件夹就会被认成已装 —— 实测能误判 50+ 个。
16
+
17
+ import { existsSync, readdirSync, cpSync, mkdirSync, rmSync } from "node:fs";
18
+ import { homedir } from "node:os";
19
+ import { join, dirname, basename } from "node:path";
20
+
21
+ /** 把 ~ / %APPDATA% 展开成绝对路径。 */
22
+ export function expand(p) {
23
+ if (p === "~") return homedir();
24
+ if (p.startsWith("~/")) return join(homedir(), p.slice(2));
25
+ if (p.startsWith("%APPDATA%/")) {
26
+ const base = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
27
+ return join(base, p.slice("%APPDATA%/".length));
28
+ }
29
+ return p;
30
+ }
31
+
32
+ /**
33
+ * 目录存在且非空才算「装了这个智能体」。
34
+ * ignore 里的子项不计入 —— Gemini CLI 和 Antigravity 共用 ~/.gemini,
35
+ * 只装了 Antigravity 时 ~/.gemini 也非空,但那不代表装了 Gemini CLI。
36
+ */
37
+ export function dirHasContent(p, ignore = []) {
38
+ try {
39
+ return readdirSync(p).some((name) => !ignore.includes(name));
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * skill 的安装目标。一个目标 = 一个落点。
47
+ *
48
+ * probes 判定「已安装」用的目录,任一非空即命中
49
+ * skillDir 用户级落点(字符串模板,或由命中目录推导的函数)
50
+ * projectDir 项目级落点(相对当前目录)
51
+ * upstream 交给上游 skills CLI 装的 agent 名;null = 我们自己 copy
52
+ */
53
+ export const SKILL_TARGETS = [
54
+ {
55
+ id: "claude-code",
56
+ label: "Claude Code",
57
+ probes: ["~/.claude"],
58
+ skillDir: "~/.claude/skills",
59
+ projectDir: ".claude/skills",
60
+ upstream: "claude-code",
61
+ },
62
+ {
63
+ id: "codex",
64
+ label: "Codex",
65
+ probes: ["~/.codex"],
66
+ skillDir: "~/.codex/skills",
67
+ projectDir: ".codex/skills",
68
+ upstream: null,
69
+ },
70
+ {
71
+ id: "cursor",
72
+ label: "Cursor",
73
+ probes: ["~/.cursor"],
74
+ skillDir: "~/.cursor/skills",
75
+ projectDir: ".cursor/skills",
76
+ upstream: null,
77
+ },
78
+ {
79
+ id: "openclaw",
80
+ label: "OpenClaw",
81
+ probes: ["~/.openclaw", "~/.clawdbot", "~/.moltbot"],
82
+ skillDir: (probe) => join(probe, "skills"),
83
+ projectDir: "skills",
84
+ upstream: "openclaw",
85
+ },
86
+ {
87
+ id: "hermes-agent",
88
+ label: "Hermes Agent",
89
+ probes: ["~/.hermes"],
90
+ skillDir: "~/.hermes/skills",
91
+ projectDir: ".hermes/skills",
92
+ upstream: "hermes-agent",
93
+ },
94
+ {
95
+ id: "trae",
96
+ label: "Trae",
97
+ probes: ["~/.trae"],
98
+ skillDir: "~/.trae/skills",
99
+ projectDir: ".trae/skills",
100
+ upstream: "trae",
101
+ },
102
+ {
103
+ id: "trae-cn",
104
+ label: "Trae CN",
105
+ probes: ["~/.trae-cn"],
106
+ skillDir: "~/.trae-cn/skills",
107
+ projectDir: ".trae/skills",
108
+ upstream: "trae-cn",
109
+ },
110
+ {
111
+ id: "gemini-cli",
112
+ label: "Gemini CLI",
113
+ probes: ["~/.gemini"],
114
+ ignore: ["antigravity", "antigravity-cli", "config"],
115
+ skillDir: "~/.gemini/skills",
116
+ projectDir: ".gemini/skills",
117
+ upstream: null,
118
+ },
119
+ {
120
+ id: "antigravity",
121
+ label: "Antigravity",
122
+ // App / CLI / IDE 现在共用 ~/.gemini/config/skills;IDE 另兼容旧的
123
+ // ~/.gemini/antigravity/skills,但 App 明确不读 ~/.gemini/skills。
124
+ probes: ["~/.gemini/config", "~/.gemini/antigravity", "~/.gemini/antigravity-cli"],
125
+ skillDir: "~/.gemini/config/skills",
126
+ projectDir: ".agents/skills",
127
+ upstream: null,
128
+ },
129
+ {
130
+ id: "github-copilot",
131
+ label: "GitHub Copilot",
132
+ probes: ["~/.copilot"],
133
+ skillDir: "~/.copilot/skills",
134
+ projectDir: ".copilot/skills",
135
+ upstream: null,
136
+ },
137
+ {
138
+ id: "agents-shared",
139
+ label: "共享目录",
140
+ // 这几个 agent 共用 ~/.agents/skills,装一次覆盖全部
141
+ probes: [
142
+ "~/.kimi-code",
143
+ "~/.kimi",
144
+ "~/.cline",
145
+ "~/.dexto",
146
+ "~/.loaf",
147
+ "~/.warp",
148
+ "~/.config/zed",
149
+ "%APPDATA%/Zed",
150
+ ],
151
+ skillDir: "~/.agents/skills",
152
+ projectDir: ".agents/skills",
153
+ upstream: "cline",
154
+ },
155
+ ];
156
+
157
+ /**
158
+ * 目标落点。项目级直接相对当前目录;用户级看 skillDir —— 字符串模板直接展开,
159
+ * 函数则用第一个存在的探测目录推导。
160
+ */
161
+ export function resolveSkillDir(target, scope = "global", cwd = process.cwd()) {
162
+ if (scope === "project") return join(cwd, target.projectDir);
163
+ if (typeof target.skillDir !== "function") return expand(target.skillDir);
164
+ for (const probe of target.probes) {
165
+ const abs = expand(probe);
166
+ if (existsSync(abs)) return target.skillDir(abs);
167
+ }
168
+ return target.skillDir(expand(target.probes[0]));
169
+ }
170
+
171
+ /** 探测本机装了哪些智能体,返回命中的目标(含实际落点)。 */
172
+ export function detectSkillTargets() {
173
+ const out = [];
174
+ for (const target of SKILL_TARGETS) {
175
+ for (const probe of target.probes) {
176
+ const abs = expand(probe);
177
+ const dir =
178
+ typeof target.skillDir === "function" ? target.skillDir(abs) : expand(target.skillDir);
179
+ // skill 落点就在探测目录底下时,别把我们自己装的那份算成「已安装」的证据
180
+ const ignore = [...(target.ignore ?? [])];
181
+ if (dirname(dir) === abs) ignore.push(basename(dir));
182
+ if (!dirHasContent(abs, ignore)) continue;
183
+ out.push({ ...target, probe: abs, skillDir: dir, ignore });
184
+ break;
185
+ }
186
+ }
187
+ return out;
188
+ }
189
+
190
+ /** 把 skill 目录直接 copy 到目标落点(上游装错位置的走这条路)。 */
191
+ export function installSelf(target, skills) {
192
+ mkdirSync(target.skillDir, { recursive: true });
193
+ for (const s of skills) {
194
+ cpSync(join(s.srcDir, s.name), join(target.skillDir, s.name), { recursive: true, force: true });
195
+ }
196
+ }
197
+
198
+ /** 删掉自己 copy 出去的 skill。 */
199
+ export function uninstallSelf(target, names) {
200
+ for (const name of names) {
201
+ rmSync(join(target.skillDir, name), { recursive: true, force: true });
202
+ }
203
+ }
204
+
205
+ // ---------------------------------------------------------------- MCP
206
+
207
+ /**
208
+ * 支持写 MCP 配置的智能体。落点由 lib/mcp-config.mjs 负责(各家格式不同)。
209
+ * probes 是「这个智能体装了没」的判据 —— 默认集合只写探测到的那些。
210
+ */
211
+ export const MCP_AGENTS = [
212
+ { id: "claude-code", label: "Claude Code", probes: ["~/.claude"], skillTarget: "claude-code" },
213
+ { id: "codex", label: "Codex", probes: ["~/.codex"], skillTarget: "codex" },
214
+ { id: "cursor", label: "Cursor", probes: ["~/.cursor"], skillTarget: "cursor" },
215
+ {
216
+ id: "claude-desktop",
217
+ label: "Claude 桌面端",
218
+ probes: ["%APPDATA%/Claude", "~/Library/Application Support/Claude", "~/.config/Claude"],
219
+ skillTarget: null,
220
+ },
221
+ { id: "vscode", label: "VS Code", probes: ["<cwd>/.vscode"], skillTarget: null },
222
+ ];
223
+
224
+ /** 探测装了哪些支持 MCP 的智能体。scope=project 时只看项目级目录。 */
225
+ export function detectMcpAgents(scope = "global", cwd = process.cwd()) {
226
+ const out = [];
227
+ for (const agent of MCP_AGENTS) {
228
+ for (const probe of agent.probes) {
229
+ const abs = probe === "<cwd>/.vscode" ? join(cwd, ".vscode") : expand(probe);
230
+ if (!dirHasContent(abs)) continue;
231
+ out.push({ ...agent, probe: abs });
232
+ break;
233
+ }
234
+ }
235
+ return out;
236
+ }
@@ -4,10 +4,11 @@
4
4
  // 格式适配,收益却只落在少数人身上。
5
5
  //
6
6
  // 支持的 agent 与配置位置:
7
- // claude-code 用户级 ~/.claude.json 的 mcpServers / 项目级 <cwd>/.mcp.json 的 mcpServers
8
- // codex 用户级 ~/.codex/config.toml 的 [mcp_servers.<name>]
9
- // cursor 项目级 <cwd>/.cursor/mcp.json / 用户级 ~/.cursor/mcp.json 的 mcpServers
10
- // vscode 项目级 <cwd>/.vscode/mcp.json 的 servers(VS Code 用 servers,不是 mcpServers)
7
+ // claude-code 用户级 ~/.claude.json 的 mcpServers / 项目级 <cwd>/.mcp.json 的 mcpServers
8
+ // codex 用户级 ~/.codex/config.toml 的 [mcp_servers.<name>]
9
+ // cursor 项目级 <cwd>/.cursor/mcp.json / 用户级 ~/.cursor/mcp.json 的 mcpServers
10
+ // vscode 项目级 <cwd>/.vscode/mcp.json 的 servers(VS Code 用 servers,不是 mcpServers)
11
+ // claude-desktop 用户级 claude_desktop_config.json 的 mcpServers(Claude 桌面聊天端)
11
12
  //
12
13
  // 写入原则:读现有文件 → 只动自己那一个条目 → 原子写回。别人的条目一律不碰。
13
14
 
@@ -55,12 +56,34 @@ export function agentTargets(agent, scope, cwd = process.cwd()) {
55
56
  return scope === "global"
56
57
  ? []
57
58
  : [{ file: resolve(cwd, ".vscode", "mcp.json"), key: "servers", label: "VS Code(项目级)" }];
59
+ case "claude-desktop": {
60
+ if (scope !== "global") return [];
61
+ return [
62
+ {
63
+ file: claudeDesktopConfig(),
64
+ key: "mcpServers",
65
+ label: "Claude 桌面端(用户级)",
66
+ },
67
+ ];
68
+ }
58
69
  default:
59
70
  return [];
60
71
  }
61
72
  }
62
73
 
63
- export const SUPPORTED_AGENTS = ["claude-code", "codex", "cursor", "vscode"];
74
+ /** Claude 桌面聊天端的 MCP 配置文件(各平台路径不同)。 */
75
+ function claudeDesktopConfig() {
76
+ if (process.platform === "darwin") {
77
+ return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
78
+ }
79
+ if (process.platform === "win32") {
80
+ const base = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
81
+ return join(base, "Claude", "claude_desktop_config.json");
82
+ }
83
+ return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
84
+ }
85
+
86
+ export const SUPPORTED_AGENTS = ["claude-code", "codex", "cursor", "vscode", "claude-desktop"];
64
87
 
65
88
  /** 从当前进程环境里挑出要透传给 MCP 的变量。 */
66
89
  export function collectEnv(env = process.env) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anov/gis-skills-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.4",
4
4
  "description": "12 个 @anov/gis (Cesium 1.143) 领域 skill + 15 个 Cesium 原生 skill,外加 anov-gis-mcp 的接入配置。一条命令装到 Claude Code / Codex / Cursor 等智能体。",
5
5
  "type": "module",
6
6
  "license": "ISC AND Apache-2.0",
@@ -38,6 +38,7 @@
38
38
  "cursor"
39
39
  ],
40
40
  "scripts": {
41
+ "release": "node scripts/release.cjs",
41
42
  "prepublishOnly": "node scripts/bump.mjs --check && node scripts/check-structure.mjs",
42
43
  "pack:check": "node scripts/build-npm.mjs"
43
44
  }
@@ -1,7 +1,7 @@
1
1
  # DEMO-SKILL-MAP.md — 137 个 demo → Skill 路由矩阵
2
2
 
3
3
  > **SDK 版本基准:** @anov/gis v1.8.6(基于 Cesium 1.143.0)
4
- > **Plugin 版本:** anov-gis-skills v0.3.0
4
+ > **Plugin 版本:** anov-gis-skills v0.3.4
5
5
  > **Demo 来源:** ANOV-GIS 仓库 `examples/public/examples/html/`(仓库内相对路径)共 137 个 .html 文件,按 13 类目分组
6
6
 
7
7
  本文档是 demo **文件名** → skill 的**完整版**路由矩阵(`using-anovgis/DEMO-MAP.md` 是场景级的简版)。当用户提到具体 demo 文件名或场景时,按本表路由到对应 skill。
@@ -1,7 +1,7 @@
1
1
  # DOMAINS.md — anov-gis-skills 权威归属
2
2
 
3
3
  > **SDK 版本基准:** @anov/gis **v1.8.6**(基于 Cesium **1.143.0**)
4
- > **Plugin 版本:** anov-gis-skills **v0.3.0**
4
+ > **Plugin 版本:** anov-gis-skills **v0.3.4**
5
5
  > **Skill 数:** **12**(含 `using-anovgis` 入口 + 3 个 v0.2.0 新增 skill)
6
6
  > **入口 skill 额外职责**:引导式初始化(AskUserQuestion 向导:底图 / 控件 / 部署;**技术栈固定 Vue 3 + Vite**)
7
7