@wot-ui/cli 1.1.0-beta.2 → 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.
@@ -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-BVgdy9fy.mjs";
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";
@@ -8,7 +8,7 @@ import { parse } from "@vue/compiler-sfc";
8
8
 
9
9
  //#region package.json
10
10
  var name = "@wot-ui/cli";
11
- var version = "1.1.0-beta.2";
11
+ var version = "1.2.0";
12
12
 
13
13
  //#endregion
14
14
  //#region src/data/loader.ts
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@wot-ui/cli",
3
3
  "type": "module",
4
- "version": "1.1.0-beta.2",
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/`、压缩数据、`data/versions.json`、`skills/wot-ui-v2` 和 `skills/wot-ui-cli`。
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`。