@anov/gis-skills-cli 0.0.0-stage → 0.3.0

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +618 -0
  2. package/INSTALL.md +478 -0
  3. package/LICENSE +15 -0
  4. package/README.md +148 -2
  5. package/RELEASING.md +160 -0
  6. package/bin/anov-gis-skills.mjs +408 -0
  7. package/lib/mcp-config.mjs +273 -0
  8. package/package.json +42 -4
  9. package/skills/anovgis-analysis/SKILL.md +585 -0
  10. package/skills/anovgis-effects/SKILL.md +260 -0
  11. package/skills/anovgis-imagery-terrain/SKILL.md +505 -0
  12. package/skills/anovgis-layers/SKILL.md +627 -0
  13. package/skills/anovgis-materials/SKILL.md +635 -0
  14. package/skills/anovgis-models-3dtiles/SKILL.md +448 -0
  15. package/skills/anovgis-pitfalls/SKILL.md +561 -0
  16. package/skills/anovgis-popup-widget/SKILL.md +474 -0
  17. package/skills/anovgis-primitives/SKILL.md +482 -0
  18. package/skills/anovgis-vector-graphics/SKILL.md +602 -0
  19. package/skills/anovgis-viewer/SKILL.md +618 -0
  20. package/skills/using-anovgis/DEMO-MAP.md +64 -0
  21. package/skills/using-anovgis/DEMO-SKILL-MAP.md +248 -0
  22. package/skills/using-anovgis/DOMAINS.md +183 -0
  23. package/skills/using-anovgis/REFERENCE.md +114 -0
  24. package/skills/using-anovgis/SKILL.md +392 -0
  25. package/skills/using-anovgis/templates/README.md +154 -0
  26. package/skills/using-anovgis/templates/combo1-vue3-vite.md +295 -0
  27. package/skills/using-anovgis/templates/combo2-vue2-cli.md +119 -0
  28. package/skills/using-anovgis/templates/combo3-html-cdn.md +107 -0
  29. package/skills/using-anovgis/templates/combo4-vanilla-webpack.md +91 -0
  30. package/skills/using-anovgis/templates/wizard-profiles.md +105 -0
  31. package/vendor/cesiumjs-skills/LICENSE +201 -0
  32. package/vendor/cesiumjs-skills/NOTICE +18 -0
  33. package/vendor/cesiumjs-skills/skills/cesiumjs-3d-tiles/SKILL.md +465 -0
  34. package/vendor/cesiumjs-skills/skills/cesiumjs-camera/SKILL.md +500 -0
  35. package/vendor/cesiumjs-skills/skills/cesiumjs-core-utilities/SKILL.md +403 -0
  36. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/REFERENCE.md +397 -0
  37. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/SKILL.md +351 -0
  38. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/01-diffuse-tint.js +38 -0
  39. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/02-texture-swap.js +42 -0
  40. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/03-feature-id-tileset.js +29 -0
  41. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/04-translucent-override.js +37 -0
  42. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/05-vertex-displacement.js +49 -0
  43. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/06-metadata-ramp.js +34 -0
  44. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/07-voxel-shader.js +27 -0
  45. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/README.md +19 -0
  46. package/vendor/cesiumjs-skills/skills/cesiumjs-custom-shader/examples/_sandcastle-template.html +9 -0
  47. package/vendor/cesiumjs-skills/skills/cesiumjs-entities/SKILL.md +389 -0
  48. package/vendor/cesiumjs-skills/skills/cesiumjs-imagery/SKILL.md +471 -0
  49. package/vendor/cesiumjs-skills/skills/cesiumjs-interaction/SKILL.md +423 -0
  50. package/vendor/cesiumjs-skills/skills/cesiumjs-materials-shaders/SKILL.md +292 -0
  51. package/vendor/cesiumjs-skills/skills/cesiumjs-models-particles/SKILL.md +423 -0
  52. package/vendor/cesiumjs-skills/skills/cesiumjs-primitives/SKILL.md +537 -0
  53. package/vendor/cesiumjs-skills/skills/cesiumjs-spatial-math/SKILL.md +358 -0
  54. package/vendor/cesiumjs-skills/skills/cesiumjs-terrain-environment/SKILL.md +401 -0
  55. package/vendor/cesiumjs-skills/skills/cesiumjs-time-properties/SKILL.md +353 -0
  56. package/vendor/cesiumjs-skills/skills/cesiumjs-viewer-setup/SKILL.md +434 -0
  57. package/vendor/cesiumjs-skills/skills/using-cesiumjs-skills/SKILL.md +35 -0
