dsh-plugin-capabilities 0.3.7 → 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,9 +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 行会注明自己不生效。添加服务器时可以选择写入哪一层,编辑、停用、移除也都落在该行所在的层。每行还有「检查」和「复制到另一层」两个动作:检查不启动服务器——stdio 行在 PATH 里确认命令真实存在(带 Windows 可执行扩展名),http 行发一个短超时的 GET,任何 HTTP 状态都算可达(MCP 端点对普通 GET 回 405 是正常的);复制把整行配置搬去另一层,不用删了重配。全局文件损坏不会连累整个页面:profile 行照常列出,顶部横幅单独报出全局文件的路径与出错行列。
31
+
30
32
  添加或编辑服务器时,表单下方有「完整格式与快速填充」区:一边是随表单实时更新的完整 YAML 行(和保存后落进 `cordis.patch.yml` 的一模一样),另一边是等价的 `mcpServers` JSON 写法,可以直接抄去别的工具。反过来也行——把 Claude Code 配置、官方文档或任意 JSON 粘进输入框,点「解析并填充」,服务器名、命令、参数、环境变量、URL、请求头都会自动填好,`{"mcpServers": {...}}` 包装、裸条目、dsh 行三种形状都认。
31
33
 
32
- 也可以从其他 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}` 环境变量引用按字面值导入,如有需要请在导入后手动改回。
33
35
 
34
36
  MCP 行的变更需要重启 dsh 才会进入组合。MCP 页头部有常驻的「重启」按钮,变更后无需离开界面:在 [DSH Desktop](https://github.com/qinyre/dsh-Desktop) 中由桌面壳层重启受监督的 sidecar,完成后窗口自动重载;直接运行 `dsh web` 时插件会拉起一个替代进程再退出自身,页面在恢复后自动刷新——若启动时端口是随机的,按横幅提示在终端查看新地址。重启会中断正在进行的回合,点击后会先弹出确认。
35
37
 
@@ -39,7 +41,7 @@ MCP 行的变更需要重启 dsh 才会进入组合。MCP 页头部有常驻的
39
41
 
40
42
  ![「MCP 市场」](docs/images/screenshot-market-mcp.png)
41
43
 
42
- 「市场」页分两栏:「技能市场」是精选的技能仓库(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 行走服务器删除(该行在全局层就删全局层的)。
43
45
 
44
46
  ## 安装
45
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);
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);
@@ -582,7 +600,8 @@ function McpTab(props) {
582
600
  const { t, injected } = props;
583
601
  const [servers, setServers] = (0, import_react2.useState)(null);
584
602
  const [editor, setEditor] = (0, import_react2.useState)(null);
585
- const [confirmId, setConfirmId] = (0, import_react2.useState)(null);
603
+ const [confirmRow, setConfirmRow] = (0, import_react2.useState)(null);
604
+ const [globalError, setGlobalError] = (0, import_react2.useState)(null);
586
605
  const [importOpen, setImportOpen] = (0, import_react2.useState)(false);
587
606
  const [importItems, setImportItems] = (0, import_react2.useState)(null);
588
607
  const [busy, setBusy] = (0, import_react2.useState)(false);
@@ -594,6 +613,8 @@ function McpTab(props) {
594
613
  const [reload, setReload] = (0, import_react2.useState)(0);
595
614
  const [pasteJson, setPasteJson] = (0, import_react2.useState)("");
596
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");
597
618
  const openImport = async () => {
598
619
  setImportOpen(true);
599
620
  setImportItems(null);
@@ -615,7 +636,7 @@ function McpTab(props) {
615
636
  const items = importItems.filter((item) => item.checked && !item.existing).map((item) => ({ agent: item.server.agent, name: item.server.name }));
616
637
  setBusy(true);
617
638
  try {
618
- const body = await injected.applyImport(items);
639
+ const body = await injected.applyImport(items, importScope);
619
640
  const failed = body.results.filter((item) => !item.ok);
620
641
  setOutcome(failed.length === 0 ? { ok: true, text: t("restartNeeded") } : { ok: false, text: `${t("failed")}: ${failed.map((item) => `${item.name} (${item.error})`).join(", ")}` });
621
642
  setImportOpen(false);
@@ -635,7 +656,9 @@ function McpTab(props) {
635
656
  let current = true;
636
657
  void injected.list().then(
637
658
  (body) => {
638
- if (current) setServers(body.servers);
659
+ if (!current) return;
660
+ setServers(body.servers);
661
+ setGlobalError(body.globalError ?? null);
639
662
  },
640
663
  (error) => {
641
664
  if (current) {
@@ -652,7 +675,7 @@ function McpTab(props) {
652
675
  setFormError(null);
653
676
  setPasteError(null);
654
677
  setPasteJson("");
655
- setEditor({ id: "", serverName: "", transport: "stdio", command: "", args: "", env: "", url: "", headers: "" });
678
+ setEditor({ id: "", scope: "profile", serverName: "", transport: "stdio", command: "", args: "", env: "", url: "", headers: "" });
656
679
  };
657
680
  const openEdit = (row) => {
658
681
  setFormError(null);
@@ -660,6 +683,7 @@ function McpTab(props) {
660
683
  setPasteJson("");
661
684
  setEditor({
662
685
  id: row.id,
686
+ scope: row.scope,
663
687
  serverName: row.serverName,
664
688
  transport: row.transport,
665
689
  command: row.command ?? "",
@@ -704,6 +728,7 @@ function McpTab(props) {
704
728
  try {
705
729
  await injected.save({
706
730
  id: editor.id,
731
+ scope: editor.scope,
707
732
  serverName: editor.serverName.trim(),
708
733
  transport: editor.transport,
709
734
  ...editor.transport === "stdio" ? {
@@ -727,7 +752,7 @@ function McpTab(props) {
727
752
  const doToggle = async (row) => {
728
753
  setBusy(true);
729
754
  try {
730
- await injected.toggle(row.id, !row.disabled);
755
+ await injected.toggle(row.id, !row.disabled, row.scope);
731
756
  setOutcome({ ok: true, text: t("restartNeeded") });
732
757
  reloadList(true);
733
758
  } catch (error) {
@@ -737,17 +762,40 @@ function McpTab(props) {
737
762
  }
738
763
  };
739
764
  const doRemove = async () => {
740
- if (confirmId === null) return;
765
+ if (confirmRow === null) return;
766
+ setBusy(true);
767
+ try {
768
+ await injected.remove(confirmRow.id, confirmRow.scope);
769
+ setOutcome({ ok: true, text: t("restartNeeded") });
770
+ reloadList(true);
771
+ } catch (error) {
772
+ setOutcome({ ok: false, text: `${t("failed")}: ${String(error instanceof Error ? error.message : error)}` });
773
+ } finally {
774
+ setBusy(false);
775
+ setConfirmRow(null);
776
+ }
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) => {
741
789
  setBusy(true);
742
790
  try {
743
- await injected.remove(confirmId);
791
+ const toScope = row.scope === "global" ? "profile" : "global";
792
+ await injected.copy(row.id, row.scope, toScope);
744
793
  setOutcome({ ok: true, text: t("restartNeeded") });
745
794
  reloadList(true);
746
795
  } catch (error) {
747
796
  setOutcome({ ok: false, text: `${t("failed")}: ${String(error instanceof Error ? error.message : error)}` });
748
797
  } finally {
749
798
  setBusy(false);
750
- setConfirmId(null);
751
799
  }
752
800
  };
753
801
  const doRestart = () => {
@@ -797,6 +845,13 @@ function McpTab(props) {
797
845
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.StateDot, { state: outcome.ok ? "done" : "error", size: 10 }),
798
846
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dpc-bannerBody", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: outcome.text }) })
799
847
  ] }),
848
+ globalError !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-banner", "data-kind": "error", role: "alert", children: [
849
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.StateDot, { state: "error", size: 10 }),
850
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-bannerBody", children: [
851
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("globalLayerError") }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-bannerHint", children: globalError })
853
+ ] })
854
+ ] }),
800
855
  (pending || restarting) && restartBanner,
801
856
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-listHead", children: [
802
857
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: t("mcpTab") }),
@@ -809,17 +864,30 @@ function McpTab(props) {
809
864
  servers !== null && servers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("ul", { className: "dpc-cards", children: servers.map((row) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("li", { className: "dpc-card", children: [
810
865
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-cardTop", children: [
811
866
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { className: "dpc-cardTitle", title: row.id, children: row.serverName }),
867
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-tag", "data-kind": row.scope === "global" ? "source" : void 0, children: row.scope === "global" ? t("scopeGlobal") : t("scopeProfile") }),
812
868
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-tag", children: row.transport }),
813
869
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-tag", "data-kind": row.disabled ? "off" : void 0, children: row.disabled ? t("disabled") : t("enabled") })
814
870
  ] }),
815
871
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-cardDesc", children: row.transport === "stdio" ? `${row.command ?? ""} ${(row.args ?? []).join(" ")}` : row.url ?? "" }),
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
+ })(),
816
882
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-cardRow", children: [
817
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") }),
818
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") }),
819
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") }),
820
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => setConfirmId(row.id), children: t("delete") })
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") })
821
889
  ] })
822
- ] }, row.id)) }),
890
+ ] }, `${row.scope}/${row.id}`)) }),
823
891
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
824
892
  import_dsh_client_ui_primitives2.Modal,
825
893
  {
@@ -829,6 +897,23 @@ function McpTab(props) {
829
897
  className: "dpc-modalForm",
830
898
  contentClassName: "dpc-modalScroll",
831
899
  children: editor !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-form", children: [
900
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dpc-label", children: [
901
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("scopeLabel") }),
902
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
903
+ "select",
904
+ {
905
+ className: "dpc-select",
906
+ value: editor.scope,
907
+ disabled: editor.id !== "",
908
+ onChange: (event) => setEditor({ ...editor, scope: event.target.value }),
909
+ children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "profile", children: t("scopeProfile") }),
911
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "global", children: t("scopeGlobal") })
912
+ ]
913
+ }
914
+ ),
915
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-formatHint", children: t("scopeHint") })
916
+ ] }),
832
917
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dpc-label", children: [
833
918
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("serverName") }),
834
919
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -927,12 +1012,12 @@ function McpTab(props) {
927
1012
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
928
1013
  import_dsh_client_ui_primitives2.Modal,
929
1014
  {
930
- open: confirmId !== null,
931
- onClose: () => setConfirmId(null),
1015
+ open: confirmRow !== null,
1016
+ onClose: () => setConfirmRow(null),
932
1017
  title: t("confirmRemove"),
933
- description: confirmId ?? void 0,
1018
+ description: confirmRow?.serverName ?? void 0,
934
1019
  footer: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
935
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", onClick: () => setConfirmId(null), children: t("cancel") }),
1020
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", onClick: () => setConfirmRow(null), children: t("cancel") }),
936
1021
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "primary", disabled: busy, onClick: () => void doRemove(), children: t("delete") })
937
1022
  ] }),
938
1023
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: t("removeWarn") })
@@ -960,6 +1045,23 @@ function McpTab(props) {
960
1045
  className: "dpc-modalWide",
961
1046
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-form", children: [
962
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
+ ] }) }),
963
1065
  importItems === null && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-empty", children: t("loading") }),
964
1066
  importItems !== null && importItems.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-empty", children: t("importEmpty") }),
965
1067
  importItems !== null && importItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dpc-importScroll", children: importGroups(importItems).map((group) => {
@@ -4456,9 +4558,20 @@ var zh = {
4456
4558
  confirmRemoveRoot: "\u786E\u8BA4\u79FB\u9664\u8BE5\u4ED3\u5E93\uFF1F",
4457
4559
  removeRootWarn: "\u5C06\u53D6\u6D88\u626B\u63CF\u8BE5\u4ED3\u5E93\u5E76\u5220\u9664\u4E0B\u8F7D\u5230 DSH_HOME \u7684\u526F\u672C\uFF08\u672C\u5730\u76EE\u5F55\u672C\u8EAB\u4E0D\u53D7\u5F71\u54CD\uFF09\u3002",
4458
4560
  mcpTitle: "MCP \u670D\u52A1\u5668",
4459
- mcpIntro: "\u7BA1\u7406 profile \u4E2D\u7684 MCP \u670D\u52A1\u5668\u884C\uFF08@deepseek-ai/dsh-mcp-client\uFF09\u3002\u65B0\u589E\u3001\u4FEE\u6539\u6216\u5220\u9664\u540E\u9700\u8981\u91CD\u542F dsh \u624D\u751F\u6548\u3002",
4561
+ mcpIntro: "\u7BA1\u7406 MCP \u670D\u52A1\u5668\u884C\uFF08@deepseek-ai/dsh-mcp-client\uFF09\u3002\u670D\u52A1\u5668\u53EF\u653E\u5728 profile \u5C42\uFF08\u4EC5\u672C profile\uFF09\u6216\u5168\u5C40\u5C42\uFF08DSH_HOME/cordis.patch.yml\uFF0C\u5BF9\u672C\u673A\u6240\u6709 profile \u751F\u6548\uFF09\uFF1B\u65B0\u589E\u3001\u4FEE\u6539\u6216\u5220\u9664\u540E\u9700\u8981\u91CD\u542F dsh \u624D\u751F\u6548\u3002",
4460
4562
  addServer: "\u6DFB\u52A0\u670D\u52A1\u5668",
4461
4563
  editServer: "\u7F16\u8F91\u670D\u52A1\u5668",
4564
+ scopeLabel: "\u751F\u6548\u8303\u56F4",
4565
+ scopeGlobal: "\u5168\u5C40",
4566
+ scopeProfile: "\u5F53\u524D profile",
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",
4568
+ shadowedByGlobal: "\u5168\u5C40\u5C42\u5B58\u5728\u540C\u540D id\uFF0C\u6B64\u884C\u4E0D\u751F\u6548",
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",
4462
4575
  serverName: "\u670D\u52A1\u5668\u540D\uFF08\u5DE5\u5177\u540D\u524D\u7F00\uFF09",
4463
4576
  transport: "\u4F20\u8F93\u65B9\u5F0F",
4464
4577
  transportStdio: "stdio\uFF08\u672C\u5730\u547D\u4EE4\uFF09",
@@ -4472,10 +4585,10 @@ var zh = {
4472
4585
  enabled: "\u542F\u7528\u4E2D",
4473
4586
  toggle: "\u505C\u7528/\u542F\u7528",
4474
4587
  confirmRemove: "\u786E\u8BA4\u79FB\u9664\u8BE5\u670D\u52A1\u5668\uFF1F",
4475
- removeWarn: "\u5C06\u4ECE profile \u914D\u7F6E\u4E2D\u79FB\u9664\u8FD9\u4E00\u884C\uFF0C\u91CD\u542F dsh \u540E\u5176\u5DE5\u5177\u4E0D\u518D\u51FA\u73B0\u3002",
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",
4476
4589
  emptyMcp: "\u8FD8\u6CA1\u6709\u914D\u7F6E MCP \u670D\u52A1\u5668",
4477
4590
  importServers: "\u4ECE\u5176\u4ED6 Agent \u5BFC\u5165",
4478
- 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",
4479
4592
  importEmpty: "\u6CA1\u6709\u53D1\u73B0\u53EF\u5BFC\u5165\u7684 MCP \u670D\u52A1\u5668",
4480
4593
  importSelectAll: "\u5168\u9009",
4481
4594
  importExisting: "\u5DF2\u5B58\u5728",
@@ -4501,7 +4614,7 @@ var zh = {
4501
4614
  marketSkills: "\u6280\u80FD\u5E02\u573A",
4502
4615
  marketMcp: "MCP \u5E02\u573A",
4503
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",
4504
- 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",
4505
4618
  marketEmpty: "\u5E02\u573A\u5217\u8868\u4E3A\u7A7A\u6216\u4E0D\u53EF\u7528",
4506
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",
4507
4620
  marketSkillCount: "{n} \u4E2A\u6280\u80FD",
@@ -4588,9 +4701,20 @@ var en = {
4588
4701
  confirmRemoveRoot: "Remove this repository?",
4589
4702
  removeRootWarn: "Stops scanning the repository and deletes the copy downloaded into DSH_HOME (a local folder itself is untouched).",
4590
4703
  mcpTitle: "MCP servers",
4591
- mcpIntro: "Manage the MCP server rows (@deepseek-ai/dsh-mcp-client) in this profile. Additions, edits, and removals take effect after a dsh restart.",
4704
+ mcpIntro: "Manage MCP server rows (@deepseek-ai/dsh-mcp-client). A server lives either in this profile or in the global layer (DSH_HOME/cordis.patch.yml, applied to every profile on this machine); additions, edits, and removals take effect after a dsh restart.",
4592
4705
  addServer: "Add server",
4593
4706
  editServer: "Edit server",
4707
+ scopeLabel: "Scope",
4708
+ scopeGlobal: "Global",
4709
+ scopeProfile: "This profile",
4710
+ scopeHint: "Global writes DSH_HOME/cordis.patch.yml and applies to every profile on this machine; this profile writes the current profile only.",
4711
+ shadowedByGlobal: "a global row with the same id wins \u2014 this row has no effect",
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",
4594
4718
  serverName: "Server name (tool name prefix)",
4595
4719
  transport: "Transport",
4596
4720
  transportStdio: "stdio (local command)",
@@ -4604,10 +4728,10 @@ var en = {
4604
4728
  enabled: "Enabled",
4605
4729
  toggle: "Enable/disable",
4606
4730
  confirmRemove: "Remove this server?",
4607
- removeWarn: "Removes the row from the profile configuration; its tools disappear after the next dsh restart.",
4731
+ removeWarn: "Removes the row from its configuration; its tools disappear after the next dsh restart.",
4608
4732
  emptyMcp: "No MCP servers configured yet",
4609
4733
  importServers: "Import from other agents",
4610
- 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.",
4611
4735
  importEmpty: "No importable MCP servers found",
4612
4736
  importSelectAll: "Select all",
4613
4737
  importExisting: "already here",
@@ -4633,7 +4757,7 @@ var en = {
4633
4757
  marketSkills: "Skills market",
4634
4758
  marketMcp: "MCP market",
4635
4759
  marketSkillsIntro: "Installs skill repositories (GitHub download, live catalog); manage installed ones on the Skills tab.",
4636
- 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.",
4637
4761
  marketEmpty: "Market list is empty or unavailable",
4638
4762
  marketOffline: "Online index unreachable \u2014 showing the bundled snapshot, which may lag behind.",
4639
4763
  marketSkillCount: "{n} skills",
@@ -4681,10 +4805,12 @@ function apply2(ctx) {
4681
4805
  const mcpInjected = {
4682
4806
  list: () => fetchJson("/dsh-plugin-capabilities/mcp"),
4683
4807
  save: (input) => post("/dsh-plugin-capabilities/mcp/save", input),
4684
- toggle: (id, disabled) => post("/dsh-plugin-capabilities/mcp/toggle", { id, disabled }),
4685
- remove: (id) => post("/dsh-plugin-capabilities/mcp/remove", { id }),
4808
+ toggle: (id, disabled, scope) => post("/dsh-plugin-capabilities/mcp/toggle", { id, disabled, scope }),
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 }),
4686
4812
  scanImport: () => fetchJson("/dsh-plugin-capabilities/import/scan"),
4687
- applyImport: (items) => post("/dsh-plugin-capabilities/import/apply", { items }),
4813
+ applyImport: (items, scope) => post("/dsh-plugin-capabilities/import/apply", { items, scope }),
4688
4814
  restart: async () => {
4689
4815
  if (window.dshDesktop !== void 0) {
4690
4816
  window.dshDesktop.restartSidecar?.();
@@ -4701,7 +4827,7 @@ function apply2(ctx) {
4701
4827
  skillsIndex: () => fetchJson("/dsh-plugin-capabilities/market/skills"),
4702
4828
  mcpIndex: () => fetchJson("/dsh-plugin-capabilities/market/mcp"),
4703
4829
  installSkillRepo: (url) => post("/dsh-plugin-capabilities/market/skills/install", { url }),
4704
- installMcp: (id) => post("/dsh-plugin-capabilities/market/mcp/install", { id }),
4830
+ installMcp: (id, scope) => post("/dsh-plugin-capabilities/market/mcp/install", { id, scope }),
4705
4831
  removeRoot: skillsInjected.removeRoot
4706
4832
  };
4707
4833
  ctx.slots.inject("settings.section", () => {