@anov/gis-skills-cli 0.3.3 → 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,11 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.4] - 2026-11-02
8
+
9
+ - feat: setup 按检测到的智能体安装 skill 与 MCP,修 Codex 装错目录 (`1379891`)
10
+
11
+
7
12
  ## [0.3.3] - 2026-11-02
8
13
 
9
14
  - fix: README 文档链接改绝对 URL,修 npm 包页面点不开的问题 (`a118366`)
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
- # 装给谁(默认:共享目录 ~/.agents/skills + 已装的 Claude Code)
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,11 +109,11 @@ npx @anov/gis-skills-cli setup --print
87
109
 
88
110
  ### A.5 MCP 接入
89
111
 
90
- `setup` 默认给 **Claude Code 和 Codex** 各写一条 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 + Codex + 用户级)
116
+ npx @anov/gis-skills-cli mcp add # 补上(默认写给检测到的智能体 + 用户级)
95
117
  npx @anov/gis-skills-cli mcp add --mcp-agent cursor
96
118
  npx @anov/gis-skills-cli mcp remove # 拿掉
97
119
  ```
@@ -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,20 +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 —— 未安装该智能体
132
157
 
133
- ── MCP · Claude Code(用户级) ── C:\Users\你\.claude.json
158
+ ── MCP ──
159
+
160
+ Claude Code(用户级) —— C:\Users\你\.claude.json
134
161
  ✓ 已配置(stdio)
135
162
 
136
- ── MCP · Codex(用户级) ── C:\Users\你\.codex\config.toml
163
+ Codex(用户级) —— C:\Users\你\.codex\config.toml
137
164
  ✓ 已配置(stdio)
165
+
166
+ ── 一致性 ──
167
+ ✓ 没有「MCP 配了但 skill 缺失」的半装状态
138
168
  ```
139
169
 
140
170
  | 检查项 | 不通过意味着 |
@@ -142,6 +172,7 @@ npx @anov/gis-skills-cli doctor
142
172
  | skill 齐全 | 装漏了,或只装了一部分(单独装部分 skill 时跨 skill 链接会失效) |
143
173
  | frontmatter 可被严格 YAML 解析 | 某些 agent 的 skill 加载器会**整个跳过**这个 skill(v0.2.17 踩过:只发现 10/12) |
144
174
  | MCP 已配置 | 少了条目 —— 跑 `mcp add` |
175
+ | 一致性 | 该智能体 MCP 配了但一个 skill 都没有 —— 跑 `setup` 补上 |
145
176
 
146
177
  有问题会逐条列出并以退出码 1 结束。
147
178
 
@@ -244,7 +275,7 @@ Claude Code 走 marketplace,多两个好处:**发版后自动升级**、**
244
275
 
245
276
  > ⚠️ **版本约束只对 `enabledPlugins` 生效**。`/plugin install` 与 `claude plugin install` 的参数只接受 `插件名` 或 `插件名@市场名`(第三段会被丢弃),所以**命令行装的时候锁不住版本** —— 要锁版本就靠 settings 里的 `enabledPlugins`。
246
277
 
247
- **完全锁死(不推荐)**:把约束写成精确版本 `anov-gis-skills@anov-gis-skills@0.3.3`。精确锁定后**不会自动升级**,每次发版都要人工改这一行 —— 这正是 v0.2.15 之前"发完版还要挨个催"的原因。
278
+ **完全锁死(不推荐)**:把约束写成精确版本 `anov-gis-skills@anov-gis-skills@0.3.4`。精确锁定后**不会自动升级**,每次发版都要人工改这一行 —— 这正是 v0.2.15 之前"发完版还要挨个催"的原因。
248
279
 
249
280
  ### B.4 插件缓存位置
250
281
 
@@ -297,7 +328,8 @@ npx skills add <repo-url> -l
297
328
  npx skills update
298
329
  ```
299
330
 
300
- - 装到 `~/.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
301
333
  - 默认**拷贝**(`--copy` 是显式开关,默认行为随版本变化);加 `--copy` 强制拷贝
302
334
  - **不带 cesiumjs-skills** —— 这条路没有 marketplace 的 `dependencies` 机制,Cesium 原生 skill 要单独装(见 §方式 B.5)
303
335
 
