dsh-plugin-capabilities 0.3.8 → 0.3.9

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
@@ -27,11 +27,11 @@
27
27
 
28
28
  「MCP」页管理 profile patch 中的 MCP 服务器行,每行对应一个 `@deepseek-ai/dsh-mcp-client` 实例。stdio 服务器填写命令与参数,streamable-http 服务器填写 URL,编辑、停用、移除都在页面上完成。YAML 读写采用文档级 API,文件中的其他行与注释不受影响。新行写在一个 `- insert:` 块里——加载器只会挂载 insert 形式的行,裸的 `- id:` 条目是对已有行的覆盖,目标不存在时会被跳过;0.1.3 之前写入的裸行会在下一次保存时自动迁入 insert 块。0.3.7 起,若 `cordis.patch.yml` 本身存在语法错误(多见于手工编辑失误),页面会直接报出文件完整路径与出错行列,并且不写入任何内容;此前这种文件会在保存时抛出内部的 "Document with errors cannot be stringified",让人无从下手。
29
29
 
30
- 服务器行不只存在于 profile 里。dsh 还有一层全局补丁(`DSH_HOME/cordis.patch.yml`),对这台机器上的所有 profile 生效,组合时排在 profile 层之后、同名 id 以全局为准。0.3.8 起,「MCP」页把两层合在一起展示:每行带「全局」或「当前 profile」标记,两层出现同名 id 时 profile 行会注明自己不生效。添加服务器时可以选择写入哪一层,编辑、停用、移除也都落在该行所在的层。全局文件损坏不会连累整个页面:profile 行照常列出,顶部横幅单独报出全局文件的路径与出错行列。
30
+ 服务器行不只存在于 profile 里。dsh 还有一层全局补丁(`DSH_HOME/cordis.patch.yml`),对这台机器上的所有 profile 生效,组合时排在 profile 层之后、同名 id 以全局为准。0.3.8 起,「MCP」页把两层合在一起展示:每行带「全局」或「当前 profile」标记,两层出现同名 id 时 profile 行会注明自己不生效。添加服务器时可以选择写入哪一层,编辑、停用、移除也都落在该行所在的层。每行还有「检查」和「复制到另一层」两个动作:检查不启动服务器——stdio 行在 PATH 里确认命令真实存在(带 Windows 可执行扩展名),http 行发一个短超时的 GET,任何 HTTP 状态都算可达(MCP 端点对普通 GET 回 405 是正常的);复制把整行配置搬去另一层,不用删了重配。全局文件损坏不会连累整个页面:profile 行照常列出,顶部横幅单独报出全局文件的路径与出错行列。
31
31
 
32
32
  添加或编辑服务器时,表单下方有「完整格式与快速填充」区:一边是随表单实时更新的完整 YAML 行(和保存后落进 `cordis.patch.yml` 的一模一样),另一边是等价的 `mcpServers` JSON 写法,可以直接抄去别的工具。反过来也行——把 Claude Code 配置、官方文档或任意 JSON 粘进输入框,点「解析并填充」,服务器名、命令、参数、环境变量、URL、请求头都会自动填好,`{"mcpServers": {...}}` 包装、裸条目、dsh 行三种形状都认。
33
33
 
34
- 也可以从其他 agent 导入:一键扫描 Claude Code(`~/.claude.json`、`~/.claude/settings.json`)与 Codex(`~/.codex/config.toml`)的 MCP 配置,勾选所需条目后转为本 profile 的服务器行。弹窗按来源分成 Claude Code Codex 两组,各自带数量与「全选」;stdio http 两种传输都会处理,已存在的同名服务器置灰跳过。需要注意的是,Claude 配置里的 `${VAR}` 环境变量引用按字面值导入,如有需要请在导入后手动改回。
34
+ 也可以从其他 agent 导入:一键扫描 Claude Code(`~/.claude.json`、`~/.claude/settings.json`)、Codex(`~/.codex/config.toml`)、Cursor(`~/.cursor/mcp.json`)与 Gemini CLI(`~/.gemini/settings.json`)的 MCP 配置,弹窗里先选写入当前 profile 还是全局层,勾选所需条目后转成服务器行。弹窗按来源分组,各自带数量与「全选」;stdio http 两种传输都会处理(Gemini SSE 条目除外,dsh 的客户端不支持),已存在的同名服务器置灰跳过。需要注意的是,Claude 配置里的 `${VAR}` 环境变量引用按字面值导入,如有需要请在导入后手动改回。
35
35
 
