opencode-skills-doctor 0.3.12 → 0.3.14
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 +16 -3
- package/docs/README.zh-CN.md +16 -3
- package/package.json +1 -1
- package/src/cli.mjs +24 -2
- package/src/i18n.mjs +10 -4
- package/src/install.mjs +108 -0
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ skills-doctor check
|
|
|
58
58
|
What a run looks like:
|
|
59
59
|
|
|
60
60
|
```text
|
|
61
|
-
skills-doctor 0.3.
|
|
61
|
+
skills-doctor 0.3.13
|
|
62
62
|
sources: 5 (2 exist) definitions: 29 unique IDs: 29 advertised: 29 description budget: ~3074 tokens
|
|
63
63
|
|
|
64
64
|
[E] token-budget Advertised skill descriptions cost about 3074 tokens per model step, over 2x the 1200 budget
|
|
@@ -201,7 +201,7 @@ export { SkillsDoctorPlugin } from "opencode-skills-doctor/v1"
|
|
|
201
201
|
}
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
It registers the same `skills_doctor` tool
|
|
204
|
+
It registers the same `skills_doctor` tool. 1.x plugins cannot register commands, so `skills-doctor install` writes a `/skills-doctor` markdown command.
|
|
205
205
|
|
|
206
206
|
> The package root entry is the V2 plugin and requires OpenCode 2.x. The CLI works on both lines.
|
|
207
207
|
|
|
@@ -225,6 +225,19 @@ Once installed you get:
|
|
|
225
225
|
- the `/skills-doctor` command (1.x: generated markdown command; 2.x: plugin command);
|
|
226
226
|
- a one-time system-prompt warning when the skill description budget is exceeded.
|
|
227
227
|
|
|
228
|
+
### Uninstall
|
|
229
|
+
|
|
230
|
+
```powershell
|
|
231
|
+
npx opencode-skills-doctor uninstall
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- **2.x** — removes `opencode-skills-doctor` from `plugins`;
|
|
235
|
+
- **1.x** — removes the config entry, deletes the generated plugin entry and `/skills-doctor` command, and uninstalls the package (`--no-npm` skips that);
|
|
236
|
+
- only files generated by the installer are deleted — anything you edited is kept and reported;
|
|
237
|
+
- config files are backed up before editing; `--dry-run` previews, `--config-dir` targets a custom location.
|
|
238
|
+
|
|
239
|
+
Your data is never touched: `.skills-doctor.json`, `.skills-doctor.lock.json` and `.skills-doctor-archive/` stay until you delete them. MCP entries in other clients (Claude Code, Codex, DSH) are per-client configs and must be removed there.
|
|
240
|
+
|
|
228
241
|
## MCP server (Claude Code, Codex, Kimi Code, any MCP client)
|
|
229
242
|
|
|
230
243
|
`skills-doctor mcp` speaks the Model Context Protocol over stdio and exposes one tool: `skills_doctor` with arguments `directory?`, `budget?`, `lang?` and `global?`. It does not need OpenCode installed.
|
|
@@ -351,7 +364,7 @@ See [SECURITY.md](SECURITY.md) for the full threat model, hardening checklist an
|
|
|
351
364
|
|
|
352
365
|
- OpenCode **2.x**: add `"plugins": ["opencode-skills-doctor"]` to `opencode.jsonc` — done.
|
|
353
366
|
- OpenCode **1.x**: needs `npm i --prefix ~/.config/opencode opencode-skills-doctor` plus a one-line local plugin file (`export { SkillsDoctorPlugin } from "opencode-skills-doctor/v1"`), because 1.x resolves plugins differently and `@opencode-ai/plugin` is an optional peer.
|
|
354
|
-
- 1.x has
|
|
367
|
+
- 1.x has no plugin-registered `/skills-doctor` command; `skills-doctor install` writes a markdown command file instead.
|
|
355
368
|
|
|
356
369
|
**Safety / behavior**
|
|
357
370
|
|
package/docs/README.zh-CN.md
CHANGED
|
@@ -58,7 +58,7 @@ skills-doctor check
|
|
|
58
58
|
跑出来大概是这样:
|
|
59
59
|
|
|
60
60
|
```text
|
|
61
|
-
skills-doctor 0.3.
|
|
61
|
+
skills-doctor 0.3.13
|
|
62
62
|
来源: 5 个(存在 2 个) 定义: 29 唯一 ID: 29 会展示: 29 描述预算: ~3074 tokens
|
|
63
63
|
|
|
64
64
|
[E] token-budget 模型每步要看到的 skill 描述约 3074 tokens,超过预算 1200 的 2 倍
|
|
@@ -201,7 +201,7 @@ export { SkillsDoctorPlugin } from "opencode-skills-doctor/v1"
|
|
|
201
201
|
}
|
|
202
202
|
```
|
|
203
203
|
|
|
204
|
-
它会注册同样的 `skills_doctor`
|
|
204
|
+
它会注册同样的 `skills_doctor` 工具。1.x 插件无法注册命令,`skills-doctor install` 会生成 `/skills-doctor` 的 markdown 命令。
|
|
205
205
|
|
|
206
206
|
> 包根入口是 V2 插件,需要 OpenCode 2.x;CLI 两条线都能用。
|
|
207
207
|
|
|
@@ -225,6 +225,19 @@ npx opencode-skills-doctor install
|
|
|
225
225
|
- `/skills-doctor` 命令(1.x 是生成的 markdown 命令,2.x 是插件命令);
|
|
226
226
|
- 当 skill 描述预算超标时,系统提示里会注入一次提醒(每个会话最多一次)。
|
|
227
227
|
|
|
228
|
+
### 卸载
|
|
229
|
+
|
|
230
|
+
```powershell
|
|
231
|
+
npx opencode-skills-doctor uninstall
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
- **2.x** —— 从 `plugins` 移除 `opencode-skills-doctor`;
|
|
235
|
+
- **1.x** —— 移除配置条目、删除生成的插件入口和 `/skills-doctor` 命令、卸载 npm 包(`--no-npm` 可跳过);
|
|
236
|
+
- 只删除安装器生成的文件;你手改过的文件会保留并提示;
|
|
237
|
+
- 改配置前会备份;`--dry-run` 预览,`--config-dir` 指定目录。
|
|
238
|
+
|
|
239
|
+
你的数据不会被自动删除:`.skills-doctor.json`、`.skills-doctor.lock.json`、`.skills-doctor-archive/` 需要手动清理。其他客户端(Claude Code、Codex、DSH)的 MCP 配置也需在各自配置里删除。
|
|
240
|
+
|
|
228
241
|
## MCP server(Claude Code、Codex、Kimi Code、任何 MCP 客户端)
|
|
229
242
|
|
|
230
243
|
`skills-doctor mcp` 用 stdio 讲 Model Context Protocol,只暴露一个工具:`skills_doctor`,参数 `directory?`、`budget?`、`lang?`、`global?`。不需要装 OpenCode。
|
|
@@ -351,7 +364,7 @@ Windows 上如果 stdio 起不来 `npx`,改成 `command: cmd`、`args: ["/c",
|
|
|
351
364
|
|
|
352
365
|
- OpenCode **2.x**:在 `opencode.jsonc` 加 `"plugins": ["opencode-skills-doctor"]` 即可。
|
|
353
366
|
- OpenCode **1.x**:需要 `npm i --prefix ~/.config/opencode opencode-skills-doctor`,再加一个一行本地插件文件(`export { SkillsDoctorPlugin } from "opencode-skills-doctor/v1"`)——因为 1.x 解析插件的机制不同,且 `@opencode-ai/plugin` 是可选 peer 依赖。
|
|
354
|
-
- 1.x
|
|
367
|
+
- 1.x 没有「插件注册 /skills-doctor 命令」的能力;`skills-doctor install` 会生成 markdown 命令文件来代替。
|
|
355
368
|
|
|
356
369
|
**安全性 / 行为**
|
|
357
370
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-skills-doctor",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.14",
|
|
4
4
|
"description": "Audit, fix and update agent skills: per-step context cost, shadowed duplicates, safe archive, catalog sync. CLI, OpenCode plugin and MCP server (Claude Code / Codex / any MCP client).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
package/src/cli.mjs
CHANGED
|
@@ -8,7 +8,7 @@ import { planFixes, applyFixes, hideSkills } from "./fix.mjs"
|
|
|
8
8
|
import { auditSkills } from "./audit.mjs"
|
|
9
9
|
import { loadConfig } from "./config.mjs"
|
|
10
10
|
import { startMcpServer } from "./mcp.mjs"
|
|
11
|
-
import { installOpenCode } from "./install.mjs"
|
|
11
|
+
import { installOpenCode, uninstallOpenCode } from "./install.mjs"
|
|
12
12
|
import { padDisplay } from "./parse.mjs"
|
|
13
13
|
import { detectLang, normalizeLang, translator } from "./i18n.mjs"
|
|
14
14
|
|
|
@@ -64,7 +64,7 @@ function parseArgs(argv) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
const args = [...argv]
|
|
67
|
-
const commands = new Set(["check", "scan", "update", "fix", "install", "mcp", "help"])
|
|
67
|
+
const commands = new Set(["check", "scan", "update", "fix", "install", "uninstall", "mcp", "help"])
|
|
68
68
|
if (args[0] && commands.has(args[0])) {
|
|
69
69
|
options.command = args.shift()
|
|
70
70
|
}
|
|
@@ -475,6 +475,28 @@ export async function main(argv) {
|
|
|
475
475
|
return
|
|
476
476
|
}
|
|
477
477
|
|
|
478
|
+
if (options.command === "uninstall") {
|
|
479
|
+
const result = uninstallOpenCode({
|
|
480
|
+
configDir: options.configDir ?? undefined,
|
|
481
|
+
dryRun: options.dryRun,
|
|
482
|
+
removePackage: !options.noNpm,
|
|
483
|
+
})
|
|
484
|
+
if (options.json) {
|
|
485
|
+
console.log(JSON.stringify(result, null, 2))
|
|
486
|
+
} else {
|
|
487
|
+
console.log(bold(t("uninstall.title")))
|
|
488
|
+
console.log(dim(t("install.version", { version: result.version ?? "unknown" })))
|
|
489
|
+
console.log(dim(t("install.configDir", { dir: result.configDir })))
|
|
490
|
+
if (result.dryRun) console.log(dim(t("install.dryRun")))
|
|
491
|
+
for (const action of result.actions) console.log(` · ${action}`)
|
|
492
|
+
for (const error of result.errors) console.log(red(t("install.error", { error })))
|
|
493
|
+
console.log("")
|
|
494
|
+
console.log(result.errors.length ? red(t("install.failed")) : dim(t("uninstall.next")))
|
|
495
|
+
}
|
|
496
|
+
process.exitCode = result.errors.length ? 1 : 0
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
|
|
478
500
|
if (options.command === "check") {
|
|
479
501
|
const audit = auditSkills({
|
|
480
502
|
cwd,
|
package/src/i18n.mjs
CHANGED
|
@@ -11,6 +11,7 @@ Usage:
|
|
|
11
11
|
skills-doctor fix [options] archive shadowed definitions (safe structural fixes)
|
|
12
12
|
skills-doctor update [options] compare and download skills from HTTP catalogs
|
|
13
13
|
skills-doctor install [options] wire the plugin into OpenCode (1.x / 2.x)
|
|
14
|
+
skills-doctor uninstall [options] remove it again (config entry, generated files, package)
|
|
14
15
|
skills-doctor mcp run as an MCP server (Claude Code, Codex, any MCP client)
|
|
15
16
|
|
|
16
17
|
Options:
|
|
@@ -28,8 +29,8 @@ Options:
|
|
|
28
29
|
--into <dir> update: output directory (default: .opencode/skills)
|
|
29
30
|
--check-only update: only report available updates
|
|
30
31
|
--prune update: delete skills that disappeared from the catalog
|
|
31
|
-
--config-dir <dir> install: OpenCode config directory (default: ~/.config/opencode)
|
|
32
|
-
--no-npm install: do not run npm
|
|
32
|
+
--config-dir <dir> install/uninstall: OpenCode config directory (default: ~/.config/opencode)
|
|
33
|
+
--no-npm install/uninstall: do not run npm (1.x)
|
|
33
34
|
-h, --help show this help
|
|
34
35
|
-v, --version print version
|
|
35
36
|
|
|
@@ -84,6 +85,8 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
|
|
|
84
85
|
"install.error": " ✗ {error}",
|
|
85
86
|
"install.failed": "Integration failed.",
|
|
86
87
|
"install.next": "Restart OpenCode to activate.",
|
|
88
|
+
"uninstall.title": "Remove OpenCode integration",
|
|
89
|
+
"uninstall.next": "Restart OpenCode to finish removal.",
|
|
87
90
|
"update.lockTooNew": " ! lock is newer than this tool; parsed in compatibility mode (unknown fields ignored)",
|
|
88
91
|
"update.detector": " check now sees these skills ({dir} is where opencode reads)",
|
|
89
92
|
"update.noCatalogs": "No HTTP catalog configured. Add https://... to the skills array in opencode.json(c).",
|
|
@@ -167,6 +170,7 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
|
|
|
167
170
|
skills-doctor fix [选项] 归档被遮蔽的定义(安全的结构性修复)
|
|
168
171
|
skills-doctor update [选项] 对比并拉取 HTTP catalog 里的 skill
|
|
169
172
|
skills-doctor install [选项] 把插件接入 OpenCode(1.x / 2.x)
|
|
173
|
+
skills-doctor uninstall [选项] 撤销安装(配置条目、生成的文件、npm 包)
|
|
170
174
|
skills-doctor mcp 以 MCP server 方式运行(Claude Code、Codex 等任何 MCP 客户端)
|
|
171
175
|
|
|
172
176
|
选项:
|
|
@@ -184,8 +188,8 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
|
|
|
184
188
|
--into <dir> update 的落盘目录(默认 .opencode/skills)
|
|
185
189
|
--check-only update 只报告可用更新,不下载
|
|
186
190
|
--prune update: 删除 catalog 中已移除的 skill
|
|
187
|
-
--config-dir <dir> install: OpenCode 配置目录(默认 ~/.config/opencode)
|
|
188
|
-
--no-npm install: 不执行 npm
|
|
191
|
+
--config-dir <dir> install/uninstall: OpenCode 配置目录(默认 ~/.config/opencode)
|
|
192
|
+
--no-npm install/uninstall: 不执行 npm(1.x)
|
|
189
193
|
-h, --help 帮助
|
|
190
194
|
-v, --version 版本
|
|
191
195
|
|
|
@@ -240,6 +244,8 @@ Exit codes: 0 ok; 1 errors (and warnings with --strict); 2 bad arguments or runt
|
|
|
240
244
|
"install.error": " ✗ {error}",
|
|
241
245
|
"install.failed": "集成失败。",
|
|
242
246
|
"install.next": "重启 OpenCode 生效。",
|
|
247
|
+
"uninstall.title": "撤销 OpenCode 集成",
|
|
248
|
+
"uninstall.next": "重启 OpenCode 完成卸载。",
|
|
243
249
|
"update.lockTooNew": " ! lock 版本比本工具新,已按兼容模式解析(未知字段忽略)",
|
|
244
250
|
"update.detector": " check 现在可以直接检测到这些 skill({dir} 是 opencode 会读取的目录)",
|
|
245
251
|
"update.noCatalogs": "没有配置 HTTP catalog。在 opencode.json(c) 的 skills 数组里写 https://... 即可。",
|
package/src/install.mjs
CHANGED
|
@@ -209,3 +209,111 @@ export function installOpenCode({ configDir = defaultConfigDir(), version = null
|
|
|
209
209
|
act("restart OpenCode; the skills_doctor tool and /skills-doctor command will be available")
|
|
210
210
|
return { version: resolvedVersion, major, configDir: dir, configFile: cfgFile, actions, errors, dryRun }
|
|
211
211
|
}
|
|
212
|
+
|
|
213
|
+
function removePackage(dir) {
|
|
214
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm"
|
|
215
|
+
const result = spawnSync(npm, ["uninstall", "--prefix", dir, PACKAGE_NAME, "--no-audit", "--no-fund"], {
|
|
216
|
+
stdio: "inherit",
|
|
217
|
+
env: process.env,
|
|
218
|
+
timeout: 180000,
|
|
219
|
+
})
|
|
220
|
+
return result.status === 0
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 撤销 install:移除配置里的插件条目、删除安装器生成的文件、可选卸载 npm 包。
|
|
225
|
+
* 只删除「由安装器生成」的文件,用户自己改过的一律保留并提示。
|
|
226
|
+
*/
|
|
227
|
+
export function uninstallOpenCode({ configDir = defaultConfigDir(), version = null, dryRun = false, removePackage: withPackage = true } = {}) {
|
|
228
|
+
const dir = path.resolve(configDir)
|
|
229
|
+
const actions = []
|
|
230
|
+
const errors = []
|
|
231
|
+
const dry = (message) => actions.push(`[dry-run] ${message}`)
|
|
232
|
+
const act = (message) => actions.push(message)
|
|
233
|
+
|
|
234
|
+
const cfgFile = configFile(dir)
|
|
235
|
+
const existingText = readText(cfgFile)
|
|
236
|
+
let config = {}
|
|
237
|
+
if (existingText !== null) {
|
|
238
|
+
try {
|
|
239
|
+
config = readJsonc(cfgFile)
|
|
240
|
+
} catch (err) {
|
|
241
|
+
errors.push(`cannot parse ${cfgFile}: ${err.message}`)
|
|
242
|
+
return { version, major: null, configDir: dir, configFile: cfgFile, actions, errors, dryRun }
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const resolvedVersion = version ?? detectVersion()
|
|
247
|
+
const inferredMajor = config.plugin !== undefined ? 1 : 2
|
|
248
|
+
const major = resolvedVersion ? Number(resolvedVersion.split(".")[0]) : inferredMajor
|
|
249
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, "-")
|
|
250
|
+
|
|
251
|
+
if (major >= 2) {
|
|
252
|
+
if (Array.isArray(config.plugins) && config.plugins.includes(PACKAGE_NAME)) {
|
|
253
|
+
const next = config.plugins.filter((entry) => entry !== PACKAGE_NAME)
|
|
254
|
+
if (!dryRun) {
|
|
255
|
+
const backupFile = backup(cfgFile, stamp)
|
|
256
|
+
if (backupFile) act(`backup ${backupFile}`)
|
|
257
|
+
writeJson(cfgFile, { ...config, plugins: next })
|
|
258
|
+
act(`removed "${PACKAGE_NAME}" from plugins in ${cfgFile}`)
|
|
259
|
+
} else dry(`remove "${PACKAGE_NAME}" from plugins in ${cfgFile}`)
|
|
260
|
+
} else {
|
|
261
|
+
act(`"${PACKAGE_NAME}" is not configured in plugins`)
|
|
262
|
+
}
|
|
263
|
+
act("restart OpenCode to finish removal")
|
|
264
|
+
return { version: resolvedVersion, major, configDir: dir, configFile: cfgFile, actions, errors, dryRun }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const pluginFile = path.join(dir, "plugins", PLUGIN_FILE)
|
|
268
|
+
const commandFile = path.join(dir, "command", COMMAND_FILE)
|
|
269
|
+
|
|
270
|
+
if (Array.isArray(config.plugin) && config.plugin.includes(pluginFile)) {
|
|
271
|
+
const next = config.plugin.filter((entry) => entry !== pluginFile)
|
|
272
|
+
if (!dryRun) {
|
|
273
|
+
const backupFile = backup(cfgFile, stamp)
|
|
274
|
+
if (backupFile) act(`backup ${backupFile}`)
|
|
275
|
+
writeJson(cfgFile, { ...config, plugin: next })
|
|
276
|
+
act(`removed plugin entry from ${cfgFile}`)
|
|
277
|
+
} else dry(`remove plugin entry from ${cfgFile}`)
|
|
278
|
+
} else {
|
|
279
|
+
act("plugin entry is not configured")
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (fs.existsSync(pluginFile)) {
|
|
283
|
+
const content = readText(pluginFile) ?? ""
|
|
284
|
+
if (content.includes("Generated by skills-doctor install")) {
|
|
285
|
+
if (!dryRun) {
|
|
286
|
+
fs.rmSync(pluginFile, { force: true })
|
|
287
|
+
act(`deleted ${pluginFile}`)
|
|
288
|
+
} else dry(`delete ${pluginFile}`)
|
|
289
|
+
} else {
|
|
290
|
+
act(`kept ${pluginFile} (not generated by skills-doctor)`)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (fs.existsSync(commandFile)) {
|
|
295
|
+
const content = readText(commandFile) ?? ""
|
|
296
|
+
if (content.includes("skills_doctor tool")) {
|
|
297
|
+
if (!dryRun) {
|
|
298
|
+
fs.rmSync(commandFile, { force: true })
|
|
299
|
+
act(`deleted ${commandFile}`)
|
|
300
|
+
} else dry(`delete ${commandFile}`)
|
|
301
|
+
} else {
|
|
302
|
+
act(`kept ${commandFile} (not generated by skills-doctor)`)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const hasPackage = fs.existsSync(path.join(dir, "node_modules", PACKAGE_NAME, "package.json"))
|
|
307
|
+
if (withPackage && hasPackage) {
|
|
308
|
+
if (dryRun) {
|
|
309
|
+
dry(`uninstall ${PACKAGE_NAME} from ${dir}`)
|
|
310
|
+
} else {
|
|
311
|
+
act(`uninstalling ${PACKAGE_NAME} from ${dir}`)
|
|
312
|
+
if (removePackage(dir)) act(`uninstalled ${PACKAGE_NAME}`)
|
|
313
|
+
else errors.push(`npm uninstall failed for ${PACKAGE_NAME}`)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
act("restart OpenCode to finish removal")
|
|
318
|
+
return { version: resolvedVersion, major, configDir: dir, configFile: cfgFile, actions, errors, dryRun }
|
|
319
|
+
}
|