@@ -451,9 +483,9 @@ git clone https://github.com/CesiumGS/cesiumjs-skills.git ~/.codex/skills/cesium
451
483
  | 场景 | 说明 |
452
484
  |---|---|
453
485
  | 首次开启 `autoUpdate` | 2026-10 之前装的,`known_marketplaces.json` 里 `autoUpdate` 默认是 `false`(第三方 marketplace 的默认值)。改一次 §方式 B.2 的配置或跑一次 `/plugin marketplace update` 即可 |
454
- | 用了精确锁版本 | `anov-gis-skills@anov-gis-skills@0.3.3` 这种精确锁定**不会自动升级**,必须改这一行 |
486
+ | 用了精确锁版本 | `anov-gis-skills@anov-gis-skills@0.3.4` 这种精确锁定**不会自动升级**,必须改这一行 |
455
487
  | 提示 restart required | 重开 Claude Code 会话 |
456
- | 想立刻跳到指定版本 | `/plugin install anov-gis-skills@anov-gis-skills@0.3.3`(降级同理) |
488
+ | 想立刻跳到指定版本 | `/plugin install anov-gis-skills@anov-gis-skills@0.3.4`(降级同理) |
457
489
 
458
490
  ### 查看当前版本
459
491
 
@@ -464,7 +496,7 @@ git clone https://github.com/CesiumGS/cesiumjs-skills.git ~/.codex/skills/cesium
464
496
 
465
497
  ### 破坏性变更
466
498
 
467
- **从 v0.1.1 升级到 v0.3.3 的破坏性变更**:无。所有 v0.1.1 skill 在 v0.3.3 仍然可用;v0.2.0 新增 3 个 skill + DOMAINS 强化;v0.2.1–v0.3.3 为修复性版本(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 名称与目录结构未变)。
468
500
 
469
501
  ---
470
502
 
@@ -477,5 +509,7 @@ git clone https://github.com/CesiumGS/cesiumjs-skills.git ~/.codex/skills/cesium
477
509
  | 插件装了但 skill 一个都没出现 | frontmatter 没被严格 YAML 解析 | 跑 `npx @anov/gis-skills-cli doctor` 看哪几条断了 |
478
510
  | skill 只有一部分能用 | 单独装了部分 skill,指向兄弟 skill 的相对链接失效 | 全量装(12 个同级),别只装一个 |
479
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`) |
480
514
  | 已装过官方 `cesiumjs-skills` 导致触发混乱 | 官方版与内网镜像版注册了同名 skill | 见 §方式 B.5 的卸载步骤二选一 |
481
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 + Codex,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 cursor # MCP 接到 Cursor(默认 Claude Code + 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](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.3/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](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.3/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`](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.3/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`](https://cdn.jsdelivr.net/npm/@anov/gis-skills-cli@0.3.3/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` 自动转过去,你一般不用直接调用。
@@ -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,14 +38,20 @@ 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";
42
- const DEFAULT_MCP_AGENTS = ["claude-code", "codex"];
43
-
44
55
  // 用 npx 跑时脚本在 npm 缓存的 _npx 目录里,此时 `anov-gis-skills` 不在 PATH 上,
45
56
  // 提示语必须给回 npx 形式,否则用户会撞上「不是内部或外部命令」。
46
57
  const CLI_CMD = /[\\/]_npx[\\/]/.test(process.argv[1] ?? "")
@@ -61,16 +72,18 @@ function parseArgs(argv) {
61
72
  else if (a === "--no-mcp") out.noMcp = true;
62
73
  else if (a === "--mcp") out.mcp = true;
63
74
  else if (a === "-a" || a === "--agent") {
64
- while (argv[++i] && !argv[i].startsWith("-")) out.agents.push(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]);
65
78
  } else if (a === "--mcp-agent") {
66
- while (argv[i + 1] && !argv[i + 1].startsWith("-")) {
79
+ while (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
67
80
  for (const v of argv[++i].split(",")) if (v.trim()) out.mcpAgents.push(v.trim());
68
81
  }
69
82
  } else if (a === "--mcp-mode") out.mcpMode = argv[++i];
70
83
  else if (a === "--mcp-url") out.mcpUrl = argv[++i];
71
84
  else if (a === "--mcp-command") out.mcpCommand = argv[++i];
72
85
  else if (a === "--mcp-arg") {
73
- 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]);
74
87
  } else if (a === "-h" || a === "--help") out.help = true;