36
36
  MCP 行的变更需要重启 dsh 才会进入组合。MCP 页头部有常驻的「重启」按钮,变更后无需离开界面:在 [DSH Desktop](https://github.com/qinyre/dsh-Desktop) 中由桌面壳层重启受监督的 sidecar,完成后窗口自动重载;直接运行 `dsh web` 时插件会拉起一个替代进程再退出自身,页面在恢复后自动刷新——若启动时端口是随机的,按横幅提示在终端查看新地址。重启会中断正在进行的回合,点击后会先弹出确认。
37
37
 
@@ -41,7 +41,7 @@ MCP 行的变更需要重启 dsh 才会进入组合。MCP 页头部有常驻的
41
41
 
42
42
  ![「MCP 市场」](docs/images/screenshot-market-mcp.png)
43
43
 
44
- 「市场」页分两栏:「技能市场」是精选的技能仓库(Anthropic 官方技能集、Superpowers 工作流集等),点「安装」走的就是上文 GitHub 仓库的下载解包流程,装完立即出现在「技能」页;「MCP 市场」是一份精选服务器列表(官方 filesystem/memory/git 等,加上 context7 这类常用第三方),点「添加」直接写入一条 profile 服务器行,和手工添加完全等价。0.3.0 起点开条目可以看详情:技能仓库列出里面具体有哪些技能,MCP 服务器列出启动命令、需要的环境变量和提供的工具,不必再去 GitHub 主页。列表数据来自[本插件仓库](https://github.com/qinyre/dsh-plugin-capabilities)的在线索引(`market/*.json`),离线时自动回退到包内快照;需要 API 密钥的服务器会在卡片上标出环境变量名,装好后到 MCP 列表里补填即可。已安装的条目可以直接卸载:技能仓库走仓库移除,MCP 行走服务器删除。
44
+ 「市场」页分两栏:「技能市场」是精选的技能仓库(Anthropic 官方技能集、Superpowers 工作流集等),点「安装」走的就是上文 GitHub 仓库的下载解包流程,装完立即出现在「技能」页;「MCP 市场」是一份精选服务器列表(官方 filesystem/memory/git 等,加上 context7 这类常用第三方),点「添加」直接写入一条服务器行,和手工添加完全等价,写入哪一层(当前 profile 还是全局)跟随列表上方的生效范围选择。0.3.0 起点开条目可以看详情:技能仓库列出里面具体有哪些技能,MCP 服务器列出启动命令、需要的环境变量和提供的工具,不必再去 GitHub 主页。列表数据来自[本插件仓库](https://github.com/qinyre/dsh-plugin-capabilities)的在线索引(`market/*.json`),离线时自动回退到包内快照;需要 API 密钥的服务器会在卡片上标出环境变量名,装好后到 MCP 列表里补填即可。已安装的条目可以直接卸载:技能仓库走仓库移除,MCP 行走服务器删除(该行在全局层就删全局层的)。
45
45
 
46
46
  ## 安装
47
47
 
package/lib/client.js CHANGED
@@ -187,6 +187,7 @@ function MarketTab(props) {
187
187
  const [servers, setServers] = (0, import_react.useState)(null);
188
188
  const [source, setSource] = (0, import_react.useState)("remote");
189
189
  const [busyId, setBusyId] = (0, import_react.useState)(null);
190
+ const [mcpScope, setMcpScope] = (0, import_react.useState)("profile");
190
191
  const [outcome, setOutcome] = (0, import_react.useState)(null);
191
192
  const [confirmUninstall, setConfirmUninstall] = (0, import_react.useState)(null);
192
193
  const [detail, setDetail] = (0, import_react.useState)(null);
@@ -239,7 +240,7 @@ function MarketTab(props) {
239
240
  setBusyId(server.id);
240
241
  setOutcome(null);
241
242
  try {
242
- await market.installMcp(server.id);
243
+ await market.installMcp(server.id, mcpScope);
243
244
  setOutcome({ ok: true, text: t("restartNeeded") });
244
245
  refreshMcp();
245
246
  } catch (error) {
@@ -259,7 +260,7 @@ function MarketTab(props) {
259
260
  setOutcome({ ok: true, text: t("rootRemoved") });
260
261
  refreshSkills();
261
262
  } else {
262
- await mcp.remove(target.id, "profile");
263
+ await mcp.remove(target.id, target.scope);
263
264
  setOutcome({ ok: true, text: t("restartNeeded") });
264
265
  refreshMcp();
265
266
  }
@@ -326,6 +327,23 @@ function MarketTab(props) {
326
327
  ] }),
327
328
  segment === "mcp" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
328
329
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-intro", children: t("marketMcpIntro") }),
330
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dpc-cardRow", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
331
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("scopeLabel") }),
332
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
333
+ "select",
334
+ {
335
+ className: "dpc-select",
336
+ style: { width: "auto" },
337
+ value: mcpScope,
338
+ disabled: busyId !== null,
339
+ onChange: (event) => setMcpScope(event.target.value),
340
+ children: [
341
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "profile", children: t("scopeProfile") }),
342
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "global", children: t("scopeGlobal") })
343
+ ]
344
+ }
345
+ )
346
+ ] }) }),
329
347
  servers === null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-empty", children: t("loading") }),
