dsh-plugin-capabilities 0.3.6 → 0.3.8

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
@@ -25,7 +25,9 @@
25
25
 
26
26
  ![「MCP」标签页](docs/images/screenshot-mcp.png)
27
27
 
28
- 「MCP」页管理 profile patch 中的 MCP 服务器行,每行对应一个 `@deepseek-ai/dsh-mcp-client` 实例。stdio 服务器填写命令与参数,streamable-http 服务器填写 URL,编辑、停用、移除都在页面上完成。YAML 读写采用文档级 API,文件中的其他行与注释不受影响。新行写在一个 `- insert:` 块里——加载器只会挂载 insert 形式的行,裸的 `- id:` 条目是对已有行的覆盖,目标不存在时会被跳过;0.1.3 之前写入的裸行会在下一次保存时自动迁入 insert 块。
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
+
30
+ 服务器行不只存在于 profile 里。dsh 还有一层全局补丁(`DSH_HOME/cordis.patch.yml`),对这台机器上的所有 profile 生效,组合时排在 profile 层之后、同名 id 以全局为准。0.3.8 起,「MCP」页把两层合在一起展示:每行带「全局」或「当前 profile」标记,两层出现同名 id 时 profile 行会注明自己不生效。添加服务器时可以选择写入哪一层,编辑、停用、移除也都落在该行所在的层。全局文件损坏不会连累整个页面:profile 行照常列出,顶部横幅单独报出全局文件的路径与出错行列。
29
31
 
30
32
  添加或编辑服务器时,表单下方有「完整格式与快速填充」区:一边是随表单实时更新的完整 YAML 行(和保存后落进 `cordis.patch.yml` 的一模一样),另一边是等价的 `mcpServers` JSON 写法,可以直接抄去别的工具。反过来也行——把 Claude Code 配置、官方文档或任意 JSON 粘进输入框,点「解析并填充」,服务器名、命令、参数、环境变量、URL、请求头都会自动填好,`{"mcpServers": {...}}` 包装、裸条目、dsh 行三种形状都认。
31
33
 
package/lib/client.js CHANGED
@@ -259,7 +259,7 @@ function MarketTab(props) {
259
259
  setOutcome({ ok: true, text: t("rootRemoved") });
260
260
  refreshSkills();
261
261
  } else {
262
- await mcp.remove(target.id);
262
+ await mcp.remove(target.id, "profile");
263
263
  setOutcome({ ok: true, text: t("restartNeeded") });
264
264
  refreshMcp();
265
265
  }
