dsh-global-todo 0.12.4 → 0.12.5
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 +16 -0
- package/README.md +11 -1
- package/README.zh.md +11 -0
- package/docs/FAQ.md +10 -0
- package/lib/index.js +1 -1
- package/package.json +7 -1
- package/tools/doctor-lib.mjs +123 -0
- package/tools/doctor.mjs +23 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## [0.12.5] — 2026-10-21
|
|
2
|
+
|
|
3
|
+
A check for the one install mistake that is invisible until the desktop complains.
|
|
4
|
+
|
|
5
|
+
- **`dsh-global-todo-doctor`** (`npx dsh-global-todo-doctor <dir>`, also `npm run doctor`): inspects
|
|
6
|
+
a directory that is supposed to contain the plugin — manifest, `dsh.bundle.patch`, host and
|
|
7
|
+
client entries, `locale/`, `docs/icon.svg`, `lib/` — and exits non-zero when something required
|
|
8
|
+
is missing. The case it exists for: a hand-copied `plugins-local` directory without `locale/`
|
|
9
|
+
still loads, while the desktop app prints `Plugin metadata … ENOENT … \locale`, which points at
|
|
10
|
+
the symptom and not the cause.
|
|
11
|
+
- **Six tests** drive the checker against real temporary directories: complete copy, missing
|
|
12
|
+
`locale/`, missing client entry, missing patch file, missing icon (warning only), and a directory
|
|
13
|
+
that is not the plugin at all.
|
|
14
|
+
- FAQ §15 now starts with the command instead of guessing, and both READMEs document it where a
|
|
15
|
+
hand-copied install would go wrong.
|
|
16
|
+
|
|
1
17
|
## [0.12.4] — 2026-10-21
|
|
2
18
|
|
|
3
19
|
The repository leaked its own location. Three real paths, and a guard that could not see them.
|
package/README.md
CHANGED
|
@@ -182,9 +182,19 @@ The plugin is a Loader row, not a hot-loaded script: **restart the host** (`dsh
|
|
|
182
182
|
after installing, then refresh the page. A "待办 / Todos" entry appears in the sidebar.
|
|
183
183
|
|
|
184
184
|
The npm package is the source of truth. A community catalog can lag behind it or point at an
|
|
185
|
-
older release; pin the version you want (`dsh plugin --profile web add dsh-global-todo@0.
|
|
185
|
+
older release; pin the version you want (`dsh plugin --profile web add dsh-global-todo@0.12.4`)
|
|
186
186
|
when it matters.
|
|
187
187
|
|
|
188
|
+
**Hand-copied a directory instead?** Check it before wondering why the plugins page looks wrong:
|
|
189
|
+
|
|
190
|
+
```sh
|
|
191
|
+
npx dsh-global-todo-doctor "$HOME/.dsh/plugins-local/全局待办插件"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
It verifies the manifest, the patch file, both entries, `locale/`, the icon and `lib/`, and exits
|
|
195
|
+
non-zero when something required is missing — the `locale/` case is the field failure that makes
|
|
196
|
+
the desktop app print `Plugin metadata … ENOENT … \locale` while the plugin still loads.
|
|
197
|
+
|
|
188
198
|
To roll back:
|
|
189
199
|
|
|
190
200
|
```sh
|
package/README.zh.md
CHANGED
|
@@ -162,6 +162,17 @@ dsh plugin --profile web add /绝对路径/dsh-global-todo
|
|
|
162
162
|
插件是宿主组合里的**一行 Loader**,不是热加载脚本:**装完要重启宿主**(`dsh web`),
|
|
163
163
|
再刷新页面。侧栏会出现「待办」入口。
|
|
164
164
|
|
|
165
|
+
**如果你是手动拷贝目录(而不是从 npm 装)**,先自检一遍,别靠猜:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
npx dsh-global-todo-doctor "$HOME/.dsh/plugins-local/全局待办插件"
|
|
169
|
+
# Windows: npx dsh-global-todo-doctor "$env:USERPROFILE\.dsh\plugins-local\全局待办插件"
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
它逐项检查 manifest、patch 文件、两个入口、`locale/`、图标和 `lib/`;缺必需项时退出码为 1。
|
|
173
|
+
最容易漏的是 `locale/`——少了它插件照样加载,但桌面端会报
|
|
174
|
+
`Plugin metadata … ENOENT … \locale`。
|
|
175
|
+
|
|
165
176
|
npm 上的包才是事实来源。社区目录可能落后于 npm、或指向某个旧 release;需要确定版本时
|
|
166
177
|
直接钉版本(`dsh plugin --profile web add dsh-global-todo@0.10.0`)。
|
|
167
178
|
|
package/docs/FAQ.md
CHANGED
|
@@ -166,6 +166,16 @@ rm -r "$DSH_HOME/global-todo"
|
|
|
166
166
|
也就是说:**你那份本地插件目录缺 `locale/` 文件夹**。常见于把仓库文件夹手动复制到
|
|
167
167
|
`%USERPROFILE%\.dsh\plugins-local\` 时只拷了 `lib`、`package.json`、`cordis.patch.yml`。
|
|
168
168
|
|
|
169
|
+
先确认到底缺什么,别靠猜:
|
|
170
|
+
|
|
171
|
+
```sh
|
|
172
|
+
npx dsh-global-todo-doctor "$HOME/.dsh/plugins-local/全局待办插件"
|
|
173
|
+
# Windows: npx dsh-global-todo-doctor "$env:USERPROFILE\.dsh\plugins-local\全局待办插件"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
它逐项检查 `package.json`、patch 文件、两个入口、`locale/`、图标与 `lib/`,缺哪样直接点出来,
|
|
177
|
+
缺必需项时退出码为 1(可以放进安装脚本里当门禁)。
|
|
178
|
+
|
|
169
179
|
修法二选一:
|
|
170
180
|
|
|
171
181
|
1. **推荐**:别用本地目录,直接装 npm 包(包里有 `locale/en.json`、`locale/zh.json`):
|
package/lib/index.js
CHANGED
|
@@ -36,7 +36,7 @@ import { createStore, resolveStorePath } from './store.js'
|
|
|
36
36
|
import { createTools } from './tools.js'
|
|
37
37
|
|
|
38
38
|
/** Must equal `package.json`'s version; `npm run check` enforces that. */
|
|
39
|
-
export const VERSION = '0.12.
|
|
39
|
+
export const VERSION = '0.12.5'
|
|
40
40
|
|
|
41
41
|
/** Plugin id. Also the Loader row id in `cordis.patch.yml`. */
|
|
42
42
|
export const name = 'dsh-global-todo'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-global-todo",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.5",
|
|
4
4
|
"icon": "./docs/icon.svg",
|
|
5
5
|
"description": "Cross-session todo list for DeepSeek Harness: one JSON file a person and an agent both write to, with step plans that ship as a numbered hand-off, session read-back, and a human confirmation before an agent's \"done\" counts.",
|
|
6
6
|
"type": "module",
|
|
@@ -52,6 +52,8 @@
|
|
|
52
52
|
"docs/FAQ.md",
|
|
53
53
|
"docs/icon.svg",
|
|
54
54
|
"docs/screenshots",
|
|
55
|
+
"tools/doctor.mjs",
|
|
56
|
+
"tools/doctor-lib.mjs",
|
|
55
57
|
"!docs/screenshots/demo.mp4",
|
|
56
58
|
"!docs/index.html",
|
|
57
59
|
"!docs/assets",
|
|
@@ -63,8 +65,12 @@
|
|
|
63
65
|
"CONTRIBUTING.md",
|
|
64
66
|
"CODE_OF_CONDUCT.md"
|
|
65
67
|
],
|
|
68
|
+
"bin": {
|
|
69
|
+
"dsh-global-todo-doctor": "./tools/doctor.mjs"
|
|
70
|
+
},
|
|
66
71
|
"scripts": {
|
|
67
72
|
"check": "node scripts/check.mjs",
|
|
73
|
+
"doctor": "node tools/doctor.mjs",
|
|
68
74
|
"test": "node --test \"test/**/*.test.mjs\"",
|
|
69
75
|
"verify": "npm run check && npm run check:render && npm test",
|
|
70
76
|
"preview": "node tools/preview.mjs",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A pre-flight check for an installed copy of this plugin.
|
|
3
|
+
*
|
|
4
|
+
* The failure this exists for is specific: a hand-copied `plugins-local` directory
|
|
5
|
+
* usually keeps `lib/`, `package.json` and the patch file, and drops `locale/` —
|
|
6
|
+
* the plugin still loads, the panel still works, and the desktop app prints
|
|
7
|
+
* `Plugin metadata … ENOENT … \locale` on the plugins page. Node resolves the
|
|
8
|
+
* `./locale/*.json` export *without* checking that the file exists, and the Host then
|
|
9
|
+
* scans the directory, which is why the symptom appears far from the cause.
|
|
10
|
+
*
|
|
11
|
+
* Everything here is a pure function of the directory contents, so the test suite can
|
|
12
|
+
* call it without spawning anything.
|
|
13
|
+
*/
|
|
14
|
+
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
15
|
+
import path from 'node:path'
|
|
16
|
+
|
|
17
|
+
const EXPORT_LOCALE = './locale/*.json'
|
|
18
|
+
|
|
19
|
+
const readJson = async (file) => {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(await readFile(file, 'utf8'))
|
|
22
|
+
} catch {
|
|
23
|
+
return null
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const exists = async (file) => {
|
|
28
|
+
try {
|
|
29
|
+
const info = await stat(file)
|
|
30
|
+
return info.isFile() || info.isDirectory()
|
|
31
|
+
} catch {
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {string} dir directory that should contain the plugin, e.g. …/plugins-local/全局待办插件
|
|
38
|
+
* @returns {Promise<{ok: boolean, dir: string, findings: {level: 'ok'|'warn'|'error', id: string, message: string, fix?: string}[], version?: string}>}
|
|
39
|
+
*/
|
|
40
|
+
export async function doctor(dir) {
|
|
41
|
+
const findings = []
|
|
42
|
+
const add = (level, id, message, fix) => findings.push({ level, id, message, ...(fix === undefined ? {} : { fix }) })
|
|
43
|
+
|
|
44
|
+
const manifestFile = path.join(dir, 'package.json')
|
|
45
|
+
const manifest = await readJson(manifestFile)
|
|
46
|
+
if (manifest === null) {
|
|
47
|
+
add('error', 'package-missing', 'package.json 读不到或不是合法 JSON', '把插件目录指到包含 package.json 的那一层(例如 …/plugins-local/全局待办插件)')
|
|
48
|
+
return { ok: false, dir, findings }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (manifest.name !== 'dsh-global-todo') {
|
|
52
|
+
add('warn', 'package-name', `package.json 里的 name 是 ${manifest.name ?? '(缺失)'},不是 dsh-global-todo`, '这个目录可能不是本插件')
|
|
53
|
+
} else {
|
|
54
|
+
add('ok', 'package-name', `${manifest.name}@${manifest.version ?? '?'}`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The patch file is what contributes the Loader row. Without it nothing loads.
|
|
58
|
+
const patch = manifest.dsh?.bundle?.patch
|
|
59
|
+
if (typeof patch !== 'string') {
|
|
60
|
+
add('error', 'patch-declared', 'package.json 没有声明 dsh.bundle.patch', '这不是一个可安装的 DSH bundle')
|
|
61
|
+
} else if (!(await exists(path.join(dir, patch)))) {
|
|
62
|
+
add('error', 'patch-file', `dsh.bundle.patch 指向 ${patch},但文件不存在`, '从 npm 包重新拷贝,或 `dsh plugin add dsh-global-todo`')
|
|
63
|
+
} else {
|
|
64
|
+
add('ok', 'patch-file', patch)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Both halves must exist; the client half is what the panel loads.
|
|
68
|
+
const entry = manifest.exports?.['.']
|
|
69
|
+
const client = manifest.exports?.['./client']
|
|
70
|
+
for (const [label, target] of [['host entry', entry], ['client entry', client]]) {
|
|
71
|
+
if (typeof target !== 'string') {
|
|
72
|
+
add('warn', 'entry-declared', `package.json 没有 ${label} 的 exports`, '宿主可能用默认解析,先确认版本是否完整')
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
if (await exists(path.join(dir, target))) add('ok', 'entry', target)
|
|
76
|
+
else add('error', 'entry-file', `${label} 指向 ${target},文件不存在`, '这个目录不是完整拷贝:重新从 npm 包复制 lib/')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The locale export is the one that fails in the field: the export exists, the
|
|
80
|
+
// directory does not, and the Host reports it as plugin-metadata trouble.
|
|
81
|
+
const localeExport = manifest.exports?.[EXPORT_LOCALE]
|
|
82
|
+
const localeDir = path.join(dir, 'locale')
|
|
83
|
+
const localeExists = await exists(localeDir)
|
|
84
|
+
const files = localeExists ? (await readdir(localeDir).catch(() => [])).filter((name) => name.endsWith('.json')) : []
|
|
85
|
+
if (typeof localeExport === 'string' && !localeExists) {
|
|
86
|
+
add(
|
|
87
|
+
'error',
|
|
88
|
+
'locale-missing',
|
|
89
|
+
'package.json 声明了 ./locale/*.json,但 locale/ 目录不存在 —— 桌面端会显示 Plugin metadata … ENOENT … \\locale',
|
|
90
|
+
'拷贝 locale/ 目录,或用 npm 包重装:dsh plugin add dsh-global-todo(插件本身仍能加载,坏的是插件页标题/描述)',
|
|
91
|
+
)
|
|
92
|
+
} else if (typeof localeExport === 'string' && files.length === 0) {
|
|
93
|
+
add('warn', 'locale-empty', 'locale/ 存在但没有 .json 文件', '至少需要 en.json 与 zh.json')
|
|
94
|
+
} else if (files.length > 0) {
|
|
95
|
+
add('ok', 'locale', `locale/ → ${files.sort().join(', ')}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// The icon is cosmetic: a missing one shows a blank mark, not an error.
|
|
99
|
+
if (await exists(path.join(dir, 'docs', 'icon.svg'))) add('ok', 'icon', 'docs/icon.svg')
|
|
100
|
+
else add('warn', 'icon-missing', 'docs/icon.svg 不存在,插件页会没有图标', '可选:一并拷过去即可,不影响加载')
|
|
101
|
+
|
|
102
|
+
const libDir = path.join(dir, 'lib')
|
|
103
|
+
const libFiles = (await exists(libDir)) ? (await readdir(libDir).catch(() => [])).filter((name) => name.endsWith('.js')) : []
|
|
104
|
+
if (libFiles.length === 0) add('error', 'lib-empty', 'lib/ 里没有 .js 文件', '从 npm 包重新复制 lib/')
|
|
105
|
+
else add('ok', 'lib', `lib/ → ${libFiles.length} 个模块`)
|
|
106
|
+
|
|
107
|
+
return { ok: !findings.some((finding) => finding.level === 'error'), dir, version: manifest.version, findings }
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** One line per finding, for a terminal. */
|
|
111
|
+
export function format(report) {
|
|
112
|
+
const mark = { ok: 'OK ', warn: 'WARN', error: 'FAIL' }
|
|
113
|
+
const lines = [`dsh-global-todo doctor — ${report.dir}`, '']
|
|
114
|
+
for (const finding of report.findings) {
|
|
115
|
+
lines.push(`${mark[finding.level]} ${finding.message}`)
|
|
116
|
+
if (finding.fix !== undefined) lines.push(` fix: ${finding.fix}`)
|
|
117
|
+
}
|
|
118
|
+
const errors = report.findings.filter((finding) => finding.level === 'error').length
|
|
119
|
+
const warns = report.findings.filter((finding) => finding.level === 'warn').length
|
|
120
|
+
lines.push('')
|
|
121
|
+
lines.push(errors === 0 ? `OK — ${warns} warning(s)` : `${errors} error(s), ${warns} warning(s)`)
|
|
122
|
+
return lines.join('\n')
|
|
123
|
+
}
|
package/tools/doctor.mjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dsh-global-todo-doctor — check that a directory really contains this plugin.
|
|
4
|
+
*
|
|
5
|
+
* npx dsh-global-todo-doctor "~/.dsh/plugins-local/全局待办插件"
|
|
6
|
+
* node tools/doctor.mjs . # from a checkout
|
|
7
|
+
*
|
|
8
|
+
* Exists because the field failure is silent-ish: a hand-copied plugin directory
|
|
9
|
+
* without `locale/` still loads, and only the desktop plugins page complains.
|
|
10
|
+
*/
|
|
11
|
+
import { doctor, format } from './doctor-lib.mjs'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2).filter((value) => value !== '')
|
|
15
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
16
|
+
process.stdout.write('用法 / usage:\n dsh-global-todo-doctor [插件目录]\n\n默认检查当前目录。\n')
|
|
17
|
+
process.exit(0)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const target = path.resolve(args[0] ?? process.cwd())
|
|
21
|
+
const report = await doctor(target)
|
|
22
|
+
process.stdout.write(format(report) + '\n')
|
|
23
|
+
process.exit(report.ok ? 0 : 1)
|