75
88
  else if (a.startsWith("-")) out.unknown.push(a);
76
89
  else out._.push(a);
@@ -92,12 +105,12 @@ const HELP = `${CLI_CMD} —— 装 skill + 接 MCP
92
105
  --scope global|project 装到哪(默认 global = 用户级,任何项目都能用)
93
106
  -g, --global 等价于 --scope global
94
107
  --project 等价于 --scope project
95
- -a, --agent <名字...> 只装给指定智能体(默认:共享目录 + 已装的 Claude Code)
108
+ -a, --agent <名字...> 只装给指定智能体(默认:本机检测到的智能体)
96
109
  -y, --yes 跳过所有交互
97
110
  --print 只打印要写的内容,不落盘
98
111
 
99
112
  MCP 选项
100
- --mcp-agent <名字...> 写到哪个智能体(${SUPPORTED_AGENTS.join(" / ")};默认 claude-code + codex)
113
+ --mcp-agent <名字...> 写到哪个智能体(${SUPPORTED_AGENTS.join(" / ")};默认:检测到的)
101
114
  --mcp-mode stdio|http 连接方式(默认 stdio)
102
115
  --mcp-url <地址> HTTP 模式的服务地址(stdio 模式不需要)
103
116
  --mcp-command <命令> stdio 模式的启动命令(默认 npx)
@@ -107,17 +120,18 @@ MCP 选项
107
120
 
108
121
  // ---------------------------------------------------------------- 基础工具
109
122
 
110
- function skillNames(dir) {
123
+ /** 一个 skill 的来源:名字 + 它在包里的目录。 */
124
+ function skillSources(dir) {
111
125
  if (!existsSync(dir)) return [];
112
126
  return readdirSync(dir, { withFileTypes: true })
113
127
  .filter((d) => d.isDirectory() && existsSync(join(dir, d.name, "SKILL.md")))
114
- .map((d) => d.name)
115
- .sort();
128
+ .map((d) => ({ name: d.name, srcDir: dir }))
129
+ .sort((a, b) => a.name.localeCompare(b.name));
116
130
  }
117
131
 
118
132
  /** 本包提供的全部 skill:anov-gis 12 个 + Cesium 15 个。 */
119
133
  function allSkillNames() {
120
- return [...skillNames(SKILLS_DIR), ...skillNames(CESIUM_DIR)];
134
+ return [...skillSources(SKILLS_DIR), ...skillSources(CESIUM_DIR)].map((s) => s.name);
121
135
  }
122
136
 
123
137
  function runSkillsCLI(extra, { quiet = false } = {}) {
@@ -139,25 +153,32 @@ function scopeFlags(scope) {
139
153
  return scope === "global" ? ["--global"] : [];
140
154
  }
141
155
 
142
- // 上游的自动检测会把 57 个 universal 系智能体全拉进来,其中 PromptScript 没有全局
143
- // 目录却仍在名单里,每个 skill 刷一条 Failed。所以这里只显式点名:
144
- // 共享目录 .agents/skills(universal 系智能体本来就读这里)+ 实际存在的 Claude Code。
145
- function defaultSkillAgents() {
146
- return existsSync(join(homedir(), ".claude")) ? ["claude-code"] : ["universal"];
156
+ /** 目标落点。scope=project 时相对当前目录,global 时按表里的用户级落点。 */
157
+ function targetDir(target, scope = "global") {
158
+ return resolveSkillDir(target, scope);
147
159
  }
148
160
 
149
- // 传 [] 时不加 --agent:remove 的上游语义是从所有智能体删,正好用来清旧的 symlink。
150
- function targetFlags(agents) {
151
- return agents.length ? ["--agent", ...agents] : [];
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;
152
174
  }
153
175
 
154
176
  // ---------------------------------------------------------------- setup
155
177
 
156
178
  function cmdSetup(opts) {
157
179
  const scope = opts.scope ?? "global";
158
- const agents = opts.agents.length ? opts.agents : defaultSkillAgents();
159
- const anov = skillNames(SKILLS_DIR);
160
- const cesium = skillNames(CESIUM_DIR);
180
+ const anov = skillSources(SKILLS_DIR);
181
+ const cesium = skillSources(CESIUM_DIR);
161
182
 
162
183
  if (!anov.length) {
163
184
  console.error("✗ 包内找不到 skill(skills/ 目录缺失或为空)");
@@ -168,18 +189,53 @@ function cmdSetup(opts) {
168
189
  process.exit(1);
169
190
  }
170
191
 
192
+ const detected = detectSkillTargets();
193
+ const targets = opts.agents.length ? targetsByName(opts.agents) : detected;
194
+
171
195
  console.log(`\n安装 ${anov.length + cesium.length} 个 skill`);
172
196
  console.log(` anov-gis ${anov.length} 个`);
173
197
  console.log(` cesiumjs ${cesium.length} 个`);
174
198
  console.log(` 范围 ${scope === "global" ? "用户级(任何项目可用)" : "项目级(当前目录)"}`);
175
- console.log(` 智能体 ${agents.join(", ")}`);
176
-
177
- for (const [label, dir] of [["anov-gis", SKILLS_DIR], ["cesiumjs", CESIUM_DIR]]) {
178
- console.log(`\n── ${label} ──`);
179
- const code = runSkillsCLI(["add", dir, "--agent", ...agents, ...scopeFlags(scope), "-y"]);
180
- if (code !== 0) {
181
- console.error(`✗ ${label} 安装失败(退出码 ${code})`);
182
- process.exit(code);
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
+ }
205
+
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}`);
183
239
  }
