@max-null/dsh-skill-mcp-center 0.1.0 → 0.2.0
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/client.js +322 -58
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -2
- package/dist/rpc.js +7 -0
- package/dist/service.d.ts +13 -1
- package/dist/service.js +35 -6
- package/package.json +1 -1
package/client.js
CHANGED
|
@@ -40,7 +40,17 @@ var CSS = `
|
|
|
40
40
|
.smc-tab:hover { color: var(--dsw-alias-label-secondary); }
|
|
41
41
|
.smc-tab.active { color: var(--dsw-alias-label-primary); border-bottom-color: var(--dsw-alias-state-business-primary); }
|
|
42
42
|
|
|
43
|
-
.smc-card { border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px;
|
|
43
|
+
.smc-card { border: 1px solid var(--dsw-alias-border-l2); border-radius: 10px; margin-bottom: 10px; background: var(--dsw-alias-bg-layer-1); min-width: 0; overflow: hidden; }
|
|
44
|
+
.smc-card-pad { padding: 14px 16px; }
|
|
45
|
+
.smc-card:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
46
|
+
.smc-card-head { display: flex; align-items: center; }
|
|
47
|
+
.smc-card-title { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; padding: 12px 8px 12px 16px; background: none; border: none; cursor: pointer; text-align: left; font-family: inherit; }
|
|
48
|
+
.smc-card-title:hover .smc-card-name { color: var(--dsw-alias-state-business-primary); }
|
|
49
|
+
.smc-card-name { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
50
|
+
.smc-card-desc { font-size: 12px; color: var(--dsw-alias-label-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
51
|
+
.smc-chevron { flex: none; width: 8px; height: 8px; border-right: 1.5px solid var(--dsw-alias-label-caption); border-bottom: 1.5px solid var(--dsw-alias-label-caption); transform: rotate(45deg); transition: transform .15s; margin: 0 14px 0 4px; }
|
|
52
|
+
.smc-chevron.open { transform: rotate(-135deg); }
|
|
53
|
+
.smc-card .smc-detail { margin: 0; padding: 10px 16px 14px; border-top: 1px solid var(--dsw-alias-border-l1); }
|
|
44
54
|
.smc-row { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
|
45
55
|
.smc-name { font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
46
56
|
.smc-desc { color: var(--dsw-alias-label-secondary); font-size: 13px; margin-top: 4px; }
|
|
@@ -84,13 +94,48 @@ var CSS = `
|
|
|
84
94
|
.smc-toast.ok { border-color: var(--dsw-alias-state-success-primary); }
|
|
85
95
|
.smc-toast.error { border-color: var(--dsw-alias-state-error-primary); }
|
|
86
96
|
|
|
87
|
-
.smc-sidebar { display: flex; flex-direction: column; gap:
|
|
97
|
+
.smc-sidebar { display: flex; flex-direction: column; gap: 0; padding: 8px; height: 100%; box-sizing: border-box; }
|
|
98
|
+
.smc-sidebar-head { flex: none; display: flex; flex-direction: column; gap: 6px; padding-bottom: 8px; border-bottom: 1px solid var(--dsw-alias-border-l1); margin-bottom: 6px; }
|
|
99
|
+
.smc-sidebar-list { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
|
|
100
|
+
.smc-search-sidebar { height: 28px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font-size: 13px; outline: none; width: 100%; font-family: inherit; box-sizing: border-box; }
|
|
101
|
+
.smc-search-sidebar:focus { border-color: var(--dsw-alias-state-business-primary); }
|
|
102
|
+
.smc-search-sidebar::placeholder { color: var(--dsw-alias-label-caption); }
|
|
88
103
|
.smc-srv { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px; }
|
|
89
104
|
.smc-srv:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
90
105
|
.smc-srv-name { font-size: 13px; font-weight: 500; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsw-alias-label-primary); }
|
|
91
|
-
.smc-srv-
|
|
106
|
+
.smc-srv-desc { font-size: 11px; color: var(--dsw-alias-label-tertiary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin: 0 10px 4px 26px; flex: none; }
|
|
92
107
|
.smc-srv-state { font-size: 11px; color: var(--dsw-alias-label-caption); padding: 0 10px 6px 26px; }
|
|
93
108
|
.smc-empty { padding: 24px; text-align: center; color: var(--dsw-alias-label-tertiary); font-size: 12.5px; }
|
|
109
|
+
.smc-ns-bar { display: flex; gap: 4px; margin-bottom: 8px; }
|
|
110
|
+
.smc-ns-btn { flex: 1; height: 26px; border: 1px solid var(--dsw-alias-border-l2); border-radius: 8px; background: none; color: var(--dsw-alias-label-secondary); font-size: 12px; cursor: pointer; font-family: inherit; }
|
|
111
|
+
.smc-ns-btn:hover { background: var(--dsw-alias-interactive-bg-hover); }
|
|
112
|
+
.smc-ns-btn.active { color: var(--dsw-alias-state-business-primary); border-color: var(--dsw-alias-state-business-primary); }
|
|
113
|
+
.smc-md { margin: 6px 0 2px; padding: 8px 10px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; background: var(--dsw-alias-bg-base); font-size: 11px; line-height: 1.6; color: var(--dsw-alias-label-secondary); white-space: pre-wrap; word-break: break-all; max-height: 240px; overflow-y: auto; font-family: ui-monospace, 'Cascadia Code', Consolas, monospace; }
|
|
114
|
+
.smc-md-err { margin: 6px 0 2px; font-size: 11px; color: var(--dsw-alias-state-error-primary); }
|
|
115
|
+
.smc-detail .smc-md { margin-top: 8px; }
|
|
116
|
+
.smc-search { position: sticky; top: 0; z-index: 1; height: 30px; padding: 0 12px; border-radius: 18px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-base); color: var(--dsw-alias-label-primary); font-size: 13px; outline: none; width: 240px; font-family: inherit; }
|
|
117
|
+
.smc-search:focus { border-color: var(--dsw-alias-state-business-primary); }
|
|
118
|
+
.smc-search::placeholder { color: var(--dsw-alias-label-caption); }
|
|
119
|
+
.smc-toolbar { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; gap: 10px; margin-bottom: 12px; padding: 6px 2px; background: var(--dsw-alias-bg-layer-2); }
|
|
120
|
+
.smc-toolbar .smc-search { position: static; width: 100%; flex: 1; margin: 0; }
|
|
121
|
+
.smc-toolbar-count { flex: none; font-size: 12px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; }
|
|
122
|
+
.smc-group { display: flex; align-items: baseline; gap: 8px; margin: 14px 0 8px; font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-secondary); }
|
|
123
|
+
.smc-group .smc-count { font-size: 11px; font-weight: 400; color: var(--dsw-alias-label-caption); }
|
|
124
|
+
.smc-detail { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--dsw-alias-border-l1); font-size: 12px; color: var(--dsw-alias-label-secondary); word-break: break-all; }
|
|
125
|
+
.smc-detail-row { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
|
|
126
|
+
/* DSH 0.1.x \u8BBE\u7F6E\u5BFC\u822A\u65E0 icon \u5951\u7EA6\uFF08external section \u4E00\u5F8B\u9ED8\u8BA4\u9F7F\u8F6E\uFF09\u3002settings-nav-icon
|
|
127
|
+
\u6807\u8BB0\u672C\u63D2\u4EF6\u884C\u540E\uFF1A\u9690\u85CF\u58F3\u6E32\u67D3\u7684\u9F7F\u8F6E SVG\uFF0C\u7528 Lucide wrench \u7684 currentColor mask \u66FF\u6362\uFF0C
|
|
128
|
+
\u8DDF\u968F\u539F\u751F\u5BFC\u822A hover/active \u989C\u8272\u4E14\u4E0D\u6539\u53D8\u58F3\u7684 16px \u56FE\u6807\u8282\u594F\u3002 */
|
|
129
|
+
[data-dsh-skill-mcp-center-settings-nav] > svg:first-child { display: none; }
|
|
130
|
+
[data-dsh-skill-mcp-center-settings-nav]::before {
|
|
131
|
+
content: '';
|
|
132
|
+
flex: none;
|
|
133
|
+
width: 16px;
|
|
134
|
+
height: 16px;
|
|
135
|
+
background: currentColor;
|
|
136
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
137
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z'/%3E%3C/svg%3E") center / contain no-repeat;
|
|
138
|
+
}
|
|
94
139
|
`;
|
|
95
140
|
var cssInjected = false;
|
|
96
141
|
function injectCss() {
|
|
@@ -101,6 +146,30 @@ function injectCss() {
|
|
|
101
146
|
style.textContent = CSS;
|
|
102
147
|
document.head.append(style);
|
|
103
148
|
}
|
|
149
|
+
var SETTINGS_NAV_MARKER = "data-dsh-skill-mcp-center-settings-nav";
|
|
150
|
+
function registerSettingsNavIcon(label) {
|
|
151
|
+
let disposed = false;
|
|
152
|
+
const sync = () => {
|
|
153
|
+
if (disposed) return;
|
|
154
|
+
const currentLabel = label().trim();
|
|
155
|
+
const buttons = document.querySelectorAll('[role="dialog"] nav button');
|
|
156
|
+
for (const button of buttons) {
|
|
157
|
+
const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel;
|
|
158
|
+
if (matches) button.setAttribute(SETTINGS_NAV_MARKER, "");
|
|
159
|
+
else button.removeAttribute(SETTINGS_NAV_MARKER);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
sync();
|
|
163
|
+
const observer = new MutationObserver(sync);
|
|
164
|
+
observer.observe(document.body, { childList: true, subtree: true, characterData: true });
|
|
165
|
+
return () => {
|
|
166
|
+
disposed = true;
|
|
167
|
+
observer.disconnect();
|
|
168
|
+
document.querySelectorAll(`[${SETTINGS_NAV_MARKER}]`).forEach((element) => {
|
|
169
|
+
element.removeAttribute(SETTINGS_NAV_MARKER);
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
}
|
|
104
173
|
var LOCALE_NS = "@max-null/dsh-skill-mcp-center";
|
|
105
174
|
var t = (key) => key;
|
|
106
175
|
var localeRevision = 0;
|
|
@@ -122,10 +191,6 @@ var zhDict = {
|
|
|
122
191
|
loading: "\u52A0\u8F7D\u4E2D\u2026",
|
|
123
192
|
loadFailed: "\u52A0\u8F7D\u5931\u8D25\uFF1A{e}",
|
|
124
193
|
noSkills: "\u672A\u53D1\u73B0\u4EFB\u4F55 skill\uFF08\u68C0\u67E5 ~/.dsh/skills \u4E0E\u9879\u76EE .agents/skills\uFF09",
|
|
125
|
-
sourceSystem: "\u7CFB\u7EDF",
|
|
126
|
-
sourceUser: "\u7528\u6237",
|
|
127
|
-
sourceWorkspace: "\u5DE5\u4F5C\u533A",
|
|
128
|
-
sourceRuntime: "\u8FD0\u884C\u65F6",
|
|
129
194
|
sectionSub: "\u7BA1\u7406\uFF08\u9759\u6001\u914D\u7F6E\uFF09\xB7 \u8FD0\u884C\u65F6\u72B6\u6001\u5728\u53F3\u4FA7\u8FB9\u680F",
|
|
130
195
|
modelDisabled: "\u6A21\u578B\u5DF2\u505C\u7528",
|
|
131
196
|
toggleModelVisible: "\u5207\u6362\u6A21\u578B\u53EF\u89C1",
|
|
@@ -158,16 +223,28 @@ var zhDict = {
|
|
|
158
223
|
noMcpServer: "\u672A\u914D\u7F6E MCP server",
|
|
159
224
|
connected: "\u5DF2\u8FDE\u63A5",
|
|
160
225
|
notSynced: "\u672A\u540C\u6B65",
|
|
161
|
-
failed: "failed"
|
|
226
|
+
failed: "failed",
|
|
227
|
+
searchSkills: "\u641C\u7D22 skill\uFF08\u540D\u79F0 / \u63CF\u8FF0\uFF09\u2026",
|
|
228
|
+
groupGlobal: "\u5168\u5C40",
|
|
229
|
+
groupWorkspace: "\u5DE5\u4F5C\u533A",
|
|
230
|
+
groupBundled: "\u5185\u7F6E\uFF08DSH \u5B98\u65B9\uFF09",
|
|
231
|
+
readOnlyBadge: "\u5185\u7F6E\u53EA\u8BFB",
|
|
232
|
+
noMatch: "\u6CA1\u6709\u5339\u914D\u7684 skill",
|
|
233
|
+
detailPath: "\u8DEF\u5F84",
|
|
234
|
+
detailProvider: "provider",
|
|
235
|
+
clickForDetail: "\u70B9\u51FB\u67E5\u770B\u8BE6\u60C5",
|
|
236
|
+
allNamespaces: "\u5168\u90E8",
|
|
237
|
+
nsGlobal: "\u5168\u5C40",
|
|
238
|
+
nsWorkspace: "\u5DE5\u4F5C\u533A",
|
|
239
|
+
noWorkspace: "\u672A\u9009\u62E9\u5DE5\u4F5C\u533A",
|
|
240
|
+
viewMd: "\u67E5\u770B SKILL.md",
|
|
241
|
+
hideMd: "\u6536\u8D77",
|
|
242
|
+
mdLoadFailed: "\u8BFB\u53D6\u5931\u8D25\uFF1A{e}"
|
|
162
243
|
};
|
|
163
244
|
var enDict = {
|
|
164
245
|
loading: "Loading\u2026",
|
|
165
246
|
loadFailed: "Failed to load: {e}",
|
|
166
247
|
noSkills: "No skills found (check ~/.dsh/skills and project .agents/skills)",
|
|
167
|
-
sourceSystem: "system",
|
|
168
|
-
sourceUser: "user",
|
|
169
|
-
sourceWorkspace: "workspace",
|
|
170
|
-
sourceRuntime: "runtime",
|
|
171
248
|
sectionSub: "Manage (static config) \xB7 runtime status lives in the sidebar",
|
|
172
249
|
modelDisabled: "Model disabled",
|
|
173
250
|
toggleModelVisible: "Toggle model visibility",
|
|
@@ -200,7 +277,23 @@ var enDict = {
|
|
|
200
277
|
noMcpServer: "No MCP server",
|
|
201
278
|
connected: "Connected",
|
|
202
279
|
notSynced: "Not synced",
|
|
203
|
-
failed: "failed"
|
|
280
|
+
failed: "failed",
|
|
281
|
+
searchSkills: "Search skills (name / description)\u2026",
|
|
282
|
+
groupGlobal: "Global",
|
|
283
|
+
groupWorkspace: "Workspace",
|
|
284
|
+
groupBundled: "Built-in (DSH official)",
|
|
285
|
+
readOnlyBadge: "built-in read-only",
|
|
286
|
+
noMatch: "No matching skills",
|
|
287
|
+
detailPath: "path",
|
|
288
|
+
detailProvider: "provider",
|
|
289
|
+
clickForDetail: "click for details",
|
|
290
|
+
allNamespaces: "All",
|
|
291
|
+
nsGlobal: "Global",
|
|
292
|
+
nsWorkspace: "Workspace",
|
|
293
|
+
noWorkspace: "No workspace selected",
|
|
294
|
+
viewMd: "View SKILL.md",
|
|
295
|
+
hideMd: "Hide",
|
|
296
|
+
mdLoadFailed: "Failed to read: {e}"
|
|
204
297
|
};
|
|
205
298
|
var rpc = async () => {
|
|
206
299
|
throw new Error("skill-mcp-center: rpc not wired");
|
|
@@ -232,19 +325,25 @@ function useToast() {
|
|
|
232
325
|
}, [t2]);
|
|
233
326
|
return t2;
|
|
234
327
|
}
|
|
235
|
-
var
|
|
236
|
-
|
|
237
|
-
"
|
|
238
|
-
"
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
328
|
+
var SKILL_GROUPS = [
|
|
329
|
+
{ key: "global", labelKey: "groupGlobal", sources: ["user-dsh", "user-agents"] },
|
|
330
|
+
{ key: "workspace", labelKey: "groupWorkspace", sources: ["project-dsh", "project-agents"] },
|
|
331
|
+
{ key: "bundled", labelKey: "groupBundled", sources: ["bundled"] }
|
|
332
|
+
];
|
|
333
|
+
function groupOf(source) {
|
|
334
|
+
const idx = SKILL_GROUPS.findIndex((g) => g.sources.includes(source));
|
|
335
|
+
return idx === -1 ? SKILL_GROUPS.length : idx;
|
|
336
|
+
}
|
|
243
337
|
function SkillView() {
|
|
244
338
|
useLocale();
|
|
245
339
|
const [items, setItems] = (0, import_react.useState)(null);
|
|
246
340
|
const [error, setError] = (0, import_react.useState)(null);
|
|
247
341
|
const [busy, setBusy] = (0, import_react.useState)(null);
|
|
342
|
+
const [query, setQuery] = (0, import_react.useState)("");
|
|
343
|
+
const [expanded, setExpanded] = (0, import_react.useState)(null);
|
|
344
|
+
const [mdPath, setMdPath] = (0, import_react.useState)(null);
|
|
345
|
+
const [mdText, setMdText] = (0, import_react.useState)(null);
|
|
346
|
+
const [mdError, setMdError] = (0, import_react.useState)(null);
|
|
248
347
|
const load = (0, import_react.useCallback)(() => {
|
|
249
348
|
void rpc("listSkills").then(
|
|
250
349
|
(v) => {
|
|
@@ -261,7 +360,6 @@ function SkillView() {
|
|
|
261
360
|
}, [load]);
|
|
262
361
|
if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "smc-sub", children: t("loadFailed", { e: error }) });
|
|
263
362
|
if (items === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "smc-sub", children: t("loading") });
|
|
264
|
-
if (items.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "smc-sub", children: t("noSkills") });
|
|
265
363
|
const toggle = (s) => {
|
|
266
364
|
setBusy(s.path);
|
|
267
365
|
void rpc("toggleSkill", { path: s.path }).then(
|
|
@@ -276,32 +374,117 @@ function SkillView() {
|
|
|
276
374
|
}
|
|
277
375
|
);
|
|
278
376
|
};
|
|
279
|
-
|
|
280
|
-
|
|
377
|
+
const openMd = (s) => {
|
|
378
|
+
if (mdPath === s.path) {
|
|
379
|
+
setMdPath(null);
|
|
380
|
+
setMdText(null);
|
|
381
|
+
setMdError(null);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
setMdPath(s.path);
|
|
385
|
+
setMdText(null);
|
|
386
|
+
setMdError(null);
|
|
387
|
+
void rpc("readSkill", { path: s.path }).then(
|
|
388
|
+
(v) => {
|
|
389
|
+
setMdText(v);
|
|
390
|
+
},
|
|
391
|
+
(e) => {
|
|
392
|
+
setMdError(e instanceof Error ? e.message : String(e));
|
|
393
|
+
}
|
|
394
|
+
);
|
|
395
|
+
};
|
|
396
|
+
const q = query.trim().toLowerCase();
|
|
397
|
+
const visible = items.filter((s) => q === "" || s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
|
|
398
|
+
const buckets = SKILL_GROUPS.map(() => []);
|
|
399
|
+
const other = [];
|
|
400
|
+
for (const s of visible) {
|
|
401
|
+
const g = groupOf(s.source);
|
|
402
|
+
if (g < SKILL_GROUPS.length) buckets[g].push(s);
|
|
403
|
+
else other.push(s);
|
|
404
|
+
}
|
|
405
|
+
const renderCard = (s) => {
|
|
406
|
+
const open = expanded === s.path;
|
|
281
407
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card", children: [
|
|
282
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-
|
|
283
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
408
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card-head", children: [
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
410
|
+
"button",
|
|
411
|
+
{
|
|
412
|
+
type: "button",
|
|
413
|
+
className: "smc-card-title",
|
|
414
|
+
"aria-expanded": open,
|
|
415
|
+
title: t("clickForDetail"),
|
|
416
|
+
onClick: () => {
|
|
417
|
+
setExpanded(open ? null : s.path);
|
|
418
|
+
},
|
|
419
|
+
children: [
|
|
420
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-card-name", children: s.name }),
|
|
421
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-card-desc", children: s.description })
|
|
422
|
+
]
|
|
423
|
+
}
|
|
424
|
+
),
|
|
425
|
+
s.writable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
288
426
|
"button",
|
|
289
427
|
{
|
|
290
428
|
type: "button",
|
|
291
|
-
className: `smc-toggle${s.
|
|
292
|
-
disabled:
|
|
293
|
-
title:
|
|
429
|
+
className: `smc-toggle${s.modelInvocable ? " on" : ""}`,
|
|
430
|
+
disabled: busy === s.path,
|
|
431
|
+
title: t("toggleModelVisible"),
|
|
294
432
|
onClick: () => {
|
|
295
433
|
toggle(s);
|
|
296
434
|
},
|
|
297
435
|
"aria-label": s.modelInvocable ? t("disable") : t("enable")
|
|
298
436
|
}
|
|
299
|
-
)
|
|
437
|
+
) : null,
|
|
438
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-chevron${open ? " open" : ""}` })
|
|
300
439
|
] }),
|
|
301
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
440
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-detail", children: [
|
|
441
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-detail-row", children: [
|
|
442
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "smc-badge", children: [
|
|
443
|
+
t("detailProvider"),
|
|
444
|
+
" \xB7 ",
|
|
445
|
+
s.provider
|
|
446
|
+
] }),
|
|
447
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: s.source }),
|
|
448
|
+
!s.modelInvocable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge disabled", children: t("modelDisabled") }),
|
|
449
|
+
!s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge disabled", children: t("readOnlyBadge") }),
|
|
450
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-spacer" }),
|
|
451
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "smc-btn", onClick: () => {
|
|
452
|
+
openMd(s);
|
|
453
|
+
}, children: mdPath === s.path ? t("hideMd") : t("viewMd") })
|
|
454
|
+
] }),
|
|
455
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-detail-row", children: [
|
|
456
|
+
t("detailPath"),
|
|
457
|
+
"\uFF1A",
|
|
458
|
+
s.path
|
|
459
|
+
] }),
|
|
460
|
+
mdPath === s.path && (mdError !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-md-err", children: t("mdLoadFailed", { e: mdError }) }) : mdText === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-md", children: t("loading") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-md", children: mdText }))
|
|
461
|
+
] })
|
|
462
|
+
] }, s.path);
|
|
463
|
+
};
|
|
464
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
465
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-toolbar", children: [
|
|
466
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "smc-search", placeholder: t("searchSkills"), value: query, onChange: (e) => {
|
|
467
|
+
setQuery(e.target.value);
|
|
468
|
+
} }),
|
|
469
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-toolbar-count", children: visible.length })
|
|
470
|
+
] }),
|
|
471
|
+
visible.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "smc-sub", children: t("noMatch") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
472
|
+
SKILL_GROUPS.map((g, i) => buckets[i].length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
473
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-group", children: [
|
|
474
|
+
t(g.labelKey),
|
|
475
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-count", children: buckets[i].length })
|
|
476
|
+
] }),
|
|
477
|
+
buckets[i].map(renderCard)
|
|
478
|
+
] }, g.key) : null),
|
|
479
|
+
other.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
480
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-group", children: [
|
|
481
|
+
other[0]?.source ?? "",
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-count", children: other.length })
|
|
483
|
+
] }),
|
|
484
|
+
other.map(renderCard)
|
|
485
|
+
] }, "other") : null
|
|
486
|
+
] })
|
|
487
|
+
] });
|
|
305
488
|
}
|
|
306
489
|
function McpView() {
|
|
307
490
|
useLocale();
|
|
@@ -361,7 +544,7 @@ function McpView() {
|
|
|
361
544
|
setEditing(null);
|
|
362
545
|
load();
|
|
363
546
|
} }),
|
|
364
|
-
items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card", children: [
|
|
547
|
+
items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card smc-card-pad", children: [
|
|
365
548
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-row", children: [
|
|
366
549
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-name", children: s.serverName }),
|
|
367
550
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: s.transport }),
|
|
@@ -522,9 +705,19 @@ function Toast() {
|
|
|
522
705
|
if (toast === null) return null;
|
|
523
706
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `smc-toast ${toast.kind}`, children: toast.message });
|
|
524
707
|
}
|
|
708
|
+
function skillInNamespace(s, ns) {
|
|
709
|
+
if (ns === "all") return true;
|
|
710
|
+
if (ns === "global") return s.source === "user-dsh" || s.source === "user-agents";
|
|
711
|
+
return s.source === "project-dsh" || s.source === "project-agents";
|
|
712
|
+
}
|
|
525
713
|
function SidebarSkillTab({ visible, cwd }) {
|
|
526
714
|
useLocale();
|
|
527
715
|
const [items, setItems] = (0, import_react.useState)([]);
|
|
716
|
+
const [ns, setNs] = (0, import_react.useState)("all");
|
|
717
|
+
const [query, setQuery] = (0, import_react.useState)("");
|
|
718
|
+
const [mdPath, setMdPath] = (0, import_react.useState)(null);
|
|
719
|
+
const [mdText, setMdText] = (0, import_react.useState)(null);
|
|
720
|
+
const [mdError, setMdError] = (0, import_react.useState)(null);
|
|
528
721
|
const load = (0, import_react.useCallback)(() => {
|
|
529
722
|
void rpc("listSkills", { cwd }).then(
|
|
530
723
|
(v) => {
|
|
@@ -539,26 +732,96 @@ function SidebarSkillTab({ visible, cwd }) {
|
|
|
539
732
|
if (!visible) return;
|
|
540
733
|
load();
|
|
541
734
|
}, [visible, load]);
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
"aria-label": s.modelInvocable ? t("disable") : t("enable")
|
|
735
|
+
const openMd = (path) => {
|
|
736
|
+
if (mdPath === path) {
|
|
737
|
+
setMdPath(null);
|
|
738
|
+
setMdText(null);
|
|
739
|
+
setMdError(null);
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
setMdPath(path);
|
|
743
|
+
setMdText(null);
|
|
744
|
+
setMdError(null);
|
|
745
|
+
void rpc("readSkill", { path, cwd }).then(
|
|
746
|
+
(v) => {
|
|
747
|
+
setMdText(v);
|
|
748
|
+
},
|
|
749
|
+
(e) => {
|
|
750
|
+
setMdError(e instanceof Error ? e.message : String(e));
|
|
559
751
|
}
|
|
560
|
-
)
|
|
561
|
-
|
|
752
|
+
);
|
|
753
|
+
};
|
|
754
|
+
const q = query.trim().toLowerCase();
|
|
755
|
+
const visibleItems = items.filter((s) => skillInNamespace(s, ns) && (q === "" || s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q)));
|
|
756
|
+
const noWorkspace = ns === "workspace" && (cwd === void 0 || cwd === "");
|
|
757
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-sidebar", children: [
|
|
758
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-sidebar-head", children: [
|
|
759
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
760
|
+
"input",
|
|
761
|
+
{
|
|
762
|
+
className: "smc-search-sidebar",
|
|
763
|
+
placeholder: t("searchSkills"),
|
|
764
|
+
value: query,
|
|
765
|
+
onChange: (e) => {
|
|
766
|
+
setQuery(e.target.value);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
),
|
|
770
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-ns-bar", style: { marginBottom: 0 }, children: ["all", "global", "workspace"].map((k) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
771
|
+
"button",
|
|
772
|
+
{
|
|
773
|
+
type: "button",
|
|
774
|
+
className: `smc-ns-btn${ns === k ? " active" : ""}`,
|
|
775
|
+
onClick: () => {
|
|
776
|
+
setNs(k);
|
|
777
|
+
setMdPath(null);
|
|
778
|
+
setMdText(null);
|
|
779
|
+
setMdError(null);
|
|
780
|
+
},
|
|
781
|
+
children: t(k === "all" ? "allNamespaces" : k === "global" ? "nsGlobal" : "nsWorkspace")
|
|
782
|
+
},
|
|
783
|
+
k
|
|
784
|
+
)) })
|
|
785
|
+
] }),
|
|
786
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-sidebar-list", children: noWorkspace ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-empty", children: t("noWorkspace") }) : visibleItems.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-empty", children: q !== "" ? t("noMatch") : t("noSkills") }) : visibleItems.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
787
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-srv", title: `${s.description}
|
|
788
|
+
${s.path}`, children: [
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-dot${s.modelInvocable ? "" : " idle"}` }),
|
|
790
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-name", children: s.name }),
|
|
791
|
+
!s.modelInvocable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-count", children: t("modelDisabled") }),
|
|
792
|
+
!s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-count", children: t("readOnlyBadge") }),
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
794
|
+
"button",
|
|
795
|
+
{
|
|
796
|
+
type: "button",
|
|
797
|
+
className: "smc-btn",
|
|
798
|
+
style: { height: 20, padding: "0 6px", fontSize: 11 },
|
|
799
|
+
onClick: () => {
|
|
800
|
+
openMd(s.path);
|
|
801
|
+
},
|
|
802
|
+
"aria-label": mdPath === s.path ? t("hideMd") : t("viewMd"),
|
|
803
|
+
children: mdPath === s.path ? t("hideMd") : "MD"
|
|
804
|
+
}
|
|
805
|
+
),
|
|
806
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
807
|
+
"button",
|
|
808
|
+
{
|
|
809
|
+
type: "button",
|
|
810
|
+
className: `smc-toggle${s.modelInvocable ? " on" : ""}`,
|
|
811
|
+
title: t("toggleModelVisible"),
|
|
812
|
+
onClick: () => {
|
|
813
|
+
void rpc("toggleSkill", { path: s.path }).then(() => {
|
|
814
|
+
load();
|
|
815
|
+
});
|
|
816
|
+
},
|
|
817
|
+
"aria-label": s.modelInvocable ? t("disable") : t("enable")
|
|
818
|
+
}
|
|
819
|
+
)
|
|
820
|
+
] }),
|
|
821
|
+
s.description !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-srv-desc", children: s.description }),
|
|
822
|
+
mdPath === s.path && (mdError !== null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-md-err", children: t("mdLoadFailed", { e: mdError }) }) : mdText === null ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-md", children: t("loading") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-md", children: mdText }))
|
|
823
|
+
] }, s.path)) })
|
|
824
|
+
] });
|
|
562
825
|
}
|
|
563
826
|
var inject = ["slots", "connection", "locale"];
|
|
564
827
|
function apply(ctx) {
|
|
@@ -581,6 +844,7 @@ function apply(ctx) {
|
|
|
581
844
|
localeRevision += 1;
|
|
582
845
|
localeListeners.forEach((l) => l());
|
|
583
846
|
}));
|
|
847
|
+
ctx.effect(() => registerSettingsNavIcon(() => "Skill & MCP"));
|
|
584
848
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
585
849
|
name: "settings.section",
|
|
586
850
|
id: "skill-mcp-center",
|
package/dist/index.d.ts
CHANGED
|
@@ -4,9 +4,10 @@
|
|
|
4
4
|
* package's `dsh.client` declaration.
|
|
5
5
|
*/
|
|
6
6
|
import type { Context } from '@deepseek-ai/cordis';
|
|
7
|
+
import type { SkillConfig } from './service.ts';
|
|
7
8
|
export { SkillMcpService } from './service.ts';
|
|
8
|
-
export type { McpConfig, McpServer, McpServerStatus, SkillView } from './service.ts';
|
|
9
|
+
export type { McpConfig, McpServer, McpServerStatus, SkillConfig, SkillView } from './service.ts';
|
|
9
10
|
export declare const name = "dsh-skill-mcp-center";
|
|
10
11
|
/** The engine declares its own loader/tools dependencies. */
|
|
11
12
|
export declare const inject: string[];
|
|
12
|
-
export declare function apply(ctx: Context): Promise<void>;
|
|
13
|
+
export declare function apply(ctx: Context, config?: SkillConfig): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { SkillMcpService } from "./service.js";
|
|
|
4
4
|
export const name = 'dsh-skill-mcp-center';
|
|
5
5
|
/** The engine declares its own loader/tools dependencies. */
|
|
6
6
|
export const inject = ['loader', 'tools'];
|
|
7
|
-
export async function apply(ctx) {
|
|
8
|
-
await ctx.plugin(SkillMcpService);
|
|
7
|
+
export async function apply(ctx, config = {}) {
|
|
8
|
+
await ctx.plugin(SkillMcpService, config);
|
|
9
9
|
await ctx.plugin(SkillMcpRpc);
|
|
10
10
|
}
|
package/dist/rpc.js
CHANGED
|
@@ -26,6 +26,13 @@ export class SkillMcpRpc extends Service {
|
|
|
26
26
|
return internal('toggleSkill: path is required');
|
|
27
27
|
return { ok: true, value: await ctx.skillMcp.toggleSkill(path) };
|
|
28
28
|
}
|
|
29
|
+
case 'readSkill': {
|
|
30
|
+
const path = p.path;
|
|
31
|
+
if (typeof path !== 'string' || path === '')
|
|
32
|
+
return internal('readSkill: path is required');
|
|
33
|
+
const cwd = p.cwd;
|
|
34
|
+
return { ok: true, value: await ctx.skillMcp.readSkill(path, typeof cwd === 'string' ? cwd : undefined) };
|
|
35
|
+
}
|
|
29
36
|
case 'listMcpServers':
|
|
30
37
|
return { ok: true, value: await ctx.skillMcp.listMcpServers() };
|
|
31
38
|
case 'createMcpServer':
|
package/dist/service.d.ts
CHANGED
|
@@ -26,6 +26,11 @@ export interface SkillView {
|
|
|
26
26
|
/** Absolute SKILL.md path (the toggle target; opaque to the client display). */
|
|
27
27
|
path: string;
|
|
28
28
|
}
|
|
29
|
+
/** Plugin configuration for the skill/MCP engine. */
|
|
30
|
+
export interface SkillConfig {
|
|
31
|
+
/** Additional read-only official/bundled skill roots (e.g. the harness repo's own `.agents/skills`). */
|
|
32
|
+
officialSkillDirs?: string[];
|
|
33
|
+
}
|
|
29
34
|
/** One MCP server as the Settings surface exposes it. */
|
|
30
35
|
export interface McpServer {
|
|
31
36
|
id: string;
|
|
@@ -59,11 +64,18 @@ export interface McpConfig {
|
|
|
59
64
|
}
|
|
60
65
|
export declare class SkillMcpService extends Service {
|
|
61
66
|
static inject: string[];
|
|
62
|
-
|
|
67
|
+
private readonly officialSkillDirs;
|
|
68
|
+
constructor(ctx: Context, config?: SkillConfig);
|
|
63
69
|
/** User-level skills plus, when a workspace is given, its project-level skills. */
|
|
64
70
|
listSkills(cwd?: string): Promise<SkillView[]>;
|
|
65
71
|
/** Flip one disk-backed skill's model invocation by rewriting its SKILL.md frontmatter. */
|
|
66
72
|
toggleSkill(path: string): Promise<SkillView>;
|
|
73
|
+
/**
|
|
74
|
+
* Read one skill's SKILL.md raw text for display. Paths must live under a
|
|
75
|
+
* known skill root — a plain path join against the same roots `listSkills`
|
|
76
|
+
* scans, so the RPC cannot be used to read arbitrary files.
|
|
77
|
+
*/
|
|
78
|
+
readSkill(path: string, cwd?: string): Promise<string>;
|
|
67
79
|
/** Every `mcp-client` loader entry as a server card. */
|
|
68
80
|
listMcpServers(): Promise<McpServer[]>;
|
|
69
81
|
/** Add one mcp-client entry — hot-connects (create → init) and persists. */
|
package/dist/service.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { Service } from '@deepseek-ai/cordis';
|
|
10
10
|
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
11
|
import { homedir } from 'node:os';
|
|
12
|
-
import { join } from 'node:path';
|
|
12
|
+
import { join, sep } from 'node:path';
|
|
13
13
|
import { parseSkillFrontmatter, setDisableModelInvocation } from "./frontmatter.js";
|
|
14
14
|
/** Runtime mirror of cordis FiberState (a cross-package const enum). */
|
|
15
15
|
const FIBER_PHASE = {
|
|
@@ -57,7 +57,7 @@ function skillPathFor(root, name, isDirectory) {
|
|
|
57
57
|
return null;
|
|
58
58
|
}
|
|
59
59
|
/** Scan one root for SKILL.md entries and parse their frontmatter. */
|
|
60
|
-
async function scanSkillRoot(root, source) {
|
|
60
|
+
async function scanSkillRoot(root, source, writable = true, provider = 'filesystem') {
|
|
61
61
|
let entries;
|
|
62
62
|
try {
|
|
63
63
|
entries = await readdir(root, { withFileTypes: true });
|
|
@@ -84,10 +84,10 @@ async function scanSkillRoot(root, source) {
|
|
|
84
84
|
name: fm.name,
|
|
85
85
|
description: fm.description,
|
|
86
86
|
source,
|
|
87
|
-
provider
|
|
87
|
+
provider,
|
|
88
88
|
modelInvocable: fm.modelInvocable,
|
|
89
89
|
userInvocable: true,
|
|
90
|
-
writable
|
|
90
|
+
writable,
|
|
91
91
|
path: skillPath,
|
|
92
92
|
});
|
|
93
93
|
}
|
|
@@ -95,8 +95,10 @@ async function scanSkillRoot(root, source) {
|
|
|
95
95
|
}
|
|
96
96
|
export class SkillMcpService extends Service {
|
|
97
97
|
static inject = ['loader', 'tools'];
|
|
98
|
-
|
|
98
|
+
officialSkillDirs;
|
|
99
|
+
constructor(ctx, config = {}) {
|
|
99
100
|
super(ctx, 'skillMcp');
|
|
101
|
+
this.officialSkillDirs = config.officialSkillDirs ?? [];
|
|
100
102
|
}
|
|
101
103
|
/** User-level skills plus, when a workspace is given, its project-level skills. */
|
|
102
104
|
async listSkills(cwd) {
|
|
@@ -108,6 +110,9 @@ export class SkillMcpService extends Service {
|
|
|
108
110
|
skills.push(...await scanSkillRoot(join(cwd, '.agents', 'skills'), 'project-agents'));
|
|
109
111
|
skills.push(...await scanSkillRoot(join(cwd, '.dsh', 'skills'), 'project-dsh'));
|
|
110
112
|
}
|
|
113
|
+
for (const dir of this.officialSkillDirs) {
|
|
114
|
+
skills.push(...await scanSkillRoot(dir, 'bundled', false, 'dsh-official'));
|
|
115
|
+
}
|
|
111
116
|
return skills;
|
|
112
117
|
}
|
|
113
118
|
/** Flip one disk-backed skill's model invocation by rewriting its SKILL.md frontmatter. */
|
|
@@ -135,6 +140,30 @@ export class SkillMcpService extends Service {
|
|
|
135
140
|
path,
|
|
136
141
|
};
|
|
137
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* Read one skill's SKILL.md raw text for display. Paths must live under a
|
|
145
|
+
* known skill root — a plain path join against the same roots `listSkills`
|
|
146
|
+
* scans, so the RPC cannot be used to read arbitrary files.
|
|
147
|
+
*/
|
|
148
|
+
async readSkill(path, cwd) {
|
|
149
|
+
const roots = [...SKILL_ROOTS.map(root => root.path)];
|
|
150
|
+
if (cwd !== undefined && cwd !== '') {
|
|
151
|
+
roots.push(join(cwd, '.agents', 'skills'), join(cwd, '.dsh', 'skills'));
|
|
152
|
+
}
|
|
153
|
+
roots.push(...this.officialSkillDirs);
|
|
154
|
+
// Segment-boundary prefix check: a sibling directory like `skills-notes`
|
|
155
|
+
// must not satisfy a `skills` root. join(root, '') normalizes to root.
|
|
156
|
+
if (!roots.some(root => path === root || path.startsWith(`${root}${sep}`)))
|
|
157
|
+
throw new Error('skill-not-found');
|
|
158
|
+
let text;
|
|
159
|
+
try {
|
|
160
|
+
text = await readFile(path, 'utf8');
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
throw new Error('skill-not-found');
|
|
164
|
+
}
|
|
165
|
+
return text;
|
|
166
|
+
}
|
|
138
167
|
/** Every `mcp-client` loader entry as a server card. */
|
|
139
168
|
async listMcpServers() {
|
|
140
169
|
const servers = [];
|
|
@@ -182,7 +211,7 @@ export class SkillMcpService extends Service {
|
|
|
182
211
|
/** Runtime status per server: upstream `mcpStatus` seam when present, else derived. */
|
|
183
212
|
async mcpStatus() {
|
|
184
213
|
const servers = await this.listMcpServers();
|
|
185
|
-
const seam = this.ctx.mcpStatus;
|
|
214
|
+
const seam = this.ctx.get('mcpStatus');
|
|
186
215
|
if (seam !== undefined) {
|
|
187
216
|
const byName = new Map(seam.list().map(s => [s.serverName, s]));
|
|
188
217
|
return servers.map(s => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@max-null/dsh-skill-mcp-center",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Skill & MCP management center for DeepSeek Harness — manage skills and MCP servers in Settings, live MCP status in the sidebar",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|