opencode-skills-doctor 0.6.3 → 0.8.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.
- package/README.md +2 -0
- package/docs/README.zh-CN.md +23 -0
- package/docs/REFERENCE.md +31 -4
- package/package.json +1 -1
- package/src/cli.mjs +43 -2
- package/src/discover.mjs +13 -3
- package/src/i18n.mjs +18 -2
- package/src/kimi.mjs +121 -0
package/README.md
CHANGED
|
@@ -116,6 +116,8 @@ All commands accept `--json`, `--lang en|zh`, `--budget`, `--root` and `--no-glo
|
|
|
116
116
|
| **DeepSeek Harness** | `install --client dsh` adds the MCP entry and the `skills-doctor-audit` skill |
|
|
117
117
|
| **Claude Code / Codex** | MCP server (`npx -y opencode-skills-doctor mcp`) |
|
|
118
118
|
| **Grok CLI / Cursor** | their skill roots are scanned natively |
|
|
119
|
+
| **Hermes Agent** | MCP entry in `~/.hermes/config.yaml`; `~/.hermes/skills` is scanned |
|
|
120
|
+
| **Kimi Code** | `install --client kimi` writes `~/.kimi-code/mcp.json` + the audit skill |
|
|
119
121
|
|
|
120
122
|
## Why it is safe
|
|
121
123
|
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -425,6 +425,29 @@ grok mcp doctor skills-doctor # ✓ server started · handshake OK · 1 tool d
|
|
|
425
425
|
|
|
426
426
|
Grok 的工具命名是 `<server>__<tool>`,所以调用 `skills_doctor__skills_doctor`;配置写入 `~/.grok/config.toml` 的 `[mcp_servers.skills-doctor]`。
|
|
427
427
|
|
|
428
|
+
### Hermes Agent(Nous Research)
|
|
429
|
+
|
|
430
|
+
Hermes 从 `~/.hermes/config.yaml` 读取 MCP server:
|
|
431
|
+
|
|
432
|
+
```yaml
|
|
433
|
+
mcp_servers:
|
|
434
|
+
skills_doctor:
|
|
435
|
+
command: "npx"
|
|
436
|
+
args: ["-y", "opencode-skills-doctor", "mcp"]
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Hermes 的工具命名是 `mcp_<server>_<tool>`,即 `mcp_skills_doctor_skills_doctor`。它的 skill 在 `~/.hermes/skills`(含导入的 `openclaw-imports/`)和项目 `.hermes/skills`,`check` 都会扫描。
|
|
440
|
+
|
|
441
|
+
### Kimi Code(Moonshot)
|
|
442
|
+
|
|
443
|
+
一条命令写入 MCP 配置和审计 skill:
|
|
444
|
+
|
|
445
|
+
```powershell
|
|
446
|
+
npx -y opencode-skills-doctor install --client kimi
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
它会把 `skills-doctor` 合并进 `~/.kimi-code/mcp.json`(或 `$KIMI_CODE_HOME/mcp.json`),并把 skill 复制到 `$KIMI_CODE_HOME/skills/`。之后开新的 Kimi Code 会话即可,工具名是 `mcp__skills_doctor__skills_doctor`。`check` 也会扫描 `~/.kimi-code/skills` 和项目 `.kimi-code/skills`;`uninstall --client kimi` 反向撤销。
|
|
450
|
+
|
|
428
451
|
## 检测规则
|
|
429
452
|
|
|
430
453
|
| 规则 | 级别 | 含义 |
|
package/docs/REFERENCE.md
CHANGED
|
@@ -10,7 +10,7 @@ Local scanner, linter, fixer, updater and **OpenCode plugin** for agent skills
|
|
|
10
10
|
|---|---|
|
|
11
11
|
| **What it is** | Audit, fix and update the skills your coding agent loads |
|
|
12
12
|
| **Interfaces** | CLI · OpenCode plugin (1.x / 2.x) · MCP server (Claude Code, Codex, DSH, …) · Grok CLI compatible |
|
|
13
|
-
| **Skill sources** | `.opencode/skills`, `.claude/skills`, `.agents/skills`, `.grok/skills`, `.cursor/skills`, `opencode.json(c)` entries, HTTP catalogs |
|
|
13
|
+
| **Skill sources** | `.opencode/skills`, `.claude/skills`, `.agents/skills`, `.grok/skills`, `.cursor/skills`, `.hermes/skills`, `.kimi-code/skills`, `opencode.json(c)` entries, HTTP catalogs |
|
|
14
14
|
| **What it finds** | shadowed IDs, duplicates, long descriptions, oversized bodies, per-step token budget, broken file references, invalid frontmatter, empty bodies, stale skills |
|
|
15
15
|
| **What it fixes** | archives shadowed definitions (reversible), hides low-frequency skills, syncs catalogs with drift detection |
|
|
16
16
|
| **Reports** | terminal, JSON, or a self-contained HTML report (`--html [file] --open`) |
|
|
@@ -408,6 +408,29 @@ grok mcp doctor skills-doctor # ✓ server started · handshake OK · 1 tool d
|
|
|
408
408
|
|
|
409
409
|
Grok namespaces tools as `<server>__<tool>`, so call `skills_doctor__skills_doctor`. The config lands in `~/.grok/config.toml` as `[mcp_servers.skills-doctor]`.
|
|
410
410
|
|
|
411
|
+
### Hermes Agent (Nous Research)
|
|
412
|
+
|
|
413
|
+
Hermes reads MCP servers from `~/.hermes/config.yaml`:
|
|
414
|
+
|
|
415
|
+
```yaml
|
|
416
|
+
mcp_servers:
|
|
417
|
+
skills_doctor:
|
|
418
|
+
command: "npx"
|
|
419
|
+
args: ["-y", "opencode-skills-doctor", "mcp"]
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Hermes names tools `mcp_<server>_<tool>`, so the tool is `mcp_skills_doctor_skills_doctor`. Its skills live in `~/.hermes/skills` (including imported `openclaw-imports/`) and project `.hermes/skills`; `check` scans both.
|
|
423
|
+
|
|
424
|
+
### Kimi Code (Moonshot)
|
|
425
|
+
|
|
426
|
+
One command writes the MCP entry and the audit skill:
|
|
427
|
+
|
|
428
|
+
```powershell
|
|
429
|
+
npx -y opencode-skills-doctor install --client kimi
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
It merges `skills-doctor` into `~/.kimi-code/mcp.json` (or `$KIMI_CODE_HOME/mcp.json`) and copies the skill to `$KIMI_CODE_HOME/skills/`. Start a new Kimi Code session afterwards; the tool is `mcp__skills_doctor__skills_doctor` (Kimi uses the same `mcp__<server>__<tool>` shape as Claude Code). `check` also scans `~/.kimi-code/skills` and project `.kimi-code/skills`; `uninstall --client kimi` reverses both.
|
|
433
|
+
|
|
411
434
|
## Rules
|
|
412
435
|
|
|
413
436
|
| Rule | Level | Meaning |
|
|
@@ -451,9 +474,13 @@ Grok namespaces tools as `<server>__<tool>`, so call `skills_doctor__skills_doct
|
|
|
451
474
|
6. project `.cursor/skills`
|
|
452
475
|
7. `~/.grok/skills`
|
|
453
476
|
8. project `.grok/skills`
|
|
454
|
-
9. `~/.
|
|
455
|
-
10. project `.
|
|
456
|
-
11.
|
|
477
|
+
9. `~/.hermes/skills`
|
|
478
|
+
10. project `.hermes/skills`
|
|
479
|
+
11. `~/.kimi-code/skills`
|
|
480
|
+
12. project `.kimi-code/skills`
|
|
481
|
+
13. `~/.config/opencode/skills`
|
|
482
|
+
14. project `.opencode/skills`
|
|
483
|
+
15. the `skills` array in `opencode.json(c)` (2.x) or `skills.paths` (1.x); relative paths resolve from the working directory; supports `~/`, absolute paths and HTTP catalogs
|
|
457
484
|
|
|
458
485
|
## JSON output
|
|
459
486
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-skills-doctor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Audit, fix and update agent skills: token cost, broken references, shadowed duplicates, HTML reports, catalog sync. CLI, OpenCode plugin, MCP server; works with OpenCode, Claude Code, Codex, Grok CLI, Cursor and DeepSeek Harness.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
package/src/cli.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { loadConfig } from "./config.mjs"
|
|
|
10
10
|
import { startMcpServer } from "./mcp.mjs"
|
|
11
11
|
import { installOpenCode, uninstallOpenCode } from "./install.mjs"
|
|
12
12
|
import { installDsh, uninstallDsh } from "./dsh.mjs"
|
|
13
|
+
import { installKimi, uninstallKimi } from "./kimi.mjs"
|
|
13
14
|
import { writeHtmlReport, openReport } from "./html.mjs"
|
|
14
15
|
import { planBudget } from "./plan.mjs"
|
|
15
16
|
import {
|
|
@@ -75,6 +76,7 @@ function parseArgs(argv) {
|
|
|
75
76
|
client: "auto",
|
|
76
77
|
profile: null,
|
|
77
78
|
dshHome: null,
|
|
79
|
+
kimiHome: null,
|
|
78
80
|
configDir: null,
|
|
79
81
|
noNpm: false,
|
|
80
82
|
html: null,
|
|
@@ -162,6 +164,9 @@ function parseArgs(argv) {
|
|
|
162
164
|
case "--dsh-home":
|
|
163
165
|
options.dshHome = path.resolve(args[++i] ?? ".")
|
|
164
166
|
break
|
|
167
|
+
case "--kimi-home":
|
|
168
|
+
options.kimiHome = path.resolve(args[++i] ?? ".")
|
|
169
|
+
break
|
|
165
170
|
case "--no-npm":
|
|
166
171
|
options.noNpm = true
|
|
167
172
|
break
|
|
@@ -600,8 +605,8 @@ export async function main(argv) {
|
|
|
600
605
|
process.exit(2)
|
|
601
606
|
}
|
|
602
607
|
|
|
603
|
-
if (!["auto", "opencode", "desktop", "dsh"].includes(options.client)) {
|
|
604
|
-
console.error("unsupported --client: expected auto, opencode, desktop or
|
|
608
|
+
if (!["auto", "opencode", "desktop", "dsh", "kimi"].includes(options.client)) {
|
|
609
|
+
console.error("unsupported --client: expected auto, opencode, desktop, dsh or kimi")
|
|
605
610
|
process.exitCode = 2
|
|
606
611
|
return
|
|
607
612
|
}
|
|
@@ -648,6 +653,25 @@ export async function main(argv) {
|
|
|
648
653
|
return
|
|
649
654
|
}
|
|
650
655
|
|
|
656
|
+
if (options.command === "install" && options.client === "kimi") {
|
|
657
|
+
const result = installKimi({ home: options.kimiHome ?? undefined, dryRun: options.dryRun })
|
|
658
|
+
if (options.json) {
|
|
659
|
+
console.log(JSON.stringify(result, null, 2))
|
|
660
|
+
} else {
|
|
661
|
+
console.log(bold(t("kimi.title")))
|
|
662
|
+
if (result.home) console.log(dim(t("kimi.home", { dir: result.home })))
|
|
663
|
+
if (result.mcpFile) console.log(dim(t("kimi.mcp", { file: result.mcpFile })))
|
|
664
|
+
if (result.skillDir) console.log(dim(t("kimi.skill", { dir: result.skillDir })))
|
|
665
|
+
if (result.dryRun) console.log(dim(t("install.dryRun")))
|
|
666
|
+
for (const action of result.actions) console.log(` · ${action}`)
|
|
667
|
+
for (const error of result.errors) console.log(red(t("install.error", { error })))
|
|
668
|
+
console.log("")
|
|
669
|
+
console.log(result.errors.length ? red(t("kimi.failed")) : dim(t("kimi.restart")))
|
|
670
|
+
}
|
|
671
|
+
process.exitCode = result.errors.length ? 1 : 0
|
|
672
|
+
return
|
|
673
|
+
}
|
|
674
|
+
|
|
651
675
|
if (options.command === "install") {
|
|
652
676
|
const result = installOpenCode({
|
|
653
677
|
configDir: options.configDir ?? undefined,
|
|
@@ -698,6 +722,23 @@ export async function main(argv) {
|
|
|
698
722
|
return
|
|
699
723
|
}
|
|
700
724
|
|
|
725
|
+
if (options.command === "uninstall" && options.client === "kimi") {
|
|
726
|
+
const result = uninstallKimi({ home: options.kimiHome ?? undefined, dryRun: options.dryRun })
|
|
727
|
+
if (options.json) {
|
|
728
|
+
console.log(JSON.stringify(result, null, 2))
|
|
729
|
+
} else {
|
|
730
|
+
console.log(bold(t("kimi.uninstallTitle")))
|
|
731
|
+
if (result.home) console.log(dim(t("kimi.home", { dir: result.home })))
|
|
732
|
+
if (result.dryRun) console.log(dim(t("install.dryRun")))
|
|
733
|
+
for (const action of result.actions) console.log(` · ${action}`)
|
|
734
|
+
for (const error of result.errors) console.log(red(t("install.error", { error })))
|
|
735
|
+
console.log("")
|
|
736
|
+
console.log(result.errors.length ? red(t("kimi.failed")) : dim(t("kimi.restart")))
|
|
737
|
+
}
|
|
738
|
+
process.exitCode = result.errors.length ? 1 : 0
|
|
739
|
+
return
|
|
740
|
+
}
|
|
741
|
+
|
|
701
742
|
if (options.command === "uninstall") {
|
|
702
743
|
const result = uninstallOpenCode({
|
|
703
744
|
configDir: options.configDir ?? undefined,
|
package/src/discover.mjs
CHANGED
|
@@ -120,9 +120,13 @@ function isUnder(parent, child) {
|
|
|
120
120
|
* 6. 项目 .cursor/skills
|
|
121
121
|
* 7. ~/.grok/skills
|
|
122
122
|
* 8. 项目 .grok/skills
|
|
123
|
-
* 9. ~/.
|
|
124
|
-
* 10. 项目 .
|
|
125
|
-
* 11.
|
|
123
|
+
* 9. ~/.hermes/skills(Hermes Agent)
|
|
124
|
+
* 10. 项目 .hermes/skills
|
|
125
|
+
* 11. ~/.kimi-code/skills(Kimi Code)
|
|
126
|
+
* 12. 项目 .kimi-code/skills
|
|
127
|
+
* 13. ~/.config/opencode/skills
|
|
128
|
+
* 14. 项目 .opencode/skills(项目根 → cwd)
|
|
129
|
+
* 15. opencode.json(c) 里的 skills 数组 / skills.paths
|
|
126
130
|
*/
|
|
127
131
|
export function discoverSources({ cwd = process.cwd(), home = os.homedir(), includeGlobal = true } = {}) {
|
|
128
132
|
const projectRoot = findProjectRoot(cwd) ?? path.resolve(cwd)
|
|
@@ -151,6 +155,12 @@ export function discoverSources({ cwd = process.cwd(), home = os.homedir(), incl
|
|
|
151
155
|
if (includeGlobal) addSource("grok-global", "global", path.join(home, ".grok", "skills"))
|
|
152
156
|
for (const dir of projectChain) addSource("grok-project", "project", path.join(dir, ".grok", "skills"))
|
|
153
157
|
|
|
158
|
+
if (includeGlobal) addSource("hermes-global", "global", path.join(home, ".hermes", "skills"))
|
|
159
|
+
for (const dir of projectChain) addSource("hermes-project", "project", path.join(dir, ".hermes", "skills"))
|
|
160
|
+
|
|
161
|
+
if (includeGlobal) addSource("kimi-global", "global", path.join(home, ".kimi-code", "skills"))
|
|
162
|
+
for (const dir of projectChain) addSource("kimi-project", "project", path.join(dir, ".kimi-code", "skills"))
|
|
163
|
+
|
|
154
164
|
if (includeGlobal) addSource("opencode-global", "global", path.join(home, ".config", "opencode", "skills"))
|
|
155
165
|
for (const dir of projectChain) addSource("opencode-project", "project", path.join(dir, ".opencode", "skills"))
|
|
156
166
|
|
package/src/i18n.mjs
CHANGED
|
@@ -36,9 +36,10 @@ Options:
|
|
|
36
36
|
--html [file] check: write a self-contained HTML report (default: skills-report.html)
|
|
37
37
|
--open check: open the HTML report after writing it
|
|
38
38
|
--config-dir <dir> install/uninstall: OpenCode config directory (default: ~/.config/opencode)
|
|
39
|
-
--client <name> install/uninstall: auto | opencode | desktop | dsh (default auto)
|
|
39
|
+
--client <name> install/uninstall: auto | opencode | desktop | dsh | kimi (default auto)
|
|
40
40
|
--profile <name> dsh: profile to install into (default: desktop, or the only one)
|
|
41
41
|
--dsh-home <dir> dsh: DSH home directory (default: ~/.dsh)
|
|
42
|
+
--kimi-home <dir> kimi: Kimi Code home (default: ~/.kimi-code or $KIMI_CODE_HOME)
|
|
42
43
|
--no-npm install/uninstall: do not run npm (1.x)
|
|
43
44
|
--apply upgrade: run npm install -g opencode-skills-doctor@latest
|
|
44
45
|
--no-update-check skip the automatic update check for this run
|
|
@@ -129,6 +130,13 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
|
|
|
129
130
|
"dsh.skill": " skill: {dir}",
|
|
130
131
|
"dsh.restart": "Restart DSH to load the MCP tool (HMR reloads the patch when enabled; the skill updates live).",
|
|
131
132
|
"dsh.failed": "DSH integration failed.",
|
|
133
|
+
"kimi.title": "Kimi Code integration",
|
|
134
|
+
"kimi.uninstallTitle": "Remove Kimi Code integration",
|
|
135
|
+
"kimi.home": " home: {dir}",
|
|
136
|
+
"kimi.mcp": " mcp: {file}",
|
|
137
|
+
"kimi.skill": " skill: {dir}",
|
|
138
|
+
"kimi.restart": "Start a new Kimi Code session to load the MCP tool.",
|
|
139
|
+
"kimi.failed": "Kimi Code integration failed.",
|
|
132
140
|
"update.toolAvailable":
|
|
133
141
|
" ! skills-doctor {latest} is available (you have {current}); update with: {cmd} · {url}",
|
|
134
142
|
"upgrade.title": "skills-doctor upgrade",
|
|
@@ -257,9 +265,10 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
|
|
|
257
265
|
--html [file] check: 生成自包含 HTML 报告(默认 skills-report.html)
|
|
258
266
|
--open check: 写完报告后用浏览器打开
|
|
259
267
|
--config-dir <dir> install/uninstall: OpenCode 配置目录(默认 ~/.config/opencode)
|
|
260
|
-
--client <name> install/uninstall: auto | opencode | desktop | dsh(默认 auto)
|
|
268
|
+
--client <name> install/uninstall: auto | opencode | desktop | dsh | kimi(默认 auto)
|
|
261
269
|
--profile <name> dsh: 安装到哪个 profile(默认 desktop,或唯一的一个)
|
|
262
270
|
--dsh-home <dir> dsh: DSH 主目录(默认 ~/.dsh)
|
|
271
|
+
--kimi-home <dir> kimi: Kimi Code 主目录(默认 ~/.kimi-code 或 $KIMI_CODE_HOME)
|
|
263
272
|
--no-npm install/uninstall: 不执行 npm(1.x)
|
|
264
273
|
--apply upgrade: 执行 npm install -g opencode-skills-doctor@latest
|
|
265
274
|
--no-update-check 本次运行跳过自动更新检查
|
|
@@ -350,6 +359,13 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
|
|
|
350
359
|
"dsh.skill": " skill: {dir}",
|
|
351
360
|
"dsh.restart": "重启 DSH 加载 MCP 工具(启用 HMR 时会自动重载 patch;skill 即时更新)。",
|
|
352
361
|
"dsh.failed": "DSH 集成失败。",
|
|
362
|
+
"kimi.title": "Kimi Code 集成",
|
|
363
|
+
"kimi.uninstallTitle": "撤销 Kimi Code 集成",
|
|
364
|
+
"kimi.home": " 主目录: {dir}",
|
|
365
|
+
"kimi.mcp": " mcp: {file}",
|
|
366
|
+
"kimi.skill": " skill: {dir}",
|
|
367
|
+
"kimi.restart": "开一个新的 Kimi Code 会话加载 MCP 工具。",
|
|
368
|
+
"kimi.failed": "Kimi Code 集成失败。",
|
|
353
369
|
"update.toolAvailable": " ! skills-doctor 有新版本 {latest}(当前 {current});更新:{cmd} · {url}",
|
|
354
370
|
"upgrade.title": "skills-doctor 自更新",
|
|
355
371
|
"upgrade.current": " 当前版本: {version}",
|
package/src/kimi.mjs
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from "node:fs"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import os from "node:os"
|
|
4
|
+
import { fileURLToPath } from "node:url"
|
|
5
|
+
|
|
6
|
+
export const SERVER_NAME = "skills-doctor"
|
|
7
|
+
export const SKILL_NAME = "skills-doctor-audit"
|
|
8
|
+
|
|
9
|
+
const ENTRY = { command: "npx", args: ["-y", "opencode-skills-doctor", "mcp"] }
|
|
10
|
+
|
|
11
|
+
export function kimiHome(env = process.env) {
|
|
12
|
+
return env.KIMI_CODE_HOME || path.join(os.homedir(), ".kimi-code")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function skillSourceDir() {
|
|
16
|
+
return fileURLToPath(new URL("../skills/dsh-skills-doctor", import.meta.url))
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function readJson(file) {
|
|
20
|
+
if (!fs.existsSync(file)) return {}
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(fs.readFileSync(file, "utf8").replace(/^\uFEFF/, ""))
|
|
23
|
+
} catch {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function copySkill(source, target, dryRun, act, dry) {
|
|
29
|
+
const file = path.join(source, "SKILL.md")
|
|
30
|
+
if (!fs.existsSync(file)) return false
|
|
31
|
+
const targetFile = path.join(target, "SKILL.md")
|
|
32
|
+
if (fs.existsSync(targetFile) && fs.readFileSync(targetFile, "utf8") === fs.readFileSync(file, "utf8")) {
|
|
33
|
+
act(`skill already up to date ${target}`)
|
|
34
|
+
return true
|
|
35
|
+
}
|
|
36
|
+
if (dryRun) {
|
|
37
|
+
dry(`install skill ${SKILL_NAME} into ${target}`)
|
|
38
|
+
return true
|
|
39
|
+
}
|
|
40
|
+
fs.mkdirSync(target, { recursive: true })
|
|
41
|
+
fs.copyFileSync(file, targetFile)
|
|
42
|
+
act(`wrote skill ${targetFile}`)
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function removeSkill(target, dryRun, act, dry) {
|
|
47
|
+
const file = path.join(target, "SKILL.md")
|
|
48
|
+
if (!fs.existsSync(file)) return false
|
|
49
|
+
if (!fs.readFileSync(file, "utf8").includes("Skill audit workflow")) {
|
|
50
|
+
act(`kept ${target} (not the generated skill)`)
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
if (dryRun) {
|
|
54
|
+
dry(`delete skill ${target}`)
|
|
55
|
+
return true
|
|
56
|
+
}
|
|
57
|
+
fs.rmSync(target, { recursive: true, force: true })
|
|
58
|
+
act(`deleted skill ${target}`)
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function installKimi({ home = kimiHome(), dryRun = false } = {}) {
|
|
63
|
+
const actions = []
|
|
64
|
+
const errors = []
|
|
65
|
+
const act = (message) => actions.push(message)
|
|
66
|
+
const dry = (message) => actions.push(`[dry-run] ${message}`)
|
|
67
|
+
|
|
68
|
+
const mcpFile = path.join(home, "mcp.json")
|
|
69
|
+
const skillDir = path.join(home, "skills", SKILL_NAME)
|
|
70
|
+
|
|
71
|
+
const config = readJson(mcpFile)
|
|
72
|
+
if (config === null) {
|
|
73
|
+
errors.push(`cannot parse ${mcpFile}`)
|
|
74
|
+
return { ok: false, home, mcpFile, skillDir, actions, errors, dryRun }
|
|
75
|
+
}
|
|
76
|
+
config.mcpServers = config.mcpServers && typeof config.mcpServers === "object" ? config.mcpServers : {}
|
|
77
|
+
|
|
78
|
+
const existing = config.mcpServers[SERVER_NAME]
|
|
79
|
+
const same = existing && existing.command === ENTRY.command && JSON.stringify(existing.args) === JSON.stringify(ENTRY.args)
|
|
80
|
+
if (same) {
|
|
81
|
+
act(`${SERVER_NAME} already present in ${mcpFile}`)
|
|
82
|
+
} else if (dryRun) {
|
|
83
|
+
dry(`add ${SERVER_NAME} to mcpServers in ${mcpFile}`)
|
|
84
|
+
} else {
|
|
85
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
86
|
+
if (fs.existsSync(mcpFile)) fs.copyFileSync(mcpFile, `${mcpFile}.bak-${stamp}`)
|
|
87
|
+
config.mcpServers[SERVER_NAME] = ENTRY
|
|
88
|
+
fs.mkdirSync(home, { recursive: true })
|
|
89
|
+
fs.writeFileSync(mcpFile, JSON.stringify(config, null, 2) + "\n", "utf8")
|
|
90
|
+
act(`wrote ${SERVER_NAME} to ${mcpFile}`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
copySkill(skillSourceDir(), skillDir, dryRun, act, dry)
|
|
94
|
+
return { ok: errors.length === 0, home, mcpFile, skillDir, actions, errors, dryRun }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function uninstallKimi({ home = kimiHome(), dryRun = false } = {}) {
|
|
98
|
+
const actions = []
|
|
99
|
+
const errors = []
|
|
100
|
+
const act = (message) => actions.push(message)
|
|
101
|
+
const dry = (message) => actions.push(`[dry-run] ${message}`)
|
|
102
|
+
|
|
103
|
+
const mcpFile = path.join(home, "mcp.json")
|
|
104
|
+
const skillDir = path.join(home, "skills", SKILL_NAME)
|
|
105
|
+
|
|
106
|
+
const config = readJson(mcpFile)
|
|
107
|
+
if (config && config.mcpServers && config.mcpServers[SERVER_NAME]) {
|
|
108
|
+
if (dryRun) {
|
|
109
|
+
dry(`remove ${SERVER_NAME} from ${mcpFile}`)
|
|
110
|
+
} else {
|
|
111
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
112
|
+
fs.copyFileSync(mcpFile, `${mcpFile}.bak-${stamp}`)
|
|
113
|
+
delete config.mcpServers[SERVER_NAME]
|
|
114
|
+
fs.writeFileSync(mcpFile, JSON.stringify(config, null, 2) + "\n", "utf8")
|
|
115
|
+
act(`removed ${SERVER_NAME} from ${mcpFile}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
removeSkill(skillDir, dryRun, act, dry)
|
|
120
|
+
return { ok: errors.length === 0, home, mcpFile, skillDir, actions, errors, dryRun }
|
|
121
|
+
}
|