@max-null/dsh-skill-mcp-center 0.1.0 → 0.3.1

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 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; padding: 14px 16px; margin-bottom: 10px; background: var(--dsw-alias-bg-layer-1); min-width: 0; }
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,50 @@ 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: 2px; padding: 8px; }
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-count { font-size: 11px; color: var(--dsw-alias-label-tertiary); }
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-ref { display: inline; padding: 0 3px; border-radius: 4px; background: var(--dsw-alias-state-business-tertiary); color: var(--dsw-alias-state-business-primary); cursor: pointer; text-decoration: underline dotted; }
115
+ .smc-ref:hover { background: var(--dsw-alias-state-business-primary); color: var(--dsw-alias-bg-base); }
116
+ .smc-md-err { margin: 6px 0 2px; font-size: 11px; color: var(--dsw-alias-state-error-primary); }
117
+ .smc-detail .smc-md { margin-top: 8px; }
118
+ .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; }
119
+ .smc-search:focus { border-color: var(--dsw-alias-state-business-primary); }
120
+ .smc-search::placeholder { color: var(--dsw-alias-label-caption); }
121
+ .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); }
122
+ .smc-toolbar .smc-search { position: static; width: 100%; flex: 1; margin: 0; }
123
+ .smc-toolbar-count { flex: none; font-size: 12px; color: var(--dsw-alias-label-tertiary); white-space: nowrap; }
124
+ .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); }
125
+ .smc-group .smc-count { font-size: 11px; font-weight: 400; color: var(--dsw-alias-label-caption); }
126
+ .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; }
127
+ .smc-detail-row { display: flex; align-items: center; gap: 6px; margin-top: 4px; }
128
+ /* DSH 0.1.x \u8BBE\u7F6E\u5BFC\u822A\u65E0 icon \u5951\u7EA6\uFF08external section \u4E00\u5F8B\u9ED8\u8BA4\u9F7F\u8F6E\uFF09\u3002settings-nav-icon
129
+ \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
130
+ \u8DDF\u968F\u539F\u751F\u5BFC\u822A hover/active \u989C\u8272\u4E14\u4E0D\u6539\u53D8\u58F3\u7684 16px \u56FE\u6807\u8282\u594F\u3002 */
131
+ [data-dsh-skill-mcp-center-settings-nav] > svg:first-child { display: none; }
132
+ [data-dsh-skill-mcp-center-settings-nav]::before {
133
+ content: '';
134
+ flex: none;
135
+ width: 16px;
136
+ height: 16px;
137
+ background: currentColor;
138
+ -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;
139
+ 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;
140
+ }
94
141
  `;
95
142
  var cssInjected = false;
96
143
  function injectCss() {
@@ -101,6 +148,30 @@ function injectCss() {
101
148
  style.textContent = CSS;
102
149
  document.head.append(style);
103
150
  }
151
+ var SETTINGS_NAV_MARKER = "data-dsh-skill-mcp-center-settings-nav";
152
+ function registerSettingsNavIcon(label) {
153
+ let disposed = false;
154
+ const sync = () => {
155
+ if (disposed) return;
156
+ const currentLabel = label().trim();
157
+ const buttons = document.querySelectorAll('[role="dialog"] nav button');
158
+ for (const button of buttons) {
159
+ const matches = currentLabel.length > 0 && button.textContent?.trim() === currentLabel;
160
+ if (matches) button.setAttribute(SETTINGS_NAV_MARKER, "");
161
+ else button.removeAttribute(SETTINGS_NAV_MARKER);
162
+ }
163
+ };
164
+ sync();
165
+ const observer = new MutationObserver(sync);
166
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true });
167
+ return () => {
168
+ disposed = true;
169
+ observer.disconnect();
170
+ document.querySelectorAll(`[${SETTINGS_NAV_MARKER}]`).forEach((element) => {
171
+ element.removeAttribute(SETTINGS_NAV_MARKER);
172
+ });
173
+ };
174
+ }
104
175
  var LOCALE_NS = "@max-null/dsh-skill-mcp-center";
105
176
  var t = (key) => key;
106
177
  var localeRevision = 0;
@@ -122,10 +193,6 @@ var zhDict = {
122
193
  loading: "\u52A0\u8F7D\u4E2D\u2026",
123
194
  loadFailed: "\u52A0\u8F7D\u5931\u8D25\uFF1A{e}",
124
195
  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
196
  sectionSub: "\u7BA1\u7406\uFF08\u9759\u6001\u914D\u7F6E\uFF09\xB7 \u8FD0\u884C\u65F6\u72B6\u6001\u5728\u53F3\u4FA7\u8FB9\u680F",
130
197
  modelDisabled: "\u6A21\u578B\u5DF2\u505C\u7528",
131
198
  toggleModelVisible: "\u5207\u6362\u6A21\u578B\u53EF\u89C1",
@@ -158,16 +225,29 @@ var zhDict = {
158
225
  noMcpServer: "\u672A\u914D\u7F6E MCP server",
159
226
  connected: "\u5DF2\u8FDE\u63A5",
160
227
  notSynced: "\u672A\u540C\u6B65",
161
- failed: "failed"
228
+ failed: "failed",
229
+ searchSkills: "\u641C\u7D22 skill\uFF08\u540D\u79F0 / \u63CF\u8FF0\uFF09\u2026",
230
+ groupGlobal: "\u5168\u5C40",
231
+ groupWorkspace: "\u5DE5\u4F5C\u533A",
232
+ groupBundled: "\u5185\u7F6E\uFF08DSH \u5B98\u65B9\uFF09",
233
+ readOnlyBadge: "\u5185\u7F6E\u53EA\u8BFB",
234
+ noMatch: "\u6CA1\u6709\u5339\u914D\u7684 skill",
235
+ detailPath: "\u8DEF\u5F84",
236
+ detailProvider: "provider",
237
+ clickForDetail: "\u70B9\u51FB\u67E5\u770B\u8BE6\u60C5",
238
+ allNamespaces: "\u5168\u90E8",
239
+ nsGlobal: "\u5168\u5C40",
240
+ nsWorkspace: "\u5DE5\u4F5C\u533A",
241
+ noWorkspace: "\u672A\u9009\u62E9\u5DE5\u4F5C\u533A",
242
+ viewMd: "\u67E5\u770B SKILL.md",
243
+ hideMd: "\u6536\u8D77",
244
+ mdLoadFailed: "\u8BFB\u53D6\u5931\u8D25\uFF1A{e}",
245
+ refOpen: "\u6253\u5F00\u6587\u4EF6\u5F15\u7528"
162
246
  };
163
247
  var enDict = {
164
248
  loading: "Loading\u2026",
165
249
  loadFailed: "Failed to load: {e}",
166
250
  noSkills: "No skills found (check ~/.dsh/skills and project .agents/skills)",
167
- sourceSystem: "system",
168
- sourceUser: "user",
169
- sourceWorkspace: "workspace",
170
- sourceRuntime: "runtime",
171
251
  sectionSub: "Manage (static config) \xB7 runtime status lives in the sidebar",
172
252
  modelDisabled: "Model disabled",
173
253
  toggleModelVisible: "Toggle model visibility",
@@ -200,11 +280,76 @@ var enDict = {
200
280
  noMcpServer: "No MCP server",
201
281
  connected: "Connected",
202
282
  notSynced: "Not synced",
203
- failed: "failed"
283
+ failed: "failed",
284
+ searchSkills: "Search skills (name / description)\u2026",
285
+ groupGlobal: "Global",
286
+ groupWorkspace: "Workspace",
287
+ groupBundled: "Built-in (DSH official)",
288
+ readOnlyBadge: "built-in read-only",
289
+ noMatch: "No matching skills",
290
+ detailPath: "path",
291
+ detailProvider: "provider",
292
+ clickForDetail: "click for details",
293
+ allNamespaces: "All",
294
+ nsGlobal: "Global",
295
+ nsWorkspace: "Workspace",
296
+ noWorkspace: "No workspace selected",
297
+ viewMd: "View SKILL.md",
298
+ hideMd: "Hide",
299
+ mdLoadFailed: "Failed to read: {e}",
300
+ refOpen: "Open file reference"
204
301
  };
205
302
  var rpc = async () => {
206
303
  throw new Error("skill-mcp-center: rpc not wired");
207
304
  };
305
+ var openFileRef = null;
306
+ var FILE_REF_RE = /@("([^"]+)"|([^\s"@]+))/g;
307
+ function looksLikePath(path) {
308
+ if (path.includes("\\")) return true;
309
+ if (path.startsWith("./") || path.startsWith("../") || path.startsWith("/") || path.startsWith("~/")) return true;
310
+ if (path.includes("/")) {
311
+ return path.includes(".");
312
+ }
313
+ const m = /\.([A-Za-z0-9]{1,6})$/.exec(path);
314
+ if (m === null) return false;
315
+ const ext = m[1].toLowerCase();
316
+ const COMMON_EXTS = /* @__PURE__ */ new Set(["md", "txt", "ts", "tsx", "js", "mjs", "cjs", "json", "yml", "yaml", "toml", "png", "jpg", "jpeg", "gif", "webp", "svg", "ico", "css", "scss", "html", "py", "rs", "go", "java", "c", "h", "cpp", "sh", "ps1", "bat", "exe", "zip", "tar", "gz", "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "log", "env", "lock", "csv", "xml", "sql", "db", "wasm"]);
317
+ return COMMON_EXTS.has(ext);
318
+ }
319
+ function splitFileRefs(text) {
320
+ const parts = [];
321
+ let last = 0;
322
+ let m;
323
+ FILE_REF_RE.lastIndex = 0;
324
+ while ((m = FILE_REF_RE.exec(text)) !== null) {
325
+ if (m.index > last) parts.push({ kind: "text", text: text.slice(last, m.index) });
326
+ const path = m[2] ?? m[3];
327
+ if (m[2] !== void 0 || looksLikePath(path)) {
328
+ parts.push({ kind: "ref", ref: { raw: m[0], path } });
329
+ } else {
330
+ parts.push({ kind: "text", text: m[0] });
331
+ }
332
+ last = m.index + m[0].length;
333
+ }
334
+ if (last < text.length) parts.push({ kind: "text", text: text.slice(last) });
335
+ return parts;
336
+ }
337
+ function MdWithRefs({ text }) {
338
+ const parts = splitFileRefs(text);
339
+ if (parts.every((p) => p.kind === "text")) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: text });
340
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: parts.map((p, i) => p.kind === "text" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: p.text }, i) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
341
+ "span",
342
+ {
343
+ className: "smc-ref",
344
+ title: t("refOpen"),
345
+ onClick: () => {
346
+ openFileRef?.(p.ref.path, p.ref.path);
347
+ },
348
+ children: p.ref.raw
349
+ },
350
+ i
351
+ )) });
352
+ }
208
353
  var toastState = null;
209
354
  var toastListeners = /* @__PURE__ */ new Set();
210
355
  function showToast(message, kind = "ok") {
@@ -232,19 +377,25 @@ function useToast() {
232
377
  }, [t2]);
233
378
  return t2;
234
379
  }
235
- var SOURCE_GROUP = {
236
- bundled: { label: "sourceSystem", cls: "bundled" },
237
- "user-dsh": { label: "sourceUser", cls: "user" },
238
- "user-agents": { label: "sourceUser", cls: "user" },
239
- "project-dsh": { label: "sourceWorkspace", cls: "workspace" },
240
- "project-agents": { label: "sourceWorkspace", cls: "workspace" },
241
- runtime: { label: "sourceRuntime", cls: "runtime" }
242
- };
380
+ var SKILL_GROUPS = [
381
+ { key: "global", labelKey: "groupGlobal", sources: ["user-dsh", "user-agents"] },
382
+ { key: "workspace", labelKey: "groupWorkspace", sources: ["project-dsh", "project-agents"] },
383
+ { key: "bundled", labelKey: "groupBundled", sources: ["bundled"] }
384
+ ];
385
+ function groupOf(source) {
386
+ const idx = SKILL_GROUPS.findIndex((g) => g.sources.includes(source));
387
+ return idx === -1 ? SKILL_GROUPS.length : idx;
388
+ }
243
389
  function SkillView() {
244
390
  useLocale();
245
391
  const [items, setItems] = (0, import_react.useState)(null);
246
392
  const [error, setError] = (0, import_react.useState)(null);
247
393
  const [busy, setBusy] = (0, import_react.useState)(null);
394
+ const [query, setQuery] = (0, import_react.useState)("");
395
+ const [expanded, setExpanded] = (0, import_react.useState)(null);
396
+ const [mdPath, setMdPath] = (0, import_react.useState)(null);
397
+ const [mdText, setMdText] = (0, import_react.useState)(null);
398
+ const [mdError, setMdError] = (0, import_react.useState)(null);
248
399
  const load = (0, import_react.useCallback)(() => {
249
400
  void rpc("listSkills").then(
250
401
  (v) => {
@@ -261,7 +412,6 @@ function SkillView() {
261
412
  }, [load]);
262
413
  if (error !== null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "smc-sub", children: t("loadFailed", { e: error }) });
263
414
  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
415
  const toggle = (s) => {
266
416
  setBusy(s.path);
267
417
  void rpc("toggleSkill", { path: s.path }).then(
@@ -276,32 +426,117 @@ function SkillView() {
276
426
  }
277
427
  );
278
428
  };
279
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { children: items.map((s) => {
280
- const g = SOURCE_GROUP[s.source] ?? { label: s.source, cls: "" };
429
+ const openMd = (s) => {
430
+ if (mdPath === s.path) {
431
+ setMdPath(null);
432
+ setMdText(null);
433
+ setMdError(null);
434
+ return;
435
+ }
436
+ setMdPath(s.path);
437
+ setMdText(null);
438
+ setMdError(null);
439
+ void rpc("readSkill", { path: s.path }).then(
440
+ (v) => {
441
+ setMdText(v);
442
+ },
443
+ (e) => {
444
+ setMdError(e instanceof Error ? e.message : String(e));
445
+ }
446
+ );
447
+ };
448
+ const q = query.trim().toLowerCase();
449
+ const visible = items.filter((s) => q === "" || s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q));
450
+ const buckets = SKILL_GROUPS.map(() => []);
451
+ const other = [];
452
+ for (const s of visible) {
453
+ const g = groupOf(s.source);
454
+ if (g < SKILL_GROUPS.length) buckets[g].push(s);
455
+ else other.push(s);
456
+ }
457
+ const renderCard = (s) => {
458
+ const open = expanded === s.path;
281
459
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card", children: [
282
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-row", children: [
283
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-name", children: s.name }),
284
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-badge ${g.cls}`, children: t(g.label) }),
285
- !s.modelInvocable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge disabled", children: t("modelDisabled") }),
286
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-spacer" }),
287
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card-head", children: [
461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
462
+ "button",
463
+ {
464
+ type: "button",
465
+ className: "smc-card-title",
466
+ "aria-expanded": open,
467
+ title: t("clickForDetail"),
468
+ onClick: () => {
469
+ setExpanded(open ? null : s.path);
470
+ },
471
+ children: [
472
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-card-name", children: s.name }),
473
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-card-desc", children: s.description })
474
+ ]
475
+ }
476
+ ),
477
+ s.writable ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
288
478
  "button",
