@wot-ui/cli 1.0.6 → 1.1.0-beta.2
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 +14 -10
- package/data/v2.3.1.json.gz +0 -0
- package/data/v2.3.2.json.gz +0 -0
- package/data/v2.json.gz +0 -0
- package/data/versions.json +1 -1
- package/data/versions.json.gz +0 -0
- package/dist/index.mjs +543 -142
- package/dist/{server-tl_9acKR.mjs → server-BgPTmKpO.mjs} +1 -1
- package/dist/{update-check-vahhrKL6.mjs → update-check-BVgdy9fy.mjs} +1 -1
- package/package.json +13 -7
- package/skills/wot-ui-cli/SKILL.md +1 -1
- package/skills/wot-ui-cli/references/overview.md +3 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="https://wot-ui.cn">
|
|
3
|
-
<img src="
|
|
2
|
+
<a href="https://cli.wot-ui.cn">
|
|
3
|
+
<img src="https://cli.wot-ui.cn/favicon.svg" width="96" alt="Open Wot">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
|
|
@@ -57,9 +57,9 @@ Instructions AI 在生成代码前主动查询真实组件知识
|
|
|
57
57
|
|
|
58
58
|
使用其他客户端时,只需替换 client id:
|
|
59
59
|
|
|
60
|
-
| Claude Code | Cursor | VS Code | Codex |
|
|
61
|
-
| --- | --- | --- | --- |
|
|
62
|
-
| `claude` | `cursor` | `vscode` | `codex` |
|
|
60
|
+
| Claude Code | Cursor | VS Code | Codex | OpenCode | Antigravity |
|
|
61
|
+
| --- | --- | --- | --- | --- | --- |
|
|
62
|
+
| `claude` | `cursor` | `vscode` | `codex` | `opencode` | `antigravity` |
|
|
63
63
|
|
|
64
64
|
同时使用多个 AI 客户端时,可以一次完成全部项目级配置:
|
|
65
65
|
|
|
@@ -68,7 +68,7 @@ wot agent init --client all
|
|
|
68
68
|
wot agent doctor --client all --timeout 30000
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
`--client all` 在 project scope 下会处理 Claude Code、Cursor、VS Code 和
|
|
71
|
+
`--client all` 在 project scope 下会处理 Claude Code、Cursor、VS Code、Codex、OpenCode 和 Antigravity;在 user scope 下只处理支持用户级配置的客户端。
|
|
72
72
|
|
|
73
73
|
配置完成后重启客户端;如果出现“信任项目”或“批准 MCP Server”的提示,请按客户端指引确认。
|
|
74
74
|
|
|
@@ -82,7 +82,7 @@ flowchart LR
|
|
|
82
82
|
B --> C["CLI"]
|
|
83
83
|
B --> D["MCP · 8 tools"]
|
|
84
84
|
B --> E["Skills + Instructions"]
|
|
85
|
-
D --> F["Cursor · Claude Code · VS Code · Codex"]
|
|
85
|
+
D --> F["Cursor · Claude Code · VS Code · Codex · OpenCode · Antigravity"]
|
|
86
86
|
E --> F
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -159,7 +159,7 @@ wot agent init --client claude --with skill,instructions
|
|
|
159
159
|
|
|
160
160
|
1. 确认 Node.js >= 20。
|
|
161
161
|
2. 安装或更新 `@wot-ui/cli@latest`;优先全局安装,不要使用 sudo。权限受限时改用 `npx -y @wot-ui/cli@latest` 执行后续命令。
|
|
162
|
-
3. 识别当前客户端:Claude Code=claude、Cursor=cursor、VS Code=vscode、Codex=codex。无法确定时先询问我。
|
|
162
|
+
3. 识别当前客户端:Claude Code=claude、Cursor=cursor、VS Code=vscode、Codex=codex、OpenCode=opencode、Antigravity=antigravity。无法确定时先询问我。
|
|
163
163
|
4. 执行 `wot agent init --client <client-id> --scope project --with mcp,skill,instructions --yes`。
|
|
164
164
|
5. 执行 `wot agent doctor --client <client-id> --scope project --with mcp,skill,instructions`。
|
|
165
165
|
6. 告诉我修改了哪些文件、doctor 结果,以及是否需要重启客户端或批准 MCP。
|
|
@@ -241,6 +241,8 @@ wot mcp remove --client cursor
|
|
|
241
241
|
| Cursor | `.cursor/mcp.json` | `mcpServers` |
|
|
242
242
|
| VS Code | `.vscode/mcp.json` | `servers` |
|
|
243
243
|
| Codex | `.codex/config.toml` | `mcp_servers.wot-ui` |
|
|
244
|
+
| OpenCode | `opencode.json[c]` / `.opencode/opencode.json[c]` | `mcp.wot-ui` |
|
|
245
|
+
| Antigravity | `.agents/mcp_config.json` | `mcpServers` |
|
|
244
246
|
|
|
245
247
|
```bash
|
|
246
248
|
wot mcp print --client cursor # 只预览配置
|
|
@@ -249,7 +251,9 @@ wot mcp init --client all # 配置所有客户端
|
|
|
249
251
|
wot mcp init --client codex --pin # 固定当前 CLI 版本
|
|
250
252
|
```
|
|
251
253
|
|
|
252
|
-
Claude Code、Cursor 和
|
|
254
|
+
Claude Code、Cursor、Codex、OpenCode 和 Antigravity 支持 `--scope user`;VS Code 当前使用 project scope。OpenCode 会按配置优先级复用项目中已有的 `opencode.json[c]` 或 `.opencode/opencode.json[c]`,用户配置写入 `~/.config/opencode/opencode.json`;Antigravity 写入 `~/.gemini/config/mcp_config.json`。`doctor` 会验证配置和真实 MCP handshake,并在客户端支持时继续检查注册状态;Antigravity 没有稳定的非交互式状态命令,因此会提示在客户端内执行 `/mcp` 确认。
|
|
255
|
+
|
|
256
|
+
Agent 接入时,OpenCode 和 Antigravity 都复用 `.agents/skills/wot-ui-v2` 与项目根目录的 `AGENTS.md`;Antigravity 1.20.5 及以上版本会读取其中的项目规则。
|
|
253
257
|
|
|
254
258
|
直接启动 stdio Server:
|
|
255
259
|
|
|
@@ -338,7 +342,7 @@ wot mcp serve # 语义明确的等价写法
|
|
|
338
342
|
|
|
339
343
|
`init`、`status`、`doctor` 和 `remove` 支持:
|
|
340
344
|
|
|
341
|
-
- `--client auto|all|claude|cursor|vscode|codex`
|
|
345
|
+
- `--client auto|all|claude|cursor|vscode|codex|opencode|antigravity`
|
|
342
346
|
- `--scope project|user`
|
|
343
347
|
- `--with mcp,skill,instructions`
|
|
344
348
|
- `--cwd <directory>`
|
|
Binary file
|
|
Binary file
|
package/data/v2.json.gz
CHANGED
|
Binary file
|
package/data/versions.json
CHANGED
package/data/versions.json.gz
CHANGED
|
Binary file
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as version, a as filterComponents, d as formatStatusLabel, f as writeStderrLine, g as name, h as loadMetadataFile, i as lintProject, m as resolveVersion, o as findComponent, p as detectVersion, r as analyzeUsage, s as listComponents, t as checkForCliUpdate, u as formatLogMessage } from "./update-check-
|
|
2
|
+
import { _ as version, a as filterComponents, d as formatStatusLabel, f as writeStderrLine, g as name, h as loadMetadataFile, i as lintProject, m as resolveVersion, o as findComponent, p as detectVersion, r as analyzeUsage, s as listComponents, t as checkForCliUpdate, u as formatLogMessage } from "./update-check-BVgdy9fy.mjs";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { constants, existsSync, readFileSync } from "node:fs";
|
|
@@ -9,9 +9,9 @@ import { fileURLToPath } from "node:url";
|
|
|
9
9
|
import { randomUUID } from "node:crypto";
|
|
10
10
|
import { createInterface } from "node:readline/promises";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
|
+
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
|
|
12
13
|
import { spawn } from "node:child_process";
|
|
13
14
|
import { isDeepStrictEqual, stripVTControlCharacters } from "node:util";
|
|
14
|
-
import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser";
|
|
15
15
|
import { parse as parse$1 } from "smol-toml";
|
|
16
16
|
import { LATEST_PROTOCOL_VERSION, ReadBuffer, serializeMessage } from "@modelcontextprotocol/server";
|
|
17
17
|
|
|
@@ -276,94 +276,6 @@ async function confirmChangePlan(plan, options = {}) {
|
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
-
//#endregion
|
|
280
|
-
//#region src/utils/redaction.ts
|
|
281
|
-
const SENSITIVE_KEY = String.raw`(?:api[_-]?key|access[_-]?token|auth(?:orization)?|bearer|client[_-]?secret|password|passwd|secret|token|cookie)`;
|
|
282
|
-
const BEARER_PATTERN = /(authorization\s*:\s*bearer\s+)[^\s,;]+/gi;
|
|
283
|
-
const ASSIGNMENT_PATTERN = new RegExp(`(["']?${SENSITIVE_KEY}["']?\\s*[:=]\\s*)(?:"[^"]*"|'[^']*'|\\S+)`, "gi");
|
|
284
|
-
function redactSensitiveText(value, secretValues = []) {
|
|
285
|
-
let redacted = value.replace(BEARER_PATTERN, "$1[REDACTED]");
|
|
286
|
-
redacted = redacted.replace(ASSIGNMENT_PATTERN, "$1[REDACTED]");
|
|
287
|
-
for (const secret of secretValues) if (secret) redacted = redacted.split(secret).join("[REDACTED]");
|
|
288
|
-
return redacted;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
//#endregion
|
|
292
|
-
//#region src/mcp/clients/client-command.ts
|
|
293
|
-
const TERMINATION_GRACE_MS = 500;
|
|
294
|
-
async function runClientCommand(executable, args, options) {
|
|
295
|
-
const platform = options.platform ?? process.platform;
|
|
296
|
-
const useShell = platform === "win32" && /\.(?:cmd|bat)$/i.test(executable);
|
|
297
|
-
const totalTimeoutMs = Math.max(1, options.timeoutMs ?? 5e3);
|
|
298
|
-
const terminationGraceMs = Math.min(TERMINATION_GRACE_MS, Math.floor(totalTimeoutMs / 5));
|
|
299
|
-
const commandTimeoutMs = totalTimeoutMs - terminationGraceMs;
|
|
300
|
-
return new Promise((resolve$1) => {
|
|
301
|
-
const child = spawn(executable, args, {
|
|
302
|
-
cwd: options.cwd,
|
|
303
|
-
env: options.env,
|
|
304
|
-
windowsHide: true,
|
|
305
|
-
shell: useShell,
|
|
306
|
-
stdio: [
|
|
307
|
-
"ignore",
|
|
308
|
-
"pipe",
|
|
309
|
-
"pipe"
|
|
310
|
-
]
|
|
311
|
-
});
|
|
312
|
-
let stdout = "";
|
|
313
|
-
let stderr = "";
|
|
314
|
-
let settled = false;
|
|
315
|
-
let timedOut = false;
|
|
316
|
-
let timer;
|
|
317
|
-
let terminationTimer;
|
|
318
|
-
const finish = (result) => {
|
|
319
|
-
if (settled) return;
|
|
320
|
-
settled = true;
|
|
321
|
-
if (timer) clearTimeout(timer);
|
|
322
|
-
if (terminationTimer) clearTimeout(terminationTimer);
|
|
323
|
-
resolve$1({
|
|
324
|
-
...result,
|
|
325
|
-
stdout: stdout.slice(-8e3),
|
|
326
|
-
stderr: stderr.slice(-8e3)
|
|
327
|
-
});
|
|
328
|
-
};
|
|
329
|
-
timer = setTimeout(() => {
|
|
330
|
-
timedOut = true;
|
|
331
|
-
if (platform === "win32" && child.pid) spawn("taskkill", [
|
|
332
|
-
"/pid",
|
|
333
|
-
String(child.pid),
|
|
334
|
-
"/T",
|
|
335
|
-
"/F"
|
|
336
|
-
], {
|
|
337
|
-
windowsHide: true,
|
|
338
|
-
stdio: "ignore"
|
|
339
|
-
}).once("error", () => child.kill("SIGTERM"));
|
|
340
|
-
else child.kill("SIGTERM");
|
|
341
|
-
terminationTimer = setTimeout(() => child.kill("SIGKILL"), terminationGraceMs);
|
|
342
|
-
terminationTimer.unref();
|
|
343
|
-
}, commandTimeoutMs);
|
|
344
|
-
timer.unref();
|
|
345
|
-
child.stdout.on("data", (chunk) => stdout += chunk.toString("utf8"));
|
|
346
|
-
child.stderr.on("data", (chunk) => stderr += chunk.toString("utf8"));
|
|
347
|
-
child.once("error", (error) => finish(timedOut ? {
|
|
348
|
-
timedOut: true,
|
|
349
|
-
error: `Client command timed out after ${totalTimeoutMs}ms`
|
|
350
|
-
} : {
|
|
351
|
-
timedOut: false,
|
|
352
|
-
error: error.message
|
|
353
|
-
}));
|
|
354
|
-
child.once("close", (code) => finish(timedOut ? {
|
|
355
|
-
timedOut: true,
|
|
356
|
-
error: `Client command timed out after ${totalTimeoutMs}ms`
|
|
357
|
-
} : {
|
|
358
|
-
timedOut: false,
|
|
359
|
-
...code === null ? {} : { exitCode: code }
|
|
360
|
-
}));
|
|
361
|
-
});
|
|
362
|
-
}
|
|
363
|
-
function clientCommandOutput(result) {
|
|
364
|
-
return redactSensitiveText(stripVTControlCharacters(`${result.stdout}\n${result.stderr}`).trim());
|
|
365
|
-
}
|
|
366
|
-
|
|
367
279
|
//#endregion
|
|
368
280
|
//#region src/mcp/clients/detect.ts
|
|
369
281
|
async function isExecutable(path, platform) {
|
|
@@ -390,7 +302,7 @@ async function findExecutable(names, context) {
|
|
|
390
302
|
|
|
391
303
|
//#endregion
|
|
392
304
|
//#region src/mcp/clients/json-adapter.ts
|
|
393
|
-
async function readOptional$
|
|
305
|
+
async function readOptional$3(path) {
|
|
394
306
|
try {
|
|
395
307
|
return await readFile(path, "utf8");
|
|
396
308
|
} catch (error) {
|
|
@@ -398,7 +310,7 @@ async function readOptional$2(path) {
|
|
|
398
310
|
throw error;
|
|
399
311
|
}
|
|
400
312
|
}
|
|
401
|
-
function parseObject(content, path) {
|
|
313
|
+
function parseObject$1(content, path) {
|
|
402
314
|
const errors = [];
|
|
403
315
|
const value = parse(content, errors, {
|
|
404
316
|
allowTrailingComma: true,
|
|
@@ -411,16 +323,16 @@ function parseObject(content, path) {
|
|
|
411
323
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Expected an object in ${path}`);
|
|
412
324
|
return value;
|
|
413
325
|
}
|
|
414
|
-
function isStringMap(value) {
|
|
326
|
+
function isStringMap$1(value) {
|
|
415
327
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
|
|
416
328
|
}
|
|
417
329
|
function isServerDefinition(value) {
|
|
418
330
|
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
419
331
|
const candidate = value;
|
|
420
332
|
const env = candidate.env;
|
|
421
|
-
return typeof candidate.command === "string" && Array.isArray(candidate.args) && candidate.args.every((argument) => typeof argument === "string") && (env === void 0 || isStringMap(env));
|
|
333
|
+
return typeof candidate.command === "string" && Array.isArray(candidate.args) && candidate.args.every((argument) => typeof argument === "string") && (env === void 0 || isStringMap$1(env));
|
|
422
334
|
}
|
|
423
|
-
function serverMatches$
|
|
335
|
+
function serverMatches$2(actual, expected) {
|
|
424
336
|
return actual?.command === expected.command && JSON.stringify(actual.args) === JSON.stringify(expected.args) && JSON.stringify(actual.env ?? {}) === JSON.stringify(expected.env ?? {});
|
|
425
337
|
}
|
|
426
338
|
function serverPreview$1(server, serverKey) {
|
|
@@ -469,7 +381,7 @@ var JsonMcpClientAdapter = class {
|
|
|
469
381
|
async inspect(context) {
|
|
470
382
|
this.assertScope(context.scope);
|
|
471
383
|
const path = this.options.configPath(context, context.scope);
|
|
472
|
-
const content = await readOptional$
|
|
384
|
+
const content = await readOptional$3(path);
|
|
473
385
|
if (content === void 0) return {
|
|
474
386
|
client: this.id,
|
|
475
387
|
displayName: this.displayName,
|
|
@@ -480,9 +392,10 @@ var JsonMcpClientAdapter = class {
|
|
|
480
392
|
matches: false
|
|
481
393
|
};
|
|
482
394
|
try {
|
|
483
|
-
const servers = parseObject(content || "{}", path)[this.options.serverKey];
|
|
395
|
+
const servers = parseObject$1(content || "{}", path)[this.options.serverKey];
|
|
484
396
|
const entry = servers && typeof servers === "object" && !Array.isArray(servers) ? servers["wot-ui"] : void 0;
|
|
485
397
|
const server = isServerDefinition(entry) ? entry : void 0;
|
|
398
|
+
const serverProblem$2 = server ? this.options.serverProblem?.(entry) : void 0;
|
|
486
399
|
return {
|
|
487
400
|
client: this.id,
|
|
488
401
|
displayName: this.displayName,
|
|
@@ -490,9 +403,9 @@ var JsonMcpClientAdapter = class {
|
|
|
490
403
|
path,
|
|
491
404
|
exists: true,
|
|
492
405
|
configured: entry !== void 0,
|
|
493
|
-
matches: serverMatches$
|
|
406
|
+
matches: !serverProblem$2 && serverMatches$2(server, context.server),
|
|
494
407
|
...server ? { server } : {},
|
|
495
|
-
|
|
408
|
+
...serverProblem$2 ? { problem: serverProblem$2 } : !server && entry !== void 0 ? { problem: "The wot-ui entry is not a supported stdio server definition" } : {}
|
|
496
409
|
};
|
|
497
410
|
} catch (error) {
|
|
498
411
|
return {
|
|
@@ -510,9 +423,9 @@ var JsonMcpClientAdapter = class {
|
|
|
510
423
|
async planInstall(context) {
|
|
511
424
|
this.assertScope(context.scope);
|
|
512
425
|
const path = this.options.configPath(context, context.scope);
|
|
513
|
-
const before = await readOptional$
|
|
426
|
+
const before = await readOptional$3(path);
|
|
514
427
|
const source = before?.trim() ? before : "{}\n";
|
|
515
|
-
parseObject(source, path);
|
|
428
|
+
parseObject$1(source, path);
|
|
516
429
|
const after = applyEdits(source, modify(source, [this.options.serverKey, "wot-ui"], context.server, { formattingOptions: {
|
|
517
430
|
insertSpaces: true,
|
|
518
431
|
tabSize: 2,
|
|
@@ -538,7 +451,7 @@ var JsonMcpClientAdapter = class {
|
|
|
538
451
|
async planRemove(context) {
|
|
539
452
|
this.assertScope(context.scope);
|
|
540
453
|
const path = this.options.configPath(context, context.scope);
|
|
541
|
-
const before = await readOptional$
|
|
454
|
+
const before = await readOptional$3(path);
|
|
542
455
|
if (before === void 0) return {
|
|
543
456
|
id: `${this.id}-${context.scope}-remove`,
|
|
544
457
|
summary: `Remove wot-ui MCP from ${this.displayName}`,
|
|
@@ -546,7 +459,7 @@ var JsonMcpClientAdapter = class {
|
|
|
546
459
|
warnings: [],
|
|
547
460
|
requiresConfirmation: false
|
|
548
461
|
};
|
|
549
|
-
parseObject(before || "{}", path);
|
|
462
|
+
parseObject$1(before || "{}", path);
|
|
550
463
|
const after = applyEdits(before, modify(before, [this.options.serverKey, "wot-ui"], void 0, { formattingOptions: {
|
|
551
464
|
insertSpaces: true,
|
|
552
465
|
tabSize: 2,
|
|
@@ -582,6 +495,141 @@ var JsonMcpClientAdapter = class {
|
|
|
582
495
|
}
|
|
583
496
|
};
|
|
584
497
|
|
|
498
|
+
//#endregion
|
|
499
|
+
//#region src/mcp/clients/types.ts
|
|
500
|
+
const MCP_CLIENT_IDS = [
|
|
501
|
+
"claude",
|
|
502
|
+
"cursor",
|
|
503
|
+
"vscode",
|
|
504
|
+
"codex",
|
|
505
|
+
"opencode",
|
|
506
|
+
"antigravity"
|
|
507
|
+
];
|
|
508
|
+
const REQUIRED_WOT_MCP_TOOLS = ["wot_status", "wot_list"];
|
|
509
|
+
function isMcpClientId(value) {
|
|
510
|
+
return MCP_CLIENT_IDS.includes(value);
|
|
511
|
+
}
|
|
512
|
+
function isMcpScope(value) {
|
|
513
|
+
return value === "project" || value === "user";
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
//#endregion
|
|
517
|
+
//#region src/mcp/clients/antigravity.ts
|
|
518
|
+
function serverProblem$1(value) {
|
|
519
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
520
|
+
const entry = value;
|
|
521
|
+
if (entry.disabled === true) return "The wot-ui MCP server is disabled in Antigravity";
|
|
522
|
+
if (entry.disabled !== void 0 && typeof entry.disabled !== "boolean") return "The Antigravity wot-ui disabled field must be a boolean";
|
|
523
|
+
const disabledTools = entry.disabledTools;
|
|
524
|
+
if (disabledTools !== void 0) {
|
|
525
|
+
if (!Array.isArray(disabledTools) || !disabledTools.every((tool) => typeof tool === "string")) return "The Antigravity wot-ui disabledTools field must be a string array";
|
|
526
|
+
const unavailableTools = REQUIRED_WOT_MCP_TOOLS.filter((tool) => disabledTools.includes(tool));
|
|
527
|
+
if (unavailableTools.length > 0) return `Antigravity disables required wot-ui tools: ${unavailableTools.join(", ")}`;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const antigravityAdapter = new JsonMcpClientAdapter({
|
|
531
|
+
id: "antigravity",
|
|
532
|
+
displayName: "Antigravity",
|
|
533
|
+
executableNames: ["agy-ide", "agy"],
|
|
534
|
+
supportedScopes: ["project", "user"],
|
|
535
|
+
configPath: (context, scope) => scope === "project" ? resolve(context.cwd, ".agents", "mcp_config.json") : join(context.homeDir, ".gemini", "config", "mcp_config.json"),
|
|
536
|
+
serverKey: "mcpServers",
|
|
537
|
+
serverProblem: serverProblem$1,
|
|
538
|
+
verifyRegistration: async () => ({
|
|
539
|
+
status: "unsupported",
|
|
540
|
+
message: "Antigravity does not expose a stable non-interactive MCP registration status check",
|
|
541
|
+
action: "Open Antigravity in this workspace and run /mcp to confirm the wot-ui server."
|
|
542
|
+
})
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region src/utils/redaction.ts
|
|
547
|
+
const SENSITIVE_KEY = String.raw`(?:api[_-]?key|access[_-]?token|auth(?:orization)?|bearer|client[_-]?secret|password|passwd|secret|token|cookie)`;
|
|
548
|
+
const BEARER_PATTERN = /(authorization\s*:\s*bearer\s+)[^\s,;]+/gi;
|
|
549
|
+
const ASSIGNMENT_PATTERN = new RegExp(`(["']?${SENSITIVE_KEY}["']?\\s*[:=]\\s*)(?:"[^"]*"|'[^']*'|\\S+)`, "gi");
|
|
550
|
+
function redactSensitiveText(value, secretValues = []) {
|
|
551
|
+
let redacted = value.replace(BEARER_PATTERN, "$1[REDACTED]");
|
|
552
|
+
redacted = redacted.replace(ASSIGNMENT_PATTERN, "$1[REDACTED]");
|
|
553
|
+
for (const secret of secretValues) if (secret) redacted = redacted.split(secret).join("[REDACTED]");
|
|
554
|
+
return redacted;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
//#endregion
|
|
558
|
+
//#region src/mcp/clients/client-command.ts
|
|
559
|
+
const TERMINATION_GRACE_MS = 500;
|
|
560
|
+
async function runClientCommand(executable, args, options) {
|
|
561
|
+
const platform = options.platform ?? process.platform;
|
|
562
|
+
const useShell = platform === "win32" && /\.(?:cmd|bat)$/i.test(executable);
|
|
563
|
+
const totalTimeoutMs = Math.max(1, options.timeoutMs ?? 5e3);
|
|
564
|
+
const terminationGraceMs = Math.min(TERMINATION_GRACE_MS, Math.floor(totalTimeoutMs / 5));
|
|
565
|
+
const commandTimeoutMs = totalTimeoutMs - terminationGraceMs;
|
|
566
|
+
return new Promise((resolve$1) => {
|
|
567
|
+
const child = spawn(executable, args, {
|
|
568
|
+
cwd: options.cwd,
|
|
569
|
+
env: options.env,
|
|
570
|
+
windowsHide: true,
|
|
571
|
+
shell: useShell,
|
|
572
|
+
stdio: [
|
|
573
|
+
"ignore",
|
|
574
|
+
"pipe",
|
|
575
|
+
"pipe"
|
|
576
|
+
]
|
|
577
|
+
});
|
|
578
|
+
let stdout = "";
|
|
579
|
+
let stderr = "";
|
|
580
|
+
let settled = false;
|
|
581
|
+
let timedOut = false;
|
|
582
|
+
let timer;
|
|
583
|
+
let terminationTimer;
|
|
584
|
+
const finish = (result) => {
|
|
585
|
+
if (settled) return;
|
|
586
|
+
settled = true;
|
|
587
|
+
if (timer) clearTimeout(timer);
|
|
588
|
+
if (terminationTimer) clearTimeout(terminationTimer);
|
|
589
|
+
resolve$1({
|
|
590
|
+
...result,
|
|
591
|
+
stdout: stdout.slice(-8e3),
|
|
592
|
+
stderr: stderr.slice(-8e3)
|
|
593
|
+
});
|
|
594
|
+
};
|
|
595
|
+
timer = setTimeout(() => {
|
|
596
|
+
timedOut = true;
|
|
597
|
+
if (platform === "win32" && child.pid) spawn("taskkill", [
|
|
598
|
+
"/pid",
|
|
599
|
+
String(child.pid),
|
|
600
|
+
"/T",
|
|
601
|
+
"/F"
|
|
602
|
+
], {
|
|
603
|
+
windowsHide: true,
|
|
604
|
+
stdio: "ignore"
|
|
605
|
+
}).once("error", () => child.kill("SIGTERM"));
|
|
606
|
+
else child.kill("SIGTERM");
|
|
607
|
+
terminationTimer = setTimeout(() => child.kill("SIGKILL"), terminationGraceMs);
|
|
608
|
+
terminationTimer.unref();
|
|
609
|
+
}, commandTimeoutMs);
|
|
610
|
+
timer.unref();
|
|
611
|
+
child.stdout.on("data", (chunk) => stdout += chunk.toString("utf8"));
|
|
612
|
+
child.stderr.on("data", (chunk) => stderr += chunk.toString("utf8"));
|
|
613
|
+
child.once("error", (error) => finish(timedOut ? {
|
|
614
|
+
timedOut: true,
|
|
615
|
+
error: `Client command timed out after ${totalTimeoutMs}ms`
|
|
616
|
+
} : {
|
|
617
|
+
timedOut: false,
|
|
618
|
+
error: error.message
|
|
619
|
+
}));
|
|
620
|
+
child.once("close", (code) => finish(timedOut ? {
|
|
621
|
+
timedOut: true,
|
|
622
|
+
error: `Client command timed out after ${totalTimeoutMs}ms`
|
|
623
|
+
} : {
|
|
624
|
+
timedOut: false,
|
|
625
|
+
...code === null ? {} : { exitCode: code }
|
|
626
|
+
}));
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function clientCommandOutput(result) {
|
|
630
|
+
return redactSensitiveText(stripVTControlCharacters(`${result.stdout}\n${result.stderr}`).trim());
|
|
631
|
+
}
|
|
632
|
+
|
|
585
633
|
//#endregion
|
|
586
634
|
//#region src/mcp/clients/claude.ts
|
|
587
635
|
const claudeAdapter = new JsonMcpClientAdapter({
|
|
@@ -640,29 +688,13 @@ const claudeAdapter = new JsonMcpClientAdapter({
|
|
|
640
688
|
}
|
|
641
689
|
});
|
|
642
690
|
|
|
643
|
-
//#endregion
|
|
644
|
-
//#region src/mcp/clients/types.ts
|
|
645
|
-
const MCP_CLIENT_IDS = [
|
|
646
|
-
"claude",
|
|
647
|
-
"cursor",
|
|
648
|
-
"vscode",
|
|
649
|
-
"codex"
|
|
650
|
-
];
|
|
651
|
-
const REQUIRED_WOT_MCP_TOOLS = ["wot_status", "wot_list"];
|
|
652
|
-
function isMcpClientId(value) {
|
|
653
|
-
return MCP_CLIENT_IDS.includes(value);
|
|
654
|
-
}
|
|
655
|
-
function isMcpScope(value) {
|
|
656
|
-
return value === "project" || value === "user";
|
|
657
|
-
}
|
|
658
|
-
|
|
659
691
|
//#endregion
|
|
660
692
|
//#region src/mcp/clients/toml-adapter.ts
|
|
661
693
|
const START = "# open-wot managed mcp server start";
|
|
662
694
|
const END = "# open-wot managed mcp server end";
|
|
663
695
|
const TABLE_PATTERN = /^\[mcp_servers\.(?:wot-ui|"wot-ui")\]\s*$/m;
|
|
664
696
|
const CHILD_TABLE_PATTERN = /^\s*\[\[?\s*mcp_servers\s*\.\s*(?:wot-ui|"wot-ui")\s*\./m;
|
|
665
|
-
async function readOptional$
|
|
697
|
+
async function readOptional$2(path) {
|
|
666
698
|
try {
|
|
667
699
|
return await readFile(path, "utf8");
|
|
668
700
|
} catch (error) {
|
|
@@ -705,21 +737,21 @@ function parseTomlDocument(content, path) {
|
|
|
705
737
|
throw new Error(`Invalid TOML in ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
706
738
|
}
|
|
707
739
|
}
|
|
708
|
-
function isRecord$
|
|
740
|
+
function isRecord$2(value) {
|
|
709
741
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
710
742
|
}
|
|
711
|
-
function getServerEntry(config) {
|
|
743
|
+
function getServerEntry$1(config) {
|
|
712
744
|
const servers = config.mcp_servers;
|
|
713
|
-
return isRecord$
|
|
745
|
+
return isRecord$2(servers) ? servers["wot-ui"] : void 0;
|
|
714
746
|
}
|
|
715
747
|
function optionalStringArray(value) {
|
|
716
748
|
if (value === void 0) return void 0;
|
|
717
749
|
return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : null;
|
|
718
750
|
}
|
|
719
751
|
function parseServer(entry) {
|
|
720
|
-
if (!isRecord$
|
|
752
|
+
if (!isRecord$2(entry) || typeof entry.command !== "string" || !Array.isArray(entry.args) || !entry.args.every((argument) => typeof argument === "string")) return void 0;
|
|
721
753
|
const env = entry.env;
|
|
722
|
-
if (env !== void 0 && (!isRecord$
|
|
754
|
+
if (env !== void 0 && (!isRecord$2(env) || !Object.values(env).every((value) => typeof value === "string"))) return void 0;
|
|
723
755
|
if (entry.enabled !== void 0 && typeof entry.enabled !== "boolean") return void 0;
|
|
724
756
|
const enabledTools = optionalStringArray(entry.enabled_tools);
|
|
725
757
|
const disabledTools = optionalStringArray(entry.disabled_tools);
|
|
@@ -743,7 +775,7 @@ function serverProblem(actual) {
|
|
|
743
775
|
const unavailableTools = [...new Set([...missingEnabledTools, ...disabledRequiredTools])];
|
|
744
776
|
if (unavailableTools.length > 0) return `The Codex tool filters exclude required tools: ${unavailableTools.join(", ")}`;
|
|
745
777
|
}
|
|
746
|
-
function serverMatches(actual, expected) {
|
|
778
|
+
function serverMatches$1(actual, expected) {
|
|
747
779
|
return !serverProblem(actual) && actual?.server.command === expected.command && JSON.stringify(actual.server.args) === JSON.stringify(expected.args) && JSON.stringify(actual.server.env ?? {}) === JSON.stringify(expected.env ?? {});
|
|
748
780
|
}
|
|
749
781
|
function assertNoExternalChildTables(content, path) {
|
|
@@ -752,10 +784,10 @@ function assertNoExternalChildTables(content, path) {
|
|
|
752
784
|
function assertManagedEntryOwnsServer(content, path) {
|
|
753
785
|
const managed = managedPattern().exec(content)?.[0];
|
|
754
786
|
if (!managed) return;
|
|
755
|
-
if (!isDeepStrictEqual(getServerEntry(parseTomlDocument(content, path)), getServerEntry(parseTomlDocument(managed, path)))) throw new Error(`Cannot safely manage mcp_servers.wot-ui settings outside the open-wot managed block in ${path}`);
|
|
787
|
+
if (!isDeepStrictEqual(getServerEntry$1(parseTomlDocument(content, path)), getServerEntry$1(parseTomlDocument(managed, path)))) throw new Error(`Cannot safely manage mcp_servers.wot-ui settings outside the open-wot managed block in ${path}`);
|
|
756
788
|
}
|
|
757
789
|
function assertInstalledEntry(content, block, path) {
|
|
758
|
-
if (!isDeepStrictEqual(getServerEntry(parseTomlDocument(content, path)), getServerEntry(parseTomlDocument(block, path)))) throw new Error(`Cannot safely install mcp_servers.wot-ui while external settings remain in ${path}`);
|
|
790
|
+
if (!isDeepStrictEqual(getServerEntry$1(parseTomlDocument(content, path)), getServerEntry$1(parseTomlDocument(block, path)))) throw new Error(`Cannot safely install mcp_servers.wot-ui while external settings remain in ${path}`);
|
|
759
791
|
}
|
|
760
792
|
function replaceServerSection(content, block) {
|
|
761
793
|
const pattern = managedPattern();
|
|
@@ -806,7 +838,7 @@ var TomlMcpClientAdapter = class {
|
|
|
806
838
|
}
|
|
807
839
|
async inspect(context) {
|
|
808
840
|
const path = this.options.configPath(context, context.scope);
|
|
809
|
-
const content = await readOptional$
|
|
841
|
+
const content = await readOptional$2(path);
|
|
810
842
|
if (content === void 0) return {
|
|
811
843
|
client: this.id,
|
|
812
844
|
displayName: this.displayName,
|
|
@@ -817,7 +849,7 @@ var TomlMcpClientAdapter = class {
|
|
|
817
849
|
matches: false
|
|
818
850
|
};
|
|
819
851
|
try {
|
|
820
|
-
const entry = getServerEntry(parseTomlDocument(content, path));
|
|
852
|
+
const entry = getServerEntry$1(parseTomlDocument(content, path));
|
|
821
853
|
const parsedServer = parseServer(entry);
|
|
822
854
|
const problem = entry === void 0 ? void 0 : serverProblem(parsedServer);
|
|
823
855
|
return {
|
|
@@ -827,7 +859,7 @@ var TomlMcpClientAdapter = class {
|
|
|
827
859
|
path,
|
|
828
860
|
exists: true,
|
|
829
861
|
configured: entry !== void 0,
|
|
830
|
-
matches: serverMatches(parsedServer, context.server),
|
|
862
|
+
matches: serverMatches$1(parsedServer, context.server),
|
|
831
863
|
...parsedServer ? { server: parsedServer.server } : {},
|
|
832
864
|
...problem ? { problem } : {}
|
|
833
865
|
};
|
|
@@ -846,7 +878,7 @@ var TomlMcpClientAdapter = class {
|
|
|
846
878
|
}
|
|
847
879
|
async planInstall(context) {
|
|
848
880
|
const path = this.options.configPath(context, context.scope);
|
|
849
|
-
const before = await readOptional$
|
|
881
|
+
const before = await readOptional$2(path);
|
|
850
882
|
parseTomlDocument(before ?? "", path);
|
|
851
883
|
assertNoExternalChildTables(before ?? "", path);
|
|
852
884
|
assertManagedEntryOwnsServer(before ?? "", path);
|
|
@@ -871,7 +903,7 @@ var TomlMcpClientAdapter = class {
|
|
|
871
903
|
}
|
|
872
904
|
async planRemove(context) {
|
|
873
905
|
const path = this.options.configPath(context, context.scope);
|
|
874
|
-
const before = await readOptional$
|
|
906
|
+
const before = await readOptional$2(path);
|
|
875
907
|
const beforeDocument = before === void 0 ? void 0 : parseTomlDocument(before, path);
|
|
876
908
|
if (before !== void 0 && managedPattern().test(before)) {
|
|
877
909
|
assertNoExternalChildTables(before, path);
|
|
@@ -880,7 +912,7 @@ var TomlMcpClientAdapter = class {
|
|
|
880
912
|
const after = before === void 0 ? void 0 : removeServerSection(before);
|
|
881
913
|
if (after !== void 0) {
|
|
882
914
|
const afterDocument = parseTomlDocument(after, path);
|
|
883
|
-
if (managedPattern().test(before) && getServerEntry(afterDocument) !== void 0) throw new Error(`Cannot safely remove mcp_servers.wot-ui because external settings remain in ${path}`);
|
|
915
|
+
if (managedPattern().test(before) && getServerEntry$1(afterDocument) !== void 0) throw new Error(`Cannot safely remove mcp_servers.wot-ui because external settings remain in ${path}`);
|
|
884
916
|
}
|
|
885
917
|
return {
|
|
886
918
|
id: `${this.id}-${context.scope}-remove`,
|
|
@@ -894,7 +926,7 @@ var TomlMcpClientAdapter = class {
|
|
|
894
926
|
reason: "Remove only the open-wot managed TOML section",
|
|
895
927
|
preview: ["- mcp_servers.wot-ui"]
|
|
896
928
|
}],
|
|
897
|
-
warnings: beforeDocument !== void 0 && getServerEntry(beforeDocument) !== void 0 && !managedPattern().test(before) ? ["A non-managed wot-ui section was found and left unchanged."] : [],
|
|
929
|
+
warnings: beforeDocument !== void 0 && getServerEntry$1(beforeDocument) !== void 0 && !managedPattern().test(before) ? ["A non-managed wot-ui section was found and left unchanged."] : [],
|
|
898
930
|
requiresConfirmation: before !== void 0 && before !== after
|
|
899
931
|
};
|
|
900
932
|
}
|
|
@@ -913,7 +945,7 @@ var TomlMcpClientAdapter = class {
|
|
|
913
945
|
|
|
914
946
|
//#endregion
|
|
915
947
|
//#region src/mcp/clients/codex.ts
|
|
916
|
-
function isRecord(value) {
|
|
948
|
+
function isRecord$1(value) {
|
|
917
949
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
918
950
|
}
|
|
919
951
|
function stringArray(value) {
|
|
@@ -951,7 +983,7 @@ const codexAdapter = new TomlMcpClientAdapter({
|
|
|
951
983
|
};
|
|
952
984
|
if (result.exitCode === 0 && !result.timedOut) try {
|
|
953
985
|
const registration = JSON.parse(result.stdout);
|
|
954
|
-
if (!isRecord(registration) || registration.name !== "wot-ui") return {
|
|
986
|
+
if (!isRecord$1(registration) || registration.name !== "wot-ui") return {
|
|
955
987
|
status: "unknown",
|
|
956
988
|
message: "Codex returned an unreadable registration response",
|
|
957
989
|
command
|
|
@@ -1015,6 +1047,363 @@ const cursorAdapter = new JsonMcpClientAdapter({
|
|
|
1015
1047
|
serverKey: "mcpServers"
|
|
1016
1048
|
});
|
|
1017
1049
|
|
|
1050
|
+
//#endregion
|
|
1051
|
+
//#region src/mcp/clients/opencode.ts
|
|
1052
|
+
function userConfigRoot(context) {
|
|
1053
|
+
return resolve((context.env ?? process.env).XDG_CONFIG_HOME || join(context.homeDir, ".config"));
|
|
1054
|
+
}
|
|
1055
|
+
async function userConfigAllowedRoot(context) {
|
|
1056
|
+
if (!(context.env ?? process.env).XDG_CONFIG_HOME) return context.homeDir;
|
|
1057
|
+
let current = userConfigRoot(context);
|
|
1058
|
+
while (true) {
|
|
1059
|
+
if (await access(current).then(() => true).catch(() => false)) return current;
|
|
1060
|
+
const parent = dirname(current);
|
|
1061
|
+
if (parent === current) return current;
|
|
1062
|
+
current = parent;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
async function readOptional$1(path) {
|
|
1066
|
+
try {
|
|
1067
|
+
return await readFile(path, "utf8");
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
if (error.code === "ENOENT") return void 0;
|
|
1070
|
+
throw error;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
async function projectLayout(cwd) {
|
|
1074
|
+
const start = resolve(cwd);
|
|
1075
|
+
const directories = [start];
|
|
1076
|
+
let current = start;
|
|
1077
|
+
while (true) {
|
|
1078
|
+
if (await access(join(current, ".git")).then(() => true).catch(() => false)) return {
|
|
1079
|
+
directories: directories.reverse(),
|
|
1080
|
+
writeRoot: current
|
|
1081
|
+
};
|
|
1082
|
+
const parent = dirname(current);
|
|
1083
|
+
if (parent === current) return {
|
|
1084
|
+
directories: directories.reverse(),
|
|
1085
|
+
writeRoot: start
|
|
1086
|
+
};
|
|
1087
|
+
directories.push(parent);
|
|
1088
|
+
current = parent;
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async function configCandidates(context, scope) {
|
|
1092
|
+
if (scope === "project") {
|
|
1093
|
+
const { directories } = await projectLayout(context.cwd);
|
|
1094
|
+
return [...directories.flatMap((directory$1) => [resolve(directory$1, "opencode.json"), resolve(directory$1, "opencode.jsonc")]), ...directories.toReversed().flatMap((directory$1) => [resolve(directory$1, ".opencode", "opencode.json"), resolve(directory$1, ".opencode", "opencode.jsonc")])];
|
|
1095
|
+
}
|
|
1096
|
+
const directory = join(userConfigRoot(context), "opencode");
|
|
1097
|
+
return [resolve(directory, "opencode.json"), resolve(directory, "opencode.jsonc")];
|
|
1098
|
+
}
|
|
1099
|
+
async function defaultConfigPath(context, scope) {
|
|
1100
|
+
if (scope === "project") return resolve((await projectLayout(context.cwd)).writeRoot, "opencode.json");
|
|
1101
|
+
return resolve(userConfigRoot(context), "opencode", "opencode.json");
|
|
1102
|
+
}
|
|
1103
|
+
function parseObject(content, path) {
|
|
1104
|
+
const errors = [];
|
|
1105
|
+
const value = parse(content, errors, {
|
|
1106
|
+
allowTrailingComma: true,
|
|
1107
|
+
disallowComments: false
|
|
1108
|
+
});
|
|
1109
|
+
if (errors.length > 0) {
|
|
1110
|
+
const first = errors[0];
|
|
1111
|
+
throw new Error(`Invalid JSONC in ${path}: ${printParseErrorCode(first.error)} at offset ${first.offset}`);
|
|
1112
|
+
}
|
|
1113
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Expected an object in ${path}`);
|
|
1114
|
+
return value;
|
|
1115
|
+
}
|
|
1116
|
+
function isRecord(value) {
|
|
1117
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1118
|
+
}
|
|
1119
|
+
function getServerEntry(config) {
|
|
1120
|
+
if (!isRecord(config.mcp)) return {
|
|
1121
|
+
present: false,
|
|
1122
|
+
value: void 0
|
|
1123
|
+
};
|
|
1124
|
+
return {
|
|
1125
|
+
present: Object.prototype.hasOwnProperty.call(config.mcp, "wot-ui"),
|
|
1126
|
+
value: config.mcp["wot-ui"]
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
async function readConfigSources(context, scope) {
|
|
1130
|
+
const sources = [];
|
|
1131
|
+
for (const path of await configCandidates(context, scope)) {
|
|
1132
|
+
const content = await readOptional$1(path);
|
|
1133
|
+
if (content === void 0) continue;
|
|
1134
|
+
const config = parseObject(content || "{}", path);
|
|
1135
|
+
const entry = getServerEntry(config);
|
|
1136
|
+
sources.push({
|
|
1137
|
+
path,
|
|
1138
|
+
content,
|
|
1139
|
+
config,
|
|
1140
|
+
serverEntry: entry.value,
|
|
1141
|
+
hasServerEntry: entry.present
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
return sources;
|
|
1145
|
+
}
|
|
1146
|
+
function selectConfigSource(sources) {
|
|
1147
|
+
return sources.findLast((source) => source.hasServerEntry) ?? sources.at(-1);
|
|
1148
|
+
}
|
|
1149
|
+
function mergeDeepValue(target, source) {
|
|
1150
|
+
if (Array.isArray(source)) return source.map((value) => mergeDeepValue(void 0, value));
|
|
1151
|
+
if (!isRecord(source)) return source;
|
|
1152
|
+
const merged = {};
|
|
1153
|
+
if (isRecord(target)) {
|
|
1154
|
+
for (const [key, value] of Object.entries(target)) if (key !== "__proto__" && key !== "constructor" && key !== "prototype") merged[key] = mergeDeepValue(void 0, value);
|
|
1155
|
+
}
|
|
1156
|
+
for (const [key, value] of Object.entries(source)) {
|
|
1157
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
1158
|
+
merged[key] = mergeDeepValue(Object.prototype.hasOwnProperty.call(merged, key) ? merged[key] : void 0, value);
|
|
1159
|
+
}
|
|
1160
|
+
return merged;
|
|
1161
|
+
}
|
|
1162
|
+
function normalizePermission(value) {
|
|
1163
|
+
return value === "allow" || value === "ask" || value === "deny" ? { "*": value } : value;
|
|
1164
|
+
}
|
|
1165
|
+
function mergeConfigSources(sources) {
|
|
1166
|
+
return sources.reduce((merged, source) => {
|
|
1167
|
+
return mergeDeepValue(merged, Object.prototype.hasOwnProperty.call(source.config, "permission") ? {
|
|
1168
|
+
...source.config,
|
|
1169
|
+
permission: normalizePermission(source.config.permission)
|
|
1170
|
+
} : source.config);
|
|
1171
|
+
}, {});
|
|
1172
|
+
}
|
|
1173
|
+
function globMatches(pattern, value) {
|
|
1174
|
+
const expression = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
1175
|
+
return (/* @__PURE__ */ new RegExp(`^${expression}$`)).test(value);
|
|
1176
|
+
}
|
|
1177
|
+
function unavailableRequiredTools(config) {
|
|
1178
|
+
const availability = new Map(REQUIRED_WOT_MCP_TOOLS.map((tool) => [tool, true]));
|
|
1179
|
+
const filters = [isRecord(config.tools) ? config.tools : void 0, isRecord(config.permission) ? config.permission : void 0];
|
|
1180
|
+
for (const filter of filters) {
|
|
1181
|
+
if (!filter) continue;
|
|
1182
|
+
for (const [pattern, setting] of Object.entries(filter)) {
|
|
1183
|
+
const enabled = typeof setting === "boolean" ? setting : setting === "deny" ? false : setting === "allow" || setting === "ask" ? true : void 0;
|
|
1184
|
+
if (enabled === void 0) continue;
|
|
1185
|
+
for (const tool of REQUIRED_WOT_MCP_TOOLS) if (pattern === "wot-ui" || globMatches(pattern, `wot-ui_${tool}`)) availability.set(tool, enabled);
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
return REQUIRED_WOT_MCP_TOOLS.filter((tool) => availability.get(tool) === false);
|
|
1189
|
+
}
|
|
1190
|
+
function isStringMap(value) {
|
|
1191
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
|
|
1192
|
+
}
|
|
1193
|
+
function decodeServer(value) {
|
|
1194
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1195
|
+
const candidate = value;
|
|
1196
|
+
const command = candidate.command;
|
|
1197
|
+
const environment = candidate.environment;
|
|
1198
|
+
if (candidate.type !== "local" || candidate.enabled !== void 0 && typeof candidate.enabled !== "boolean" || candidate.enabled === false || !Array.isArray(command) || command.length === 0 || !command.every((item) => typeof item === "string") || environment !== void 0 && !isStringMap(environment)) return;
|
|
1199
|
+
return {
|
|
1200
|
+
command: command[0],
|
|
1201
|
+
args: command.slice(1),
|
|
1202
|
+
...environment === void 0 ? {} : { env: environment }
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
function encodeServer(server, existing) {
|
|
1206
|
+
const existingServer = isRecord(existing) ? existing : void 0;
|
|
1207
|
+
const existingLocalServer = existingServer?.type === "local" ? existingServer : void 0;
|
|
1208
|
+
const cwd = typeof existingLocalServer?.cwd === "string" ? existingLocalServer.cwd : void 0;
|
|
1209
|
+
const existingEnvironment = isStringMap(existingLocalServer?.environment) ? existingLocalServer.environment : void 0;
|
|
1210
|
+
const timeout = typeof existingServer?.timeout === "number" && Number.isInteger(existingServer.timeout) && existingServer.timeout > 0 ? existingServer.timeout : void 0;
|
|
1211
|
+
const environment = existingEnvironment || server.env ? {
|
|
1212
|
+
...existingEnvironment,
|
|
1213
|
+
...server.env
|
|
1214
|
+
} : void 0;
|
|
1215
|
+
return {
|
|
1216
|
+
type: "local",
|
|
1217
|
+
command: [server.command, ...server.args],
|
|
1218
|
+
...cwd === void 0 ? {} : { cwd },
|
|
1219
|
+
enabled: true,
|
|
1220
|
+
...environment ? { environment } : {},
|
|
1221
|
+
...timeout === void 0 ? {} : { timeout }
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function serverMatches(actual, expected) {
|
|
1225
|
+
if (!actual) return false;
|
|
1226
|
+
return actual.command === expected.command && JSON.stringify(actual.args) === JSON.stringify(expected.args) && Object.entries(expected.env ?? {}).every(([key, value]) => actual.env?.[key] === value);
|
|
1227
|
+
}
|
|
1228
|
+
const opencodeAdapter = {
|
|
1229
|
+
id: "opencode",
|
|
1230
|
+
displayName: "OpenCode",
|
|
1231
|
+
supportedScopes: ["project", "user"],
|
|
1232
|
+
async detect(context) {
|
|
1233
|
+
const configLocations = [...await configCandidates(context, "project"), ...await configCandidates(context, "user")];
|
|
1234
|
+
const executable = await findExecutable(["opencode"], context);
|
|
1235
|
+
const hasConfig = (await Promise.all(configLocations.map((path) => access(path).then(() => true).catch(() => false)))).some(Boolean);
|
|
1236
|
+
return {
|
|
1237
|
+
client: "opencode",
|
|
1238
|
+
displayName: "OpenCode",
|
|
1239
|
+
installed: Boolean(executable) || hasConfig,
|
|
1240
|
+
...executable ? { executable } : {},
|
|
1241
|
+
supportedScopes: ["project", "user"],
|
|
1242
|
+
configLocations,
|
|
1243
|
+
preferredMethod: "config-file"
|
|
1244
|
+
};
|
|
1245
|
+
},
|
|
1246
|
+
async inspect(context) {
|
|
1247
|
+
let path = await defaultConfigPath(context, context.scope);
|
|
1248
|
+
try {
|
|
1249
|
+
const sources = await readConfigSources(context, context.scope);
|
|
1250
|
+
const selected = selectConfigSource(sources);
|
|
1251
|
+
if (!selected) return {
|
|
1252
|
+
client: "opencode",
|
|
1253
|
+
displayName: "OpenCode",
|
|
1254
|
+
scope: context.scope,
|
|
1255
|
+
path,
|
|
1256
|
+
exists: false,
|
|
1257
|
+
configured: false,
|
|
1258
|
+
matches: false
|
|
1259
|
+
};
|
|
1260
|
+
path = selected.path;
|
|
1261
|
+
const config = mergeConfigSources(sources);
|
|
1262
|
+
const entry = getServerEntry(config);
|
|
1263
|
+
const server = decodeServer(entry.value);
|
|
1264
|
+
const unavailableTools = unavailableRequiredTools(config);
|
|
1265
|
+
const problem = entry.present ? !server ? "The wot-ui entry is not an enabled OpenCode local MCP server definition" : unavailableTools.length > 0 ? `OpenCode tool filters exclude required tools: ${unavailableTools.join(", ")}` : void 0 : void 0;
|
|
1266
|
+
return {
|
|
1267
|
+
client: "opencode",
|
|
1268
|
+
displayName: "OpenCode",
|
|
1269
|
+
scope: context.scope,
|
|
1270
|
+
path,
|
|
1271
|
+
exists: true,
|
|
1272
|
+
configured: entry.present,
|
|
1273
|
+
matches: !problem && serverMatches(server, context.server),
|
|
1274
|
+
...server ? { server } : {},
|
|
1275
|
+
...problem ? { problem } : {}
|
|
1276
|
+
};
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
return {
|
|
1279
|
+
client: "opencode",
|
|
1280
|
+
displayName: "OpenCode",
|
|
1281
|
+
scope: context.scope,
|
|
1282
|
+
path,
|
|
1283
|
+
exists: true,
|
|
1284
|
+
configured: false,
|
|
1285
|
+
matches: false,
|
|
1286
|
+
problem: error instanceof Error ? error.message : String(error)
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
},
|
|
1290
|
+
async planInstall(context) {
|
|
1291
|
+
const sources = await readConfigSources(context, context.scope);
|
|
1292
|
+
const selected = selectConfigSource(sources);
|
|
1293
|
+
const path = selected?.path ?? await defaultConfigPath(context, context.scope);
|
|
1294
|
+
const allowedRoot = context.scope === "project" ? dirname(path) : await userConfigAllowedRoot(context);
|
|
1295
|
+
const before = selected?.content;
|
|
1296
|
+
const source = before?.trim() ? before : "{}\n";
|
|
1297
|
+
parseObject(source, path);
|
|
1298
|
+
const after = serverMatches(decodeServer(getServerEntry(mergeConfigSources(sources)).value), context.server) ? source : applyEdits(source, modify(source, ["mcp", "wot-ui"], encodeServer(context.server, selected?.serverEntry), { formattingOptions: {
|
|
1299
|
+
insertSpaces: true,
|
|
1300
|
+
tabSize: 2,
|
|
1301
|
+
eol: source.includes("\r\n") ? "\r\n" : "\n"
|
|
1302
|
+
} }));
|
|
1303
|
+
const unchanged = before !== void 0 && before === after;
|
|
1304
|
+
return {
|
|
1305
|
+
id: `opencode-${context.scope}-install`,
|
|
1306
|
+
summary: "Configure wot-ui MCP for OpenCode",
|
|
1307
|
+
changes: unchanged ? [] : [{
|
|
1308
|
+
type: "write-file",
|
|
1309
|
+
path,
|
|
1310
|
+
allowedRoot,
|
|
1311
|
+
before,
|
|
1312
|
+
after,
|
|
1313
|
+
reason: "Add or update mcp.wot-ui as an OpenCode local server",
|
|
1314
|
+
preview: [
|
|
1315
|
+
"+ mcp.wot-ui",
|
|
1316
|
+
` command: ${context.server.command} ${context.server.args.join(" ")}`,
|
|
1317
|
+
...Object.keys(context.server.env ?? {}).map((key) => ` environment.${key}: [REDACTED]`)
|
|
1318
|
+
]
|
|
1319
|
+
}],
|
|
1320
|
+
warnings: [],
|
|
1321
|
+
requiresConfirmation: !unchanged
|
|
1322
|
+
};
|
|
1323
|
+
},
|
|
1324
|
+
async planRemove(context) {
|
|
1325
|
+
const sources = await readConfigSources(context, context.scope);
|
|
1326
|
+
const userAllowedRoot = context.scope === "user" ? await userConfigAllowedRoot(context) : void 0;
|
|
1327
|
+
const changes = sources.flatMap((source) => {
|
|
1328
|
+
if (!source.hasServerEntry) return [];
|
|
1329
|
+
const after = applyEdits(source.content, modify(source.content, ["mcp", "wot-ui"], void 0, { formattingOptions: {
|
|
1330
|
+
insertSpaces: true,
|
|
1331
|
+
tabSize: 2,
|
|
1332
|
+
eol: source.content.includes("\r\n") ? "\r\n" : "\n"
|
|
1333
|
+
} }));
|
|
1334
|
+
if (source.content === after) return [];
|
|
1335
|
+
return [{
|
|
1336
|
+
type: "write-file",
|
|
1337
|
+
path: source.path,
|
|
1338
|
+
allowedRoot: userAllowedRoot ?? dirname(source.path),
|
|
1339
|
+
before: source.content,
|
|
1340
|
+
after,
|
|
1341
|
+
reason: "Remove only the wot-ui OpenCode MCP server entry",
|
|
1342
|
+
preview: ["- mcp.wot-ui"]
|
|
1343
|
+
}];
|
|
1344
|
+
});
|
|
1345
|
+
return {
|
|
1346
|
+
id: `opencode-${context.scope}-remove`,
|
|
1347
|
+
summary: "Remove wot-ui MCP from OpenCode",
|
|
1348
|
+
changes,
|
|
1349
|
+
warnings: [],
|
|
1350
|
+
requiresConfirmation: changes.length > 0
|
|
1351
|
+
};
|
|
1352
|
+
},
|
|
1353
|
+
async print(context) {
|
|
1354
|
+
return JSON.stringify({ mcp: { "wot-ui": encodeServer(context.server) } }, null, 2);
|
|
1355
|
+
},
|
|
1356
|
+
async verifyRegistration(context, _state, options) {
|
|
1357
|
+
const executable = await findExecutable(["opencode"], context);
|
|
1358
|
+
if (!executable) return {
|
|
1359
|
+
status: "unknown",
|
|
1360
|
+
message: "OpenCode executable was not found; registration could not be verified",
|
|
1361
|
+
action: "Open OpenCode in this project and inspect its MCP servers."
|
|
1362
|
+
};
|
|
1363
|
+
const result = await runClientCommand(executable, ["mcp", "list"], {
|
|
1364
|
+
...context,
|
|
1365
|
+
timeoutMs: options?.timeoutMs
|
|
1366
|
+
});
|
|
1367
|
+
const serverLine = clientCommandOutput(result).split(/\r?\n/).find((line) => /^\s*(?:(?:[│┃]\s*)?[✓●○⚠✗×✔!]\s+){0,2}wot-ui(?=\s|$)/i.test(line));
|
|
1368
|
+
const command = `${executable} mcp list`;
|
|
1369
|
+
if (result.timedOut || result.error) return {
|
|
1370
|
+
status: "failed",
|
|
1371
|
+
message: result.error || "OpenCode registration check failed",
|
|
1372
|
+
action: "Retry opencode mcp list and inspect the client process if it still fails.",
|
|
1373
|
+
command
|
|
1374
|
+
};
|
|
1375
|
+
if (serverLine && /pending approval|approval.*pending|untrusted|not trusted/i.test(serverLine)) return {
|
|
1376
|
+
status: "pending",
|
|
1377
|
+
message: "OpenCode requires MCP approval",
|
|
1378
|
+
action: "Approve the wot-ui MCP server in OpenCode and retry the doctor command.",
|
|
1379
|
+
command
|
|
1380
|
+
};
|
|
1381
|
+
if (serverLine && /not initialized|disabled|disconnected|failed|error|needs authentication|needs client registration/i.test(serverLine)) return {
|
|
1382
|
+
status: "failed",
|
|
1383
|
+
message: "OpenCode recognizes the wot-ui MCP server, but it is not ready",
|
|
1384
|
+
action: "Inspect wot-ui with opencode mcp list, then enable or repair the server.",
|
|
1385
|
+
command
|
|
1386
|
+
};
|
|
1387
|
+
if (result.exitCode === 0 && serverLine && /\bconnected\b/i.test(serverLine)) return {
|
|
1388
|
+
status: "ready",
|
|
1389
|
+
message: "OpenCode recognizes the wot-ui MCP server",
|
|
1390
|
+
command
|
|
1391
|
+
};
|
|
1392
|
+
if (result.exitCode === 0 && serverLine) return {
|
|
1393
|
+
status: "unknown",
|
|
1394
|
+
message: "OpenCode recognizes the wot-ui MCP server, but its connection status is unknown",
|
|
1395
|
+
action: "Inspect wot-ui with opencode mcp list and confirm that it is connected.",
|
|
1396
|
+
command
|
|
1397
|
+
};
|
|
1398
|
+
return {
|
|
1399
|
+
status: "failed",
|
|
1400
|
+
message: "OpenCode did not recognize the wot-ui MCP server",
|
|
1401
|
+
action: "Run opencode mcp list and inspect the configured scope.",
|
|
1402
|
+
command
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1018
1407
|
//#endregion
|
|
1019
1408
|
//#region src/mcp/clients/vscode.ts
|
|
1020
1409
|
const vscodeAdapter = new JsonMcpClientAdapter({
|
|
@@ -1032,7 +1421,9 @@ const mcpClientAdapters = [
|
|
|
1032
1421
|
claudeAdapter,
|
|
1033
1422
|
cursorAdapter,
|
|
1034
1423
|
vscodeAdapter,
|
|
1035
|
-
codexAdapter
|
|
1424
|
+
codexAdapter,
|
|
1425
|
+
opencodeAdapter,
|
|
1426
|
+
antigravityAdapter
|
|
1036
1427
|
];
|
|
1037
1428
|
function getMcpClientAdapter(id) {
|
|
1038
1429
|
const adapter = mcpClientAdapters.find((candidate) => candidate.id === id);
|
|
@@ -1645,6 +2036,11 @@ function formatCssVars(cssVars) {
|
|
|
1645
2036
|
|
|
1646
2037
|
//#endregion
|
|
1647
2038
|
//#region src/commands/agent.ts
|
|
2039
|
+
const mcpClientChoices$1 = [
|
|
2040
|
+
"auto",
|
|
2041
|
+
"all",
|
|
2042
|
+
...MCP_CLIENT_IDS
|
|
2043
|
+
].join(", ");
|
|
1648
2044
|
function parseCapabilities(value) {
|
|
1649
2045
|
const capabilities = (value ?? "mcp,skill,instructions").split(",").map((item) => item.trim()).filter(Boolean);
|
|
1650
2046
|
const supported = [
|
|
@@ -1666,7 +2062,7 @@ function parseClient$1(value) {
|
|
|
1666
2062
|
throw new Error(`Unsupported Agent client "${client}".`);
|
|
1667
2063
|
}
|
|
1668
2064
|
function addOptions(command, defaultClient = "auto") {
|
|
1669
|
-
return command.option("--client <client>",
|
|
2065
|
+
return command.option("--client <client>", `Agent client: ${mcpClientChoices$1}`, defaultClient).option("--scope <scope>", "MCP configuration scope: project or user", "project").option("--with <capabilities>", "capabilities: mcp,skill,instructions", "mcp,skill,instructions").option("--cwd <directory>", "project directory", process.cwd()).option("--format <format>", "output format: text or json", "text").option("--pin [version]", "pin @wot-ui/cli to the current or specified version");
|
|
1670
2066
|
}
|
|
1671
2067
|
async function runSafely$1(options, action) {
|
|
1672
2068
|
try {
|
|
@@ -2101,13 +2497,18 @@ function registerListCommand(program) {
|
|
|
2101
2497
|
|
|
2102
2498
|
//#endregion
|
|
2103
2499
|
//#region src/commands/mcp.ts
|
|
2500
|
+
const mcpClientChoices = [
|
|
2501
|
+
"auto",
|
|
2502
|
+
"all",
|
|
2503
|
+
...MCP_CLIENT_IDS
|
|
2504
|
+
].join(", ");
|
|
2104
2505
|
function outputFormat(value) {
|
|
2105
2506
|
return value === "json" ? "json" : "text";
|
|
2106
2507
|
}
|
|
2107
2508
|
function parseClient(value, fallback = "auto") {
|
|
2108
2509
|
const client = value ?? fallback;
|
|
2109
2510
|
if (client === "auto" || client === "all" || isMcpClientId(client)) return client;
|
|
2110
|
-
throw new Error(`Unsupported MCP client "${client}". Use
|
|
2511
|
+
throw new Error(`Unsupported MCP client "${client}". Use ${mcpClientChoices}.`);
|
|
2111
2512
|
}
|
|
2112
2513
|
function parseScope(value) {
|
|
2113
2514
|
const scope = value ?? "project";
|
|
@@ -2115,7 +2516,7 @@ function parseScope(value) {
|
|
|
2115
2516
|
return scope;
|
|
2116
2517
|
}
|
|
2117
2518
|
function addCommonOptions(command, defaultClient = "auto") {
|
|
2118
|
-
return command.option("--client <client>",
|
|
2519
|
+
return command.option("--client <client>", `MCP client: ${mcpClientChoices}`, defaultClient).option("--scope <scope>", "configuration scope: project or user", "project").option("--cwd <directory>", "project directory", process.cwd()).option("--format <format>", "output format: text or json", "text").option("--pin [version]", "pin @wot-ui/cli to the current or specified version");
|
|
2119
2520
|
}
|
|
2120
2521
|
async function runSafely(options, action) {
|
|
2121
2522
|
try {
|
|
@@ -2126,7 +2527,7 @@ async function runSafely(options, action) {
|
|
|
2126
2527
|
}
|
|
2127
2528
|
}
|
|
2128
2529
|
async function startServer() {
|
|
2129
|
-
const { startMcpServer } = await import("./server-
|
|
2530
|
+
const { startMcpServer } = await import("./server-BgPTmKpO.mjs");
|
|
2130
2531
|
await startMcpServer();
|
|
2131
2532
|
}
|
|
2132
2533
|
function registerMcpCommand(program) {
|
|
@@ -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-BVgdy9fy.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,17 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wot-ui/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.1.0-beta.2",
|
|
5
5
|
"description": "面向 wot-ui 的 CLI、MCP 与数据提取工具集",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"funding": "https://github.com/sponsors
|
|
7
|
+
"funding": "https://github.com/Moonofweisheng/sponsors",
|
|
8
8
|
"homepage": "https://github.com/wot-ui/open-wot#readme",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "git+https://github.com/wot-ui/open-wot.git"
|
|
12
12
|
},
|
|
13
13
|
"bugs": "https://github.com/wot-ui/open-wot/issues",
|
|
14
|
-
"keywords": [
|
|
14
|
+
"keywords": [
|
|
15
|
+
"wot-ui",
|
|
16
|
+
"uni-app",
|
|
17
|
+
"vue",
|
|
18
|
+
"cli",
|
|
19
|
+
"mcp",
|
|
20
|
+
"model-context-protocol",
|
|
21
|
+
"ai",
|
|
22
|
+
"coding-agent"
|
|
23
|
+
],
|
|
15
24
|
"sideEffects": false,
|
|
16
25
|
"exports": {
|
|
17
26
|
".": "./dist/index.mjs",
|
|
@@ -53,13 +62,10 @@
|
|
|
53
62
|
"lint-staged": "^16.2.7",
|
|
54
63
|
"publint": "^0.3.16",
|
|
55
64
|
"simple-git-hooks": "^2.13.1",
|
|
56
|
-
"tinyexec": "^1.0.2",
|
|
57
65
|
"tsdown": "^0.17.3",
|
|
58
66
|
"tsx": "^4.21.0",
|
|
59
67
|
"typescript": "^5.9.3",
|
|
60
|
-
"vitest": "^4.0.15"
|
|
61
|
-
"vitest-package-exports": "^0.1.1",
|
|
62
|
-
"yaml": "^2.8.2"
|
|
68
|
+
"vitest": "^4.0.15"
|
|
63
69
|
},
|
|
64
70
|
"simple-git-hooks": {
|
|
65
71
|
"pre-commit": "pnpm i --frozen-lockfile --ignore-scripts --offline && npx lint-staged"
|
|
@@ -100,7 +100,7 @@ npm pack --dry-run --json
|
|
|
100
100
|
|
|
101
101
|
- `wot mcp` 使用 stdio,终端无普通输出通常正常。
|
|
102
102
|
- `doctor` 验证配置和真实 handshake;部分客户端还会检查注册状态。
|
|
103
|
-
- `--client all` 在 project scope
|
|
103
|
+
- `--client all` 在 project scope 处理六个支持客户端。
|
|
104
104
|
- 保留已有 Server、JSONC 注释、非托管 TOML 和用户 Instructions。
|
|
105
105
|
|
|
106
106
|
## 参考
|
|
@@ -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`、`--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`、`--scope`、`--with`、`--cwd`、`--format` 和 `--pin`。`init/remove` 支持 `--dry-run`、`--yes`;`doctor` 支持 `--timeout`。
|
|
51
51
|
|
|
52
52
|
### MCP
|
|
53
53
|
|
|
@@ -71,6 +71,8 @@
|
|
|
71
71
|
| Cursor | `.cursor/mcp.json` |
|
|
72
72
|
| VS Code | `.vscode/mcp.json` |
|
|
73
73
|
| Codex | `.codex/config.toml` |
|
|
74
|
+
| OpenCode | `opencode.json[c]` / `.opencode/opencode.json[c]` |
|
|
75
|
+
| Antigravity | `.agents/mcp_config.json` |
|
|
74
76
|
|
|
75
77
|
一次接入所有客户端:
|
|
76
78
|
|