184
240
  }
185
241
 
@@ -192,7 +248,10 @@ function cmdSetup(opts) {
192
248
  args: opts.mcpArgs.length ? opts.mcpArgs : undefined,
193
249
  env: collectEnv(),
194
250
  });
195
- writeMcp(mcpAgents(opts), scope, config, { printOnly: opts.print, strict: opts.mcpAgents.length > 0 });
251
+ writeMcp(mcpAgents(opts, scope), scope, config, {
252
+ printOnly: opts.print,
253
+ strict: opts.mcpAgents.length > 0,
254
+ });
196
255
  }
197
256
 
198
257
  console.log("\n✓ 完成");
@@ -203,9 +262,11 @@ function cmdSetup(opts) {
203
262
 
204
263
  // ---------------------------------------------------------------- MCP
205
264
 
206
- /** 默认 Claude Code + Codex;`--mcp-agent` 指定时只用指定的那些。 */
207
- function mcpAgents(opts) {
208
- return opts.mcpAgents.length ? opts.mcpAgents : DEFAULT_MCP_AGENTS;
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);
209
270
  }
210
271
 
211
272
  /**
@@ -270,7 +331,7 @@ function writeMcp(agents, scope, config, { printOnly = false, strict = false } =
270
331
  function cmdMcp(opts) {
271
332
  const action = opts._[0] ?? "show";
272
333
  const scope = opts.scope ?? "global";
273
- const agents = mcpAgents(opts);
334
+ const agents = mcpAgents(opts, scope);
274
335
  const strict = opts.mcpAgents.length > 0;
275
336
 
276
337
  if (action === "show") {
@@ -329,43 +390,32 @@ function frontmatterOk(text) {
329
390
  return { ok: true, name: name[1] };
330
391
  }
331
392
 
332
- /** 智能体的 skills 目录(用户级 / 项目级),与 skills CLI 的落点一致。 */
333
- function installedSkillDirs(scope) {
334
- const home = homedir();
335
- const bases =
336
- scope === "global"
337
- ? [
338
- ["Claude Code", join(home, ".claude", "skills")],
339
- ["Codex", join(home, ".codex", "skills")],
340
- ["通用 / Cursor", join(home, ".agents", "skills")],
341
- ]
342
- : [
343
- ["Claude Code", join(process.cwd(), ".claude", "skills")],
344
- ["Codex / 通用", join(process.cwd(), ".agents", "skills")],
345
- ];
346
- return bases.filter(([, dir]) => existsSync(dir));
347
- }
348
-
349
393
  function cmdDoctor(opts) {
350
394
  const scope = opts.scope ?? "global";
351
395
  const expected = allSkillNames();
396
+ const detected = detectSkillTargets();
352
397
  let problems = 0;
353
398
 
354
399
  console.log(`\n检查 @anov/gis-skills-cli\n`);
355
400
  console.log(`包内 skill 数:${expected.length}`);
356
401
 
357
- 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);
358
407
  const present = expected.filter((n) => existsSync(join(dir, n, "SKILL.md")));