289
479
  {
290
480
  type: "button",
291
- className: `smc-toggle${s.writable && s.modelInvocable ? " on" : ""}`,
292
- disabled: !s.writable || busy === s.name,
293
- title: s.writable ? t("toggleModelVisible") : t("skillReadonly"),
481
+ className: `smc-toggle${s.modelInvocable ? " on" : ""}`,
482
+ disabled: busy === s.path,
483
+ title: t("toggleModelVisible"),
294
484
  onClick: () => {
295
485
  toggle(s);
296
486
  },
297
487
  "aria-label": s.modelInvocable ? t("disable") : t("enable")
298
488
  }
299
- )
489
+ ) : null,
490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-chevron${open ? " open" : ""}` })
300
491
  ] }),
301
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-desc", children: s.description }),
302
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-meta", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: t("provider", { name: s.provider }) }) })
303
- ] }, s.name);
304
- }) });
492
+ open && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-detail", children: [
493
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-detail-row", children: [
494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "smc-badge", children: [
495
+ t("detailProvider"),
496
+ " \xB7 ",
497
+ s.provider
498
+ ] }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: s.source }),
500
+ !s.modelInvocable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge disabled", children: t("modelDisabled") }),
501
+ !s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge disabled", children: t("readOnlyBadge") }),
502
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-spacer" }),
503
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "smc-btn", onClick: () => {
504
+ openMd(s);
505
+ }, children: mdPath === s.path ? t("hideMd") : t("viewMd") })
506
+ ] }),
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-detail-row", children: [
508
+ t("detailPath"),
509
+ "\uFF1A",
510
+ s.path
511
+ ] }),
512
+ 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: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MdWithRefs, { text: mdText }) }))
513
+ ] })
514
+ ] }, s.path);
515
+ };
516
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
517
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-toolbar", children: [
518
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { className: "smc-search", placeholder: t("searchSkills"), value: query, onChange: (e) => {
519
+ setQuery(e.target.value);
520
+ } }),
521
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-toolbar-count", children: visible.length })
522
+ ] }),
523
+ 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: [
524
+ SKILL_GROUPS.map((g, i) => buckets[i].length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
525
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-group", children: [
526
+ t(g.labelKey),
527
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-count", children: buckets[i].length })
528
+ ] }),
529
+ buckets[i].map(renderCard)
530
+ ] }, g.key) : null),
531
+ other.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
532
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-group", children: [
533
+ other[0]?.source ?? "",
534
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-count", children: other.length })
535
+ ] }),
536
+ other.map(renderCard)
537
+ ] }, "other") : null
538
+ ] })
539
+ ] });
305
540
  }
306
541
  function McpView() {
307
542
  useLocale();
@@ -361,7 +596,7 @@ function McpView() {
361
596
  setEditing(null);
362
597
  load();
363
598
  } }),
364
- items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card", children: [
599
+ items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-card smc-card-pad", children: [
365
600
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-row", children: [
366
601
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-name", children: s.serverName }),
367
602
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: s.transport }),
@@ -522,9 +757,19 @@ function Toast() {
522
757
  if (toast === null) return null;
523
758
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: `smc-toast ${toast.kind}`, children: toast.message });
524
759
  }
760
+ function skillInNamespace(s, ns) {
761
+ if (ns === "all") return true;
762
+ if (ns === "global") return s.source === "user-dsh" || s.source === "user-agents";
763
+ return s.source === "project-dsh" || s.source === "project-agents";
764
+ }
525
765
  function SidebarSkillTab({ visible, cwd }) {
526
766
  useLocale();
527
767
  const [items, setItems] = (0, import_react.useState)([]);
768
+ const [ns, setNs] = (0, import_react.useState)("all");
769
+ const [query, setQuery] = (0, import_react.useState)("");
770
+ const [mdPath, setMdPath] = (0, import_react.useState)(null);
771
+ const [mdText, setMdText] = (0, import_react.useState)(null);
772
+ const [mdError, setMdError] = (0, import_react.useState)(null);
528
773
  const load = (0, import_react.useCallback)(() => {
529
774
  void rpc("listSkills", { cwd }).then(
530
775
  (v) => {
@@ -539,26 +784,96 @@ function SidebarSkillTab({ visible, cwd }) {
539
784
  if (!visible) return;
540
785
  load();
541
786
  }, [visible, load]);
542
- if (items.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-empty", children: t("noSkills") });
543
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-sidebar", children: items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-srv", title: `${s.description}
544
- ${s.path}`, children: [
545
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-dot${s.modelInvocable ? "" : " idle"}` }),
546
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-name", children: s.name }),
547
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
548
- "button",
549
- {
550
- type: "button",
551
- className: `smc-toggle${s.modelInvocable ? " on" : ""}`,
552
- title: t("toggleModelVisible"),
553
- onClick: () => {
554
- void rpc("toggleSkill", { path: s.path }).then(() => {
555
- load();
556
- });
557
- },
558
- "aria-label": s.modelInvocable ? t("disable") : t("enable")
787
+ const openMd = (path) => {
788
+ if (mdPath === path) {
789
+ setMdPath(null);
790
+ setMdText(null);
791
+ setMdError(null);
792
+ return;
793
+ }
794
+ setMdPath(path);
795
+ setMdText(null);
796
+ setMdError(null);
797
+ void rpc("readSkill", { path, cwd }).then(
798
+ (v) => {
799
+ setMdText(v);
800
+ },
801
+ (e) => {
802
+ setMdError(e instanceof Error ? e.message : String(e));
559
803
  }
560
- )
561
- ] }, s.path)) });
804
+ );
805
+ };
806
+ const q = query.trim().toLowerCase();
807
+ const visibleItems = items.filter((s) => skillInNamespace(s, ns) && (q === "" || s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q)));
808
+ const noWorkspace = ns === "workspace" && (cwd === void 0 || cwd === "");
809
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-sidebar", children: [
810
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-sidebar-head", children: [
811
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
812
+ "input",
813
+ {
814
+ className: "smc-search-sidebar",
815
+ placeholder: t("searchSkills"),
816
+ value: query,
817
+ onChange: (e) => {
818
+ setQuery(e.target.value);
819
+ }
820
+ }
821
+ ),
822
+ /* @__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)(
823
+ "button",
824
+ {
825
+ type: "button",
826
+ className: `smc-ns-btn${ns === k ? " active" : ""}`,
827
+ onClick: () => {
828
+ setNs(k);
829
+ setMdPath(null);
830
+ setMdText(null);
831
+ setMdError(null);
832
+ },
833
+ children: t(k === "all" ? "allNamespaces" : k === "global" ? "nsGlobal" : "nsWorkspace")
834
+ },
835
+ k
836
+ )) })
837
+ ] }),
838
+ /* @__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: [
839
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-srv", title: `${s.description}
840
+ ${s.path}`, children: [
841
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-dot${s.modelInvocable ? "" : " idle"}` }),
842
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-name", children: s.name }),
843
+ !s.modelInvocable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-count", children: t("modelDisabled") }),
844
+ !s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-count", children: t("readOnlyBadge") }),
845
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
846
+ "button",
847
+ {
848
+ type: "button",
849
+ className: "smc-btn",
850
+ style: { height: 20, padding: "0 6px", fontSize: 11 },
851
+ onClick: () => {
852
+ openMd(s.path);
853
+ },
854
+ "aria-label": mdPath === s.path ? t("hideMd") : t("viewMd"),
855
+ children: mdPath === s.path ? t("hideMd") : "MD"
856
+ }
857
+ ),
858
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
859
+ "button",
860
+ {
861
+ type: "button",
862
+ className: `smc-toggle${s.modelInvocable ? " on" : ""}`,
863
+ title: t("toggleModelVisible"),
864
+ onClick: () => {
865
+ void rpc("toggleSkill", { path: s.path }).then(() => {
866
+ load();
867
+ });
868
+ },
869
+ "aria-label": s.modelInvocable ? t("disable") : t("enable")
870
+ }
871
+ )
872
+ ] }),
873
+ s.description !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-srv-desc", children: s.description }),
874
+ 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: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MdWithRefs, { text: mdText }) }))
875
+ ] }, s.path)) })
876
+ ] });
562
877
  }
563
878
  var inject = ["slots", "connection", "locale"];
564
879
  function apply(ctx) {
@@ -581,6 +896,7 @@ function apply(ctx) {
581
896
  localeRevision += 1;
582
897
  localeListeners.forEach((l) => l());
583
898
  }));
899
+ ctx.effect(() => registerSettingsNavIcon(() => "Skill & MCP"));
584
900
  ctx.slots.inject("settings.section", () => ctx.slots.register({
585
901
  name: "settings.section",
586
902
  id: "skill-mcp-center",
@@ -590,6 +906,11 @@ function apply(ctx) {
590
906
  ctx.inject(["betterSidebar"], (sidebarCtx) => {
591
907
  const service = sidebarCtx.betterSidebar;
592
908
  if (service === void 0) return;
909
+ if (typeof service.openFile === "function") {
910
+ openFileRef = (path, title) => {
911
+ service.openFile?.({}, path, title);
912
+ };
913
+ }
593
914
  sidebarCtx.effect(() => service.registerTab({
594
915
  id: "@max-null/dsh-skill-mcp-center:mcp",
595
916
  title: () => "MCP",
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
- constructor(ctx: Context);
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: 'filesystem',
87
+ provider,
88
88
  modelInvocable: fm.modelInvocable,
89
89
  userInvocable: true,
90
- writable: true,
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
- constructor(ctx) {
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.1.0",
3
+ "version": "0.3.1",
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": {