@wot-ui/cli 1.0.6 → 1.1.0-beta.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  <p align="center">
2
- <a href="https://wot-ui.cn">
3
- <img src="./docs/assets/wot-ui-logo.svg" width="112" alt="Wot UI">
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 和 Codex;在 user scope 下只处理支持用户级配置的客户端。
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 和 Codex 支持 `--scope user`;VS Code 当前使用 project scope。`doctor` 会验证配置和真实 MCP handshake,并在客户端支持时继续检查注册状态。
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
@@ -11,6 +11,6 @@
11
11
  "2.0": "2.0.8",
12
12
  "2.1": "2.1.0",
13
13
  "2.2": "2.2.0",
14
- "2.3": "2.3.0"
14
+ "2.3": "2.3.2"
15
15
  }
16
16
  }
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-vahhrKL6.mjs";
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-wvtkBaTT.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$2(path) {
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$1(actual, expected) {
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$2(path);
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$1(server, context.server),
406
+ matches: !serverProblem$2 && serverMatches$2(server, context.server),
494
407
  ...server ? { server } : {},
495
- ...!server && entry !== void 0 ? { problem: "The wot-ui entry is not a supported stdio server definition" } : {}
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$2(path);
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$2(path);
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$1(path) {
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$1(value) {
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$1(servers) ? servers["wot-ui"] : void 0;
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$1(entry) || typeof entry.command !== "string" || !Array.isArray(entry.args) || !entry.args.every((argument) => typeof argument === "string")) return void 0;
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$1(env) || !Object.values(env).every((value) => typeof value === "string"))) return void 0;
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$1(path);
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$1(path);
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$1(path);
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>", "Agent client: auto, all, claude, cursor, vscode, codex", 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");
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 claude, cursor, vscode, codex, auto, or all.`);
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>", "MCP client: auto, all, claude, cursor, vscode, codex", 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");
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-tl_9acKR.mjs");
2530
+ const { startMcpServer } = await import("./server-DtGPHfAw.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-vahhrKL6.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-wvtkBaTT.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.0.6";
11
+ var version = "1.1.0-beta.1";
12
12
 
13
13
  //#endregion
14
14
  //#region src/data/loader.ts
package/package.json CHANGED
@@ -1,17 +1,26 @@
1
1
  {
2
2
  "name": "@wot-ui/cli",
3
3
  "type": "module",
4
- "version": "1.0.6",
4
+ "version": "1.1.0-beta.1",
5
5
  "description": "面向 wot-ui 的 CLI、MCP 与数据提取工具集",
6
6
  "license": "MIT",
7
- "funding": "https://github.com/sponsors/antfu",
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