359
- console.log(`\n── ${label} ── ${dir}`);
360
- if (!present.length) {
361
- 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} —— 未安装该智能体`);
362
412
  continue;
363
413
  }
364
414
  if (present.length === expected.length) {
365
- console.log(` ✓ ${present.length} 个 skill 齐全`);
415
+ console.log(` ✓ ${target.label.padEnd(16)} ${dir} —— ${present.length}/${expected.length} 齐全`);
366
416
  } else {
367
417
  const missing = expected.filter((n) => !present.includes(n));
368
- console.log(` ! 只装了 ${present.length}/${expected.length} 个,缺:${missing.join(", ")}`);
418
+ console.log(` ! ${target.label.padEnd(16)} ${dir} —— 只装了 ${present.length}/${expected.length},缺:${missing.join(", ")}`);
369
419
  problems++;
370
420
  }
371
421
 
@@ -381,11 +431,14 @@ function cmdDoctor(opts) {
381
431
  if (bad) problems++;
382
432
  }
383
433
 
434
+ console.log(`\n── MCP ──`);
435
+ const mcpConfigured = new Set();
384
436
  for (const agent of SUPPORTED_AGENTS) {
385
437
  for (const target of agentTargets(agent, scope)) {
386
438
  const existing = readServer(target, MCP_SERVER_NAME);
387
- console.log(`\n── MCP · ${target.label} ── ${target.file}`);
439
+ console.log(`\n ${target.label} —— ${target.file}`);
388
440
  if (existing) {
441
+ mcpConfigured.add(agent);
389
442
  console.log(` ✓ 已配置(${existing.type ?? "unknown"})`);
390
443
  } else {
391
444
  console.log(` · 未配置 —— ${CLI_CMD} mcp add`);
@@ -393,6 +446,18 @@ function cmdDoctor(opts) {
393
446
  }
394
447
  }
395
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
+
396
461
  console.log("");
397
462
  if (problems) {
398
463
  console.error(`✗ 发现 ${problems} 处问题 —— 重装:${CLI_CMD} setup\n`);
@@ -412,14 +477,20 @@ function cmdUninstall(opts) {
412
477
  }
413
478
 
414
479
  console.log(`\n移除 ${names.length} 个 skill(${scope === "global" ? "用户级" : "项目级"})`);
480
+ // 不传 --agent:上游的语义是从所有智能体删,正好清掉历史上 --all 铺出去的残留。
415
481
  // 只按名字删本包提供的 skill —— skills remove --skill '*' 会连别的来源的一起删掉
416
- const code = runSkillsCLI(["remove", ...names, ...targetFlags(opts.agents), ...scopeFlags(scope), "-y"]);
482
+ const code = runSkillsCLI(["remove", ...names, ...scopeFlags(scope), "-y"]);
417
483
  if (code !== 0) {
418
484
  console.error(`✗ 移除 skill 失败(退出码 ${code})`);
419
485
  process.exit(code);
420
486
  }
421
487
 
422
- const agents = mcpAgents(opts);
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);
423
494
  for (const target of mcpTargets(agents, scope, opts.mcpAgents.length > 0)) {
424
495
  const r = removeServer(target, MCP_SERVER_NAME);
425
496
  console.log(r.removed ? ` ✓ 已删除 MCP 条目:${target.file}` : ` · 没有 MCP 条目:${target.file}`);
@@ -440,8 +511,8 @@ function main() {
440
511
  return;
441
512
  }
442
513
  if (opts.unknown.length) {
443
- console.error(`✗ 未知选项:${opts.unknown.join(", ")}`);
444
- console.error(" 用 anov-gis-skills --help 看用法");
514
+ console.error(`✗ 未知选项:${opts.unknown.join(" ")}`);
515
+ console.error(` 用 anov-gis-skills --help 看用法`);
445
516
  process.exit(2);
446
517
  }
447
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.3",
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",
@@ -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.3
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.3**
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