package/RELEASING.md ADDED
@@ -0,0 +1,160 @@
1
+ # 发版与贡献
2
+
3
+ PR 到 GitLab `zhf/anov-gis-skills` 主仓。本文两件事:**改 skill 的规范**、**怎么发一个版**。
4
+
5
+ ---
6
+
7
+ ## 一、贡献规范
8
+
9
+ ### 新增 skill 要求
10
+
11
+ - 目录名格式:`anovgis-<name>`(如 `anovgis-three-layer`)
12
+ - `SKILL.md` 是入口,**本身 ≤ 400 行**;重的部分拆成同目录子文件(`REFERENCE.md` 速查表 / `DEMO-MAP.md` 场景路由 / `templates/` 工程骨架 / `examples/` 可跑示例),子文件仍是 skill 的一部分,靠 SKILL.md 里的指针跳转
13
+ - frontmatter 必填字段:`name`、`description`、`anov-gis-baseline`、`cesium-baseline`
14
+ - description 含 "Use when..." 短语列表 + 触发关键词
15
+ - 必含章节:Out-of-scope / Version baseline / Quickstart / 类速查表 / Snippet / 反模式
16
+ - 在 `skills/using-anovgis/DOMAINS.md` § 1 标注该 skill 拥有的类条目
17
+ - 在 `skills/using-anovgis/DEMO-SKILL-MAP.md` 标注覆盖的 demo
18
+ - 在 `skills/using-anovgis/SKILL.md` 路由表中添加该 skill
19
+
20
+ ### 修改现有 skill 要求
21
+
22
+ - 保持 description 关键词密度(每 50 字至少 1 个具体 API / demo 文件名关键词)
23
+ - 新增 snippet 必须带 `// 来源: ...` 注释
24
+ - Out-of-scope 章节变动要同步更新 DOMAINS.md § 3 反归属清单
25
+
26
+ ### 铁律
27
+
28
+ - **不要手工改版本号** —— `scripts/bump.mjs` 是唯一来源,`--check` 会把任何漏改 / 多改揪出来
29
+ - **不要在 skill 里写逃出所属 skills 树的相对链接** —— skill 会被拷到 `<agent>/skills/<name>/` 单个目录,逃出去的链接必断。`scripts/check-structure.mjs` 与 `scripts/build-npm.mjs` 都会拦(`skills/` 与 `vendor/cesiumjs-skills/skills/` 各算一棵树)
30
+ - **不要手改 `vendor/cesiumjs-skills/`** —— 那是上游 Apache-2.0 内容的原样副本,改动会在下次 `sync-cesium.mjs` 时被覆盖。要改就在上游改,再同步回来
31
+ - **不要给 SKILL.md frontmatter 加 `version:` 字段** —— 非 schema 键,`bump.mjs --check` 会拒绝
32
+ - **不要改 skill 目录名** —— 装到用户机器上后是按 `name` 字段寻址,目录名只是本地约定
33
+ - **SDK 已封装的能力不要用 Cesium.\* 自拼** —— 天地图 / 地形 / 模型等一律走 `@anov/gis` 的封装
34
+
35
+ ---
36
+
37
+ ## 二、语义化版本纪律
38
+
39
+ 团队在 `.claude/settings.json` 的 `enabledPlugins` 里用 `anov-gis-skills@anov-gis-skills@^0.3.0` 这种 **锁 minor** 的方式安装,版本号怎么涨直接决定用户收到什么:
40
+
41
+ | 变更类型 | 版本怎么涨 | 用户收到吗 |
42
+ |---|---|---|
43
+ | 修 bug、改 skill 内容、加 snippet | **patch**(0.2.x → 0.2.x+1) | ✅ 自动收到(`autoUpdate` 开着) |
44
+ | 新增 skill、**改 skill 名或目录结构** | **minor**(0.2.x → 0.3.0) | ❌ 不自动跟,需显式改安装串 |
45
+ | skill 目录内新增子文件(`REFERENCE.md` / `templates/` / `examples/`) | **patch** | skill 的 `name` 与顶层结构未变,对所有智能体的寻址方式无影响 |
46
+ | 同步 vendor 的 Cesium skill(上游更新) | **patch** | 用户重跑 `setup` 即拿到 |
47
+ | 改动 CLI 命令族 / 新增安装方式 | **minor** | 安装方式变了,需显式改文档与流程 |
48
+ | 破坏性契约变更 | **major**(0.x → 1.0.0) | ❌ 不自动跟 |
49
+
50
+ 因为 skill 名和目录结构从 v0.1.1 至今没变过,**日常发版只用 patch**。
51
+
52
+ **改版本号只有一个入口**:
53
+
54
+ ```bash
55
+ node scripts/bump.mjs patch # 例:0.2.15 → 0.2.16,同时插 CHANGELOG 条目
56
+ node scripts/bump.mjs --check # 校验所有版本位一致(发版前自检,不一致就 fail)
57
+ ```
58
+
59
+ 不要手工去改 `INSTALL.md` / `RELEASING.md` / `skills/using-anovgis/*.md` 里的版本号 —— `bump.mjs` 是唯一来源,`--check` 会把任何漏改 / 多改的地方揪出来(`skills/using-anovgis/DEMO-SKILL-MAP.md` 之前就长期停在 v0.2.0,正是这个闸门要防的事)。
60
+
61
+ ---
62
+
63
+ ## 三、发版流程
64
+
65
+ `@anov/gis` 升级到 1.9.x 后(未来),或任何一次 skill 内容变更后:
66
+
67
+ 1. 在 ANOV-GIS 仓库拉新版源码:`git pull`
68
+ 2. 对比 ANOV-GIS 仓库 `src/` 与本仓库各 `skills/*/SKILL.md`:
69
+ - 新增类 → 加到对应 skill 的速查表 + snippet
70
+ - 删除类 → 从 skill 移除
71
+ - 改名 / 移动 → 更新 DOMAINS.md 归属表
72
+ 3. 检查行号是否漂移:v1.8.4 → v1.8.6 抽样核对显示 DEF_OPTS 等顶部声明稳定,但新文件(如 `PositonEditor`/`TilesetEditor`)需要作为新章节追加
73
+ 4. 检查 DOMAINS.md「空白域待补」清单是否需要更新
74
+ 5. 跑 `grep -r "v1.8.6" skills/` 确认每个 skill 顶部 baseline 字段同步
75
+ 6. 跑 trigger 复测:5 个真实提问覆盖新 API
76
+ 7. **改 skill 内容 + 更新 CHANGELOG.md**
77
+ 8. **bump 版本号**:`node scripts/bump.mjs patch`(或 `minor` / `major` / `--set <版本>`)—— 一次改掉所有版本位
78
+ 9. **自检**:`node scripts/bump.mjs --check` —— 不一致就 exit 1
79
+ 10. **结构自检**:`node scripts/check-structure.mjs` —— 相对链接 / 锚点 / frontmatter / 章节完整性,不一致就 exit 1
80
+ 11. **npm 包自检**:`node scripts/build-npm.mjs` —— 校验两棵 skill 树的相对链接、无本机绝对路径、vendor 齐全(15 个 skill + LICENSE + NOTICE),不一致就 exit 1
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,见 §四
84
+ 14. **发 npm 包**(方式 A 的分发源):`npm publish` —— 发到哪由仓库根 `.npmrc` 决定(内网 / 公网),见 §五
85
+ 15. **验证已发布的包**:`node scripts/verify-published.mjs` —— 确认可安装、内容与源码一致、CLI 能发现全部 skill、链接全解析,不一致就 exit 1
86
+
87
+ > v0.2.15 起,第 8 步之前的手工改版本号(各 manifest + DOMAINS.md + DEMO-SKILL-MAP.md)全部由 `scripts/bump.mjs` 代劳,避免版本漂移。
88
+
89
+ ---
90
+
91
+ ## 四、手工打的 tag
92
+
93
+ 本仓库**不跑 CI**(内网 GitLab 上没有 runner),下面这步要人工执行。
94
+
95
+ **打 tag** —— 带版本约束的安装串(`"anov-gis-skills@anov-gis-skills@^0.3.0"`)靠这个 tag 解析版本,没有匹配 tag 会报 `no-matching-tag`:
96
+
97
+ ```bash
98
+ VERSION=$(node -p "require('./.claude-plugin/plugin.json').version")
99
+ git tag -a "anov-gis-skills--v${VERSION}" -m "release ${VERSION}"
100
+ git push origin "anov-gis-skills--v${VERSION}"
101
+ ```
102
+
103
+ 不带约束的安装(`"anov-gis-skills@anov-gis-skills": true`)只认 marketplace 里的版本号,**不依赖 tag** —— 但 tag 仍是不可变的发布点,建议每次发版都打。
104
+
105
+ > tag 已存在时跳过重复打,避免覆盖历史注解。
106
+
107
+ ---
108
+
109
+ ## 五、npm 包发版
110
+
111
+ 跟 tag 一样要人工执行。**默认发到公网 npm**(`https://registry.npmjs.org/`)—— 内网直连公网即可安装,用户不需要配任何 registry。
112
+
113
+ ```bash
114
+ npm publish
115
+ ```
116
+
117
+ > ⚠️ **发布目标由仓库根 `.npmrc` 控制**(与 ANOV-GIS 仓库 `@anov/gis` 的做法一致)。`.npmrc` 里 `registry=` 那行启用哪个就发到哪个;两行都注释掉则回退到全局默认。
118
+ >
119
+ > ⚠️ **`package.json` 里不能有 `publishConfig.registry`** —— 它会**覆盖** `.npmrc`,把这个开关锁死。`scripts/bump.mjs --check` 会拦。
120
+ >
121
+ > ⚠️ **npmjs.org 账号开了双因素认证** —— `npm publish` 要 OTP 验证码:`npm publish --otp=<6位码>`。没带 OTP 会报 `EOTP`。
122
+ >
123
+ > ⚠️ **同版本号不能重发**(内网 Verdaccio 报 `409 Conflict`,`--force` 也不行)。改了内容就得 bump 一个新版本再发。
124
+
125
+ ### 发版前自检
126
+
127
+ ```bash
128
+ node scripts/bump.mjs --check # 版本一致性(含 package.json 与 manifest 同步)
129
+ node scripts/check-structure.mjs # 结构 / 链接 / 锚点
130
+ node scripts/build-npm.mjs # 包内容(链接不逃出 skills/ 树、无本机路径)
131
+ ```
132
+
133
+ ### 发完验证
134
+
135
+ ```bash
136
+ node scripts/verify-published.mjs
137
+ ```
138
+
139
+ 验五件事:
140
+
141
+ | # | 验什么 | 失败意味着 |
142
+ |---|---|---|
143
+ | 1 | registry 上查得到该版本,元数据齐全(version / bin / engines / license) | 包没发上去,或发到了别的 registry |
144
+ | 2 | 能真的装下来(tarball 可下载可解包) | tarball 损坏 / registry 权限问题 |
145
+ | 3 | 装出来的 skill 与仓库源码**逐字节一致** | `files` 白名单漏了文件,或发了旧版本 |
146
+ | 4 | skills CLI 能发现**全部 12 个** skill | frontmatter 没被严格 YAML 解析(v0.2.17 踩过:只发现 10/12) |
147
+ | 5 | skill 内相对链接在拷贝安装语义下**全部可解析** | 有链接逃出 `skills/` 树,别的机器上会断 |
148
+
149
+ 出问题时会逐条列出并以**退出码 1** 结束,可以直接挂到流水线里。
150
+
151
+ 指定 registry / 版本:
152
+
153
+ ```bash
154
+ node scripts/verify-published.mjs --registry https://registry.npmjs.org/
155
+ node scripts/verify-published.mjs --version 0.2.19
156
+ ```
157
+
158
+ > ⚠️ **npm 包版本号与 plugin 版本号必须一致** —— `scripts/bump.mjs` 会同时改两处,`--check` 会把它们揪出来。别手工改 `package.json` 的 `version`。
159
+ >
160
+ > ⚠️ **公网包里的内网信息**:`skills/anovgis-imagery-terrain/SKILL.md` 的三处瓦片服务示例地址是内网真实服务,外部网络访问不到。README / package.json / CHANGELOG 里的内网 GitLab 地址已全部移除(改为占位说明)。
@@ -0,0 +1,408 @@
1
+ #!/usr/bin/env node
2
+ // @anov/gis-skills-cli —— 把 anov-gis 与 Cesium 两套 skill 装到 AI 编码智能体,
3
+ // 并把 anov-gis-mcp 的 MCP 条目写进各智能体的配置。
4
+ //
5
+ // anov-gis-skills setup 装全部 skill(+ 写 MCP 配置)
6
+ // anov-gis-skills mcp add 只写 MCP 配置
7
+ // anov-gis-skills mcp remove 删掉 MCP 配置
8
+ // anov-gis-skills mcp show 看当前配置状态
9
+ // anov-gis-skills doctor 自检
10
+ // anov-gis-skills uninstall 清理
11
+ //
12
+ // 底层用 vercel-labs 的 skills CLI(npm 包 `skills`)搬 skill —— 它管着 30+ 智能体的
13
+ // skills 目录映射。锁 1.5.18 而不是 latest:1.6.0+ 的 engines 要求 node >= 22.20.0,
14
+ // 而团队环境实测是 node 22.19.0,锁低版本才能在 node >= 18 全覆盖。
15
+ //
16
+ // MCP 配置由我们自己写(lib/mcp-config.mjs),不引第三方库 —— 只覆盖我们真正常用的
17
+ // 几个智能体,且只动自己那一个条目。
18
+
19
+ import { spawnSync } from "node:child_process";
20
+ import { readdirSync, existsSync, readFileSync } from "node:fs";
21
+ import { fileURLToPath } from "node:url";
22
+ import { dirname, join, resolve } from "node:path";
23
+ import { homedir } from "node:os";
24
+
25
+ import {
26
+ MCP_SERVER_NAME,
27
+ SUPPORTED_AGENTS,
28
+ agentTargets,
29
+ buildServerConfig,
30
+ collectEnv,
31
+ readServer,
32
+ removeServer,
33
+ writeServer,
34
+ } from "../lib/mcp-config.mjs";
35
+
36
+ const PKG_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
37
+ const SKILLS_DIR = join(PKG_ROOT, "skills");
38
+ const CESIUM_DIR = join(PKG_ROOT, "vendor", "cesiumjs-skills", "skills");
39
+ const SKILLS_CLI = "skills@1.5.18";
40
+
41
+ const MCP_PACKAGE = "anov-gis-mcp";
42
+
43
+ // ---------------------------------------------------------------- 参数解析
44
+
45
+ function parseArgs(argv) {
46
+ const out = { _: [], agents: [], mcpArgs: [], unknown: [] };
47
+ for (let i = 0; i < argv.length; i++) {
48
+ const a = argv[i];
49
+ if (a === "-g" || a === "--global") out.scope = "global";
50
+ else if (a === "--project") out.scope = "project";
51
+ else if (a === "--scope") out.scope = argv[++i];
52
+ else if (a === "-y" || a === "--yes") out.yes = true;
53
+ else if (a === "--print") out.print = true;
54
+ else if (a === "--no-mcp") out.noMcp = true;
55
+ else if (a === "--mcp") out.mcp = true;
56
+ 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];
60
+ else if (a === "--mcp-url") out.mcpUrl = argv[++i];
61
+ else if (a === "--mcp-command") out.mcpCommand = argv[++i];
62
+ else if (a === "--mcp-arg") {
63
+ while (argv[++i] && !argv[i].startsWith("-")) out.mcpArgs.push(argv[i]);
64
+ } else if (a === "-h" || a === "--help") out.help = true;
65
+ else if (a.startsWith("-")) out.unknown.push(a);
66
+ else out._.push(a);
67
+ }
68
+ return out;
69
+ }
70
+
71
+ const HELP = `anov-gis-skills —— 装 skill + 接 MCP
72
+
73
+ 用法
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 的东西)
80
+
81
+ 通用选项
82
+ --scope global|project 装到哪(默认 global = 用户级,任何项目都能用)
83
+ -g, --global 等价于 --scope global
84
+ --project 等价于 --scope project
85
+ -a, --agent <名字...> 只装给指定智能体(默认全部检测到的)
86
+ -y, --yes 跳过所有交互
87
+ --print 只打印要写的内容,不落盘
88
+
89
+ MCP 选项
90
+ --mcp-agent <名字> 写到哪个智能体(${SUPPORTED_AGENTS.join(" / ")},默认 claude-code)
91
+ --mcp-mode stdio|http 连接方式(默认 stdio)
92
+ --mcp-url <地址> HTTP 模式的服务地址(stdio 模式不需要)
93
+ --mcp-command <命令> stdio 模式的启动命令(默认 npx)
94
+ --mcp-arg <参数...> stdio 模式的参数(默认 -y anov-gis-mcp --stdio)
95
+ --no-mcp setup 时不写 MCP 配置
96
+ `;
97
+
98
+ // ---------------------------------------------------------------- 基础工具
99
+
100
+ function skillNames(dir) {
101
+ if (!existsSync(dir)) return [];
102
+ return readdirSync(dir, { withFileTypes: true })
103
+ .filter((d) => d.isDirectory() && existsSync(join(dir, d.name, "SKILL.md")))
104
+ .map((d) => d.name)
105
+ .sort();
106
+ }
107
+
108
+ /** 本包提供的全部 skill:anov-gis 12 个 + Cesium 15 个。 */
109
+ function allSkillNames() {
110
+ return [...skillNames(SKILLS_DIR), ...skillNames(CESIUM_DIR)];
111
+ }
112
+
113
+ function runSkillsCLI(extra, { quiet = false } = {}) {
114
+ const r = spawnSync("npx", ["-y", SKILLS_CLI, ...extra], {
115
+ stdio: quiet ? "pipe" : "inherit",
116
+ shell: process.platform === "win32",
117
+ encoding: "utf8",
118
+ });
119
+ if (r.error) {
120
+ console.error(`✗ 无法启动 npx:${r.error.message}`);
121
+ console.error(" 若 npm registry 不可达,先确认:npm config get registry");
122
+ process.exit(1);
123
+ }
124
+ return r.status ?? 1;
125
+ }
126
+
127
+ /** skill 装到用户级还是项目级。 */
128
+ function scopeFlags(scope) {
129
+ return scope === "global" ? ["--global"] : [];
130
+ }
131
+
132
+ function targetFlags(agents) {
133
+ return agents.length ? ["--agent", ...agents] : ["--all"];
134
+ }
135
+
136
+ // ---------------------------------------------------------------- setup
137
+
138
+ function cmdSetup(opts) {
139
+ const scope = opts.scope ?? "global";
140
+ const anov = skillNames(SKILLS_DIR);
141
+ const cesium = skillNames(CESIUM_DIR);
142
+
143
+ if (!anov.length) {
144
+ console.error("✗ 包内找不到 skill(skills/ 目录缺失或为空)");
145
+ process.exit(1);
146
+ }
147
+ if (!cesium.length) {
148
+ console.error("✗ 找不到 vendor/cesiumjs-skills/skills —— 重新发布本包,或手工同步 vendor");
149
+ process.exit(1);
150
+ }
151
+
152
+ console.log(`\n安装 ${anov.length + cesium.length} 个 skill`);
153
+ console.log(` anov-gis ${anov.length} 个`);
154
+ console.log(` cesiumjs ${cesium.length} 个`);
155
+ console.log(` 范围 ${scope === "global" ? "用户级(任何项目可用)" : "项目级(当前目录)"}`);
156
+
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);
163
+ }
164
+ }
165
+
166
+ if (!opts.noMcp) {
167
+ const mode = opts.mcpMode ?? "stdio";
168
+ const agent = opts.mcpAgent ?? "claude-code";
169
+ const config = buildServerConfig({
170
+ mode,
171
+ url: opts.mcpUrl,
172
+ command: opts.mcpCommand,
173
+ args: opts.mcpArgs.length ? opts.mcpArgs : undefined,
174
+ env: collectEnv(),
175
+ });
176
+ writeMcp(agent, scope, config, opts.print);
177
+ }
178
+
179
+ console.log("\n✓ 完成");
180
+ if (!opts.noMcp) {
181
+ console.log(" 验证:anov-gis-skills doctor");
182
+ }
183
+ }
184
+
185
+ // ---------------------------------------------------------------- MCP
186
+
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);
193
+ }
194
+ return targets;
195
+ }
196
+
197
+ function writeMcp(agent, scope, config, printOnly) {
198
+ for (const target of mcpTargets(agent, scope)) {
199
+ if (printOnly) {
200
+ console.log(`\n# ${target.label} → ${target.file}`);
201
+ console.log(JSON.stringify({ mcpServers: { [MCP_SERVER_NAME]: config } }, null, 2));
202
+ continue;
203
+ }
204
+ writeServer(target, MCP_SERVER_NAME, config);
205
+ console.log(` ✓ ${target.label}:${target.file} → ${MCP_SERVER_NAME}`);
206
+ }
207
+ }
208
+
209
+ function cmdMcp(opts) {
210
+ const action = opts._[0] ?? "show";
211
+ const scope = opts.scope ?? "global";
212
+ const agent = opts.mcpAgent ?? "claude-code";
213
+
214
+ if (action === "show") {
215
+ for (const target of mcpTargets(agent, scope)) {
216
+ const existing = readServer(target, MCP_SERVER_NAME);
217
+ console.log(`\n${target.label} —— ${target.file}`);
218
+ if (!existing) {
219
+ console.log(" · 未配置");
220
+ } else {
221
+ console.log(` ✓ 已配置:${JSON.stringify(existing)}`);
222
+ }
223
+ }
224
+ return;
225
+ }
226
+
227
+ if (action === "remove") {
228
+ for (const target of mcpTargets(agent, scope)) {
229
+ const r = removeServer(target, MCP_SERVER_NAME);
230
+ console.log(r.removed ? ` ✓ 已删除:${target.file}` : ` · 本来就没有:${target.file}`);
231
+ }
232
+ return;
233
+ }
234
+
235
+ if (action !== "add") {
236
+ console.error(`✗ 未知的 mcp 子命令:${action}(可用:add / remove / show)`);
237
+ process.exit(2);
238
+ }
239
+
240
+ const mode = opts.mcpMode ?? "stdio";
241
+ const config = buildServerConfig({
242
+ mode,
243
+ url: opts.mcpUrl,
244
+ command: opts.mcpCommand,
245
+ args: opts.mcpArgs.length ? opts.mcpArgs : undefined,
246
+ env: collectEnv(),
247
+ });
248
+ console.log(`\n接入 MCP(${mode})`);
249
+ writeMcp(agent, scope, config, opts.print);
250
+ if (!opts.print) console.log("\n✓ 完成。重启智能体生效。");
251
+ }
252
+
253
+ // ---------------------------------------------------------------- doctor
254
+
255
+ function frontmatterOk(text) {
256
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
257
+ if (!m) return { ok: false, why: "缺 frontmatter" };
258
+ const name = /^name\s*:\s*(\S+)\s*$/m.exec(m[1]);
259
+ const desc = /^description\s*:/m.exec(m[1]);
260
+ if (!name) return { ok: false, why: "frontmatter 缺 name" };
261
+ if (!desc) return { ok: false, why: "frontmatter 缺 description" };
262
+ const descLine = m[1].split(/\r?\n/).find((l) => l.startsWith("description:"));
263
+ const val = descLine.slice("description:".length).trim();
264
+ if (!val.startsWith("'") && !val.startsWith('"') && val.includes(":")) {
265
+ return { ok: false, why: "description 未加引号却含冒号(严格 YAML 解析会失败)" };
266
+ }
267
+ return { ok: true, name: name[1] };
268
+ }
269
+
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
+ function cmdDoctor(opts) {
288
+ const scope = opts.scope ?? "global";
289
+ const expected = allSkillNames();
290
+ let problems = 0;
291
+
292
+ console.log(`\n检查 @anov/gis-skills-cli\n`);
293
+ console.log(`包内 skill 数:${expected.length}`);
294
+
295
+ for (const [label, dir] of installedSkillDirs(scope)) {
296
+ 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(跳过)");
300
+ continue;
301
+ }
302
+ if (present.length === expected.length) {
303
+ console.log(` ✓ ${present.length} 个 skill 齐全`);
304
+ } else {
305
+ const missing = expected.filter((n) => !present.includes(n));
306
+ console.log(` ! 只装了 ${present.length}/${expected.length} 个,缺:${missing.join(", ")}`);
307
+ problems++;
308
+ }
309
+
310
+ let bad = 0;
311
+ for (const n of present) {
312
+ const r = frontmatterOk(readFileSync(join(dir, n, "SKILL.md"), "utf8"));
313
+ if (!r.ok) {
314
+ console.log(` ✗ ${n}: ${r.why}`);
315
+ bad++;
316
+ }
317
+ }
318
+ if (!bad && present.length) console.log(" ✓ frontmatter 全部可被严格 YAML 解析");
319
+ if (bad) problems++;
320
+ }
321
+
322
+ for (const agent of SUPPORTED_AGENTS) {
323
+ for (const target of agentTargets(agent, scope)) {
324
+ const existing = readServer(target, MCP_SERVER_NAME);
325
+ console.log(`\n── MCP · ${target.label} ── ${target.file}`);
326
+ if (existing) {
327
+ console.log(` ✓ 已配置(${existing.type ?? "unknown"})`);
328
+ } else {
329
+ console.log(" · 未配置 —— anov-gis-skills mcp add");
330
+ }
331
+ }
332
+ }
333
+
334
+ console.log("");
335
+ if (problems) {
336
+ console.error(`✗ 发现 ${problems} 处问题 —— 重装:anov-gis-skills setup\n`);
337
+ process.exit(1);
338
+ }
339
+ console.log("✓ 检查通过\n");
340
+ }
341
+
342
+ // ---------------------------------------------------------------- uninstall
343
+
344
+ function cmdUninstall(opts) {
345
+ const scope = opts.scope ?? "global";
346
+ const names = allSkillNames();
347
+ if (!names.length) {
348
+ console.error("✗ 找不到包内 skill 清单");
349
+ process.exit(1);
350
+ }
351
+
352
+ console.log(`\n移除 ${names.length} 个 skill(${scope === "global" ? "用户级" : "项目级"})`);
353
+ // 只按名字删本包提供的 skill —— skills remove --skill '*' 会连别的来源的一起删掉
354
+ const code = runSkillsCLI(["remove", ...names, ...targetFlags(opts.agents), ...scopeFlags(scope), "-y"]);
355
+ if (code !== 0) {
356
+ console.error(`✗ 移除 skill 失败(退出码 ${code})`);
357
+ process.exit(code);
358
+ }
359
+
360
+ const agent = opts.mcpAgent ?? "claude-code";
361
+ for (const target of mcpTargets(agent, scope)) {
362
+ const r = removeServer(target, MCP_SERVER_NAME);
363
+ console.log(r.removed ? ` ✓ 已删除 MCP 条目:${target.file}` : ` · 没有 MCP 条目:${target.file}`);
364
+ }
365
+
366
+ console.log("\n✓ 清理完成");
367
+ }
368
+
369
+ // ---------------------------------------------------------------- main
370
+
371
+ function main() {
372
+ const argv = process.argv.slice(2);
373
+ const cmd = argv[0] && !argv[0].startsWith("-") ? argv[0] : "setup";
374
+ const opts = parseArgs(argv[0] && !argv[0].startsWith("-") ? argv.slice(1) : argv);
375
+
376
+ if (opts.help) {
377
+ console.log(HELP);
378
+ return;
379
+ }
380
+ if (opts.unknown.length) {
381
+ console.error(`✗ 未知选项:${opts.unknown.join(", ")}`);
382
+ console.error(" 用 anov-gis-skills --help 看用法");
383
+ process.exit(2);
384
+ }
385
+
386
+ switch (cmd) {
387
+ case "setup":
388
+ cmdSetup(opts);
389
+ break;
390
+ case "mcp":
391
+ cmdMcp(opts);
392
+ break;
393
+ case "doctor":
394
+ case "verify":
395
+ cmdDoctor(opts);
396
+ break;
397
+ case "uninstall":
398
+ case "remove":
399
+ cmdUninstall(opts);
400
+ break;
401
+ default:
402
+ console.error(`✗ 未知命令:${cmd}`);
403
+ console.error(" 用 anov-gis-skills --help 看用法");
404
+ process.exit(2);
405
+ }
406
+ }
407
+
408
+ main();