330
348
  servers !== null && servers.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-empty", children: t("marketEmpty") }),
331
349
  servers !== null && servers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "dpc-cards", children: servers.map((server) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "dpc-card", onClick: () => setDetail({ kind: "mcp", id: server.id }), children: [
@@ -350,7 +368,7 @@ function MarketTab(props) {
350
368
  void mcp.list().then(
351
369
  (body) => {
352
370
  const row = body.servers.find((item) => item.serverName === server.id);
353
- if (row !== void 0) setConfirmUninstall({ kind: "mcp", id: row.id, name: server.id });
371
+ if (row !== void 0) setConfirmUninstall({ kind: "mcp", id: row.id, name: server.id, scope: row.scope });
354
372
  },
355
373
  (error) => setOutcome({ ok: false, text: `${t("failed")}: ${String(error.message ?? error)}` })
356
374
  );
@@ -382,7 +400,7 @@ function MarketTab(props) {
382
400
  (body) => {
383
401
  const row = body.servers.find((item) => item.serverName === detailServer.id);
384
402
  if (row !== void 0) {
385
- setConfirmUninstall({ kind: "mcp", id: row.id, name: title(detailServer) });
403
+ setConfirmUninstall({ kind: "mcp", id: row.id, name: title(detailServer), scope: row.scope });
386
404
  setDetail(null);
387
405
  }
388
406
  },
@@ -454,8 +472,8 @@ var import_react2 = require("react");
454
472
  var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
455
473
  var import_jsx_runtime2 = require("react/jsx-runtime");
456
474
  function importGroups(items) {
457
- const label = (agent) => agent === "claude-code" ? "Claude Code" : agent === "codex" ? "Codex" : agent;
458
- const order = ["claude-code", "codex"];
475
+ const label = (agent) => agent === "claude-code" ? "Claude Code" : agent === "codex" ? "Codex" : agent === "cursor" ? "Cursor" : agent === "gemini" ? "Gemini CLI" : agent;
476
+ const order = ["claude-code", "codex", "cursor", "gemini"];
459
477
  const agents = [...new Set(items.map((item) => item.server.agent))].sort((a, b2) => {
460
478
  const rank = (agent) => {
461
479
  const at2 = order.indexOf(agent);
@@ -595,6 +613,8 @@ function McpTab(props) {
595
613
  const [reload, setReload] = (0, import_react2.useState)(0);
596
614
  const [pasteJson, setPasteJson] = (0, import_react2.useState)("");
597
615
  const [pasteError, setPasteError] = (0, import_react2.useState)(null);
616
+ const [checks, setChecks] = (0, import_react2.useState)({});
617
+ const [importScope, setImportScope] = (0, import_react2.useState)("profile");
598
618
  const openImport = async () => {
599
619
  setImportOpen(true);
600
620
  setImportItems(null);
@@ -616,7 +636,7 @@ function McpTab(props) {
616
636
  const items = importItems.filter((item) => item.checked && !item.existing).map((item) => ({ agent: item.server.agent, name: item.server.name }));
617
637
  setBusy(true);
618
638
  try {
619
- const body = await injected.applyImport(items);
639
+ const body = await injected.applyImport(items, importScope);
620
640
  const failed = body.results.filter((item) => !item.ok);
621
641
  setOutcome(failed.length === 0 ? { ok: true, text: t("restartNeeded") } : { ok: false, text: `${t("failed")}: ${failed.map((item) => `${item.name} (${item.error})`).join(", ")}` });
622
642
  setImportOpen(false);
@@ -755,6 +775,29 @@ function McpTab(props) {
755
775
  setConfirmRow(null);
756
776
  }
757
777
  };
778
+ const doCheck = async (row) => {
779
+ const key = `${row.scope}/${row.id}`;
780
+ setChecks((current) => ({ ...current, [key]: "busy" }));
781
+ try {
782
+ const result = await injected.check(row.id, row.scope);
783
+ setChecks((current) => ({ ...current, [key]: result }));
784
+ } catch (error) {
785
+ setChecks((current) => ({ ...current, [key]: { ok: false, detail: String(error instanceof Error ? error.message : error) } }));
786
+ }
787
+ };
788
+ const doCopy = async (row) => {
789
+ setBusy(true);
790
+ try {
791
+ const toScope = row.scope === "global" ? "profile" : "global";
792
+ await injected.copy(row.id, row.scope, toScope);
793
+ setOutcome({ ok: true, text: t("restartNeeded") });
794
+ reloadList(true);
795
+ } catch (error) {
796
+ setOutcome({ ok: false, text: `${t("failed")}: ${String(error instanceof Error ? error.message : error)}` });
797
+ } finally {
798
+ setBusy(false);
799
+ }
800
+ };
758
801
  const doRestart = () => {
759
802
  setRestartConfirm(false);
760
803
  setRestarting(true);
@@ -827,8 +870,19 @@ function McpTab(props) {
827
870
  ] }),
828
871
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-cardDesc", children: row.transport === "stdio" ? `${row.command ?? ""} ${(row.args ?? []).join(" ")}` : row.url ?? "" }),
829
872
  row.shadowed === true && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-formError", children: t("shadowedByGlobal") }),
873
+ (() => {
874
+ const check = checks[`${row.scope}/${row.id}`];
875
+ if (check === void 0) return null;
876
+ if (check === "busy") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-cardDesc", children: t("checkRunning") });
877
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: check.ok ? "dpc-cardDesc" : "dpc-formError", children: [
878
+ check.ok ? `\u2713 ${t("checkOk")}` : `\u2717 ${t("checkFail")}`,
879
+ check.detail !== void 0 && ` \xB7 ${check.detail}`
880
+ ] });
881
+ })(),
830
882
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-cardRow", children: [
831
883
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-spacer" }),
884
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => void doCheck(row), children: t("checkLabel") }),
885
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => void doCopy(row), children: t("copyToOther") }),
832
886
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => void doToggle(row), children: t("toggle") }),
833
887
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => openEdit(row), children: t("edit") }),
834
888
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => setConfirmRow(row), children: t("delete") })
@@ -991,6 +1045,23 @@ function McpTab(props) {
991
1045
  className: "dpc-modalWide",
992
1046
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-form", children: [
993
1047
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-intro", style: { margin: 0 }, children: t("importIntro") }),
1048
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dpc-cardRow", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
1049
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("scopeLabel") }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1051
+ "select",
1052
+ {
1053
+ className: "dpc-select",
1054
+ style: { width: "auto" },
1055
+ value: importScope,
1056
+ disabled: busy,
1057
+ onChange: (event) => setImportScope(event.target.value),
1058
+ children: [
1059
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "profile", children: t("scopeProfile") }),
1060
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "global", children: t("scopeGlobal") })
1061
+ ]
1062
+ }
1063
+ )
1064
+ ] }) }),
994
1065
  importItems === null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-empty", children: t("loading") }),
