@wot-ui/cli 1.1.0 → 1.2.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 +57 -10
- package/dist/index.mjs +1304 -447
- package/dist/{server-TCmh-5rB.mjs → server-CxOzENpT.mjs} +1 -1
- package/dist/{update-check-DjA3lf-3.mjs → update-check-C6Y0GckX.mjs} +1 -1
- package/package.json +4 -1
- package/runtime/deepseek-project-bridge.mjs +197 -0
- package/skills/wot-ui-cli/references/overview.md +6 -2
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { _ as version, c as toComponentSummary, g as name, h as loadMetadataFile, i as lintProject, l as toDemoSummary, m as resolveVersion, n as getCliUpdateStatus, o as findComponent, s as listComponents } from "./update-check-
|
|
1
|
+
import { _ as version, c as toComponentSummary, g as name, h as loadMetadataFile, i as lintProject, l as toDemoSummary, m as resolveVersion, n as getCliUpdateStatus, o as findComponent, s as listComponents } from "./update-check-C6Y0GckX.mjs";
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
|
|
4
4
|
import * as z from "zod/v4";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wot-ui/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0",
|
|
5
5
|
"description": "面向 wot-ui 的 CLI、MCP 与数据提取工具集",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/Moonofweisheng/sponsors",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"files": [
|
|
36
36
|
"data",
|
|
37
37
|
"dist",
|
|
38
|
+
"runtime/*.mjs",
|
|
38
39
|
"skills/wot-ui-cli",
|
|
39
40
|
"skills/wot-ui-v2"
|
|
40
41
|
],
|
|
@@ -48,12 +49,14 @@
|
|
|
48
49
|
"commander": "^14.0.0",
|
|
49
50
|
"jsonc-parser": "^3.3.1",
|
|
50
51
|
"smol-toml": "^1.7.0",
|
|
52
|
+
"yaml": "2.9.0",
|
|
51
53
|
"zod": "^4.1.12"
|
|
52
54
|
},
|
|
53
55
|
"devDependencies": {
|
|
54
56
|
"@antfu/eslint-config": "^6.6.1",
|
|
55
57
|
"@antfu/ni": "^28.0.0",
|
|
56
58
|
"@antfu/utils": "^9.3.0",
|
|
59
|
+
"@fission-ai/openspec": "1.9.0",
|
|
57
60
|
"@types/node": "^25.0.1",
|
|
58
61
|
"@vitest/coverage-v8": "4.0.15",
|
|
59
62
|
"baseline-browser-mapping": "^2.10.18",
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { access, readFile, realpath, stat } from 'node:fs/promises'
|
|
3
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
|
|
4
|
+
|
|
5
|
+
export const inject = ['loader', 'tools']
|
|
6
|
+
export const name = 'open-wot-project-bridge'
|
|
7
|
+
export const declarationPath = '.dsh/wot-ui.json'
|
|
8
|
+
|
|
9
|
+
export function digest(source) {
|
|
10
|
+
return createHash('sha256').update(source).digest('hex')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function keys(value, expected) {
|
|
14
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
15
|
+
&& Object.keys(value).length === expected.length && expected.every(key => Object.hasOwn(value, key))
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseDeclaration(source) {
|
|
19
|
+
let value
|
|
20
|
+
try {
|
|
21
|
+
value = JSON.parse(source)
|
|
22
|
+
}
|
|
23
|
+
catch { throw new Error('Invalid DeepSeek project JSON') }
|
|
24
|
+
const server = value?.server
|
|
25
|
+
if (!keys(value, ['schemaVersion', 'server']) || value.schemaVersion !== 1
|
|
26
|
+
|| !keys(server, ['command', 'args']) || server.command !== 'npx'
|
|
27
|
+
|| !Array.isArray(server.args) || server.args.length !== 3
|
|
28
|
+
|| server.args[0] !== '-y' || server.args[2] !== 'mcp'
|
|
29
|
+
|| typeof server.args[1] !== 'string'
|
|
30
|
+
|| !/^@wot-ui\/cli(?:@[a-z\d][\w.+-]*)?$/i.test(server.args[1])) {
|
|
31
|
+
throw new Error('Unsupported DeepSeek project declaration; use wot mcp print --client deepseek')
|
|
32
|
+
}
|
|
33
|
+
return value
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function validateRegistry(config) {
|
|
37
|
+
if (!keys(config, ['schemaVersion', 'bridgeHash', 'projects']) || config.schemaVersion !== 1
|
|
38
|
+
|| !/^[a-f\d]{64}$/.test(config.bridgeHash) || !Array.isArray(config.projects)
|
|
39
|
+
|| config.projects.some(project => !keys(project, ['root', 'digest'])
|
|
40
|
+
|| typeof project.root !== 'string' || !isAbsolute(project.root) || resolve(project.root) !== project.root
|
|
41
|
+
|| !/^[a-f\d]{64}$/.test(project.digest))
|
|
42
|
+
|| new Set(config.projects.map(project => project.root)).size !== config.projects.length) {
|
|
43
|
+
throw new Error('Conflicting or modified DeepSeek project bridge registration')
|
|
44
|
+
}
|
|
45
|
+
return config
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function within(root, path) {
|
|
49
|
+
const rel = relative(root, path)
|
|
50
|
+
return rel === '' || (rel !== '..' && !rel.startsWith(`..${sep}`) && !isAbsolute(rel))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function gitRoot(cwd) {
|
|
54
|
+
let current = await realpath(cwd)
|
|
55
|
+
for (;;) {
|
|
56
|
+
try {
|
|
57
|
+
await access(join(current, '.git'))
|
|
58
|
+
return current
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error.code !== 'ENOENT')
|
|
62
|
+
throw error
|
|
63
|
+
}
|
|
64
|
+
const parent = dirname(current)
|
|
65
|
+
if (parent === current)
|
|
66
|
+
return undefined
|
|
67
|
+
current = parent
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export class DeepseekProjectLocationError extends Error {}
|
|
72
|
+
|
|
73
|
+
export async function installRoot(cwd) {
|
|
74
|
+
const root = await realpath(cwd)
|
|
75
|
+
if (!(await stat(root)).isDirectory())
|
|
76
|
+
throw new Error('DeepSeek project cwd must be a directory')
|
|
77
|
+
const git = await gitRoot(root)
|
|
78
|
+
if (git && git !== root)
|
|
79
|
+
throw new DeepseekProjectLocationError(`Run from the Git root using --cwd ${JSON.stringify(git)}`)
|
|
80
|
+
return root
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function selectProject(cwd, projects) {
|
|
84
|
+
if (typeof cwd !== 'string' || !isAbsolute(cwd))
|
|
85
|
+
return undefined
|
|
86
|
+
const actual = await realpath(cwd)
|
|
87
|
+
const git = await gitRoot(actual)
|
|
88
|
+
const candidates = projects.filter(project => git ? project.root === git : within(project.root, actual))
|
|
89
|
+
.sort((a, b) => b.root.length - a.root.length)
|
|
90
|
+
for (const project of candidates) {
|
|
91
|
+
// A registration is tied to its real directory, not a movable symlink.
|
|
92
|
+
if (await realpath(project.root) === project.root)
|
|
93
|
+
return project
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function readDeclaration(root) {
|
|
98
|
+
const path = join(root, declarationPath)
|
|
99
|
+
if (!within(root, await realpath(path)))
|
|
100
|
+
throw new Error('DeepSeek project declaration escapes its registered root')
|
|
101
|
+
const source = await readFile(path, 'utf8')
|
|
102
|
+
return { source, declaration: parseDeclaration(source) }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function apply(ctx, config) {
|
|
106
|
+
validateRegistry(config)
|
|
107
|
+
const mounts = new Map()
|
|
108
|
+
let stopped = false
|
|
109
|
+
let modulePromise
|
|
110
|
+
const cleanup = async () => {
|
|
111
|
+
stopped = true
|
|
112
|
+
await Promise.all([...mounts.values()].map(record => record.dispose()))
|
|
113
|
+
}
|
|
114
|
+
ctx.effect(() => cleanup)
|
|
115
|
+
ctx.on('agent/created', async ({ agent, signal }) => {
|
|
116
|
+
if (stopped || signal?.aborted || mounts.has(agent))
|
|
117
|
+
return
|
|
118
|
+
const record = {
|
|
119
|
+
disposed: false,
|
|
120
|
+
fiber: undefined,
|
|
121
|
+
disposal: undefined,
|
|
122
|
+
dispose() {
|
|
123
|
+
this.disposed = true
|
|
124
|
+
// Share pending/successful cleanup; let later lifecycles retry failures.
|
|
125
|
+
this.disposal ??= Promise.resolve().then(() => this.fiber?.dispose()).catch((error) => {
|
|
126
|
+
this.disposal = undefined
|
|
127
|
+
throw error
|
|
128
|
+
})
|
|
129
|
+
return this.disposal
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
mounts.set(agent, record)
|
|
133
|
+
const cancel = () => {
|
|
134
|
+
void record.dispose().catch(() => {})
|
|
135
|
+
}
|
|
136
|
+
signal?.addEventListener('abort', cancel, { once: true })
|
|
137
|
+
const release = agent.ctx.effect(() => async () => {
|
|
138
|
+
await record.dispose()
|
|
139
|
+
if (mounts.get(agent) === record)
|
|
140
|
+
mounts.delete(agent)
|
|
141
|
+
})
|
|
142
|
+
let diagnostic = 'project directory could not be resolved'
|
|
143
|
+
try {
|
|
144
|
+
const project = await selectProject(agent.session.header.cwd, config.projects)
|
|
145
|
+
if (!project) {
|
|
146
|
+
await release()
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
diagnostic = 'project declaration is unreadable or invalid'
|
|
150
|
+
const { source, declaration } = await readDeclaration(project.root)
|
|
151
|
+
diagnostic = 'project declaration changed; run init to renew its registration'
|
|
152
|
+
if (digest(source) !== project.digest)
|
|
153
|
+
throw new Error('Project declaration changed; run wot agent init --client deepseek again')
|
|
154
|
+
diagnostic = 'global wot-ui service conflicts with project isolation'
|
|
155
|
+
if (ctx.tools.schemas(agent).some(tool => tool.name.startsWith('mcp__wot-ui__')))
|
|
156
|
+
throw new Error('Global wot-ui service conflicts with project isolation; remove its user/profile registration')
|
|
157
|
+
diagnostic = 'host module resolution is incompatible with the project bridge'
|
|
158
|
+
modulePromise ??= Promise.all([
|
|
159
|
+
ctx.loader.import('@deepseek-ai/dsh-mcp-client'),
|
|
160
|
+
ctx.loader.import('@deepseek-ai/dsh-scope'),
|
|
161
|
+
])
|
|
162
|
+
const [plugin, { scopeOf }] = await modulePromise
|
|
163
|
+
// A global client may be connecting or expose no tools yet. Inspect its
|
|
164
|
+
// public loader identity as well as the visible tool namespace.
|
|
165
|
+
diagnostic = 'global wot-ui registration conflicts with project isolation'
|
|
166
|
+
for (const entry of ctx.loader.entries()) {
|
|
167
|
+
if (!entry.disabled && scopeOf(entry.context) === undefined
|
|
168
|
+
&& entry.options.name === '@deepseek-ai/dsh-mcp-client'
|
|
169
|
+
&& (entry.fiber?.config ?? entry.options.config)?.serverName === 'wot-ui') {
|
|
170
|
+
throw new Error('Global wot-ui registration conflicts with project isolation')
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (stopped || record.disposed || signal?.aborted)
|
|
174
|
+
return
|
|
175
|
+
diagnostic = 'project MCP startup failed or was cancelled'
|
|
176
|
+
record.fiber = agent.ctx.plugin(plugin, {
|
|
177
|
+
...declaration.server,
|
|
178
|
+
serverName: 'wot-ui',
|
|
179
|
+
transport: 'stdio',
|
|
180
|
+
cwd: project.root,
|
|
181
|
+
failOnStartupError: true,
|
|
182
|
+
reconnect: { enabled: false },
|
|
183
|
+
})
|
|
184
|
+
await record.fiber.await()
|
|
185
|
+
if (stopped || record.disposed || signal?.aborted)
|
|
186
|
+
await record.dispose()
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
await record.dispose().catch(() => {})
|
|
190
|
+
// Never log parser, command or plugin errors: they may include project secrets.
|
|
191
|
+
ctx.logger.warn(`open-wot: ${diagnostic}. Run wot agent doctor --client deepseek; reopen the session after fixing it.`)
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
signal?.removeEventListener('abort', cancel)
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
}
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
| `doctor` | 检查文件和真实 MCP handshake |
|
|
48
48
|
| `remove` | 删除 open-wot 托管内容 |
|
|
49
49
|
|
|
50
|
-
`init/status/doctor/remove` 支持 `--client auto|all|claude|cursor|vscode|codex|opencode|antigravity`、`--scope`、`--with`、`--cwd`、`--format` 和 `--pin`。`init/remove` 支持 `--dry-run`、`--yes`;`doctor` 支持 `--timeout`。
|
|
50
|
+
`init/status/doctor/remove` 支持 `--client auto|all|claude|cursor|vscode|codex|opencode|antigravity|deepseek`、`--scope`、`--with`、`--cwd`、`--format` 和 `--pin`。`init/remove` 支持 `--dry-run`、`--yes`;`doctor` 支持 `--timeout`。
|
|
51
51
|
|
|
52
52
|
### MCP
|
|
53
53
|
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
| Codex | `.codex/config.toml` |
|
|
74
74
|
| OpenCode | `opencode.json[c]` / `.opencode/opencode.json[c]` |
|
|
75
75
|
| Antigravity | `.agents/mcp_config.json` |
|
|
76
|
+
| DeepSeek Harness | `.dsh/wot-ui.json`(另需本机桥接登记) |
|
|
76
77
|
|
|
77
78
|
一次接入所有客户端:
|
|
78
79
|
|
|
@@ -83,6 +84,8 @@ wot agent doctor --client all --timeout 30000
|
|
|
83
84
|
|
|
84
85
|
默认 Agent 接入安装 MCP、`wot-ui-v2` Skill 和托管 Instructions。`wot-ui-cli` Skill 随 npm 包发布,但不默认安装到组件使用项目。
|
|
85
86
|
|
|
87
|
+
DeepSeek 默认按项目隔离,初始化时会在 `$DSH_HOME`(默认 `~/.dsh`)安装共享 bridge 并登记项目;每台机器都需初始化,首次安装后需重启桌面版。显式接入请在 Git 根目录执行;`--client all` 初始化或移除时,如子目录不满足 DeepSeek 的项目 MCP 或 Skill 位置要求,会提示并跳过该客户端。`--scope user` 仅改变 MCP 配置位置,Skill 与 Instructions 仍在项目中。完整接入与迁移说明以 README 为准。
|
|
88
|
+
|
|
86
89
|
MCP 提供 8 个 tools:
|
|
87
90
|
|
|
88
91
|
```text
|
|
@@ -104,6 +107,7 @@ src/application MCP/Agent 编排和 ChangePlan
|
|
|
104
107
|
src/mcp Server、tools、prompts、adapters
|
|
105
108
|
src/data loader 和版本解析
|
|
106
109
|
src/utils 文件、终端、项目扫描等公共能力
|
|
110
|
+
runtime DeepSeek 桌面项目加载 bridge
|
|
107
111
|
scripts extract、sync、compress
|
|
108
112
|
data 多版本离线数据
|
|
109
113
|
skills Agent Skills
|
|
@@ -161,6 +165,6 @@ pnpm exec publint
|
|
|
161
165
|
npm pack --dry-run --json
|
|
162
166
|
```
|
|
163
167
|
|
|
164
|
-
发布包应包含 `dist
|
|
168
|
+
发布包应包含 `dist/`、`runtime/deepseek-project-bridge.mjs`、压缩数据、`data/versions.json`、`skills/wot-ui-v2` 和 `skills/wot-ui-cli`。
|
|
165
169
|
|
|
166
170
|
维护者使用 `pnpm release`。bumpp 默认更新版本、提交、创建 tag 并推送;`v*` tag 触发 GitHub Actions 发布。发布后检查 npm 版本、文件和 dist-tags,预发布版本不要占用 `latest`。
|