@@ -582,7 +582,8 @@ function McpTab(props) {
582
582
  const { t, injected } = props;
583
583
  const [servers, setServers] = (0, import_react2.useState)(null);
584
584
  const [editor, setEditor] = (0, import_react2.useState)(null);
585
- const [confirmId, setConfirmId] = (0, import_react2.useState)(null);
585
+ const [confirmRow, setConfirmRow] = (0, import_react2.useState)(null);
586
+ const [globalError, setGlobalError] = (0, import_react2.useState)(null);
586
587
  const [importOpen, setImportOpen] = (0, import_react2.useState)(false);
587
588
  const [importItems, setImportItems] = (0, import_react2.useState)(null);
588
589
  const [busy, setBusy] = (0, import_react2.useState)(false);
@@ -635,7 +636,9 @@ function McpTab(props) {
635
636
  let current = true;
636
637
  void injected.list().then(
637
638
  (body) => {
638
- if (current) setServers(body.servers);
639
+ if (!current) return;
640
+ setServers(body.servers);
641
+ setGlobalError(body.globalError ?? null);
639
642
  },
640
643
  (error) => {
641
644
  if (current) {
@@ -652,7 +655,7 @@ function McpTab(props) {
652
655
  setFormError(null);
653
656
  setPasteError(null);
654
657
  setPasteJson("");
655
- setEditor({ id: "", serverName: "", transport: "stdio", command: "", args: "", env: "", url: "", headers: "" });
658
+ setEditor({ id: "", scope: "profile", serverName: "", transport: "stdio", command: "", args: "", env: "", url: "", headers: "" });
656
659
  };
657
660
  const openEdit = (row) => {
658
661
  setFormError(null);
@@ -660,6 +663,7 @@ function McpTab(props) {
660
663
  setPasteJson("");
661
664
  setEditor({
662
665
  id: row.id,
666
+ scope: row.scope,
663
667
  serverName: row.serverName,
664
668
  transport: row.transport,
665
669
  command: row.command ?? "",
@@ -704,6 +708,7 @@ function McpTab(props) {
704
708
  try {
705
709
  await injected.save({
706
710
  id: editor.id,
711
+ scope: editor.scope,
707
712
  serverName: editor.serverName.trim(),
708
713
  transport: editor.transport,
709
714
  ...editor.transport === "stdio" ? {
@@ -727,7 +732,7 @@ function McpTab(props) {
727
732
  const doToggle = async (row) => {
728
733
  setBusy(true);
729
734
  try {
730
- await injected.toggle(row.id, !row.disabled);
735
+ await injected.toggle(row.id, !row.disabled, row.scope);
731
736
  setOutcome({ ok: true, text: t("restartNeeded") });
732
737
  reloadList(true);
733
738
  } catch (error) {
@@ -737,17 +742,17 @@ function McpTab(props) {
737
742
  }
738
743
  };
739
744
  const doRemove = async () => {
740
- if (confirmId === null) return;
745
+ if (confirmRow === null) return;
741
746
  setBusy(true);
742
747
  try {
743
- await injected.remove(confirmId);
748
+ await injected.remove(confirmRow.id, confirmRow.scope);
744
749
  setOutcome({ ok: true, text: t("restartNeeded") });
745
750
  reloadList(true);
746
751
  } catch (error) {
747
752
  setOutcome({ ok: false, text: `${t("failed")}: ${String(error instanceof Error ? error.message : error)}` });
748
753
  } finally {
749
754
  setBusy(false);
750
- setConfirmId(null);
755
+ setConfirmRow(null);
751
756
  }
752
757
  };
753
758
  const doRestart = () => {
@@ -797,6 +802,13 @@ function McpTab(props) {
797
802
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.StateDot, { state: outcome.ok ? "done" : "error", size: 10 }),
798
803
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dpc-bannerBody", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: outcome.text }) })
799
804
  ] }),
805
+ globalError !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-banner", "data-kind": "error", role: "alert", children: [
806
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.StateDot, { state: "error", size: 10 }),
807
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-bannerBody", children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("globalLayerError") }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-bannerHint", children: globalError })
810
+ ] })
811
+ ] }),
800
812
  (pending || restarting) && restartBanner,
801
813
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-listHead", children: [
802
814
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { children: t("mcpTab") }),
@@ -809,17 +821,19 @@ function McpTab(props) {
809
821
  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
822
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-cardTop", children: [
811
823
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("strong", { className: "dpc-cardTitle", title: row.id, children: row.serverName }),
824
+ /* @__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
825
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-tag", children: row.transport }),
813
826
  /* @__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
827
  ] }),
815
828
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-cardDesc", children: row.transport === "stdio" ? `${row.command ?? ""} ${(row.args ?? []).join(" ")}` : row.url ?? "" }),
829
+ row.shadowed === true && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "dpc-formError", children: t("shadowedByGlobal") }),
816
830
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-cardRow", children: [
817
831
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-spacer" }),
818
832
  /* @__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
833
  /* @__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") })
834
+ /* @__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
835
  ] })
822
- ] }, row.id)) }),
836
+ ] }, `${row.scope}/${row.id}`)) }),
823
837
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
824
838
  import_dsh_client_ui_primitives2.Modal,
825
839
  {
@@ -829,6 +843,23 @@ function McpTab(props) {
829
843
  className: "dpc-modalForm",
830
844
  contentClassName: "dpc-modalScroll",
831
845
  children: editor !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-form", children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dpc-label", children: [
847
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("scopeLabel") }),
848
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
849
+ "select",
850
+ {
851
+ className: "dpc-select",
852
+ value: editor.scope,
853
+ disabled: editor.id !== "",
854
+ onChange: (event) => setEditor({ ...editor, scope: event.target.value }),
855
+ children: [
856
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "profile", children: t("scopeProfile") }),
857
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "global", children: t("scopeGlobal") })
858
+ ]
859
+ }
860
+ ),
861
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "dpc-formatHint", children: t("scopeHint") })
862
+ ] }),
832
863
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dpc-label", children: [
833
864
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("serverName") }),
834
865
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -927,12 +958,12 @@ function McpTab(props) {
927
958
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
928
959
  import_dsh_client_ui_primitives2.Modal,
929
960
  {
930
- open: confirmId !== null,
931
- onClose: () => setConfirmId(null),
961
+ open: confirmRow !== null,
962
+ onClose: () => setConfirmRow(null),
932
963
  title: t("confirmRemove"),
933
- description: confirmId ?? void 0,
964
+ description: confirmRow?.serverName ?? void 0,
934
965
  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") }),
966
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "ghost", onClick: () => setConfirmRow(null), children: t("cancel") }),
936
967
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_dsh_client_ui_primitives2.Button, { variant: "primary", disabled: busy, onClick: () => void doRemove(), children: t("delete") })
937
968
  ] }),
938
969
  children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: t("removeWarn") })
@@ -4456,9 +4487,15 @@ var zh = {
4456
4487
  confirmRemoveRoot: "\u786E\u8BA4\u79FB\u9664\u8BE5\u4ED3\u5E93\uFF1F",
4457
4488
  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
4489
  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",
4490
+ 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
4491
  addServer: "\u6DFB\u52A0\u670D\u52A1\u5668",
4461
4492
  editServer: "\u7F16\u8F91\u670D\u52A1\u5668",
4493
+ scopeLabel: "\u751F\u6548\u8303\u56F4",
4494
+ scopeGlobal: "\u5168\u5C40",
4495
+ scopeProfile: "\u5F53\u524D profile",
4496
+ 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
+ shadowedByGlobal: "\u5168\u5C40\u5C42\u5B58\u5728\u540C\u540D id\uFF0C\u6B64\u884C\u4E0D\u751F\u6548",
4498
+ 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",
4462
4499
  serverName: "\u670D\u52A1\u5668\u540D\uFF08\u5DE5\u5177\u540D\u524D\u7F00\uFF09",
4463
4500
  transport: "\u4F20\u8F93\u65B9\u5F0F",
4464
4501
  transportStdio: "stdio\uFF08\u672C\u5730\u547D\u4EE4\uFF09",
@@ -4472,7 +4509,7 @@ var zh = {
4472
4509
  enabled: "\u542F\u7528\u4E2D",
4473
4510
  toggle: "\u505C\u7528/\u542F\u7528",
4474
4511
  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",
4512
+ 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
4513
  emptyMcp: "\u8FD8\u6CA1\u6709\u914D\u7F6E MCP \u670D\u52A1\u5668",
4477
4514
  importServers: "\u4ECE\u5176\u4ED6 Agent \u5BFC\u5165",
4478
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",
@@ -4588,9 +4625,15 @@ var en = {
4588
4625
  confirmRemoveRoot: "Remove this repository?",
4589
4626
  removeRootWarn: "Stops scanning the repository and deletes the copy downloaded into DSH_HOME (a local folder itself is untouched).",
4590
4627
  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.",
4628
+ 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
4629
  addServer: "Add server",
4593
4630
  editServer: "Edit server",
4631
+ scopeLabel: "Scope",
4632
+ scopeGlobal: "Global",
4633
+ scopeProfile: "This profile",
4634
+ scopeHint: "Global writes DSH_HOME/cordis.patch.yml and applies to every profile on this machine; this profile writes the current profile only.",
4635
+ shadowedByGlobal: "a global row with the same id wins \u2014 this row has no effect",
4636
+ globalLayerError: "Failed to read the global layer (DSH_HOME/cordis.patch.yml); global servers are hidden:",
4594
4637
  serverName: "Server name (tool name prefix)",
4595
4638
  transport: "Transport",
4596
4639
  transportStdio: "stdio (local command)",
@@ -4604,7 +4647,7 @@ var en = {
4604
4647
  enabled: "Enabled",
4605
4648
  toggle: "Enable/disable",
4606
4649
  confirmRemove: "Remove this server?",
4607
- removeWarn: "Removes the row from the profile configuration; its tools disappear after the next dsh restart.",
4650
+ removeWarn: "Removes the row from its configuration; its tools disappear after the next dsh restart.",
4608
4651
  emptyMcp: "No MCP servers configured yet",
4609
4652
  importServers: "Import from other agents",
4610
4653
  importIntro: "Scans Claude Code (~/.claude.json) and Codex (~/.codex/config.toml) MCP server configs; selected entries become server rows in this profile.",
@@ -4681,8 +4724,8 @@ function apply2(ctx) {
4681
4724
  const mcpInjected = {
4682
4725
  list: () => fetchJson("/dsh-plugin-capabilities/mcp"),
4683
4726
  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 }),
4727
+ toggle: (id, disabled, scope) => post("/dsh-plugin-capabilities/mcp/toggle", { id, disabled, scope }),
4728
+ remove: (id, scope) => post("/dsh-plugin-capabilities/mcp/remove", { id, scope }),
4686
4729
  scanImport: () => fetchJson("/dsh-plugin-capabilities/import/scan"),
4687
4730
  applyImport: (items) => post("/dsh-plugin-capabilities/import/apply", { items }),
4688
4731
  restart: async () => {