995
1066
  importItems !== null && importItems.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-empty", children: t("importEmpty") }),
996
1067
  importItems !== null && importItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dpc-importScroll", children: importGroups(importItems).map((group) => {
@@ -4496,6 +4567,11 @@ var zh = {
4496
4567
  scopeHint: "\u5168\u5C40\uFF1A\u5199\u5165 DSH_HOME/cordis.patch.yml\uFF0C\u5BF9\u672C\u673A\u6240\u6709 profile \u751F\u6548\uFF1B\u5F53\u524D profile\uFF1A\u53EA\u5F71\u54CD\u672C profile\u3002",
4497
4568
  shadowedByGlobal: "\u5168\u5C40\u5C42\u5B58\u5728\u540C\u540D id\uFF0C\u6B64\u884C\u4E0D\u751F\u6548",
4498
4569
  globalLayerError: "\u5168\u5C40\u914D\u7F6E\uFF08DSH_HOME/cordis.patch.yml\uFF09\u8BFB\u53D6\u5931\u8D25\uFF0C\u5168\u5C40\u670D\u52A1\u5668\u6682\u672A\u663E\u793A\uFF1A",
4570
+ checkLabel: "\u68C0\u67E5",
4571
+ checkRunning: "\u68C0\u67E5\u4E2D\u2026",
4572
+ checkOk: "\u68C0\u67E5\u901A\u8FC7",
4573
+ checkFail: "\u68C0\u67E5\u672A\u901A\u8FC7",
4574
+ copyToOther: "\u590D\u5236\u5230\u53E6\u4E00\u5C42",
4499
4575
  serverName: "\u670D\u52A1\u5668\u540D\uFF08\u5DE5\u5177\u540D\u524D\u7F00\uFF09",
4500
4576
  transport: "\u4F20\u8F93\u65B9\u5F0F",
4501
4577
  transportStdio: "stdio\uFF08\u672C\u5730\u547D\u4EE4\uFF09",
@@ -4512,7 +4588,7 @@ var zh = {
4512
4588
  removeWarn: "\u5C06\u4ECE\u914D\u7F6E\u4E2D\u79FB\u9664\u8FD9\u4E00\u884C\uFF0C\u91CD\u542F dsh \u540E\u5176\u5DE5\u5177\u4E0D\u518D\u51FA\u73B0\u3002",
4513
4589
  emptyMcp: "\u8FD8\u6CA1\u6709\u914D\u7F6E MCP \u670D\u52A1\u5668",
4514
4590
  importServers: "\u4ECE\u5176\u4ED6 Agent \u5BFC\u5165",
4515
- importIntro: "\u626B\u63CF Claude Code\uFF08~/.claude.json\uFF09\u4E0E Codex\uFF08~/.codex/config.toml\uFF09\u7684 MCP \u670D\u52A1\u5668\u914D\u7F6E\uFF0C\u52FE\u9009\u540E\u5BFC\u5165\u4E3A\u672C profile \u7684\u670D\u52A1\u5668\u884C\u3002",
4591
+ importIntro: "\u626B\u63CF Claude Code\uFF08~/.claude.json\uFF09\u3001Codex\uFF08~/.codex/config.toml\uFF09\u3001Cursor\uFF08~/.cursor/mcp.json\uFF09\u4E0E Gemini CLI\uFF08~/.gemini/settings.json\uFF09\u7684 MCP \u670D\u52A1\u5668\u914D\u7F6E\uFF0C\u52FE\u9009\u540E\u5BFC\u5165\u5230\u6240\u9009\u8303\u56F4\u3002",
4516
4592
  importEmpty: "\u6CA1\u6709\u53D1\u73B0\u53EF\u5BFC\u5165\u7684 MCP \u670D\u52A1\u5668",
4517
4593
  importSelectAll: "\u5168\u9009",
4518
4594
  importExisting: "\u5DF2\u5B58\u5728",
@@ -4538,7 +4614,7 @@ var zh = {
4538
4614
  marketSkills: "\u6280\u80FD\u5E02\u573A",
4539
4615
  marketMcp: "MCP \u5E02\u573A",
4540
4616
  marketSkillsIntro: "\u5B89\u88C5\u6280\u80FD\u4ED3\u5E93\uFF08GitHub \u4E0B\u8F7D\u89E3\u5305\uFF0C\u5B9E\u65F6\u8FDB\u5165\u626B\u63CF\u76EE\u5F55\uFF09\uFF1B\u5DF2\u5B89\u88C5\u7684\u4ED3\u5E93\u53EF\u5728\u300C\u6280\u80FD\u300D\u9875\u7BA1\u7406\u3002",
4541
- marketMcpIntro: "\u6DFB\u52A0 MCP \u670D\u52A1\u5668\u884C\uFF08\u4E0E\u300CMCP\u300D\u9875\u540C\u4E00\u5B58\u50A8\uFF0C\u6DFB\u52A0\u540E\u9700\u91CD\u542F\u751F\u6548\uFF09\uFF1B\u9700\u8981\u5BC6\u94A5\u7684\u670D\u52A1\u5728\u5B89\u88C5\u540E\u4E8E\u5217\u8868\u4E2D\u8865\u586B\u73AF\u5883\u53D8\u91CF\u3002",
4617
+ marketMcpIntro: "\u6DFB\u52A0 MCP \u670D\u52A1\u5668\u884C\uFF08\u4E0A\u65B9\u53EF\u9009\u5199\u5165\u5F53\u524D profile \u8FD8\u662F\u5168\u5C40\u5C42\uFF0C\u6DFB\u52A0\u540E\u9700\u91CD\u542F\u751F\u6548\uFF09\uFF1B\u9700\u8981\u5BC6\u94A5\u7684\u670D\u52A1\u5728\u5B89\u88C5\u540E\u4E8E\u5217\u8868\u4E2D\u8865\u586B\u73AF\u5883\u53D8\u91CF\u3002",
4542
4618
  marketEmpty: "\u5E02\u573A\u5217\u8868\u4E3A\u7A7A\u6216\u4E0D\u53EF\u7528",
4543
4619
  marketOffline: "\u5728\u7EBF\u7D22\u5F15\u4E0D\u53EF\u8FBE\uFF0C\u5F53\u524D\u5C55\u793A\u5305\u5185\u5FEB\u7167\uFF08\u53EF\u80FD\u4E0E\u6700\u65B0\u5217\u8868\u6709\u5DEE\u5F02\uFF09\u3002",
4544
4620
  marketSkillCount: "{n} \u4E2A\u6280\u80FD",
@@ -4634,6 +4710,11 @@ var en = {
4634
4710
  scopeHint: "Global writes DSH_HOME/cordis.patch.yml and applies to every profile on this machine; this profile writes the current profile only.",
4635
4711
  shadowedByGlobal: "a global row with the same id wins \u2014 this row has no effect",
4636
4712
  globalLayerError: "Failed to read the global layer (DSH_HOME/cordis.patch.yml); global servers are hidden:",
4713
+ checkLabel: "Check",
4714
+ checkRunning: "Checking\u2026",
4715
+ checkOk: "OK",
4716
+ checkFail: "Failed",
4717
+ copyToOther: "Copy to other layer",
4637
4718
  serverName: "Server name (tool name prefix)",
4638
4719
  transport: "Transport",
4639
4720
  transportStdio: "stdio (local command)",
@@ -4650,7 +4731,7 @@ var en = {
4650
4731
  removeWarn: "Removes the row from its configuration; its tools disappear after the next dsh restart.",
4651
4732
  emptyMcp: "No MCP servers configured yet",
4652
4733
  importServers: "Import from other agents",
4653
- importIntro: "Scans Claude Code (~/.claude.json) and Codex (~/.codex/config.toml) MCP server configs; selected entries become server rows in this profile.",
4734
+ importIntro: "Scans the MCP server configs of Claude Code (~/.claude.json), Codex (~/.codex/config.toml), Cursor (~/.cursor/mcp.json), and Gemini CLI (~/.gemini/settings.json); selected entries land in the scope picked below.",
4654
4735
  importEmpty: "No importable MCP servers found",
4655
4736
  importSelectAll: "Select all",
4656
4737
  importExisting: "already here",
@@ -4676,7 +4757,7 @@ var en = {
4676
4757
  marketSkills: "Skills market",
4677
4758
  marketMcp: "MCP market",
4678
4759
  marketSkillsIntro: "Installs skill repositories (GitHub download, live catalog); manage installed ones on the Skills tab.",
4679
- marketMcpIntro: "Adds MCP server rows (same storage as the MCP tab; restart to apply). Servers needing keys get their environment filled in on the row after install.",
4760
+ marketMcpIntro: "Adds MCP server rows (pick the target layer \u2014 this profile or global \u2014 above; restart to apply). Servers needing keys get their environment filled in on the row after install.",
4680
4761
  marketEmpty: "Market list is empty or unavailable",
4681
4762
  marketOffline: "Online index unreachable \u2014 showing the bundled snapshot, which may lag behind.",
4682
4763
  marketSkillCount: "{n} skills",
@@ -4726,8 +4807,10 @@ function apply2(ctx) {
4726
4807
  save: (input) => post("/dsh-plugin-capabilities/mcp/save", input),
4727
4808
  toggle: (id, disabled, scope) => post("/dsh-plugin-capabilities/mcp/toggle", { id, disabled, scope }),
4728
4809
  remove: (id, scope) => post("/dsh-plugin-capabilities/mcp/remove", { id, scope }),
4810
+ check: (id, scope) => post("/dsh-plugin-capabilities/mcp/check", { id, scope }),
4811
+ copy: (id, scope, toScope) => post("/dsh-plugin-capabilities/mcp/copy", { id, scope, toScope }),
4729
4812
  scanImport: () => fetchJson("/dsh-plugin-capabilities/import/scan"),
4730
- applyImport: (items) => post("/dsh-plugin-capabilities/import/apply", { items }),
4813
+ applyImport: (items, scope) => post("/dsh-plugin-capabilities/import/apply", { items, scope }),
4731
4814
  restart: async () => {
4732
4815
  if (window.dshDesktop !== void 0) {
4733
4816
  window.dshDesktop.restartSidecar?.();
@@ -4744,7 +4827,7 @@ function apply2(ctx) {
4744
4827
  skillsIndex: () => fetchJson("/dsh-plugin-capabilities/market/skills"),
4745
4828
  mcpIndex: () => fetchJson("/dsh-plugin-capabilities/market/mcp"),
4746
4829
  installSkillRepo: (url) => post("/dsh-plugin-capabilities/market/skills/install", { url }),
4747
- installMcp: (id) => post("/dsh-plugin-capabilities/market/mcp/install", { id }),
4830
+ installMcp: (id, scope) => post("/dsh-plugin-capabilities/market/mcp/install", { id, scope }),
4748
4831
  removeRoot: skillsInjected.removeRoot
4749
4832
  };
4750
4833
  ctx.slots.inject("settings.section", () => {