dsh-plugin-capabilities 0.1.3 → 0.1.5
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 +3 -3
- package/lib/client.js +196 -46
- package/lib/client.js.map +4 -4
- package/package.json +1 -1
- package/src/client/CapabilitiesSection.tsx +102 -0
- package/src/client/McpTab.tsx +83 -29
- package/src/client/SkillsTab.tsx +2 -0
- package/src/client/css.ts +25 -2
- package/src/client/index.ts +15 -22
- package/src/client/locales.ts +4 -0
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/dsh-plugin-capabilities)
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
|
|
6
|
-
在 dsh 设置页管理技能与 MCP
|
|
6
|
+
在 dsh 设置页管理技能与 MCP 服务器。本插件在设置里新增一级分区「技能与 MCP」(与「通用设置」「模型」并列),内含「技能」「MCP」两个标签页,技能目录和 profile 的 MCP 服务器行都能直接在页面上查看与维护,不必手工编辑文件;来自 Claude Code、Codex 等其他 agent 的技能与 MCP 配置也能一并纳入。`dsh web` 与 [DSH Desktop](https://github.com/qinyre/dsh-Desktop) 均可使用。
|
|
7
7
|
|
|
8
8
|
## 技能
|
|
9
9
|
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
「MCP」页管理 profile patch 中的 MCP 服务器行,每行对应一个 `@deepseek-ai/dsh-mcp-client` 实例。stdio 服务器填写命令与参数,streamable-http 服务器填写 URL,编辑、停用、移除都在页面上完成。YAML 读写采用文档级 API,文件中的其他行与注释不受影响。新行写在一个 `- insert:` 块里——加载器只会挂载 insert 形式的行,裸的 `- id:` 条目是对已有行的覆盖,目标不存在时会被跳过;0.1.3 之前写入的裸行会在下一次保存时自动迁入 insert 块。
|
|
21
21
|
|
|
22
|
-
也可以从其他 agent 导入:一键扫描 Claude Code(`~/.claude.json`、`~/.claude/settings.json`)与 Codex(`~/.codex/config.toml`)的 MCP 配置,勾选所需条目后转为本 profile
|
|
22
|
+
也可以从其他 agent 导入:一键扫描 Claude Code(`~/.claude.json`、`~/.claude/settings.json`)与 Codex(`~/.codex/config.toml`)的 MCP 配置,勾选所需条目后转为本 profile 的服务器行。弹窗按来源分成 Claude Code 和 Codex 两组,各自带数量与「全选」;stdio 与 http 两种传输都会处理,已存在的同名服务器置灰跳过。需要注意的是,Claude 配置里的 `${VAR}` 环境变量引用按字面值导入,如有需要请在导入后手动改回。
|
|
23
23
|
|
|
24
24
|
MCP 行的变更需要重启 dsh 才会进入组合。MCP 页头部有常驻的「重启」按钮,变更后无需离开界面:在 [DSH Desktop](https://github.com/qinyre/dsh-Desktop) 中由桌面壳层重启受监督的 sidecar,完成后窗口自动重载;直接运行 `dsh web` 时插件会拉起一个替代进程再退出自身,页面在恢复后自动刷新——若启动时端口是随机的,按横幅提示在终端查看新地址。重启会中断正在进行的回合,点击后会先弹出确认。
|
|
25
25
|
|
|
@@ -29,7 +29,7 @@ MCP 行的变更需要重启 dsh 才会进入组合。MCP 页头部有常驻的
|
|
|
29
29
|
dsh plugin --profile web add dsh-plugin-capabilities
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
安装后打开设置,即可在一级导航里看到「技能与 MCP」分区(位于「模型」与「插件」之间)。开发时也可以直接安装本地源码检出:`dsh plugin --profile web add file:/path/to/dsh-plugin-capabilities`,包内的 `prepare` 脚本会自动构建出 `lib/`。
|
|
33
33
|
|
|
34
34
|
## 工作原理
|
|
35
35
|
|
package/lib/client.js
CHANGED
|
@@ -28,15 +28,23 @@ __export(index_exports, {
|
|
|
28
28
|
name: () => name
|
|
29
29
|
});
|
|
30
30
|
module.exports = __toCommonJS(index_exports);
|
|
31
|
-
var
|
|
31
|
+
var import_react4 = require("react");
|
|
32
32
|
|
|
33
|
-
// src/client/
|
|
34
|
-
var
|
|
35
|
-
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
33
|
+
// src/client/CapabilitiesSection.tsx
|
|
34
|
+
var import_react3 = require("react");
|
|
36
35
|
|
|
37
36
|
// src/client/css.ts
|
|
38
37
|
var CSS = `
|
|
39
38
|
.dpc-section{display:flex;flex-direction:column;gap:14px;width:100%;max-width:760px;color:var(--dsw-alias-label-primary)}
|
|
39
|
+
.dpc-heading{margin:0;font-size:18px;line-height:26px;font-weight:600}
|
|
40
|
+
/* Top-level section's internal tabs \u2014 same underline-tab look as the host's
|
|
41
|
+
Plugins section so the promoted placement still reads as one family. */
|
|
42
|
+
.dpc-tabs{display:flex;align-items:flex-end;gap:22px;border-bottom:1px solid var(--dsw-alias-border-l2);margin-top:2px}
|
|
43
|
+
.dpc-tab{position:relative;border:0;padding:7px 1px 9px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
|
|
44
|
+
.dpc-tab:hover,.dpc-tab[data-active='true']{color:var(--dsw-alias-label-primary)}
|
|
45
|
+
.dpc-tab[data-active='true']::after,.dpc-tab:focus-visible::after{position:absolute;right:0;bottom:-1px;left:0;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary);content:''}
|
|
46
|
+
.dpc-tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:2px;color:var(--dsw-alias-label-primary)}
|
|
47
|
+
.dpc-tabPanel{min-width:0;padding-top:2px}
|
|
40
48
|
.dpc-head{display:flex;align-items:center;gap:8px}
|
|
41
49
|
.dpc-head h3{margin:0;font-size:13px;line-height:20px;font-weight:600}
|
|
42
50
|
.dpc-head>svg{flex:none;color:var(--dsw-alias-label-tertiary)}
|
|
@@ -70,17 +78,49 @@ var CSS = `
|
|
|
70
78
|
.dpc-label{display:flex;flex-direction:column;gap:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
71
79
|
.dpc-label>span:first-child{color:var(--dsw-alias-label-tertiary)}
|
|
72
80
|
.dpc-input,.dpc-textarea,.dpc-select{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;outline:none;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
|
|
73
|
-
.dpc-textarea{min-height:
|
|
74
|
-
.dpc-textarea[data-short='true']{min-height:
|
|
81
|
+
.dpc-textarea{min-height:240px;resize:vertical;font-family:var(--ds-font-family-code);line-height:1.5}
|
|
82
|
+
.dpc-textarea[data-short='true']{min-height:96px}
|
|
75
83
|
.dpc-input:focus-visible,.dpc-textarea:focus-visible,.dpc-select:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}
|
|
76
84
|
.dpc-checks{display:flex;gap:16px;font-size:13px;line-height:20px}
|
|
77
85
|
.dpc-checks label{display:inline-flex;align-items:center;gap:6px;cursor:pointer}
|
|
78
86
|
.dpc-formError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
|
|
87
|
+
/* Import dialog: wider than the host's 380px default; the server list gets
|
|
88
|
+
its own scroll so title, intro, and footer stay pinned. The doubled class
|
|
89
|
+
beats the host dialog's module CSS regardless of injection order. */
|
|
90
|
+
.dpc-modalWide.dpc-modalWide{width:min(680px,100%)}
|
|
91
|
+
/* Editor dialogs (new skill / server): 640px wide so markdown bodies and
|
|
92
|
+
command/arg/env lines stop wrapping mid-token; the content column scrolls
|
|
93
|
+
on short viewports instead of clipping past the dialog edge. */
|
|
94
|
+
.dpc-modalForm.dpc-modalForm{width:min(640px,100%)}
|
|
95
|
+
.dpc-modalScroll.dpc-modalScroll{max-height:calc(100vh - 160px);overflow-y:auto}
|
|
96
|
+
.dpc-importScroll{display:flex;flex-direction:column;gap:14px;max-height:min(400px,52vh);overflow-y:auto;padding:2px 4px 2px 2px}
|
|
97
|
+
.dpc-importGroup{display:flex;flex-direction:column;gap:8px}
|
|
98
|
+
.dpc-importHead{display:flex;align-items:center;gap:8px;padding:0 2px}
|
|
99
|
+
.dpc-importCount{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}
|
|
100
|
+
.dpc-importAll{margin-left:auto;display:inline-flex;align-items:center;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
79
101
|
@media(max-width:680px){.dpc-cards{grid-template-columns:minmax(0,1fr)}}
|
|
80
102
|
`;
|
|
81
103
|
|
|
82
104
|
// src/client/McpTab.tsx
|
|
105
|
+
var import_react = require("react");
|
|
106
|
+
var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
83
107
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
108
|
+
function importGroups(items) {
|
|
109
|
+
const label = (agent) => agent === "claude-code" ? "Claude Code" : agent === "codex" ? "Codex" : agent;
|
|
110
|
+
const order = ["claude-code", "codex"];
|
|
111
|
+
const agents = [...new Set(items.map((item) => item.server.agent))].sort((a, b) => {
|
|
112
|
+
const rank = (agent) => {
|
|
113
|
+
const at = order.indexOf(agent);
|
|
114
|
+
return at === -1 ? order.length : at;
|
|
115
|
+
};
|
|
116
|
+
return rank(a) - rank(b) || a.localeCompare(b);
|
|
117
|
+
});
|
|
118
|
+
return agents.map((agent) => ({
|
|
119
|
+
agent,
|
|
120
|
+
label: label(agent),
|
|
121
|
+
items: items.map((item, index) => ({ item, index })).filter(({ item }) => item.server.agent === agent)
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
84
124
|
function parsePairs(text, separator) {
|
|
85
125
|
const map = {};
|
|
86
126
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -310,6 +350,8 @@ function McpTab(props) {
|
|
|
310
350
|
open: editor !== null,
|
|
311
351
|
onClose: () => setEditor(null),
|
|
312
352
|
title: editor !== null && editor.id !== "" ? t("editServer") : t("addServer"),
|
|
353
|
+
className: "dpc-modalForm",
|
|
354
|
+
contentClassName: "dpc-modalScroll",
|
|
313
355
|
children: editor !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dpc-form", children: [
|
|
314
356
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dpc-label", children: [
|
|
315
357
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("serverName") }),
|
|
@@ -404,34 +446,62 @@ function McpTab(props) {
|
|
|
404
446
|
open: importOpen,
|
|
405
447
|
onClose: () => setImportOpen(false),
|
|
406
448
|
title: t("importServers"),
|
|
449
|
+
className: "dpc-modalWide",
|
|
407
450
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dpc-form", children: [
|
|
408
451
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-intro", style: { margin: 0 }, children: t("importIntro") }),
|
|
409
452
|
importItems === null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-empty", children: t("loading") }),
|
|
410
453
|
importItems !== null && importItems.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-empty", children: t("importEmpty") }),
|
|
411
|
-
importItems !== null && importItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
454
|
+
importItems !== null && importItems.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dpc-importScroll", children: importGroups(importItems).map((group) => {
|
|
455
|
+
const selectable = group.items.filter(({ item }) => !item.existing).map(({ index }) => index);
|
|
456
|
+
const allChecked = selectable.length > 0 && selectable.every((index) => importItems[index].checked);
|
|
457
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dpc-importGroup", children: [
|
|
458
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dpc-importHead", children: [
|
|
459
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dpc-tag", "data-kind": "source", children: group.label }),
|
|
460
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dpc-importCount", children: group.items.length }),
|
|
461
|
+
selectable.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dpc-importAll", children: [
|
|
462
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
463
|
+
"input",
|
|
464
|
+
{
|
|
465
|
+
type: "checkbox",
|
|
466
|
+
checked: allChecked,
|
|
467
|
+
onChange: (event) => {
|
|
468
|
+
const next = importItems.slice();
|
|
469
|
+
for (const index of selectable) next[index] = { ...next[index], checked: event.target.checked };
|
|
470
|
+
setImportItems(next);
|
|
471
|
+
}
|
|
424
472
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
473
|
+
),
|
|
474
|
+
t("importSelectAll")
|
|
475
|
+
] })
|
|
428
476
|
] }),
|
|
429
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
477
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "dpc-cards", style: { gridTemplateColumns: "minmax(0, 1fr)" }, children: group.items.map(({ item, index }) => {
|
|
478
|
+
const command = item.server.transport === "stdio" ? `${item.server.command ?? ""} ${(item.server.args ?? []).join(" ")}`.trim() : item.server.url ?? "";
|
|
479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "dpc-card", style: { opacity: item.existing ? 0.55 : 1 }, children: [
|
|
480
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dpc-cardTop", children: [
|
|
481
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: { display: "inline-flex", alignItems: "center", gap: 8, minWidth: 0, cursor: item.existing ? "default" : "pointer" }, children: [
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
483
|
+
"input",
|
|
484
|
+
{
|
|
485
|
+
type: "checkbox",
|
|
486
|
+
checked: item.checked,
|
|
487
|
+
disabled: item.existing,
|
|
488
|
+
onChange: (event) => {
|
|
489
|
+
const next = importItems.slice();
|
|
490
|
+
next[index] = { ...item, checked: event.target.checked };
|
|
491
|
+
setImportItems(next);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
),
|
|
495
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { className: "dpc-cardTitle", title: item.server.name, children: item.server.name })
|
|
496
|
+
] }),
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dpc-tag", children: item.server.transport }),
|
|
498
|
+
item.existing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dpc-tag", children: t("importExisting") })
|
|
499
|
+
] }),
|
|
500
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-cardDesc", title: command, children: command })
|
|
501
|
+
] }, `${item.server.agent}/${item.server.name}`);
|
|
502
|
+
}) })
|
|
503
|
+
] }, group.agent);
|
|
504
|
+
}) }),
|
|
435
505
|
formError !== null && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dpc-formError", children: formError }),
|
|
436
506
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dpc-cardRow", children: [
|
|
437
507
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dpc-spacer" }),
|
|
@@ -611,6 +681,8 @@ function SkillsTab(props) {
|
|
|
611
681
|
open: editor !== null,
|
|
612
682
|
onClose: () => setEditor(null),
|
|
613
683
|
title: editor === null ? "" : editor.mode === "create" ? t("newSkill") : editor.mode === "edit" ? t("editSkill") : t("viewSkill"),
|
|
684
|
+
className: "dpc-modalForm",
|
|
685
|
+
contentClassName: "dpc-modalScroll",
|
|
614
686
|
children: editor !== null && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dpc-form", children: [
|
|
615
687
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dpc-label", children: [
|
|
616
688
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: t("skillName") }),
|
|
@@ -712,8 +784,94 @@ function SkillsTab(props) {
|
|
|
712
784
|
] });
|
|
713
785
|
}
|
|
714
786
|
|
|
787
|
+
// src/client/CapabilitiesSection.tsx
|
|
788
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
789
|
+
function CapabilitiesSection(props) {
|
|
790
|
+
const { t, skills, mcp } = props;
|
|
791
|
+
const tabsId = (0, import_react3.useId)();
|
|
792
|
+
const tabRefs = (0, import_react3.useRef)([]);
|
|
793
|
+
const rows = [
|
|
794
|
+
{ id: "skills", label: t("skillsTab") },
|
|
795
|
+
{ id: "mcp", label: t("mcpTab") }
|
|
796
|
+
];
|
|
797
|
+
const [activeId, setActiveId] = (0, import_react3.useState)("skills");
|
|
798
|
+
const [visitedIds, setVisitedIds] = (0, import_react3.useState)(() => /* @__PURE__ */ new Set(["skills"]));
|
|
799
|
+
(0, import_react3.useEffect)(() => {
|
|
800
|
+
setVisitedIds((previous) => {
|
|
801
|
+
if (previous.has(activeId)) return previous;
|
|
802
|
+
return /* @__PURE__ */ new Set([...previous, activeId]);
|
|
803
|
+
});
|
|
804
|
+
}, [activeId]);
|
|
805
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "dpc-section", children: [
|
|
806
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: CSS }),
|
|
807
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "dpc-heading", children: t("sectionNav") }),
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "dpc-tabs", role: "tablist", "aria-label": t("sectionNav"), children: rows.map((row, index) => {
|
|
809
|
+
const selected = row.id === activeId;
|
|
810
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
811
|
+
"button",
|
|
812
|
+
{
|
|
813
|
+
ref: (element) => {
|
|
814
|
+
tabRefs.current[index] = element;
|
|
815
|
+
},
|
|
816
|
+
id: `${tabsId}-tab-${row.id}`,
|
|
817
|
+
type: "button",
|
|
818
|
+
role: "tab",
|
|
819
|
+
className: "dpc-tab",
|
|
820
|
+
"aria-selected": selected,
|
|
821
|
+
"aria-controls": `${tabsId}-panel-${row.id}`,
|
|
822
|
+
"data-active": selected ? "true" : void 0,
|
|
823
|
+
tabIndex: selected ? 0 : -1,
|
|
824
|
+
onClick: () => {
|
|
825
|
+
setActiveId(row.id);
|
|
826
|
+
},
|
|
827
|
+
onKeyDown: (event) => {
|
|
828
|
+
let nextIndex;
|
|
829
|
+
switch (event.key) {
|
|
830
|
+
case "ArrowRight":
|
|
831
|
+
nextIndex = (index + 1) % rows.length;
|
|
832
|
+
break;
|
|
833
|
+
case "ArrowLeft":
|
|
834
|
+
nextIndex = (index - 1 + rows.length) % rows.length;
|
|
835
|
+
break;
|
|
836
|
+
case "Home":
|
|
837
|
+
nextIndex = 0;
|
|
838
|
+
break;
|
|
839
|
+
case "End":
|
|
840
|
+
nextIndex = rows.length - 1;
|
|
841
|
+
break;
|
|
842
|
+
default:
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
event.preventDefault();
|
|
846
|
+
setActiveId(rows[nextIndex]?.id ?? "skills");
|
|
847
|
+
tabRefs.current[nextIndex]?.focus();
|
|
848
|
+
},
|
|
849
|
+
children: row.label
|
|
850
|
+
},
|
|
851
|
+
row.id
|
|
852
|
+
);
|
|
853
|
+
}) }),
|
|
854
|
+
rows.filter((row) => row.id === activeId || visitedIds.has(row.id)).map((row) => {
|
|
855
|
+
const selected = row.id === activeId;
|
|
856
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
857
|
+
"div",
|
|
858
|
+
{
|
|
859
|
+
id: `${tabsId}-panel-${row.id}`,
|
|
860
|
+
className: "dpc-tabPanel",
|
|
861
|
+
role: "tabpanel",
|
|
862
|
+
"aria-labelledby": `${tabsId}-tab-${row.id}`,
|
|
863
|
+
hidden: !selected,
|
|
864
|
+
children: row.id === "skills" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SkillsTab, { t, injected: skills }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(McpTab, { t, injected: mcp })
|
|
865
|
+
},
|
|
866
|
+
row.id
|
|
867
|
+
);
|
|
868
|
+
})
|
|
869
|
+
] });
|
|
870
|
+
}
|
|
871
|
+
|
|
715
872
|
// src/client/locales.ts
|
|
716
873
|
var zh = {
|
|
874
|
+
sectionNav: "\u6280\u80FD\u4E0E MCP",
|
|
717
875
|
skillsTab: "\u6280\u80FD",
|
|
718
876
|
mcpTab: "MCP",
|
|
719
877
|
skillsTitle: "\u6280\u80FD\u7BA1\u7406",
|
|
@@ -769,6 +927,7 @@ var zh = {
|
|
|
769
927
|
importServers: "\u4ECE\u5176\u4ED6 Agent \u5BFC\u5165",
|
|
770
928
|
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",
|
|
771
929
|
importEmpty: "\u6CA1\u6709\u53D1\u73B0\u53EF\u5BFC\u5165\u7684 MCP \u670D\u52A1\u5668",
|
|
930
|
+
importSelectAll: "\u5168\u9009",
|
|
772
931
|
importExisting: "\u5DF2\u5B58\u5728",
|
|
773
932
|
importSelected: "\u5BFC\u5165\u9009\u4E2D\u9879",
|
|
774
933
|
restartNeeded: "\u914D\u7F6E\u5DF2\u5199\u5165\uFF0C\u91CD\u542F dsh \u540E\u751F\u6548\u3002",
|
|
@@ -783,6 +942,7 @@ var zh = {
|
|
|
783
942
|
failed: "\u64CD\u4F5C\u5931\u8D25"
|
|
784
943
|
};
|
|
785
944
|
var en = {
|
|
945
|
+
sectionNav: "Skills & MCP",
|
|
786
946
|
skillsTab: "Skills",
|
|
787
947
|
mcpTab: "MCP",
|
|
788
948
|
skillsTitle: "Skills",
|
|
@@ -838,6 +998,7 @@ var en = {
|
|
|
838
998
|
importServers: "Import from other agents",
|
|
839
999
|
importIntro: "Scans Claude Code (~/.claude.json) and Codex (~/.codex/config.toml) MCP server configs; selected entries become server rows in this profile.",
|
|
840
1000
|
importEmpty: "No importable MCP servers found",
|
|
1001
|
+
importSelectAll: "Select all",
|
|
841
1002
|
importExisting: "already here",
|
|
842
1003
|
importSelected: "Import selected",
|
|
843
1004
|
restartNeeded: "Configuration written \u2014 restart dsh to apply.",
|
|
@@ -891,25 +1052,14 @@ function apply(ctx) {
|
|
|
891
1052
|
},
|
|
892
1053
|
desktop: window.dshDesktop !== void 0
|
|
893
1054
|
};
|
|
894
|
-
ctx.slots.inject("settings.
|
|
895
|
-
return ctx.slots.register({
|
|
896
|
-
name: "settings.plugins.tab",
|
|
897
|
-
id: "capabilities-skills",
|
|
898
|
-
order: 30,
|
|
899
|
-
label: () => t("skillsTab"),
|
|
900
|
-
locale: NS,
|
|
901
|
-
inject: () => skillsInjected
|
|
902
|
-
}, () => (0, import_react3.createElement)(SkillsTab, { t, injected: skillsInjected }));
|
|
903
|
-
});
|
|
904
|
-
ctx.slots.inject("settings.plugins.tab", () => {
|
|
1055
|
+
ctx.slots.inject("settings.section", () => {
|
|
905
1056
|
return ctx.slots.register({
|
|
906
|
-
name: "settings.
|
|
907
|
-
id: "capabilities
|
|
908
|
-
order:
|
|
909
|
-
label: () => t("
|
|
910
|
-
locale: NS
|
|
911
|
-
|
|
912
|
-
}, () => (0, import_react3.createElement)(McpTab, { t, injected: mcpInjected }));
|
|
1057
|
+
name: "settings.section",
|
|
1058
|
+
id: "capabilities",
|
|
1059
|
+
order: 12,
|
|
1060
|
+
label: () => t("sectionNav"),
|
|
1061
|
+
locale: NS
|
|
1062
|
+
}, () => (0, import_react4.createElement)(CapabilitiesSection, { t, skills: skillsInjected, mcp: mcpInjected }));
|
|
913
1063
|
});
|
|
914
1064
|
}
|
|
915
1065
|
|
package/lib/client.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/client/index.ts", "../src/client/
|
|
4
|
-
"sourcesContent": ["/** dsh-plugin-capabilities client entry: contributes the \u201C\u6280\u80FD/Skills\u201D and\n * \u201CMCP\u201D tabs into Settings \u2192 Plugins. Calls the host routes with fetch. */\n\nimport { createElement as h } from 'react'\nimport { McpTab, type McpInjected, type McpRow } from './McpTab.tsx'\nimport { SkillsTab, type SkillsInjected, type SkillRowView } from './SkillsTab.tsx'\nimport { zh, en } from './locales.ts'\n\n/** Locale dictionary namespace owned by this plugin. */\nexport const NS = 'settings.pluginCapabilities'\n\n/** The `t` function bound by the locale service. */\nexport interface Translate {\n (key: string): string\n}\n\n/** Minimal structural subset of the slots service. */\ninterface SlotsService {\n inject(slot: string, register: () => unknown): void\n register(meta: Record<string, unknown>, component: () => unknown): unknown\n}\n\n/** Minimal structural subset of the locale service. */\ninterface LocaleService {\n register(namespace: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): Translate\n}\n\n/** The client cordis context this plugin relies on (structural). */\ninterface CapabilitiesClientContext {\n effect(callback: () => unknown, label?: string): void\n locale: LocaleService\n slots: SlotsService\n}\n\n/** Same-origin fetch of the manager's host routes. */\nasync function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(path, init)\n const body = (await response.json()) as T & { error?: string }\n if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`)\n return body\n}\n\nconst post = (path: string, body: unknown): Promise<unknown> =>\n fetchJson(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })\n\nexport const name = 'dsh-plugin-capabilities'\nexport const inject = ['slots', 'locale']\n\nexport interface SkillBody { content: string }\n\n/** One foreign-agent server from the import scan. */\nexport interface ImportedServerView {\n agent: 'claude-code' | 'codex'\n name: string\n transport: 'stdio' | 'streamable-http'\n command?: string\n args?: string[]\n env?: Record<string, string>\n url?: string\n headers?: Record<string, string>\n}\n\nexport function apply(ctx: CapabilitiesClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-capabilities: dictionaries')\n const t = ctx.locale.bind(NS)\n\n const skillsInjected: SkillsInjected = {\n list: () => fetchJson<{ skills: SkillRowView[] }>('/dsh-plugin-capabilities/skills'),\n get: (name: string) => fetchJson<SkillBody>(`/dsh-plugin-capabilities/skill?name=${encodeURIComponent(name)}`),\n save: (input: unknown) => post('/dsh-plugin-capabilities/skill/save', input) as Promise<{ ok: boolean }>,\n remove: (name: string) => post('/dsh-plugin-capabilities/skill/delete', { name }) as Promise<{ ok: boolean }>,\n }\n\n const mcpInjected: McpInjected = {\n list: () => fetchJson<{ servers: McpRow[] }>('/dsh-plugin-capabilities/mcp'),\n save: (input: unknown) => post('/dsh-plugin-capabilities/mcp/save', input) as Promise<{ ok: boolean; id: string }>,\n toggle: (id: string, disabled: boolean) => post('/dsh-plugin-capabilities/mcp/toggle', { id, disabled }) as Promise<{ ok: boolean }>,\n remove: (id: string) => post('/dsh-plugin-capabilities/mcp/remove', { id }) as Promise<{ ok: boolean }>,\n scanImport: () => fetchJson<{ servers: ImportedServerView[]; existing: string[] }>('/dsh-plugin-capabilities/import/scan'),\n applyImport: (items: Array<{ agent: string; name: string }>) =>\n post('/dsh-plugin-capabilities/import/apply', { items }) as Promise<{ ok: boolean; results: Array<{ name: string; ok: boolean; error?: string }> }>,\n restart: async (): Promise<void> => {\n if (window.dshDesktop !== undefined) {\n window.dshDesktop.restartSidecar?.()\n return\n }\n // \u72EC\u7ACB dsh web\uFF1A\u81EA\u91CD\u542F\u8DEF\u7531\u3002\u8FDE\u63A5\u5728\u5173\u505C\u9014\u4E2D\u65AD\u5F00\u5C5E\u9884\u671F\uFF0C\u4E0D\u7B97\u5931\u8D25\u3002\n try { await post('/dsh-plugin-capabilities/restart', {}) } catch { /* dying mid-restart is expected */ }\n },\n desktop: window.dshDesktop !== undefined,\n }\n\n ctx.slots.inject('settings.plugins.tab', () => {\n return ctx.slots.register({\n name: 'settings.plugins.tab',\n id: 'capabilities-skills',\n order: 30,\n label: () => t('skillsTab'),\n locale: NS,\n inject: () => skillsInjected,\n }, () => h(SkillsTab, { t, injected: skillsInjected }))\n })\n\n ctx.slots.inject('settings.plugins.tab', () => {\n return ctx.slots.register({\n name: 'settings.plugins.tab',\n id: 'capabilities-mcp',\n order: 40,\n label: () => t('mcpTab'),\n locale: NS,\n inject: () => mcpInjected,\n }, () => h(McpTab, { t, injected: mcpInjected }))\n })\n}\n", "/** Settings \u2192 Plugins \u201CMCP\u201D tab: manage the profile's mcp-client rows.\n * Mutations rewrite the profile patch and need a dsh restart \u2014 the banner\n * hands the restart to the desktop shell when one is present. */\n\nimport { useEffect, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { Button, IconApiOutline14, IconRefreshOutline14, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { CSS } from './css.ts'\nimport type { ImportedServerView, Translate } from './index.ts'\n\nexport interface McpRow {\n id: string\n serverName: string\n transport: 'stdio' | 'streamable-http'\n disabled: boolean\n command?: string\n args?: string[]\n env?: Record<string, string>\n cwd?: string\n url?: string\n headers?: Record<string, string>\n}\n\nexport interface McpInjected {\n list(): Promise<{ servers: McpRow[] }>\n save(input: Record<string, unknown>): Promise<{ ok: boolean; id: string }>\n toggle(id: string, disabled: boolean): Promise<{ ok: boolean }>\n remove(id: string): Promise<{ ok: boolean }>\n scanImport(): Promise<{ servers: ImportedServerView[]; existing: string[] }>\n applyImport(items: Array<{ agent: string; name: string }>): Promise<{ ok: boolean; results: Array<{ name: string; ok: boolean; error?: string }> }>\n restart(): Promise<void>\n desktop: boolean\n}\n\n/** Editor dialog state; null when closed. Textareas hold line-based values. */\ninterface EditorState {\n id: string\n serverName: string\n transport: 'stdio' | 'streamable-http'\n command: string\n args: string\n env: string\n url: string\n headers: string\n}\n\n/** KEY=VALUE / KEY: VALUE lines to a map. */\nfunction parsePairs(text: string, separator: ':' | '='): Record<string, string> {\n const map: Record<string, string> = {}\n for (const line of text.split(/\\r?\\n/)) {\n const trimmed = line.trim()\n if (trimmed === '' || trimmed.startsWith('#')) continue\n const at = trimmed.indexOf(separator)\n if (at <= 0) continue\n map[trimmed.slice(0, at).trim()] = trimmed.slice(at + 1).trim()\n }\n return map\n}\n\nfunction mapToPairs(map: Record<string, string> | undefined, separator: string): string {\n if (map === undefined) return ''\n return Object.entries(map).map(([key, value]) => `${key}${separator}${value.includes('\\n') ? JSON.stringify(value) : value}`).join('\\n')\n}\n\nexport function McpTab(props: { t: Translate; injected: McpInjected }): ReactElement {\n const { t, injected } = props\n const [servers, setServers] = useState<McpRow[] | null>(null)\n const [editor, setEditor] = useState<EditorState | null>(null)\n const [confirmId, setConfirmId] = useState<string | null>(null)\n const [importOpen, setImportOpen] = useState(false)\n const [importItems, setImportItems] = useState<Array<{ server: ImportedServerView; existing: boolean; checked: boolean }> | null>(null)\n const [busy, setBusy] = useState(false)\n const [pending, setPending] = useState(false)\n const [restartConfirm, setRestartConfirm] = useState(false)\n const [restarting, setRestarting] = useState(false)\n const [outcome, setOutcome] = useState<{ ok: boolean; text: string } | null>(null)\n const [formError, setFormError] = useState<string | null>(null)\n const [reload, setReload] = useState(0)\n\n const openImport = async (): Promise<void> => {\n setImportOpen(true)\n setImportItems(null)\n try {\n const body = await injected.scanImport()\n const existing = new Set(body.existing)\n setImportItems(body.servers.map(server => ({\n server,\n existing: existing.has(server.name),\n checked: !existing.has(server.name),\n })))\n } catch (error) {\n setImportItems([])\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n }\n }\n\n const doImport = async (): Promise<void> => {\n if (importItems === null) return\n const items = importItems.filter(item => item.checked && !item.existing).map(item => ({ agent: item.server.agent, name: item.server.name }))\n setBusy(true)\n try {\n const body = await injected.applyImport(items)\n const failed = body.results.filter(item => !item.ok)\n setOutcome(failed.length === 0\n ? { ok: true, text: t('restartNeeded') }\n : { ok: false, text: `${t('failed')}: ${failed.map(item => `${item.name} (${item.error})`).join(', ')}` })\n setImportOpen(false)\n setPending(true)\n setReload((value) => value + 1)\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n }\n }\n\n const reloadList = (showPending: boolean): void => {\n setReload((value) => value + 1)\n if (showPending) setPending(true)\n }\n\n useEffect(() => {\n let current = true\n void injected.list().then(\n (body) => { if (current) setServers(body.servers) },\n (error: Error) => { if (current) { setServers([]); setOutcome({ ok: false, text: `${t('failed')}: ${String(error.message ?? error)}` }) } },\n )\n return () => { current = false }\n }, [injected, reload, t])\n\n const openCreate = (): void => {\n setFormError(null)\n setEditor({ id: '', serverName: '', transport: 'stdio', command: '', args: '', env: '', url: '', headers: '' })\n }\n\n const openEdit = (row: McpRow): void => {\n setFormError(null)\n setEditor({\n id: row.id,\n serverName: row.serverName,\n transport: row.transport,\n command: row.command ?? '',\n args: (row.args ?? []).join('\\n'),\n env: mapToPairs(row.env, '='),\n url: row.url ?? '',\n headers: mapToPairs(row.headers, ':'),\n })\n }\n\n const doSave = async (): Promise<void> => {\n if (editor === null) return\n setBusy(true)\n setFormError(null)\n try {\n await injected.save({\n id: editor.id,\n serverName: editor.serverName.trim(),\n transport: editor.transport,\n ...(editor.transport === 'stdio'\n ? {\n command: editor.command.trim(),\n args: editor.args.split(/\\r?\\n/).map(line => line.trim()).filter(line => line !== ''),\n env: parsePairs(editor.env, '='),\n }\n : {\n url: editor.url.trim(),\n headers: parsePairs(editor.headers, ':'),\n }),\n })\n setEditor(null)\n setOutcome({ ok: true, text: t('restartNeeded') })\n reloadList(true)\n } catch (error) {\n setFormError(String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const doToggle = async (row: McpRow): Promise<void> => {\n setBusy(true)\n try {\n await injected.toggle(row.id, !row.disabled)\n setOutcome({ ok: true, text: t('restartNeeded') })\n reloadList(true)\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n }\n }\n\n const doRemove = async (): Promise<void> => {\n if (confirmId === null) return\n setBusy(true)\n try {\n await injected.remove(confirmId)\n setOutcome({ ok: true, text: t('restartNeeded') })\n reloadList(true)\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n setConfirmId(null)\n }\n }\n\n const doRestart = (): void => {\n setRestartConfirm(false)\n setRestarting(true)\n void injected.restart()\n // \u684C\u9762\u6A21\u5F0F\uFF1A\u58F3\u5C42\u91CD\u542F\u5B8C\u6210\u540E\u4F1A\u91CD\u8F7D\u7A97\u53E3\u3002\u72EC\u7ACB\u6A21\u5F0F\uFF1A\u8F6E\u8BE2\u672C\u6E90\uFF0C\u6062\u590D\u5373\u5237\u65B0\u3002\n if (injected.desktop) return\n const deadline = Date.now() + 60_000\n const poll = (): void => {\n if (Date.now() > deadline) return\n window.setTimeout(() => {\n void injected.list().then(\n () => { window.location.reload() },\n () => { poll() },\n )\n }, 1500)\n }\n window.setTimeout(poll, 3000)\n }\n\n const restartBanner = (\n <div className=\"dpc-banner\" data-kind=\"info\" role=\"status\">\n <StateDot state=\"ongoing\" size={10} />\n <div className=\"dpc-bannerBody\">\n <span>{restarting ? t('restarting') : t('restartNeeded')}</span>\n <span className=\"dpc-bannerHint\">\n {restarting\n ? (!injected.desktop && t('restartPortHint'))\n : injected.desktop\n ? <>{t('restartDesktopHint')}{' '}<Button variant=\"outline\" size=\"sm\" onClick={() => setRestartConfirm(true)}>{t('restartNow')}</Button></>\n : t('restartOtherHint')}\n </span>\n </div>\n </div>\n )\n\n return (\n <div className=\"dpc-section\">\n <style>{CSS}</style>\n\n <div className=\"dpc-head\">\n <IconApiOutline14 aria-hidden=\"true\" />\n <h3>{t('mcpTitle')}</h3>\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" size=\"sm\" disabled={restarting} onClick={() => setRestartConfirm(true)}>{t('restart')}</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void openImport()}>{t('importServers')}</Button>\n <Button variant=\"primary\" size=\"sm\" onClick={openCreate}>{t('addServer')}</Button>\n </div>\n <p className=\"dpc-intro\">{t('mcpIntro')}</p>\n\n {outcome !== null && (\n <div className=\"dpc-banner\" data-kind={outcome.ok ? 'ok' : 'error'} role=\"status\">\n <StateDot state={outcome.ok ? 'done' : 'error'} size={10} />\n <div className=\"dpc-bannerBody\"><span>{outcome.text}</span></div>\n </div>\n )}\n {(pending || restarting) && restartBanner}\n\n <div className=\"dpc-listHead\">\n <h3>{t('mcpTab')}</h3>\n {servers !== null && <span className=\"dpc-count\">{servers.length}</span>}\n <span className=\"dpc-spacer\" />\n <button type=\"button\" className=\"dpc-refresh\" aria-label={t('view')} title={t('view')} disabled={busy} onClick={() => setReload((value) => value + 1)}>\n <IconRefreshOutline14 size={14} aria-hidden=\"true\" />\n </button>\n </div>\n\n {servers === null && <p className=\"dpc-empty\">{t('loading')}</p>}\n {servers !== null && servers.length === 0 && <p className=\"dpc-empty\">{t('emptyMcp')}</p>}\n {servers !== null && servers.length > 0 && (\n <ul className=\"dpc-cards\">\n {servers.map((row) => (\n <li className=\"dpc-card\" key={row.id}>\n <div className=\"dpc-cardTop\">\n <strong className=\"dpc-cardTitle\" title={row.id}>{row.serverName}</strong>\n <span className=\"dpc-tag\">{row.transport}</span>\n <span className=\"dpc-tag\" data-kind={row.disabled ? 'off' : undefined}>{row.disabled ? t('disabled') : t('enabled')}</span>\n </div>\n <p className=\"dpc-cardDesc\">\n {row.transport === 'stdio' ? `${row.command ?? ''} ${(row.args ?? []).join(' ')}` : row.url ?? ''}\n </p>\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => void doToggle(row)}>{t('toggle')}</Button>\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => openEdit(row)}>{t('edit')}</Button>\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => setConfirmId(row.id)}>{t('delete')}</Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n\n <Modal\n open={editor !== null}\n onClose={() => setEditor(null)}\n title={editor !== null && editor.id !== '' ? t('editServer') : t('addServer')}\n >\n {editor !== null && (\n <div className=\"dpc-form\">\n <label className=\"dpc-label\">\n <span>{t('serverName')}</span>\n <input\n className=\"dpc-input\"\n value={editor.serverName}\n disabled={editor.id !== ''}\n onChange={(event) => setEditor({ ...editor, serverName: event.target.value })}\n />\n </label>\n <label className=\"dpc-label\">\n <span>{t('transport')}</span>\n <select\n className=\"dpc-select\"\n value={editor.transport}\n disabled={editor.id !== ''}\n onChange={(event) => setEditor({ ...editor, transport: event.target.value as EditorState['transport'] })}\n >\n <option value=\"stdio\">{t('transportStdio')}</option>\n <option value=\"streamable-http\">{t('transportHttp')}</option>\n </select>\n </label>\n {editor.transport === 'stdio' ? (\n <>\n <label className=\"dpc-label\">\n <span>{t('command')}</span>\n <input className=\"dpc-input\" value={editor.command} onChange={(event) => setEditor({ ...editor, command: event.target.value })} />\n </label>\n <label className=\"dpc-label\">\n <span>{t('args')}</span>\n <textarea className=\"dpc-textarea\" data-short=\"true\" value={editor.args} onChange={(event) => setEditor({ ...editor, args: event.target.value })} />\n </label>\n <label className=\"dpc-label\">\n <span>{t('envPairs')}</span>\n <textarea className=\"dpc-textarea\" data-short=\"true\" value={editor.env} onChange={(event) => setEditor({ ...editor, env: event.target.value })} />\n </label>\n </>\n ) : (\n <>\n <label className=\"dpc-label\">\n <span>{t('url')}</span>\n <input className=\"dpc-input\" value={editor.url} onChange={(event) => setEditor({ ...editor, url: event.target.value })} />\n </label>\n <label className=\"dpc-label\">\n <span>{t('headersPairs')}</span>\n <textarea className=\"dpc-textarea\" data-short=\"true\" value={editor.headers} onChange={(event) => setEditor({ ...editor, headers: event.target.value })} />\n </label>\n </>\n )}\n {formError !== null && <p className=\"dpc-formError\">{formError}</p>}\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" onClick={() => setEditor(null)}>{t('cancel')}</Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => void doSave()}>{t('save')}</Button>\n </div>\n </div>\n )}\n </Modal>\n\n <Modal\n open={confirmId !== null}\n onClose={() => setConfirmId(null)}\n title={t('confirmRemove')}\n description={confirmId ?? undefined}\n footer={\n <>\n <Button variant=\"ghost\" onClick={() => setConfirmId(null)}>{t('cancel')}</Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => void doRemove()}>{t('delete')}</Button>\n </>\n }\n >\n <p>{t('removeWarn')}</p>\n </Modal>\n\n <Modal\n open={restartConfirm}\n onClose={() => setRestartConfirm(false)}\n title={t('restartConfirmTitle')}\n footer={\n <>\n <Button variant=\"ghost\" onClick={() => setRestartConfirm(false)}>{t('cancel')}</Button>\n <Button variant=\"primary\" onClick={doRestart}>{t('restartNow')}</Button>\n </>\n }\n >\n <p>{t('restartConfirmBody')}</p>\n </Modal>\n\n <Modal\n open={importOpen}\n onClose={() => setImportOpen(false)}\n title={t('importServers')}\n >\n <div className=\"dpc-form\">\n <p className=\"dpc-intro\" style={{ margin: 0 }}>{t('importIntro')}</p>\n {importItems === null && <p className=\"dpc-empty\">{t('loading')}</p>}\n {importItems !== null && importItems.length === 0 && <p className=\"dpc-empty\">{t('importEmpty')}</p>}\n {importItems !== null && importItems.length > 0 && (\n <ul className=\"dpc-cards\" style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}>\n {importItems.map((item, index) => (\n <li className=\"dpc-card\" key={`${item.server.agent}/${item.server.name}`} style={{ opacity: item.existing ? 0.55 : 1 }}>\n <div className=\"dpc-cardTop\">\n <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0, cursor: item.existing ? 'default' : 'pointer' }}>\n <input\n type=\"checkbox\"\n checked={item.checked}\n disabled={item.existing}\n onChange={(event) => {\n const next = importItems.slice()\n next[index] = { ...item, checked: event.target.checked }\n setImportItems(next)\n }}\n />\n <strong className=\"dpc-cardTitle\" title={item.server.name}>{item.server.name}</strong>\n </label>\n <span className=\"dpc-tag\" data-kind=\"source\">{item.server.agent === 'claude-code' ? 'Claude Code' : 'Codex'}</span>\n <span className=\"dpc-tag\">{item.server.transport}</span>\n {item.existing && <span className=\"dpc-tag\">{t('importExisting')}</span>}\n </div>\n <p className=\"dpc-cardDesc\">\n {item.server.transport === 'stdio'\n ? `${item.server.command ?? ''} ${(item.server.args ?? []).join(' ')}`\n : item.server.url ?? ''}\n </p>\n </li>\n ))}\n </ul>\n )}\n {formError !== null && <p className=\"dpc-formError\">{formError}</p>}\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" onClick={() => setImportOpen(false)}>{t('cancel')}</Button>\n <Button\n variant=\"primary\"\n disabled={busy || importItems === null || !importItems.some(item => item.checked && !item.existing)}\n onClick={() => void doImport()}\n >\n {t('importSelected')}\n </Button>\n </div>\n </div>\n </Modal>\n </div>\n )\n}\n", "/** Shared scoped stylesheet for the capabilities tabs \u2014 rides the host's\n * --dsw-* tokens (same design language as the plugin-inventory and install\n * tabs) so light and dark themes both stay correct. Prefix: dpc-. */\n\nexport const CSS = `\n.dpc-section{display:flex;flex-direction:column;gap:14px;width:100%;max-width:760px;color:var(--dsw-alias-label-primary)}\n.dpc-head{display:flex;align-items:center;gap:8px}\n.dpc-head h3{margin:0;font-size:13px;line-height:20px;font-weight:600}\n.dpc-head>svg{flex:none;color:var(--dsw-alias-label-tertiary)}\n.dpc-intro{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}\n.dpc-listHead{display:flex;align-items:baseline;gap:7px;padding:0 2px;margin-top:2px}\n.dpc-listHead h3{margin:0;font-size:13px;line-height:20px;font-weight:600}\n.dpc-count{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}\n.dpc-spacer{flex:1}\n.dpc-refresh{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer}\n.dpc-refresh:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}\n.dpc-refresh:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}\n.dpc-empty{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}\n.dpc-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:stretch;gap:10px;margin:0;padding:0;list-style:none}\n.dpc-card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3);padding:12px 14px}\n.dpc-card:hover{background:var(--dsw-alias-interactive-bg-hover)}\n.dpc-cardTop{display:flex;align-items:center;gap:8px}\n.dpc-cardTitle{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px;font-weight:600;font-family:var(--ds-font-family-code)}\n.dpc-cardDesc{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n.dpc-cardRow{display:flex;align-items:center;gap:6px;flex-wrap:wrap}\n.dpc-tag{display:inline-flex;align-items:center;min-height:20px;border-radius:5px;padding:1px 6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px;white-space:nowrap}\n.dpc-tag[data-kind='source']{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);color:var(--dsw-alias-state-business-primary)}\n.dpc-tag[data-kind='off']{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-tertiary)) 12%,transparent);color:var(--dsw-alias-label-secondary)}\n.dpc-cardActions{display:flex;align-items:center;gap:6px;margin-left:auto}\n.dpc-banner{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px 12px;background:var(--dsw-alias-bg-layer-3);font-size:13px;line-height:20px}\n.dpc-banner[data-kind='ok']{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent)}\n.dpc-banner[data-kind='error']{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent)}\n.dpc-banner[data-kind='info']{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 8%,transparent)}\n.dpc-bannerBody{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}\n.dpc-bannerHint{display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}\n.dpc-form{display:flex;flex-direction:column;gap:10px}\n.dpc-label{display:flex;flex-direction:column;gap:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}\n.dpc-label>span:first-child{color:var(--dsw-alias-label-tertiary)}\n.dpc-input,.dpc-textarea,.dpc-select{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;outline:none;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}\n.dpc-textarea{min-height:120px;resize:vertical;font-family:var(--ds-font-family-code);line-height:1.5}\n.dpc-textarea[data-short='true']{min-height:64px}\n.dpc-input:focus-visible,.dpc-textarea:focus-visible,.dpc-select:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}\n.dpc-checks{display:flex;gap:16px;font-size:13px;line-height:20px}\n.dpc-checks label{display:inline-flex;align-items:center;gap:6px;cursor:pointer}\n.dpc-formError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}\n@media(max-width:680px){.dpc-cards{grid-template-columns:minmax(0,1fr)}}\n`\n", "/** Settings \u2192 Plugins \u201C\u6280\u80FD/Skills\u201D tab: view the discovered catalog, edit the\n * user-owned root. Pure presentation-layer \u2014 data arrives through props. */\n\nimport { useEffect, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { Button, IconRefreshOutline14, IconSkillOutline16, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { CSS } from './css.ts'\nimport type { Translate } from './index.ts'\n\n/** One skill as the host reports it (plus the editable flag the route adds). */\nexport interface SkillRowView {\n name: string\n description: string\n whenToUse?: string\n invocation: { modelInvocable: boolean; userInvocable: boolean }\n source: string\n provider: string\n editable: boolean\n}\n\nexport interface SkillsInjected {\n list(): Promise<{ skills: SkillRowView[] }>\n get(name: string): Promise<{ content: string }>\n save(input: { name: string; description: string; whenToUse?: string; modelInvocable: boolean; userInvocable: boolean; content: string }): Promise<{ ok: boolean }>\n remove(name: string): Promise<{ ok: boolean }>\n}\n\n/** Editor dialog state; null when closed. */\ninterface EditorState {\n mode: 'create' | 'edit' | 'view'\n name: string\n description: string\n whenToUse: string\n modelInvocable: boolean\n userInvocable: boolean\n content: string\n}\n\nconst SOURCE_KEYS: Record<string, string> = {\n 'project-dsh': 'sourceProjectDsh',\n 'project-agents': 'sourceProjectAgents',\n 'user-dsh': 'sourceUserDsh',\n 'user-agents': 'sourceUserAgents',\n runtime: 'sourceRuntime',\n bundled: 'sourceBundled',\n custom: 'sourceCustom',\n}\n\nexport function SkillsTab(props: { t: Translate; injected: SkillsInjected }): ReactElement {\n const { t, injected } = props\n const [skills, setSkills] = useState<SkillRowView[] | null>(null)\n const [editor, setEditor] = useState<EditorState | null>(null)\n const [confirmName, setConfirmName] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n const [outcome, setOutcome] = useState<{ ok: boolean; text: string } | null>(null)\n const [formError, setFormError] = useState<string | null>(null)\n const [reload, setReload] = useState(0)\n\n useEffect(() => {\n let current = true\n void injected.list().then(\n (body) => { if (current) setSkills(body.skills) },\n (error: Error) => { if (current) { setSkills([]); setOutcome({ ok: false, text: `${t('failed')}: ${String(error.message ?? error)}` }) } },\n )\n return () => { current = false }\n }, [injected, reload, t])\n\n const openCreate = (): void => {\n setFormError(null)\n setEditor({ mode: 'create', name: '', description: '', whenToUse: '', modelInvocable: true, userInvocable: true, content: '' })\n }\n\n const openExisting = async (skill: SkillRowView): Promise<void> => {\n setBusy(true)\n setFormError(null)\n try {\n const body = await injected.get(skill.name)\n setEditor({\n mode: skill.editable ? 'edit' : 'view',\n name: skill.name,\n description: skill.description,\n whenToUse: skill.whenToUse ?? '',\n modelInvocable: skill.invocation.modelInvocable,\n userInvocable: skill.invocation.userInvocable,\n content: body.content,\n })\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n }\n }\n\n /**\n * After a write/delete, the watched directory takes a moment to invalidate\n * the catalog \u2014 poll until the change is visible (bounded), then stop.\n */\n const refreshUntil = (predicate: (skills: SkillRowView[]) => boolean, timeoutMs = 10_000): void => {\n const deadline = Date.now() + timeoutMs\n const tick = (): void => {\n if (Date.now() > deadline) return\n setTimeout(() => {\n void injected.list().then(\n (body) => {\n setSkills(body.skills)\n if (!predicate(body.skills)) tick()\n },\n () => tick(),\n )\n }, 1000)\n }\n tick()\n }\n\n const doSave = async (): Promise<void> => {\n if (editor === null) return\n setBusy(true)\n setFormError(null)\n try {\n const name = editor.name.trim()\n await injected.save({\n name,\n description: editor.description,\n whenToUse: editor.whenToUse.trim() === '' ? undefined : editor.whenToUse,\n modelInvocable: editor.modelInvocable,\n userInvocable: editor.userInvocable,\n content: editor.content,\n })\n setOutcome({ ok: true, text: t('saved') })\n setEditor(null)\n refreshUntil(skills => skills.some(skill => skill.name === name))\n } catch (error) {\n setFormError(String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const doDelete = async (): Promise<void> => {\n if (confirmName === null) return\n const name = confirmName\n setBusy(true)\n try {\n await injected.remove(name)\n setOutcome({ ok: true, text: t('saved') })\n refreshUntil(skills => !skills.some(skill => skill.name === name))\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n setConfirmName(null)\n }\n }\n\n const readOnly = editor?.mode === 'view'\n\n return (\n <div className=\"dpc-section\">\n <style>{CSS}</style>\n\n <div className=\"dpc-head\">\n <IconSkillOutline16 aria-hidden=\"true\" />\n <h3>{t('skillsTitle')}</h3>\n <span className=\"dpc-spacer\" />\n <Button variant=\"primary\" size=\"sm\" onClick={openCreate}>{t('newSkill')}</Button>\n </div>\n <p className=\"dpc-intro\">{t('skillsIntro')}</p>\n\n {outcome !== null && (\n <div className=\"dpc-banner\" data-kind={outcome.ok ? 'ok' : 'error'} role=\"status\">\n <StateDot state={outcome.ok ? 'done' : 'error'} size={10} />\n <div className=\"dpc-bannerBody\"><span>{outcome.text}</span></div>\n </div>\n )}\n\n <div className=\"dpc-listHead\">\n <h3>{t('skillsTab')}</h3>\n {skills !== null && <span className=\"dpc-count\">{skills.length}</span>}\n <span className=\"dpc-spacer\" />\n <button type=\"button\" className=\"dpc-refresh\" aria-label={t('view')} title={t('view')} disabled={busy} onClick={() => setReload((value) => value + 1)}>\n <IconRefreshOutline14 size={14} aria-hidden=\"true\" />\n </button>\n </div>\n\n {skills === null && <p className=\"dpc-empty\">{t('loading')}</p>}\n {skills !== null && skills.length === 0 && <p className=\"dpc-empty\">{t('emptySkills')}</p>}\n {skills !== null && skills.length > 0 && (\n <ul className=\"dpc-cards\">\n {skills.map((skill) => (\n <li className=\"dpc-card\" key={`${skill.source}/${skill.name}`}>\n <div className=\"dpc-cardTop\">\n <strong className=\"dpc-cardTitle\" title={skill.name}>{skill.name}</strong>\n <span className=\"dpc-tag\" data-kind=\"source\">{t(SOURCE_KEYS[skill.source] ?? 'sourceCustom')}</span>\n </div>\n <p className=\"dpc-cardDesc\" title={skill.description}>{skill.description}</p>\n <div className=\"dpc-cardRow\">\n {!skill.invocation.modelInvocable && <span className=\"dpc-tag\">\u2699</span>}\n {!skill.invocation.userInvocable && <span className=\"dpc-tag\">/</span>}\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => void openExisting(skill)}>\n {skill.editable ? t('edit') : t('view')}\n </Button>\n {skill.editable && (\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => setConfirmName(skill.name)}>{t('delete')}</Button>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n\n <Modal\n open={editor !== null}\n onClose={() => setEditor(null)}\n title={editor === null ? '' : editor.mode === 'create' ? t('newSkill') : editor.mode === 'edit' ? t('editSkill') : t('viewSkill')}\n >\n {editor !== null && (\n <div className=\"dpc-form\">\n <label className=\"dpc-label\">\n <span>{t('skillName')}</span>\n <input\n className=\"dpc-input\"\n value={editor.name}\n disabled={readOnly || editor.mode === 'edit'}\n onChange={(event) => setEditor({ ...editor, name: event.target.value })}\n />\n </label>\n <label className=\"dpc-label\">\n <span>{t('skillDescription')}</span>\n <input\n className=\"dpc-input\"\n value={editor.description}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, description: event.target.value })}\n />\n </label>\n <label className=\"dpc-label\">\n <span>{t('skillWhenToUse')}</span>\n <input\n className=\"dpc-input\"\n value={editor.whenToUse}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, whenToUse: event.target.value })}\n />\n </label>\n <div className=\"dpc-checks\">\n <label>\n <input\n type=\"checkbox\"\n checked={editor.modelInvocable}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, modelInvocable: event.target.checked })}\n />\n {t('modelInvocable')}\n </label>\n <label>\n <input\n type=\"checkbox\"\n checked={editor.userInvocable}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, userInvocable: event.target.checked })}\n />\n {t('userInvocable')}\n </label>\n </div>\n <label className=\"dpc-label\">\n <span>{t('skillContent')}</span>\n <textarea\n className=\"dpc-textarea\"\n value={editor.content}\n readOnly={readOnly}\n onChange={(event) => setEditor({ ...editor, content: event.target.value })}\n />\n </label>\n {formError !== null && <p className=\"dpc-formError\">{formError}</p>}\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" onClick={() => setEditor(null)}>{readOnly ? t('close') : t('cancel')}</Button>\n {!readOnly && <Button variant=\"primary\" disabled={busy} onClick={() => void doSave()}>{t('save')}</Button>}\n </div>\n </div>\n )}\n </Modal>\n\n <Modal\n open={confirmName !== null}\n onClose={() => setConfirmName(null)}\n title={t('confirmDelete')}\n description={confirmName ?? undefined}\n footer={\n <>\n <Button variant=\"ghost\" onClick={() => setConfirmName(null)}>{t('cancel')}</Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => void doDelete()}>{t('delete')}</Button>\n </>\n }\n >\n <p>{t('deleteWarn')}</p>\n </Modal>\n </div>\n )\n}\n", "/** zh/en dictionaries for the Settings capabilities tabs. */\n\nexport const zh = {\n skillsTab: '\u6280\u80FD',\n mcpTab: 'MCP',\n skillsTitle: '\u6280\u80FD\u7BA1\u7406',\n skillsIntro: '\u67E5\u770B\u4E0E\u7F16\u8F91 dsh \u53D1\u73B0\u7684\u6280\u80FD\uFF1B\u7528\u6237\u7EA7\u6280\u80FD\uFF08DSH_HOME/skills\uFF09\u53EF\u5728\u6B64\u65B0\u5EFA\u3001\u4FEE\u6539\u3001\u5220\u9664\uFF0C\u6587\u4EF6\u88AB\u76D1\u542C\uFF0C\u4FDD\u5B58\u5373\u751F\u6548\u3002\u82E5\u5B58\u5728 ~/.claude/skills \u6216 ~/.codex/skills\uFF0C\u4F1A\u81EA\u52A8\u7EB3\u5165\u626B\u63CF\uFF08\u96F6\u62F7\u8D1D\u3001\u5B9E\u65F6\u540C\u6B65\uFF09\u3002',\n newSkill: '\u65B0\u5EFA\u6280\u80FD',\n editSkill: '\u7F16\u8F91\u6280\u80FD',\n viewSkill: '\u67E5\u770B\u6280\u80FD',\n skillName: '\u540D\u79F0\uFF08kebab-case\uFF09',\n skillDescription: '\u63CF\u8FF0',\n skillWhenToUse: '\u4F7F\u7528\u65F6\u673A\uFF08\u53EF\u9009\uFF09',\n skillContent: '\u6B63\u6587\uFF08Markdown\uFF09',\n modelInvocable: '\u5141\u8BB8\u6A21\u578B\u8C03\u7528',\n userInvocable: '\u5141\u8BB8\u7528\u6237 / \u8C03\u7528',\n save: '\u4FDD\u5B58',\n cancel: '\u53D6\u6D88',\n delete: '\u5220\u9664',\n edit: '\u7F16\u8F91',\n view: '\u67E5\u770B',\n close: '\u5173\u95ED',\n emptySkills: '\u8FD8\u6CA1\u6709\u53D1\u73B0\u4EFB\u4F55\u6280\u80FD',\n loading: '\u8F7D\u5165\u4E2D\u2026',\n source: '\u6765\u6E90',\n sourceProjectDsh: '\u9879\u76EE',\n sourceProjectAgents: '\u9879\u76EE',\n sourceUserDsh: '\u7528\u6237',\n sourceUserAgents: '\u7528\u6237',\n sourceRuntime: '\u8FD0\u884C\u65F6',\n sourceBundled: '\u5185\u7F6E',\n sourceCustom: '\u81EA\u5B9A\u4E49',\n readOnly: '\u53EA\u8BFB',\n confirmDelete: '\u786E\u8BA4\u5220\u9664\u6280\u80FD\uFF1F',\n deleteWarn: '\u5C06\u5220\u9664 DSH_HOME/skills \u4E0B\u7684\u6574\u4E2A\u6280\u80FD\u76EE\u5F55\uFF0C\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002',\n saved: '\u5DF2\u4FDD\u5B58\uFF0C\u6280\u80FD\u76EE\u5F55\u88AB\u76D1\u542C\uFF0C\u7A0D\u5019\u5373\u53EF\u5728\u4F1A\u8BDD\u4E2D\u4F7F\u7528\u3002',\n mcpTitle: 'MCP \u670D\u52A1\u5668',\n 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',\n addServer: '\u6DFB\u52A0\u670D\u52A1\u5668',\n editServer: '\u7F16\u8F91\u670D\u52A1\u5668',\n serverName: '\u670D\u52A1\u5668\u540D\uFF08\u5DE5\u5177\u540D\u524D\u7F00\uFF09',\n transport: '\u4F20\u8F93\u65B9\u5F0F',\n transportStdio: 'stdio\uFF08\u672C\u5730\u547D\u4EE4\uFF09',\n transportHttp: 'streamable-http\uFF08URL\uFF09',\n command: '\u547D\u4EE4',\n args: '\u53C2\u6570\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF09',\n envPairs: '\u73AF\u5883\u53D8\u91CF\uFF08\u6BCF\u884C KEY=VALUE\uFF09',\n url: 'URL',\n headersPairs: '\u8BF7\u6C42\u5934\uFF08\u6BCF\u884C KEY: VALUE\uFF09',\n disabled: '\u5DF2\u505C\u7528',\n enabled: '\u542F\u7528\u4E2D',\n toggle: '\u505C\u7528/\u542F\u7528',\n confirmRemove: '\u786E\u8BA4\u79FB\u9664\u8BE5\u670D\u52A1\u5668\uFF1F',\n 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',\n emptyMcp: '\u8FD8\u6CA1\u6709\u914D\u7F6E MCP \u670D\u52A1\u5668',\n importServers: '\u4ECE\u5176\u4ED6 Agent \u5BFC\u5165',\n 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',\n importEmpty: '\u6CA1\u6709\u53D1\u73B0\u53EF\u5BFC\u5165\u7684 MCP \u670D\u52A1\u5668',\n importExisting: '\u5DF2\u5B58\u5728',\n importSelected: '\u5BFC\u5165\u9009\u4E2D\u9879',\n restartNeeded: '\u914D\u7F6E\u5DF2\u5199\u5165\uFF0C\u91CD\u542F dsh \u540E\u751F\u6548\u3002',\n restartDesktopHint: '\u91CD\u542F\u7531\u684C\u9762\u5E94\u7528\u8D1F\u8D23\uFF1A\u6258\u76D8\u83DC\u5355\u300C\u91CD\u542F\u670D\u52A1\u300D\u3002',\n restartOtherHint: '\u91CD\u542F\u65B9\u5F0F\uFF1A\u5173\u95ED\u5F53\u524D dsh \u8FDB\u7A0B\u540E\u91CD\u65B0\u8FD0\u884C\u3002',\n restartNow: '\u91CD\u542F\u670D\u52A1',\n restart: '\u91CD\u542F',\n restartConfirmTitle: '\u91CD\u542F dsh\uFF1F',\n restartConfirmBody: '\u91CD\u542F\u4F1A\u4E2D\u65AD\u6B63\u5728\u8FDB\u884C\u7684\u56DE\u5408\uFF0C\u672A\u4FDD\u5B58\u7684\u8F93\u5165\u53EF\u80FD\u4E22\u5931\uFF1BMCP \u884C\u7684\u53D8\u66F4\u5C06\u5728\u91CD\u542F\u540E\u751F\u6548\u3002',\n restarting: '\u6B63\u5728\u91CD\u542F\uFF0C\u6062\u590D\u540E\u5C06\u81EA\u52A8\u5237\u65B0\u9875\u9762\u2026',\n restartPortHint: '\u82E5\u91CD\u542F\u540E\u9875\u9762\u957F\u65F6\u95F4\u672A\u6062\u590D\uFF0C\u53EF\u80FD\u662F\u7AEF\u53E3\u5DF2\u53D8\u5316\uFF1A\u5728\u7EC8\u7AEF\u67E5\u770B\u65B0\u5730\u5740\u540E\u6253\u5F00\u3002',\n failed: '\u64CD\u4F5C\u5931\u8D25',\n}\n\nexport const en = {\n skillsTab: 'Skills',\n mcpTab: 'MCP',\n skillsTitle: 'Skills',\n skillsIntro: 'View and edit the skills dsh discovers; user-level skills (DSH_HOME/skills) can be created, edited, and deleted here \u2014 the directory is watched, saves apply without a restart. ~/.claude/skills and ~/.codex/skills are scanned too when present (zero-copy, live-synced).',\n newSkill: 'New skill',\n editSkill: 'Edit skill',\n viewSkill: 'View skill',\n skillName: 'Name (kebab-case)',\n skillDescription: 'Description',\n skillWhenToUse: 'When to use (optional)',\n skillContent: 'Body (Markdown)',\n modelInvocable: 'Model-invocable',\n userInvocable: 'User-invocable (/)',\n save: 'Save',\n cancel: 'Cancel',\n delete: 'Delete',\n edit: 'Edit',\n view: 'View',\n close: 'Close',\n emptySkills: 'No skills discovered yet',\n loading: 'Loading\u2026',\n source: 'Source',\n sourceProjectDsh: 'project',\n sourceProjectAgents: 'project',\n sourceUserDsh: 'user',\n sourceUserAgents: 'user',\n sourceRuntime: 'runtime',\n sourceBundled: 'bundled',\n sourceCustom: 'custom',\n readOnly: 'read-only',\n confirmDelete: 'Delete this skill?',\n deleteWarn: 'Removes the whole skill directory under DSH_HOME/skills. This cannot be undone.',\n saved: 'Saved. The skills directory is watched; the skill is usable in sessions shortly.',\n mcpTitle: 'MCP servers',\n mcpIntro: 'Manage the MCP server rows (@deepseek-ai/dsh-mcp-client) in this profile. Additions, edits, and removals take effect after a dsh restart.',\n addServer: 'Add server',\n editServer: 'Edit server',\n serverName: 'Server name (tool name prefix)',\n transport: 'Transport',\n transportStdio: 'stdio (local command)',\n transportHttp: 'streamable-http (URL)',\n command: 'Command',\n args: 'Arguments (one per line)',\n envPairs: 'Environment (KEY=VALUE per line)',\n url: 'URL',\n headersPairs: 'Headers (KEY: VALUE per line)',\n disabled: 'Disabled',\n enabled: 'Enabled',\n toggle: 'Enable/disable',\n confirmRemove: 'Remove this server?',\n removeWarn: 'Removes the row from the profile configuration; its tools disappear after the next dsh restart.',\n emptyMcp: 'No MCP servers configured yet',\n importServers: 'Import from other agents',\n importIntro: 'Scans Claude Code (~/.claude.json) and Codex (~/.codex/config.toml) MCP server configs; selected entries become server rows in this profile.',\n importEmpty: 'No importable MCP servers found',\n importExisting: 'already here',\n importSelected: 'Import selected',\n restartNeeded: 'Configuration written \u2014 restart dsh to apply.',\n restartDesktopHint: 'The desktop app owns restarts: use the tray \u201CRestart service\u201D.',\n restartOtherHint: 'Restart by closing this dsh process and running it again.',\n restartNow: 'Restart service',\n restart: 'Restart',\n restartConfirmTitle: 'Restart dsh?',\n restartConfirmBody: 'Restarting interrupts any running turn and may lose unsaved input; pending MCP changes apply after the restart.',\n restarting: 'Restarting \u2014 the page will reload once the host is back\u2026',\n restartPortHint: 'If the page does not recover, the port may have changed: check the terminal for the new URL.',\n failed: 'Operation failed',\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["import_react", "import_react", "import_dsh_client_ui_primitives", "import_jsx_runtime", "name", "skills", "name", "h"]
|
|
3
|
+
"sources": ["../src/client/index.ts", "../src/client/CapabilitiesSection.tsx", "../src/client/css.ts", "../src/client/McpTab.tsx", "../src/client/SkillsTab.tsx", "../src/client/locales.ts"],
|
|
4
|
+
"sourcesContent": ["/** dsh-plugin-capabilities client entry: contributes the top-level\n * \u201C\u6280\u80FD\u4E0E MCP\u201D section into Settings (beside \u901A\u7528\u8BBE\u7F6E/\u6A21\u578B, with Skills and\n * MCP as internal tabs). Calls the host routes with fetch. */\n\nimport { createElement as h } from 'react'\nimport { CapabilitiesSection } from './CapabilitiesSection.tsx'\nimport type { McpInjected, McpRow } from './McpTab.tsx'\nimport type { SkillsInjected, SkillRowView } from './SkillsTab.tsx'\nimport { zh, en } from './locales.ts'\n\n/** Locale dictionary namespace owned by this plugin. */\nexport const NS = 'settings.pluginCapabilities'\n\n/** The `t` function bound by the locale service. */\nexport interface Translate {\n (key: string): string\n}\n\n/** Minimal structural subset of the slots service. */\ninterface SlotsService {\n inject(slot: string, register: () => unknown): void\n register(meta: Record<string, unknown>, component: () => unknown): unknown\n}\n\n/** Minimal structural subset of the locale service. */\ninterface LocaleService {\n register(namespace: string, dicts: { zh: Record<string, string>; en: Record<string, string> }): unknown\n bind(namespace: string): Translate\n}\n\n/** The client cordis context this plugin relies on (structural). */\ninterface CapabilitiesClientContext {\n effect(callback: () => unknown, label?: string): void\n locale: LocaleService\n slots: SlotsService\n}\n\n/** Same-origin fetch of the manager's host routes. */\nasync function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(path, init)\n const body = (await response.json()) as T & { error?: string }\n if (!response.ok) throw new Error(body.error ?? `HTTP ${response.status}`)\n return body\n}\n\nconst post = (path: string, body: unknown): Promise<unknown> =>\n fetchJson(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })\n\nexport const name = 'dsh-plugin-capabilities'\nexport const inject = ['slots', 'locale']\n\nexport interface SkillBody { content: string }\n\n/** One foreign-agent server from the import scan. */\nexport interface ImportedServerView {\n agent: 'claude-code' | 'codex'\n name: string\n transport: 'stdio' | 'streamable-http'\n command?: string\n args?: string[]\n env?: Record<string, string>\n url?: string\n headers?: Record<string, string>\n}\n\nexport function apply(ctx: CapabilitiesClientContext): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-plugin-capabilities: dictionaries')\n const t = ctx.locale.bind(NS)\n\n const skillsInjected: SkillsInjected = {\n list: () => fetchJson<{ skills: SkillRowView[] }>('/dsh-plugin-capabilities/skills'),\n get: (name: string) => fetchJson<SkillBody>(`/dsh-plugin-capabilities/skill?name=${encodeURIComponent(name)}`),\n save: (input: unknown) => post('/dsh-plugin-capabilities/skill/save', input) as Promise<{ ok: boolean }>,\n remove: (name: string) => post('/dsh-plugin-capabilities/skill/delete', { name }) as Promise<{ ok: boolean }>,\n }\n\n const mcpInjected: McpInjected = {\n list: () => fetchJson<{ servers: McpRow[] }>('/dsh-plugin-capabilities/mcp'),\n save: (input: unknown) => post('/dsh-plugin-capabilities/mcp/save', input) as Promise<{ ok: boolean; id: string }>,\n toggle: (id: string, disabled: boolean) => post('/dsh-plugin-capabilities/mcp/toggle', { id, disabled }) as Promise<{ ok: boolean }>,\n remove: (id: string) => post('/dsh-plugin-capabilities/mcp/remove', { id }) as Promise<{ ok: boolean }>,\n scanImport: () => fetchJson<{ servers: ImportedServerView[]; existing: string[] }>('/dsh-plugin-capabilities/import/scan'),\n applyImport: (items: Array<{ agent: string; name: string }>) =>\n post('/dsh-plugin-capabilities/import/apply', { items }) as Promise<{ ok: boolean; results: Array<{ name: string; ok: boolean; error?: string }> }>,\n restart: async (): Promise<void> => {\n if (window.dshDesktop !== undefined) {\n window.dshDesktop.restartSidecar?.()\n return\n }\n // \u72EC\u7ACB dsh web\uFF1A\u81EA\u91CD\u542F\u8DEF\u7531\u3002\u8FDE\u63A5\u5728\u5173\u505C\u9014\u4E2D\u65AD\u5F00\u5C5E\u9884\u671F\uFF0C\u4E0D\u7B97\u5931\u8D25\u3002\n try { await post('/dsh-plugin-capabilities/restart', {}) } catch { /* dying mid-restart is expected */ }\n },\n desktop: window.dshDesktop !== undefined,\n }\n\n // One top-level nav entry (between \u6A21\u578B and \u63D2\u4EF6), not a tab under the\n // Plugins section \u2014 the section component owns its internal Skills/MCP\n // tabs directly, so nothing registers into settings.plugins.tab anymore.\n ctx.slots.inject('settings.section', () => {\n return ctx.slots.register({\n name: 'settings.section',\n id: 'capabilities',\n order: 12,\n label: () => t('sectionNav'),\n locale: NS,\n }, () => h(CapabilitiesSection, { t, skills: skillsInjected, mcp: mcpInjected }))\n })\n}\n", "/** Settings top-level \u201C\u6280\u80FD\u4E0E MCP\u201D section: one nav entry beside \u901A\u7528\u8BBE\u7F6E and\n * \u6A21\u578B, with the Skills and MCP pages as internal tabs. The two pages are the\n * same components the Plugins section used to host \u2014 promoted a level instead\n * of squeezed under \u63D2\u4EF6. Pure composition; data still arrives through the\n * injected faces. */\n\nimport { useEffect, useId, useRef, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { CSS } from './css.ts'\nimport { McpTab } from './McpTab.tsx'\nimport type { McpInjected } from './McpTab.tsx'\nimport { SkillsTab } from './SkillsTab.tsx'\nimport type { SkillsInjected } from './SkillsTab.tsx'\nimport type { Translate } from './index.ts'\n\nexport function CapabilitiesSection(props: {\n t: Translate\n skills: SkillsInjected\n mcp: McpInjected\n}): ReactElement {\n const { t, skills, mcp } = props\n const tabsId = useId()\n const tabRefs = useRef<Array<HTMLButtonElement | null>>([])\n const rows = [\n { id: 'skills', label: t('skillsTab') },\n { id: 'mcp', label: t('mcpTab') },\n ]\n const [activeId, setActiveId] = useState('skills')\n // The skills page mounts immediately; a tab mounts only when first\n // selected, then stays mounted while hidden so editor drafts and outcome\n // banners survive switching between the two views.\n const [visitedIds, setVisitedIds] = useState<ReadonlySet<string>>(() => new Set(['skills']))\n\n useEffect(() => {\n setVisitedIds((previous) => {\n if (previous.has(activeId)) return previous\n return new Set([...previous, activeId])\n })\n }, [activeId])\n\n return (\n <div className=\"dpc-section\">\n <style>{CSS}</style>\n\n <h2 className=\"dpc-heading\">{t('sectionNav')}</h2>\n <div className=\"dpc-tabs\" role=\"tablist\" aria-label={t('sectionNav')}>\n {rows.map((row, index) => {\n const selected = row.id === activeId\n return (\n <button\n key={row.id}\n ref={(element) => { tabRefs.current[index] = element }}\n id={`${tabsId}-tab-${row.id}`}\n type=\"button\"\n role=\"tab\"\n className=\"dpc-tab\"\n aria-selected={selected}\n aria-controls={`${tabsId}-panel-${row.id}`}\n data-active={selected ? 'true' : undefined}\n tabIndex={selected ? 0 : -1}\n onClick={() => { setActiveId(row.id) }}\n onKeyDown={(event) => {\n let nextIndex: number\n switch (event.key) {\n case 'ArrowRight': nextIndex = (index + 1) % rows.length; break\n case 'ArrowLeft': nextIndex = (index - 1 + rows.length) % rows.length; break\n case 'Home': nextIndex = 0; break\n case 'End': nextIndex = rows.length - 1; break\n default: return\n }\n event.preventDefault()\n setActiveId(rows[nextIndex]?.id ?? 'skills')\n tabRefs.current[nextIndex]?.focus()\n }}\n >\n {row.label}\n </button>\n )\n })}\n </div>\n {rows\n .filter(row => row.id === activeId || visitedIds.has(row.id))\n .map((row) => {\n const selected = row.id === activeId\n return (\n <div\n key={row.id}\n id={`${tabsId}-panel-${row.id}`}\n className=\"dpc-tabPanel\"\n role=\"tabpanel\"\n aria-labelledby={`${tabsId}-tab-${row.id}`}\n hidden={!selected}\n >\n {row.id === 'skills'\n ? <SkillsTab t={t} injected={skills} />\n : <McpTab t={t} injected={mcp} />}\n </div>\n )\n })}\n </div>\n )\n}\n", "/** Shared scoped stylesheet for the capabilities tabs \u2014 rides the host's\n * --dsw-* tokens (same design language as the plugin-inventory and install\n * tabs) so light and dark themes both stay correct. Prefix: dpc-. */\n\nexport const CSS = `\n.dpc-section{display:flex;flex-direction:column;gap:14px;width:100%;max-width:760px;color:var(--dsw-alias-label-primary)}\n.dpc-heading{margin:0;font-size:18px;line-height:26px;font-weight:600}\n/* Top-level section's internal tabs \u2014 same underline-tab look as the host's\n Plugins section so the promoted placement still reads as one family. */\n.dpc-tabs{display:flex;align-items:flex-end;gap:22px;border-bottom:1px solid var(--dsw-alias-border-l2);margin-top:2px}\n.dpc-tab{position:relative;border:0;padding:7px 1px 9px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}\n.dpc-tab:hover,.dpc-tab[data-active='true']{color:var(--dsw-alias-label-primary)}\n.dpc-tab[data-active='true']::after,.dpc-tab:focus-visible::after{position:absolute;right:0;bottom:-1px;left:0;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary);content:''}\n.dpc-tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:2px;color:var(--dsw-alias-label-primary)}\n.dpc-tabPanel{min-width:0;padding-top:2px}\n.dpc-head{display:flex;align-items:center;gap:8px}\n.dpc-head h3{margin:0;font-size:13px;line-height:20px;font-weight:600}\n.dpc-head>svg{flex:none;color:var(--dsw-alias-label-tertiary)}\n.dpc-intro{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}\n.dpc-listHead{display:flex;align-items:baseline;gap:7px;padding:0 2px;margin-top:2px}\n.dpc-listHead h3{margin:0;font-size:13px;line-height:20px;font-weight:600}\n.dpc-count{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}\n.dpc-spacer{flex:1}\n.dpc-refresh{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border:0;border-radius:6px;background:transparent;color:var(--dsw-alias-label-tertiary);cursor:pointer}\n.dpc-refresh:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}\n.dpc-refresh:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:-2px}\n.dpc-empty{margin:0;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}\n.dpc-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));align-items:stretch;gap:10px;margin:0;padding:0;list-style:none}\n.dpc-card{display:flex;flex-direction:column;gap:8px;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-3);padding:12px 14px}\n.dpc-card:hover{background:var(--dsw-alias-interactive-bg-hover)}\n.dpc-cardTop{display:flex;align-items:center;gap:8px}\n.dpc-cardTitle{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:14px;line-height:20px;font-weight:600;font-family:var(--ds-font-family-code)}\n.dpc-cardDesc{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n.dpc-cardRow{display:flex;align-items:center;gap:6px;flex-wrap:wrap}\n.dpc-tag{display:inline-flex;align-items:center;min-height:20px;border-radius:5px;padding:1px 6px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-size:11px;line-height:16px;white-space:nowrap}\n.dpc-tag[data-kind='source']{background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);color:var(--dsw-alias-state-business-primary)}\n.dpc-tag[data-kind='off']{background:color-mix(in srgb,var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-tertiary)) 12%,transparent);color:var(--dsw-alias-label-secondary)}\n.dpc-cardActions{display:flex;align-items:center;gap:6px;margin-left:auto}\n.dpc-banner{display:flex;align-items:flex-start;gap:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:10px 12px;background:var(--dsw-alias-bg-layer-3);font-size:13px;line-height:20px}\n.dpc-banner[data-kind='ok']{border-color:color-mix(in srgb,var(--dsw-alias-state-success-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent)}\n.dpc-banner[data-kind='error']{border-color:color-mix(in srgb,var(--dsw-alias-state-error-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent)}\n.dpc-banner[data-kind='info']{border-color:color-mix(in srgb,var(--dsw-alias-state-business-primary) 35%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 8%,transparent)}\n.dpc-bannerBody{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}\n.dpc-bannerHint{display:flex;align-items:center;gap:8px;flex-wrap:wrap;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}\n.dpc-form{display:flex;flex-direction:column;gap:10px}\n.dpc-label{display:flex;flex-direction:column;gap:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}\n.dpc-label>span:first-child{color:var(--dsw-alias-label-tertiary)}\n.dpc-input,.dpc-textarea,.dpc-select{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;outline:none;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}\n.dpc-textarea{min-height:240px;resize:vertical;font-family:var(--ds-font-family-code);line-height:1.5}\n.dpc-textarea[data-short='true']{min-height:96px}\n.dpc-input:focus-visible,.dpc-textarea:focus-visible,.dpc-select:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}\n.dpc-checks{display:flex;gap:16px;font-size:13px;line-height:20px}\n.dpc-checks label{display:inline-flex;align-items:center;gap:6px;cursor:pointer}\n.dpc-formError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}\n/* Import dialog: wider than the host's 380px default; the server list gets\n its own scroll so title, intro, and footer stay pinned. The doubled class\n beats the host dialog's module CSS regardless of injection order. */\n.dpc-modalWide.dpc-modalWide{width:min(680px,100%)}\n/* Editor dialogs (new skill / server): 640px wide so markdown bodies and\n command/arg/env lines stop wrapping mid-token; the content column scrolls\n on short viewports instead of clipping past the dialog edge. */\n.dpc-modalForm.dpc-modalForm{width:min(640px,100%)}\n.dpc-modalScroll.dpc-modalScroll{max-height:calc(100vh - 160px);overflow-y:auto}\n.dpc-importScroll{display:flex;flex-direction:column;gap:14px;max-height:min(400px,52vh);overflow-y:auto;padding:2px 4px 2px 2px}\n.dpc-importGroup{display:flex;flex-direction:column;gap:8px}\n.dpc-importHead{display:flex;align-items:center;gap:8px;padding:0 2px}\n.dpc-importCount{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}\n.dpc-importAll{margin-left:auto;display:inline-flex;align-items:center;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);cursor:pointer}\n@media(max-width:680px){.dpc-cards{grid-template-columns:minmax(0,1fr)}}\n`\n", "/** Settings \u2192 Plugins \u201CMCP\u201D tab: manage the profile's mcp-client rows.\n * Mutations rewrite the profile patch and need a dsh restart \u2014 the banner\n * hands the restart to the desktop shell when one is present. */\n\nimport { useEffect, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { Button, IconApiOutline14, IconRefreshOutline14, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { CSS } from './css.ts'\nimport type { ImportedServerView, Translate } from './index.ts'\n\nexport interface McpRow {\n id: string\n serverName: string\n transport: 'stdio' | 'streamable-http'\n disabled: boolean\n command?: string\n args?: string[]\n env?: Record<string, string>\n cwd?: string\n url?: string\n headers?: Record<string, string>\n}\n\nexport interface McpInjected {\n list(): Promise<{ servers: McpRow[] }>\n save(input: Record<string, unknown>): Promise<{ ok: boolean; id: string }>\n toggle(id: string, disabled: boolean): Promise<{ ok: boolean }>\n remove(id: string): Promise<{ ok: boolean }>\n scanImport(): Promise<{ servers: ImportedServerView[]; existing: string[] }>\n applyImport(items: Array<{ agent: string; name: string }>): Promise<{ ok: boolean; results: Array<{ name: string; ok: boolean; error?: string }> }>\n restart(): Promise<void>\n desktop: boolean\n}\n\n/** Editor dialog state; null when closed. Textareas hold line-based values. */\ninterface EditorState {\n id: string\n serverName: string\n transport: 'stdio' | 'streamable-http'\n command: string\n args: string\n env: string\n url: string\n headers: string\n}\n\n/** One import entry together with its index in the flat state array \u2014\n * checkbox handlers rewrite the flat array, so indices must survive grouping. */\ntype ImportItem = { server: ImportedServerView; existing: boolean; checked: boolean }\n\n/** Group import candidates by source agent, known agents first. */\nexport function importGroups(items: ImportItem[]): Array<{ agent: string; label: string; items: Array<{ item: ImportItem; index: number }> }> {\n const label = (agent: string) => agent === 'claude-code' ? 'Claude Code' : agent === 'codex' ? 'Codex' : agent\n const order = ['claude-code', 'codex']\n const agents = [...new Set(items.map(item => item.server.agent))]\n .sort((a, b) => {\n const rank = (agent: string) => { const at = order.indexOf(agent); return at === -1 ? order.length : at }\n return rank(a) - rank(b) || a.localeCompare(b)\n })\n return agents.map(agent => ({\n agent,\n label: label(agent),\n items: items.map((item, index) => ({ item, index })).filter(({ item }) => item.server.agent === agent),\n }))\n}\n\n/** KEY=VALUE / KEY: VALUE lines to a map. */\nfunction parsePairs(text: string, separator: ':' | '='): Record<string, string> {\n const map: Record<string, string> = {}\n for (const line of text.split(/\\r?\\n/)) {\n const trimmed = line.trim()\n if (trimmed === '' || trimmed.startsWith('#')) continue\n const at = trimmed.indexOf(separator)\n if (at <= 0) continue\n map[trimmed.slice(0, at).trim()] = trimmed.slice(at + 1).trim()\n }\n return map\n}\n\nfunction mapToPairs(map: Record<string, string> | undefined, separator: string): string {\n if (map === undefined) return ''\n return Object.entries(map).map(([key, value]) => `${key}${separator}${value.includes('\\n') ? JSON.stringify(value) : value}`).join('\\n')\n}\n\nexport function McpTab(props: { t: Translate; injected: McpInjected }): ReactElement {\n const { t, injected } = props\n const [servers, setServers] = useState<McpRow[] | null>(null)\n const [editor, setEditor] = useState<EditorState | null>(null)\n const [confirmId, setConfirmId] = useState<string | null>(null)\n const [importOpen, setImportOpen] = useState(false)\n const [importItems, setImportItems] = useState<Array<{ server: ImportedServerView; existing: boolean; checked: boolean }> | null>(null)\n const [busy, setBusy] = useState(false)\n const [pending, setPending] = useState(false)\n const [restartConfirm, setRestartConfirm] = useState(false)\n const [restarting, setRestarting] = useState(false)\n const [outcome, setOutcome] = useState<{ ok: boolean; text: string } | null>(null)\n const [formError, setFormError] = useState<string | null>(null)\n const [reload, setReload] = useState(0)\n\n const openImport = async (): Promise<void> => {\n setImportOpen(true)\n setImportItems(null)\n try {\n const body = await injected.scanImport()\n const existing = new Set(body.existing)\n setImportItems(body.servers.map(server => ({\n server,\n existing: existing.has(server.name),\n checked: !existing.has(server.name),\n })))\n } catch (error) {\n setImportItems([])\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n }\n }\n\n const doImport = async (): Promise<void> => {\n if (importItems === null) return\n const items = importItems.filter(item => item.checked && !item.existing).map(item => ({ agent: item.server.agent, name: item.server.name }))\n setBusy(true)\n try {\n const body = await injected.applyImport(items)\n const failed = body.results.filter(item => !item.ok)\n setOutcome(failed.length === 0\n ? { ok: true, text: t('restartNeeded') }\n : { ok: false, text: `${t('failed')}: ${failed.map(item => `${item.name} (${item.error})`).join(', ')}` })\n setImportOpen(false)\n setPending(true)\n setReload((value) => value + 1)\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n }\n }\n\n const reloadList = (showPending: boolean): void => {\n setReload((value) => value + 1)\n if (showPending) setPending(true)\n }\n\n useEffect(() => {\n let current = true\n void injected.list().then(\n (body) => { if (current) setServers(body.servers) },\n (error: Error) => { if (current) { setServers([]); setOutcome({ ok: false, text: `${t('failed')}: ${String(error.message ?? error)}` }) } },\n )\n return () => { current = false }\n }, [injected, reload, t])\n\n const openCreate = (): void => {\n setFormError(null)\n setEditor({ id: '', serverName: '', transport: 'stdio', command: '', args: '', env: '', url: '', headers: '' })\n }\n\n const openEdit = (row: McpRow): void => {\n setFormError(null)\n setEditor({\n id: row.id,\n serverName: row.serverName,\n transport: row.transport,\n command: row.command ?? '',\n args: (row.args ?? []).join('\\n'),\n env: mapToPairs(row.env, '='),\n url: row.url ?? '',\n headers: mapToPairs(row.headers, ':'),\n })\n }\n\n const doSave = async (): Promise<void> => {\n if (editor === null) return\n setBusy(true)\n setFormError(null)\n try {\n await injected.save({\n id: editor.id,\n serverName: editor.serverName.trim(),\n transport: editor.transport,\n ...(editor.transport === 'stdio'\n ? {\n command: editor.command.trim(),\n args: editor.args.split(/\\r?\\n/).map(line => line.trim()).filter(line => line !== ''),\n env: parsePairs(editor.env, '='),\n }\n : {\n url: editor.url.trim(),\n headers: parsePairs(editor.headers, ':'),\n }),\n })\n setEditor(null)\n setOutcome({ ok: true, text: t('restartNeeded') })\n reloadList(true)\n } catch (error) {\n setFormError(String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const doToggle = async (row: McpRow): Promise<void> => {\n setBusy(true)\n try {\n await injected.toggle(row.id, !row.disabled)\n setOutcome({ ok: true, text: t('restartNeeded') })\n reloadList(true)\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n }\n }\n\n const doRemove = async (): Promise<void> => {\n if (confirmId === null) return\n setBusy(true)\n try {\n await injected.remove(confirmId)\n setOutcome({ ok: true, text: t('restartNeeded') })\n reloadList(true)\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n setConfirmId(null)\n }\n }\n\n const doRestart = (): void => {\n setRestartConfirm(false)\n setRestarting(true)\n void injected.restart()\n // \u684C\u9762\u6A21\u5F0F\uFF1A\u58F3\u5C42\u91CD\u542F\u5B8C\u6210\u540E\u4F1A\u91CD\u8F7D\u7A97\u53E3\u3002\u72EC\u7ACB\u6A21\u5F0F\uFF1A\u8F6E\u8BE2\u672C\u6E90\uFF0C\u6062\u590D\u5373\u5237\u65B0\u3002\n if (injected.desktop) return\n const deadline = Date.now() + 60_000\n const poll = (): void => {\n if (Date.now() > deadline) return\n window.setTimeout(() => {\n void injected.list().then(\n () => { window.location.reload() },\n () => { poll() },\n )\n }, 1500)\n }\n window.setTimeout(poll, 3000)\n }\n\n const restartBanner = (\n <div className=\"dpc-banner\" data-kind=\"info\" role=\"status\">\n <StateDot state=\"ongoing\" size={10} />\n <div className=\"dpc-bannerBody\">\n <span>{restarting ? t('restarting') : t('restartNeeded')}</span>\n <span className=\"dpc-bannerHint\">\n {restarting\n ? (!injected.desktop && t('restartPortHint'))\n : injected.desktop\n ? <>{t('restartDesktopHint')}{' '}<Button variant=\"outline\" size=\"sm\" onClick={() => setRestartConfirm(true)}>{t('restartNow')}</Button></>\n : t('restartOtherHint')}\n </span>\n </div>\n </div>\n )\n\n return (\n <div className=\"dpc-section\">\n <style>{CSS}</style>\n\n <div className=\"dpc-head\">\n <IconApiOutline14 aria-hidden=\"true\" />\n <h3>{t('mcpTitle')}</h3>\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" size=\"sm\" disabled={restarting} onClick={() => setRestartConfirm(true)}>{t('restart')}</Button>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => void openImport()}>{t('importServers')}</Button>\n <Button variant=\"primary\" size=\"sm\" onClick={openCreate}>{t('addServer')}</Button>\n </div>\n <p className=\"dpc-intro\">{t('mcpIntro')}</p>\n\n {outcome !== null && (\n <div className=\"dpc-banner\" data-kind={outcome.ok ? 'ok' : 'error'} role=\"status\">\n <StateDot state={outcome.ok ? 'done' : 'error'} size={10} />\n <div className=\"dpc-bannerBody\"><span>{outcome.text}</span></div>\n </div>\n )}\n {(pending || restarting) && restartBanner}\n\n <div className=\"dpc-listHead\">\n <h3>{t('mcpTab')}</h3>\n {servers !== null && <span className=\"dpc-count\">{servers.length}</span>}\n <span className=\"dpc-spacer\" />\n <button type=\"button\" className=\"dpc-refresh\" aria-label={t('view')} title={t('view')} disabled={busy} onClick={() => setReload((value) => value + 1)}>\n <IconRefreshOutline14 size={14} aria-hidden=\"true\" />\n </button>\n </div>\n\n {servers === null && <p className=\"dpc-empty\">{t('loading')}</p>}\n {servers !== null && servers.length === 0 && <p className=\"dpc-empty\">{t('emptyMcp')}</p>}\n {servers !== null && servers.length > 0 && (\n <ul className=\"dpc-cards\">\n {servers.map((row) => (\n <li className=\"dpc-card\" key={row.id}>\n <div className=\"dpc-cardTop\">\n <strong className=\"dpc-cardTitle\" title={row.id}>{row.serverName}</strong>\n <span className=\"dpc-tag\">{row.transport}</span>\n <span className=\"dpc-tag\" data-kind={row.disabled ? 'off' : undefined}>{row.disabled ? t('disabled') : t('enabled')}</span>\n </div>\n <p className=\"dpc-cardDesc\">\n {row.transport === 'stdio' ? `${row.command ?? ''} ${(row.args ?? []).join(' ')}` : row.url ?? ''}\n </p>\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => void doToggle(row)}>{t('toggle')}</Button>\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => openEdit(row)}>{t('edit')}</Button>\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => setConfirmId(row.id)}>{t('delete')}</Button>\n </div>\n </li>\n ))}\n </ul>\n )}\n\n <Modal\n open={editor !== null}\n onClose={() => setEditor(null)}\n title={editor !== null && editor.id !== '' ? t('editServer') : t('addServer')}\n className=\"dpc-modalForm\"\n contentClassName=\"dpc-modalScroll\"\n >\n {editor !== null && (\n <div className=\"dpc-form\">\n <label className=\"dpc-label\">\n <span>{t('serverName')}</span>\n <input\n className=\"dpc-input\"\n value={editor.serverName}\n disabled={editor.id !== ''}\n onChange={(event) => setEditor({ ...editor, serverName: event.target.value })}\n />\n </label>\n <label className=\"dpc-label\">\n <span>{t('transport')}</span>\n <select\n className=\"dpc-select\"\n value={editor.transport}\n disabled={editor.id !== ''}\n onChange={(event) => setEditor({ ...editor, transport: event.target.value as EditorState['transport'] })}\n >\n <option value=\"stdio\">{t('transportStdio')}</option>\n <option value=\"streamable-http\">{t('transportHttp')}</option>\n </select>\n </label>\n {editor.transport === 'stdio' ? (\n <>\n <label className=\"dpc-label\">\n <span>{t('command')}</span>\n <input className=\"dpc-input\" value={editor.command} onChange={(event) => setEditor({ ...editor, command: event.target.value })} />\n </label>\n <label className=\"dpc-label\">\n <span>{t('args')}</span>\n <textarea className=\"dpc-textarea\" data-short=\"true\" value={editor.args} onChange={(event) => setEditor({ ...editor, args: event.target.value })} />\n </label>\n <label className=\"dpc-label\">\n <span>{t('envPairs')}</span>\n <textarea className=\"dpc-textarea\" data-short=\"true\" value={editor.env} onChange={(event) => setEditor({ ...editor, env: event.target.value })} />\n </label>\n </>\n ) : (\n <>\n <label className=\"dpc-label\">\n <span>{t('url')}</span>\n <input className=\"dpc-input\" value={editor.url} onChange={(event) => setEditor({ ...editor, url: event.target.value })} />\n </label>\n <label className=\"dpc-label\">\n <span>{t('headersPairs')}</span>\n <textarea className=\"dpc-textarea\" data-short=\"true\" value={editor.headers} onChange={(event) => setEditor({ ...editor, headers: event.target.value })} />\n </label>\n </>\n )}\n {formError !== null && <p className=\"dpc-formError\">{formError}</p>}\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" onClick={() => setEditor(null)}>{t('cancel')}</Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => void doSave()}>{t('save')}</Button>\n </div>\n </div>\n )}\n </Modal>\n\n <Modal\n open={confirmId !== null}\n onClose={() => setConfirmId(null)}\n title={t('confirmRemove')}\n description={confirmId ?? undefined}\n footer={\n <>\n <Button variant=\"ghost\" onClick={() => setConfirmId(null)}>{t('cancel')}</Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => void doRemove()}>{t('delete')}</Button>\n </>\n }\n >\n <p>{t('removeWarn')}</p>\n </Modal>\n\n <Modal\n open={restartConfirm}\n onClose={() => setRestartConfirm(false)}\n title={t('restartConfirmTitle')}\n footer={\n <>\n <Button variant=\"ghost\" onClick={() => setRestartConfirm(false)}>{t('cancel')}</Button>\n <Button variant=\"primary\" onClick={doRestart}>{t('restartNow')}</Button>\n </>\n }\n >\n <p>{t('restartConfirmBody')}</p>\n </Modal>\n\n <Modal\n open={importOpen}\n onClose={() => setImportOpen(false)}\n title={t('importServers')}\n className=\"dpc-modalWide\"\n >\n <div className=\"dpc-form\">\n <p className=\"dpc-intro\" style={{ margin: 0 }}>{t('importIntro')}</p>\n {importItems === null && <p className=\"dpc-empty\">{t('loading')}</p>}\n {importItems !== null && importItems.length === 0 && <p className=\"dpc-empty\">{t('importEmpty')}</p>}\n {importItems !== null && importItems.length > 0 && (\n <div className=\"dpc-importScroll\">\n {importGroups(importItems).map(group => {\n const selectable = group.items\n .filter(({ item }) => !item.existing)\n .map(({ index }) => index)\n const allChecked = selectable.length > 0\n && selectable.every(index => importItems[index].checked)\n return (\n <section className=\"dpc-importGroup\" key={group.agent}>\n <div className=\"dpc-importHead\">\n <span className=\"dpc-tag\" data-kind=\"source\">{group.label}</span>\n <span className=\"dpc-importCount\">{group.items.length}</span>\n {selectable.length > 0 && (\n <label className=\"dpc-importAll\">\n <input\n type=\"checkbox\"\n checked={allChecked}\n onChange={(event) => {\n const next = importItems.slice()\n for (const index of selectable) next[index] = { ...next[index], checked: event.target.checked }\n setImportItems(next)\n }}\n />\n {t('importSelectAll')}\n </label>\n )}\n </div>\n <ul className=\"dpc-cards\" style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}>\n {group.items.map(({ item, index }) => {\n const command = item.server.transport === 'stdio'\n ? `${item.server.command ?? ''} ${(item.server.args ?? []).join(' ')}`.trim()\n : item.server.url ?? ''\n return (\n <li className=\"dpc-card\" key={`${item.server.agent}/${item.server.name}`} style={{ opacity: item.existing ? 0.55 : 1 }}>\n <div className=\"dpc-cardTop\">\n <label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0, cursor: item.existing ? 'default' : 'pointer' }}>\n <input\n type=\"checkbox\"\n checked={item.checked}\n disabled={item.existing}\n onChange={(event) => {\n const next = importItems.slice()\n next[index] = { ...item, checked: event.target.checked }\n setImportItems(next)\n }}\n />\n <strong className=\"dpc-cardTitle\" title={item.server.name}>{item.server.name}</strong>\n </label>\n <span className=\"dpc-tag\">{item.server.transport}</span>\n {item.existing && <span className=\"dpc-tag\">{t('importExisting')}</span>}\n </div>\n <p className=\"dpc-cardDesc\" title={command}>{command}</p>\n </li>\n )\n })}\n </ul>\n </section>\n )\n })}\n </div>\n )}\n {formError !== null && <p className=\"dpc-formError\">{formError}</p>}\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" onClick={() => setImportOpen(false)}>{t('cancel')}</Button>\n <Button\n variant=\"primary\"\n disabled={busy || importItems === null || !importItems.some(item => item.checked && !item.existing)}\n onClick={() => void doImport()}\n >\n {t('importSelected')}\n </Button>\n </div>\n </div>\n </Modal>\n </div>\n )\n}\n", "/** Settings \u2192 Plugins \u201C\u6280\u80FD/Skills\u201D tab: view the discovered catalog, edit the\n * user-owned root. Pure presentation-layer \u2014 data arrives through props. */\n\nimport { useEffect, useState } from 'react'\nimport type { ReactElement } from 'react'\nimport { Button, IconRefreshOutline14, IconSkillOutline16, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives'\nimport { CSS } from './css.ts'\nimport type { Translate } from './index.ts'\n\n/** One skill as the host reports it (plus the editable flag the route adds). */\nexport interface SkillRowView {\n name: string\n description: string\n whenToUse?: string\n invocation: { modelInvocable: boolean; userInvocable: boolean }\n source: string\n provider: string\n editable: boolean\n}\n\nexport interface SkillsInjected {\n list(): Promise<{ skills: SkillRowView[] }>\n get(name: string): Promise<{ content: string }>\n save(input: { name: string; description: string; whenToUse?: string; modelInvocable: boolean; userInvocable: boolean; content: string }): Promise<{ ok: boolean }>\n remove(name: string): Promise<{ ok: boolean }>\n}\n\n/** Editor dialog state; null when closed. */\ninterface EditorState {\n mode: 'create' | 'edit' | 'view'\n name: string\n description: string\n whenToUse: string\n modelInvocable: boolean\n userInvocable: boolean\n content: string\n}\n\nconst SOURCE_KEYS: Record<string, string> = {\n 'project-dsh': 'sourceProjectDsh',\n 'project-agents': 'sourceProjectAgents',\n 'user-dsh': 'sourceUserDsh',\n 'user-agents': 'sourceUserAgents',\n runtime: 'sourceRuntime',\n bundled: 'sourceBundled',\n custom: 'sourceCustom',\n}\n\nexport function SkillsTab(props: { t: Translate; injected: SkillsInjected }): ReactElement {\n const { t, injected } = props\n const [skills, setSkills] = useState<SkillRowView[] | null>(null)\n const [editor, setEditor] = useState<EditorState | null>(null)\n const [confirmName, setConfirmName] = useState<string | null>(null)\n const [busy, setBusy] = useState(false)\n const [outcome, setOutcome] = useState<{ ok: boolean; text: string } | null>(null)\n const [formError, setFormError] = useState<string | null>(null)\n const [reload, setReload] = useState(0)\n\n useEffect(() => {\n let current = true\n void injected.list().then(\n (body) => { if (current) setSkills(body.skills) },\n (error: Error) => { if (current) { setSkills([]); setOutcome({ ok: false, text: `${t('failed')}: ${String(error.message ?? error)}` }) } },\n )\n return () => { current = false }\n }, [injected, reload, t])\n\n const openCreate = (): void => {\n setFormError(null)\n setEditor({ mode: 'create', name: '', description: '', whenToUse: '', modelInvocable: true, userInvocable: true, content: '' })\n }\n\n const openExisting = async (skill: SkillRowView): Promise<void> => {\n setBusy(true)\n setFormError(null)\n try {\n const body = await injected.get(skill.name)\n setEditor({\n mode: skill.editable ? 'edit' : 'view',\n name: skill.name,\n description: skill.description,\n whenToUse: skill.whenToUse ?? '',\n modelInvocable: skill.invocation.modelInvocable,\n userInvocable: skill.invocation.userInvocable,\n content: body.content,\n })\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n }\n }\n\n /**\n * After a write/delete, the watched directory takes a moment to invalidate\n * the catalog \u2014 poll until the change is visible (bounded), then stop.\n */\n const refreshUntil = (predicate: (skills: SkillRowView[]) => boolean, timeoutMs = 10_000): void => {\n const deadline = Date.now() + timeoutMs\n const tick = (): void => {\n if (Date.now() > deadline) return\n setTimeout(() => {\n void injected.list().then(\n (body) => {\n setSkills(body.skills)\n if (!predicate(body.skills)) tick()\n },\n () => tick(),\n )\n }, 1000)\n }\n tick()\n }\n\n const doSave = async (): Promise<void> => {\n if (editor === null) return\n setBusy(true)\n setFormError(null)\n try {\n const name = editor.name.trim()\n await injected.save({\n name,\n description: editor.description,\n whenToUse: editor.whenToUse.trim() === '' ? undefined : editor.whenToUse,\n modelInvocable: editor.modelInvocable,\n userInvocable: editor.userInvocable,\n content: editor.content,\n })\n setOutcome({ ok: true, text: t('saved') })\n setEditor(null)\n refreshUntil(skills => skills.some(skill => skill.name === name))\n } catch (error) {\n setFormError(String(error instanceof Error ? error.message : error))\n } finally {\n setBusy(false)\n }\n }\n\n const doDelete = async (): Promise<void> => {\n if (confirmName === null) return\n const name = confirmName\n setBusy(true)\n try {\n await injected.remove(name)\n setOutcome({ ok: true, text: t('saved') })\n refreshUntil(skills => !skills.some(skill => skill.name === name))\n } catch (error) {\n setOutcome({ ok: false, text: `${t('failed')}: ${String(error instanceof Error ? error.message : error)}` })\n } finally {\n setBusy(false)\n setConfirmName(null)\n }\n }\n\n const readOnly = editor?.mode === 'view'\n\n return (\n <div className=\"dpc-section\">\n <style>{CSS}</style>\n\n <div className=\"dpc-head\">\n <IconSkillOutline16 aria-hidden=\"true\" />\n <h3>{t('skillsTitle')}</h3>\n <span className=\"dpc-spacer\" />\n <Button variant=\"primary\" size=\"sm\" onClick={openCreate}>{t('newSkill')}</Button>\n </div>\n <p className=\"dpc-intro\">{t('skillsIntro')}</p>\n\n {outcome !== null && (\n <div className=\"dpc-banner\" data-kind={outcome.ok ? 'ok' : 'error'} role=\"status\">\n <StateDot state={outcome.ok ? 'done' : 'error'} size={10} />\n <div className=\"dpc-bannerBody\"><span>{outcome.text}</span></div>\n </div>\n )}\n\n <div className=\"dpc-listHead\">\n <h3>{t('skillsTab')}</h3>\n {skills !== null && <span className=\"dpc-count\">{skills.length}</span>}\n <span className=\"dpc-spacer\" />\n <button type=\"button\" className=\"dpc-refresh\" aria-label={t('view')} title={t('view')} disabled={busy} onClick={() => setReload((value) => value + 1)}>\n <IconRefreshOutline14 size={14} aria-hidden=\"true\" />\n </button>\n </div>\n\n {skills === null && <p className=\"dpc-empty\">{t('loading')}</p>}\n {skills !== null && skills.length === 0 && <p className=\"dpc-empty\">{t('emptySkills')}</p>}\n {skills !== null && skills.length > 0 && (\n <ul className=\"dpc-cards\">\n {skills.map((skill) => (\n <li className=\"dpc-card\" key={`${skill.source}/${skill.name}`}>\n <div className=\"dpc-cardTop\">\n <strong className=\"dpc-cardTitle\" title={skill.name}>{skill.name}</strong>\n <span className=\"dpc-tag\" data-kind=\"source\">{t(SOURCE_KEYS[skill.source] ?? 'sourceCustom')}</span>\n </div>\n <p className=\"dpc-cardDesc\" title={skill.description}>{skill.description}</p>\n <div className=\"dpc-cardRow\">\n {!skill.invocation.modelInvocable && <span className=\"dpc-tag\">\u2699</span>}\n {!skill.invocation.userInvocable && <span className=\"dpc-tag\">/</span>}\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => void openExisting(skill)}>\n {skill.editable ? t('edit') : t('view')}\n </Button>\n {skill.editable && (\n <Button variant=\"ghost\" size=\"sm\" disabled={busy} onClick={() => setConfirmName(skill.name)}>{t('delete')}</Button>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n\n <Modal\n open={editor !== null}\n onClose={() => setEditor(null)}\n title={editor === null ? '' : editor.mode === 'create' ? t('newSkill') : editor.mode === 'edit' ? t('editSkill') : t('viewSkill')}\n className=\"dpc-modalForm\"\n contentClassName=\"dpc-modalScroll\"\n >\n {editor !== null && (\n <div className=\"dpc-form\">\n <label className=\"dpc-label\">\n <span>{t('skillName')}</span>\n <input\n className=\"dpc-input\"\n value={editor.name}\n disabled={readOnly || editor.mode === 'edit'}\n onChange={(event) => setEditor({ ...editor, name: event.target.value })}\n />\n </label>\n <label className=\"dpc-label\">\n <span>{t('skillDescription')}</span>\n <input\n className=\"dpc-input\"\n value={editor.description}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, description: event.target.value })}\n />\n </label>\n <label className=\"dpc-label\">\n <span>{t('skillWhenToUse')}</span>\n <input\n className=\"dpc-input\"\n value={editor.whenToUse}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, whenToUse: event.target.value })}\n />\n </label>\n <div className=\"dpc-checks\">\n <label>\n <input\n type=\"checkbox\"\n checked={editor.modelInvocable}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, modelInvocable: event.target.checked })}\n />\n {t('modelInvocable')}\n </label>\n <label>\n <input\n type=\"checkbox\"\n checked={editor.userInvocable}\n disabled={readOnly}\n onChange={(event) => setEditor({ ...editor, userInvocable: event.target.checked })}\n />\n {t('userInvocable')}\n </label>\n </div>\n <label className=\"dpc-label\">\n <span>{t('skillContent')}</span>\n <textarea\n className=\"dpc-textarea\"\n value={editor.content}\n readOnly={readOnly}\n onChange={(event) => setEditor({ ...editor, content: event.target.value })}\n />\n </label>\n {formError !== null && <p className=\"dpc-formError\">{formError}</p>}\n <div className=\"dpc-cardRow\">\n <span className=\"dpc-spacer\" />\n <Button variant=\"ghost\" onClick={() => setEditor(null)}>{readOnly ? t('close') : t('cancel')}</Button>\n {!readOnly && <Button variant=\"primary\" disabled={busy} onClick={() => void doSave()}>{t('save')}</Button>}\n </div>\n </div>\n )}\n </Modal>\n\n <Modal\n open={confirmName !== null}\n onClose={() => setConfirmName(null)}\n title={t('confirmDelete')}\n description={confirmName ?? undefined}\n footer={\n <>\n <Button variant=\"ghost\" onClick={() => setConfirmName(null)}>{t('cancel')}</Button>\n <Button variant=\"primary\" disabled={busy} onClick={() => void doDelete()}>{t('delete')}</Button>\n </>\n }\n >\n <p>{t('deleteWarn')}</p>\n </Modal>\n </div>\n )\n}\n", "/** zh/en dictionaries for the Settings capabilities tabs. */\n\nexport const zh = {\n sectionNav: '\u6280\u80FD\u4E0E MCP',\n skillsTab: '\u6280\u80FD',\n mcpTab: 'MCP',\n skillsTitle: '\u6280\u80FD\u7BA1\u7406',\n skillsIntro: '\u67E5\u770B\u4E0E\u7F16\u8F91 dsh \u53D1\u73B0\u7684\u6280\u80FD\uFF1B\u7528\u6237\u7EA7\u6280\u80FD\uFF08DSH_HOME/skills\uFF09\u53EF\u5728\u6B64\u65B0\u5EFA\u3001\u4FEE\u6539\u3001\u5220\u9664\uFF0C\u6587\u4EF6\u88AB\u76D1\u542C\uFF0C\u4FDD\u5B58\u5373\u751F\u6548\u3002\u82E5\u5B58\u5728 ~/.claude/skills \u6216 ~/.codex/skills\uFF0C\u4F1A\u81EA\u52A8\u7EB3\u5165\u626B\u63CF\uFF08\u96F6\u62F7\u8D1D\u3001\u5B9E\u65F6\u540C\u6B65\uFF09\u3002',\n newSkill: '\u65B0\u5EFA\u6280\u80FD',\n editSkill: '\u7F16\u8F91\u6280\u80FD',\n viewSkill: '\u67E5\u770B\u6280\u80FD',\n skillName: '\u540D\u79F0\uFF08kebab-case\uFF09',\n skillDescription: '\u63CF\u8FF0',\n skillWhenToUse: '\u4F7F\u7528\u65F6\u673A\uFF08\u53EF\u9009\uFF09',\n skillContent: '\u6B63\u6587\uFF08Markdown\uFF09',\n modelInvocable: '\u5141\u8BB8\u6A21\u578B\u8C03\u7528',\n userInvocable: '\u5141\u8BB8\u7528\u6237 / \u8C03\u7528',\n save: '\u4FDD\u5B58',\n cancel: '\u53D6\u6D88',\n delete: '\u5220\u9664',\n edit: '\u7F16\u8F91',\n view: '\u67E5\u770B',\n close: '\u5173\u95ED',\n emptySkills: '\u8FD8\u6CA1\u6709\u53D1\u73B0\u4EFB\u4F55\u6280\u80FD',\n loading: '\u8F7D\u5165\u4E2D\u2026',\n source: '\u6765\u6E90',\n sourceProjectDsh: '\u9879\u76EE',\n sourceProjectAgents: '\u9879\u76EE',\n sourceUserDsh: '\u7528\u6237',\n sourceUserAgents: '\u7528\u6237',\n sourceRuntime: '\u8FD0\u884C\u65F6',\n sourceBundled: '\u5185\u7F6E',\n sourceCustom: '\u81EA\u5B9A\u4E49',\n readOnly: '\u53EA\u8BFB',\n confirmDelete: '\u786E\u8BA4\u5220\u9664\u6280\u80FD\uFF1F',\n deleteWarn: '\u5C06\u5220\u9664 DSH_HOME/skills \u4E0B\u7684\u6574\u4E2A\u6280\u80FD\u76EE\u5F55\uFF0C\u6B64\u64CD\u4F5C\u4E0D\u53EF\u64A4\u9500\u3002',\n saved: '\u5DF2\u4FDD\u5B58\uFF0C\u6280\u80FD\u76EE\u5F55\u88AB\u76D1\u542C\uFF0C\u7A0D\u5019\u5373\u53EF\u5728\u4F1A\u8BDD\u4E2D\u4F7F\u7528\u3002',\n mcpTitle: 'MCP \u670D\u52A1\u5668',\n 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',\n addServer: '\u6DFB\u52A0\u670D\u52A1\u5668',\n editServer: '\u7F16\u8F91\u670D\u52A1\u5668',\n serverName: '\u670D\u52A1\u5668\u540D\uFF08\u5DE5\u5177\u540D\u524D\u7F00\uFF09',\n transport: '\u4F20\u8F93\u65B9\u5F0F',\n transportStdio: 'stdio\uFF08\u672C\u5730\u547D\u4EE4\uFF09',\n transportHttp: 'streamable-http\uFF08URL\uFF09',\n command: '\u547D\u4EE4',\n args: '\u53C2\u6570\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF09',\n envPairs: '\u73AF\u5883\u53D8\u91CF\uFF08\u6BCF\u884C KEY=VALUE\uFF09',\n url: 'URL',\n headersPairs: '\u8BF7\u6C42\u5934\uFF08\u6BCF\u884C KEY: VALUE\uFF09',\n disabled: '\u5DF2\u505C\u7528',\n enabled: '\u542F\u7528\u4E2D',\n toggle: '\u505C\u7528/\u542F\u7528',\n confirmRemove: '\u786E\u8BA4\u79FB\u9664\u8BE5\u670D\u52A1\u5668\uFF1F',\n 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',\n emptyMcp: '\u8FD8\u6CA1\u6709\u914D\u7F6E MCP \u670D\u52A1\u5668',\n importServers: '\u4ECE\u5176\u4ED6 Agent \u5BFC\u5165',\n 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',\n importEmpty: '\u6CA1\u6709\u53D1\u73B0\u53EF\u5BFC\u5165\u7684 MCP \u670D\u52A1\u5668',\n importSelectAll: '\u5168\u9009',\n importExisting: '\u5DF2\u5B58\u5728',\n importSelected: '\u5BFC\u5165\u9009\u4E2D\u9879',\n restartNeeded: '\u914D\u7F6E\u5DF2\u5199\u5165\uFF0C\u91CD\u542F dsh \u540E\u751F\u6548\u3002',\n restartDesktopHint: '\u91CD\u542F\u7531\u684C\u9762\u5E94\u7528\u8D1F\u8D23\uFF1A\u6258\u76D8\u83DC\u5355\u300C\u91CD\u542F\u670D\u52A1\u300D\u3002',\n restartOtherHint: '\u91CD\u542F\u65B9\u5F0F\uFF1A\u5173\u95ED\u5F53\u524D dsh \u8FDB\u7A0B\u540E\u91CD\u65B0\u8FD0\u884C\u3002',\n restartNow: '\u91CD\u542F\u670D\u52A1',\n restart: '\u91CD\u542F',\n restartConfirmTitle: '\u91CD\u542F dsh\uFF1F',\n restartConfirmBody: '\u91CD\u542F\u4F1A\u4E2D\u65AD\u6B63\u5728\u8FDB\u884C\u7684\u56DE\u5408\uFF0C\u672A\u4FDD\u5B58\u7684\u8F93\u5165\u53EF\u80FD\u4E22\u5931\uFF1BMCP \u884C\u7684\u53D8\u66F4\u5C06\u5728\u91CD\u542F\u540E\u751F\u6548\u3002',\n restarting: '\u6B63\u5728\u91CD\u542F\uFF0C\u6062\u590D\u540E\u5C06\u81EA\u52A8\u5237\u65B0\u9875\u9762\u2026',\n restartPortHint: '\u82E5\u91CD\u542F\u540E\u9875\u9762\u957F\u65F6\u95F4\u672A\u6062\u590D\uFF0C\u53EF\u80FD\u662F\u7AEF\u53E3\u5DF2\u53D8\u5316\uFF1A\u5728\u7EC8\u7AEF\u67E5\u770B\u65B0\u5730\u5740\u540E\u6253\u5F00\u3002',\n failed: '\u64CD\u4F5C\u5931\u8D25',\n}\n\nexport const en = {\n sectionNav: 'Skills & MCP',\n skillsTab: 'Skills',\n mcpTab: 'MCP',\n skillsTitle: 'Skills',\n skillsIntro: 'View and edit the skills dsh discovers; user-level skills (DSH_HOME/skills) can be created, edited, and deleted here \u2014 the directory is watched, saves apply without a restart. ~/.claude/skills and ~/.codex/skills are scanned too when present (zero-copy, live-synced).',\n newSkill: 'New skill',\n editSkill: 'Edit skill',\n viewSkill: 'View skill',\n skillName: 'Name (kebab-case)',\n skillDescription: 'Description',\n skillWhenToUse: 'When to use (optional)',\n skillContent: 'Body (Markdown)',\n modelInvocable: 'Model-invocable',\n userInvocable: 'User-invocable (/)',\n save: 'Save',\n cancel: 'Cancel',\n delete: 'Delete',\n edit: 'Edit',\n view: 'View',\n close: 'Close',\n emptySkills: 'No skills discovered yet',\n loading: 'Loading\u2026',\n source: 'Source',\n sourceProjectDsh: 'project',\n sourceProjectAgents: 'project',\n sourceUserDsh: 'user',\n sourceUserAgents: 'user',\n sourceRuntime: 'runtime',\n sourceBundled: 'bundled',\n sourceCustom: 'custom',\n readOnly: 'read-only',\n confirmDelete: 'Delete this skill?',\n deleteWarn: 'Removes the whole skill directory under DSH_HOME/skills. This cannot be undone.',\n saved: 'Saved. The skills directory is watched; the skill is usable in sessions shortly.',\n mcpTitle: 'MCP servers',\n mcpIntro: 'Manage the MCP server rows (@deepseek-ai/dsh-mcp-client) in this profile. Additions, edits, and removals take effect after a dsh restart.',\n addServer: 'Add server',\n editServer: 'Edit server',\n serverName: 'Server name (tool name prefix)',\n transport: 'Transport',\n transportStdio: 'stdio (local command)',\n transportHttp: 'streamable-http (URL)',\n command: 'Command',\n args: 'Arguments (one per line)',\n envPairs: 'Environment (KEY=VALUE per line)',\n url: 'URL',\n headersPairs: 'Headers (KEY: VALUE per line)',\n disabled: 'Disabled',\n enabled: 'Enabled',\n toggle: 'Enable/disable',\n confirmRemove: 'Remove this server?',\n removeWarn: 'Removes the row from the profile configuration; its tools disappear after the next dsh restart.',\n emptyMcp: 'No MCP servers configured yet',\n importServers: 'Import from other agents',\n importIntro: 'Scans Claude Code (~/.claude.json) and Codex (~/.codex/config.toml) MCP server configs; selected entries become server rows in this profile.',\n importEmpty: 'No importable MCP servers found',\n importSelectAll: 'Select all',\n importExisting: 'already here',\n importSelected: 'Import selected',\n restartNeeded: 'Configuration written \u2014 restart dsh to apply.',\n restartDesktopHint: 'The desktop app owns restarts: use the tray \u201CRestart service\u201D.',\n restartOtherHint: 'Restart by closing this dsh process and running it again.',\n restartNow: 'Restart service',\n restart: 'Restart',\n restartConfirmTitle: 'Restart dsh?',\n restartConfirmBody: 'Restarting interrupts any running turn and may lose unsaved input; pending MCP changes apply after the restart.',\n restarting: 'Restarting \u2014 the page will reload once the host is back\u2026',\n restartPortHint: 'If the page does not recover, the port may have changed: check the terminal for the new URL.',\n failed: 'Operation failed',\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_react", "import_react", "import_react", "import_dsh_client_ui_primitives", "import_jsx_runtime", "name", "skills", "import_jsx_runtime", "name", "h"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** Settings top-level “技能与 MCP” section: one nav entry beside 通用设置 and
|
|
2
|
+
* 模型, with the Skills and MCP pages as internal tabs. The two pages are the
|
|
3
|
+
* same components the Plugins section used to host — promoted a level instead
|
|
4
|
+
* of squeezed under 插件. Pure composition; data still arrives through the
|
|
5
|
+
* injected faces. */
|
|
6
|
+
|
|
7
|
+
import { useEffect, useId, useRef, useState } from 'react'
|
|
8
|
+
import type { ReactElement } from 'react'
|
|
9
|
+
import { CSS } from './css.ts'
|
|
10
|
+
import { McpTab } from './McpTab.tsx'
|
|
11
|
+
import type { McpInjected } from './McpTab.tsx'
|
|
12
|
+
import { SkillsTab } from './SkillsTab.tsx'
|
|
13
|
+
import type { SkillsInjected } from './SkillsTab.tsx'
|
|
14
|
+
import type { Translate } from './index.ts'
|
|
15
|
+
|
|
16
|
+
export function CapabilitiesSection(props: {
|
|
17
|
+
t: Translate
|
|
18
|
+
skills: SkillsInjected
|
|
19
|
+
mcp: McpInjected
|
|
20
|
+
}): ReactElement {
|
|
21
|
+
const { t, skills, mcp } = props
|
|
22
|
+
const tabsId = useId()
|
|
23
|
+
const tabRefs = useRef<Array<HTMLButtonElement | null>>([])
|
|
24
|
+
const rows = [
|
|
25
|
+
{ id: 'skills', label: t('skillsTab') },
|
|
26
|
+
{ id: 'mcp', label: t('mcpTab') },
|
|
27
|
+
]
|
|
28
|
+
const [activeId, setActiveId] = useState('skills')
|
|
29
|
+
// The skills page mounts immediately; a tab mounts only when first
|
|
30
|
+
// selected, then stays mounted while hidden so editor drafts and outcome
|
|
31
|
+
// banners survive switching between the two views.
|
|
32
|
+
const [visitedIds, setVisitedIds] = useState<ReadonlySet<string>>(() => new Set(['skills']))
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
setVisitedIds((previous) => {
|
|
36
|
+
if (previous.has(activeId)) return previous
|
|
37
|
+
return new Set([...previous, activeId])
|
|
38
|
+
})
|
|
39
|
+
}, [activeId])
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<div className="dpc-section">
|
|
43
|
+
<style>{CSS}</style>
|
|
44
|
+
|
|
45
|
+
<h2 className="dpc-heading">{t('sectionNav')}</h2>
|
|
46
|
+
<div className="dpc-tabs" role="tablist" aria-label={t('sectionNav')}>
|
|
47
|
+
{rows.map((row, index) => {
|
|
48
|
+
const selected = row.id === activeId
|
|
49
|
+
return (
|
|
50
|
+
<button
|
|
51
|
+
key={row.id}
|
|
52
|
+
ref={(element) => { tabRefs.current[index] = element }}
|
|
53
|
+
id={`${tabsId}-tab-${row.id}`}
|
|
54
|
+
type="button"
|
|
55
|
+
role="tab"
|
|
56
|
+
className="dpc-tab"
|
|
57
|
+
aria-selected={selected}
|
|
58
|
+
aria-controls={`${tabsId}-panel-${row.id}`}
|
|
59
|
+
data-active={selected ? 'true' : undefined}
|
|
60
|
+
tabIndex={selected ? 0 : -1}
|
|
61
|
+
onClick={() => { setActiveId(row.id) }}
|
|
62
|
+
onKeyDown={(event) => {
|
|
63
|
+
let nextIndex: number
|
|
64
|
+
switch (event.key) {
|
|
65
|
+
case 'ArrowRight': nextIndex = (index + 1) % rows.length; break
|
|
66
|
+
case 'ArrowLeft': nextIndex = (index - 1 + rows.length) % rows.length; break
|
|
67
|
+
case 'Home': nextIndex = 0; break
|
|
68
|
+
case 'End': nextIndex = rows.length - 1; break
|
|
69
|
+
default: return
|
|
70
|
+
}
|
|
71
|
+
event.preventDefault()
|
|
72
|
+
setActiveId(rows[nextIndex]?.id ?? 'skills')
|
|
73
|
+
tabRefs.current[nextIndex]?.focus()
|
|
74
|
+
}}
|
|
75
|
+
>
|
|
76
|
+
{row.label}
|
|
77
|
+
</button>
|
|
78
|
+
)
|
|
79
|
+
})}
|
|
80
|
+
</div>
|
|
81
|
+
{rows
|
|
82
|
+
.filter(row => row.id === activeId || visitedIds.has(row.id))
|
|
83
|
+
.map((row) => {
|
|
84
|
+
const selected = row.id === activeId
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
key={row.id}
|
|
88
|
+
id={`${tabsId}-panel-${row.id}`}
|
|
89
|
+
className="dpc-tabPanel"
|
|
90
|
+
role="tabpanel"
|
|
91
|
+
aria-labelledby={`${tabsId}-tab-${row.id}`}
|
|
92
|
+
hidden={!selected}
|
|
93
|
+
>
|
|
94
|
+
{row.id === 'skills'
|
|
95
|
+
? <SkillsTab t={t} injected={skills} />
|
|
96
|
+
: <McpTab t={t} injected={mcp} />}
|
|
97
|
+
</div>
|
|
98
|
+
)
|
|
99
|
+
})}
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
package/src/client/McpTab.tsx
CHANGED
|
@@ -44,6 +44,26 @@ interface EditorState {
|
|
|
44
44
|
headers: string
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/** One import entry together with its index in the flat state array —
|
|
48
|
+
* checkbox handlers rewrite the flat array, so indices must survive grouping. */
|
|
49
|
+
type ImportItem = { server: ImportedServerView; existing: boolean; checked: boolean }
|
|
50
|
+
|
|
51
|
+
/** Group import candidates by source agent, known agents first. */
|
|
52
|
+
export function importGroups(items: ImportItem[]): Array<{ agent: string; label: string; items: Array<{ item: ImportItem; index: number }> }> {
|
|
53
|
+
const label = (agent: string) => agent === 'claude-code' ? 'Claude Code' : agent === 'codex' ? 'Codex' : agent
|
|
54
|
+
const order = ['claude-code', 'codex']
|
|
55
|
+
const agents = [...new Set(items.map(item => item.server.agent))]
|
|
56
|
+
.sort((a, b) => {
|
|
57
|
+
const rank = (agent: string) => { const at = order.indexOf(agent); return at === -1 ? order.length : at }
|
|
58
|
+
return rank(a) - rank(b) || a.localeCompare(b)
|
|
59
|
+
})
|
|
60
|
+
return agents.map(agent => ({
|
|
61
|
+
agent,
|
|
62
|
+
label: label(agent),
|
|
63
|
+
items: items.map((item, index) => ({ item, index })).filter(({ item }) => item.server.agent === agent),
|
|
64
|
+
}))
|
|
65
|
+
}
|
|
66
|
+
|
|
47
67
|
/** KEY=VALUE / KEY: VALUE lines to a map. */
|
|
48
68
|
function parsePairs(text: string, separator: ':' | '='): Record<string, string> {
|
|
49
69
|
const map: Record<string, string> = {}
|
|
@@ -300,6 +320,8 @@ export function McpTab(props: { t: Translate; injected: McpInjected }): ReactEle
|
|
|
300
320
|
open={editor !== null}
|
|
301
321
|
onClose={() => setEditor(null)}
|
|
302
322
|
title={editor !== null && editor.id !== '' ? t('editServer') : t('addServer')}
|
|
323
|
+
className="dpc-modalForm"
|
|
324
|
+
contentClassName="dpc-modalScroll"
|
|
303
325
|
>
|
|
304
326
|
{editor !== null && (
|
|
305
327
|
<div className="dpc-form">
|
|
@@ -394,41 +416,73 @@ export function McpTab(props: { t: Translate; injected: McpInjected }): ReactEle
|
|
|
394
416
|
open={importOpen}
|
|
395
417
|
onClose={() => setImportOpen(false)}
|
|
396
418
|
title={t('importServers')}
|
|
419
|
+
className="dpc-modalWide"
|
|
397
420
|
>
|
|
398
421
|
<div className="dpc-form">
|
|
399
422
|
<p className="dpc-intro" style={{ margin: 0 }}>{t('importIntro')}</p>
|
|
400
423
|
{importItems === null && <p className="dpc-empty">{t('loading')}</p>}
|
|
401
424
|
{importItems !== null && importItems.length === 0 && <p className="dpc-empty">{t('importEmpty')}</p>}
|
|
402
425
|
{importItems !== null && importItems.length > 0 && (
|
|
403
|
-
<
|
|
404
|
-
{importItems.map(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
426
|
+
<div className="dpc-importScroll">
|
|
427
|
+
{importGroups(importItems).map(group => {
|
|
428
|
+
const selectable = group.items
|
|
429
|
+
.filter(({ item }) => !item.existing)
|
|
430
|
+
.map(({ index }) => index)
|
|
431
|
+
const allChecked = selectable.length > 0
|
|
432
|
+
&& selectable.every(index => importItems[index].checked)
|
|
433
|
+
return (
|
|
434
|
+
<section className="dpc-importGroup" key={group.agent}>
|
|
435
|
+
<div className="dpc-importHead">
|
|
436
|
+
<span className="dpc-tag" data-kind="source">{group.label}</span>
|
|
437
|
+
<span className="dpc-importCount">{group.items.length}</span>
|
|
438
|
+
{selectable.length > 0 && (
|
|
439
|
+
<label className="dpc-importAll">
|
|
440
|
+
<input
|
|
441
|
+
type="checkbox"
|
|
442
|
+
checked={allChecked}
|
|
443
|
+
onChange={(event) => {
|
|
444
|
+
const next = importItems.slice()
|
|
445
|
+
for (const index of selectable) next[index] = { ...next[index], checked: event.target.checked }
|
|
446
|
+
setImportItems(next)
|
|
447
|
+
}}
|
|
448
|
+
/>
|
|
449
|
+
{t('importSelectAll')}
|
|
450
|
+
</label>
|
|
451
|
+
)}
|
|
452
|
+
</div>
|
|
453
|
+
<ul className="dpc-cards" style={{ gridTemplateColumns: 'minmax(0, 1fr)' }}>
|
|
454
|
+
{group.items.map(({ item, index }) => {
|
|
455
|
+
const command = item.server.transport === 'stdio'
|
|
456
|
+
? `${item.server.command ?? ''} ${(item.server.args ?? []).join(' ')}`.trim()
|
|
457
|
+
: item.server.url ?? ''
|
|
458
|
+
return (
|
|
459
|
+
<li className="dpc-card" key={`${item.server.agent}/${item.server.name}`} style={{ opacity: item.existing ? 0.55 : 1 }}>
|
|
460
|
+
<div className="dpc-cardTop">
|
|
461
|
+
<label style={{ display: 'inline-flex', alignItems: 'center', gap: 8, minWidth: 0, cursor: item.existing ? 'default' : 'pointer' }}>
|
|
462
|
+
<input
|
|
463
|
+
type="checkbox"
|
|
464
|
+
checked={item.checked}
|
|
465
|
+
disabled={item.existing}
|
|
466
|
+
onChange={(event) => {
|
|
467
|
+
const next = importItems.slice()
|
|
468
|
+
next[index] = { ...item, checked: event.target.checked }
|
|
469
|
+
setImportItems(next)
|
|
470
|
+
}}
|
|
471
|
+
/>
|
|
472
|
+
<strong className="dpc-cardTitle" title={item.server.name}>{item.server.name}</strong>
|
|
473
|
+
</label>
|
|
474
|
+
<span className="dpc-tag">{item.server.transport}</span>
|
|
475
|
+
{item.existing && <span className="dpc-tag">{t('importExisting')}</span>}
|
|
476
|
+
</div>
|
|
477
|
+
<p className="dpc-cardDesc" title={command}>{command}</p>
|
|
478
|
+
</li>
|
|
479
|
+
)
|
|
480
|
+
})}
|
|
481
|
+
</ul>
|
|
482
|
+
</section>
|
|
483
|
+
)
|
|
484
|
+
})}
|
|
485
|
+
</div>
|
|
432
486
|
)}
|
|
433
487
|
{formError !== null && <p className="dpc-formError">{formError}</p>}
|
|
434
488
|
<div className="dpc-cardRow">
|
package/src/client/SkillsTab.tsx
CHANGED
|
@@ -213,6 +213,8 @@ export function SkillsTab(props: { t: Translate; injected: SkillsInjected }): Re
|
|
|
213
213
|
open={editor !== null}
|
|
214
214
|
onClose={() => setEditor(null)}
|
|
215
215
|
title={editor === null ? '' : editor.mode === 'create' ? t('newSkill') : editor.mode === 'edit' ? t('editSkill') : t('viewSkill')}
|
|
216
|
+
className="dpc-modalForm"
|
|
217
|
+
contentClassName="dpc-modalScroll"
|
|
216
218
|
>
|
|
217
219
|
{editor !== null && (
|
|
218
220
|
<div className="dpc-form">
|
package/src/client/css.ts
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
export const CSS = `
|
|
6
6
|
.dpc-section{display:flex;flex-direction:column;gap:14px;width:100%;max-width:760px;color:var(--dsw-alias-label-primary)}
|
|
7
|
+
.dpc-heading{margin:0;font-size:18px;line-height:26px;font-weight:600}
|
|
8
|
+
/* Top-level section's internal tabs — same underline-tab look as the host's
|
|
9
|
+
Plugins section so the promoted placement still reads as one family. */
|
|
10
|
+
.dpc-tabs{display:flex;align-items:flex-end;gap:22px;border-bottom:1px solid var(--dsw-alias-border-l2);margin-top:2px}
|
|
11
|
+
.dpc-tab{position:relative;border:0;padding:7px 1px 9px;background:transparent;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:13px;line-height:20px;cursor:pointer}
|
|
12
|
+
.dpc-tab:hover,.dpc-tab[data-active='true']{color:var(--dsw-alias-label-primary)}
|
|
13
|
+
.dpc-tab[data-active='true']::after,.dpc-tab:focus-visible::after{position:absolute;right:0;bottom:-1px;left:0;height:2px;border-radius:2px 2px 0 0;background:var(--dsw-alias-label-primary);content:''}
|
|
14
|
+
.dpc-tab:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px;border-radius:2px;color:var(--dsw-alias-label-primary)}
|
|
15
|
+
.dpc-tabPanel{min-width:0;padding-top:2px}
|
|
7
16
|
.dpc-head{display:flex;align-items:center;gap:8px}
|
|
8
17
|
.dpc-head h3{margin:0;font-size:13px;line-height:20px;font-weight:600}
|
|
9
18
|
.dpc-head>svg{flex:none;color:var(--dsw-alias-label-tertiary)}
|
|
@@ -37,11 +46,25 @@ export const CSS = `
|
|
|
37
46
|
.dpc-label{display:flex;flex-direction:column;gap:4px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}
|
|
38
47
|
.dpc-label>span:first-child{color:var(--dsw-alias-label-tertiary)}
|
|
39
48
|
.dpc-input,.dpc-textarea,.dpc-select{width:100%;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 10px;outline:none;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;font-size:13px}
|
|
40
|
-
.dpc-textarea{min-height:
|
|
41
|
-
.dpc-textarea[data-short='true']{min-height:
|
|
49
|
+
.dpc-textarea{min-height:240px;resize:vertical;font-family:var(--ds-font-family-code);line-height:1.5}
|
|
50
|
+
.dpc-textarea[data-short='true']{min-height:96px}
|
|
42
51
|
.dpc-input:focus-visible,.dpc-textarea:focus-visible,.dpc-select:focus-visible{border-color:var(--dsw-alias-state-business-primary);box-shadow:0 0 0 2px color-mix(in srgb,var(--dsw-alias-state-business-primary) 18%,transparent)}
|
|
43
52
|
.dpc-checks{display:flex;gap:16px;font-size:13px;line-height:20px}
|
|
44
53
|
.dpc-checks label{display:inline-flex;align-items:center;gap:6px;cursor:pointer}
|
|
45
54
|
.dpc-formError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}
|
|
55
|
+
/* Import dialog: wider than the host's 380px default; the server list gets
|
|
56
|
+
its own scroll so title, intro, and footer stay pinned. The doubled class
|
|
57
|
+
beats the host dialog's module CSS regardless of injection order. */
|
|
58
|
+
.dpc-modalWide.dpc-modalWide{width:min(680px,100%)}
|
|
59
|
+
/* Editor dialogs (new skill / server): 640px wide so markdown bodies and
|
|
60
|
+
command/arg/env lines stop wrapping mid-token; the content column scrolls
|
|
61
|
+
on short viewports instead of clipping past the dialog edge. */
|
|
62
|
+
.dpc-modalForm.dpc-modalForm{width:min(640px,100%)}
|
|
63
|
+
.dpc-modalScroll.dpc-modalScroll{max-height:calc(100vh - 160px);overflow-y:auto}
|
|
64
|
+
.dpc-importScroll{display:flex;flex-direction:column;gap:14px;max-height:min(400px,52vh);overflow-y:auto;padding:2px 4px 2px 2px}
|
|
65
|
+
.dpc-importGroup{display:flex;flex-direction:column;gap:8px}
|
|
66
|
+
.dpc-importHead{display:flex;align-items:center;gap:8px;padding:0 2px}
|
|
67
|
+
.dpc-importCount{font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary);font-variant-numeric:tabular-nums}
|
|
68
|
+
.dpc-importAll{margin-left:auto;display:inline-flex;align-items:center;gap:6px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);cursor:pointer}
|
|
46
69
|
@media(max-width:680px){.dpc-cards{grid-template-columns:minmax(0,1fr)}}
|
|
47
70
|
`
|
package/src/client/index.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
/** dsh-plugin-capabilities client entry: contributes the
|
|
2
|
-
*
|
|
1
|
+
/** dsh-plugin-capabilities client entry: contributes the top-level
|
|
2
|
+
* “技能与 MCP” section into Settings (beside 通用设置/模型, with Skills and
|
|
3
|
+
* MCP as internal tabs). Calls the host routes with fetch. */
|
|
3
4
|
|
|
4
5
|
import { createElement as h } from 'react'
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { CapabilitiesSection } from './CapabilitiesSection.tsx'
|
|
7
|
+
import type { McpInjected, McpRow } from './McpTab.tsx'
|
|
8
|
+
import type { SkillsInjected, SkillRowView } from './SkillsTab.tsx'
|
|
7
9
|
import { zh, en } from './locales.ts'
|
|
8
10
|
|
|
9
11
|
/** Locale dictionary namespace owned by this plugin. */
|
|
@@ -91,25 +93,16 @@ export function apply(ctx: CapabilitiesClientContext): void {
|
|
|
91
93
|
desktop: window.dshDesktop !== undefined,
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
// One top-level nav entry (between 模型 and 插件), not a tab under the
|
|
97
|
+
// Plugins section — the section component owns its internal Skills/MCP
|
|
98
|
+
// tabs directly, so nothing registers into settings.plugins.tab anymore.
|
|
99
|
+
ctx.slots.inject('settings.section', () => {
|
|
95
100
|
return ctx.slots.register({
|
|
96
|
-
name: 'settings.
|
|
97
|
-
id: 'capabilities
|
|
98
|
-
order:
|
|
99
|
-
label: () => t('
|
|
101
|
+
name: 'settings.section',
|
|
102
|
+
id: 'capabilities',
|
|
103
|
+
order: 12,
|
|
104
|
+
label: () => t('sectionNav'),
|
|
100
105
|
locale: NS,
|
|
101
|
-
|
|
102
|
-
}, () => h(SkillsTab, { t, injected: skillsInjected }))
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
ctx.slots.inject('settings.plugins.tab', () => {
|
|
106
|
-
return ctx.slots.register({
|
|
107
|
-
name: 'settings.plugins.tab',
|
|
108
|
-
id: 'capabilities-mcp',
|
|
109
|
-
order: 40,
|
|
110
|
-
label: () => t('mcpTab'),
|
|
111
|
-
locale: NS,
|
|
112
|
-
inject: () => mcpInjected,
|
|
113
|
-
}, () => h(McpTab, { t, injected: mcpInjected }))
|
|
106
|
+
}, () => h(CapabilitiesSection, { t, skills: skillsInjected, mcp: mcpInjected }))
|
|
114
107
|
})
|
|
115
108
|
}
|
package/src/client/locales.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** zh/en dictionaries for the Settings capabilities tabs. */
|
|
2
2
|
|
|
3
3
|
export const zh = {
|
|
4
|
+
sectionNav: '技能与 MCP',
|
|
4
5
|
skillsTab: '技能',
|
|
5
6
|
mcpTab: 'MCP',
|
|
6
7
|
skillsTitle: '技能管理',
|
|
@@ -56,6 +57,7 @@ export const zh = {
|
|
|
56
57
|
importServers: '从其他 Agent 导入',
|
|
57
58
|
importIntro: '扫描 Claude Code(~/.claude.json)与 Codex(~/.codex/config.toml)的 MCP 服务器配置,勾选后导入为本 profile 的服务器行。',
|
|
58
59
|
importEmpty: '没有发现可导入的 MCP 服务器',
|
|
60
|
+
importSelectAll: '全选',
|
|
59
61
|
importExisting: '已存在',
|
|
60
62
|
importSelected: '导入选中项',
|
|
61
63
|
restartNeeded: '配置已写入,重启 dsh 后生效。',
|
|
@@ -71,6 +73,7 @@ export const zh = {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
export const en = {
|
|
76
|
+
sectionNav: 'Skills & MCP',
|
|
74
77
|
skillsTab: 'Skills',
|
|
75
78
|
mcpTab: 'MCP',
|
|
76
79
|
skillsTitle: 'Skills',
|
|
@@ -126,6 +129,7 @@ export const en = {
|
|
|
126
129
|
importServers: 'Import from other agents',
|
|
127
130
|
importIntro: 'Scans Claude Code (~/.claude.json) and Codex (~/.codex/config.toml) MCP server configs; selected entries become server rows in this profile.',
|
|
128
131
|
importEmpty: 'No importable MCP servers found',
|
|
132
|
+
importSelectAll: 'Select all',
|
|
129
133
|
importExisting: 'already here',
|
|
130
134
|
importSelected: 'Import selected',
|
|
131
135
|
restartNeeded: 'Configuration written — restart dsh to apply.',
|