@max-null/dsh-skill-mcp-center 0.6.0 → 0.6.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
@@ -81,6 +81,10 @@ var CSS = `
81
81
  .smc-toggle::after { content: ''; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%; background: #fff; transition: left .15s; }
82
82
  .smc-toggle.on::after { left: 21px; }
83
83
  .smc-toggle:disabled { opacity: 0.4; cursor: default; }
84
+ .smc-skill-group { margin-bottom: 10px; }
85
+ .smc-skill-group-head { display: flex; align-items: baseline; gap: 6px; padding: 6px 2px 4px; font-size: 11px; font-weight: 600; letter-spacing: .04em; color: var(--dsw-alias-label-caption); }
86
+ .smc-skill-group-count { font-weight: 400; opacity: .7; }
87
+ .smc-skill-group-note { padding: 0 2px 6px; font-size: 11px; line-height: 1.5; color: var(--dsw-alias-label-caption); }
84
88
 
85
89
  .smc-input { height: 34px; padding: 0 12px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-3); color: var(--dsw-alias-label-primary); font-size: 13px; line-height: 1.5; outline: none; font-family: inherit; }
86
90
  .smc-input:focus { border-color: var(--dsw-alias-brand-primary); }
@@ -236,6 +240,11 @@ var zhDict = {
236
240
  groupGlobal: "\u5168\u5C40",
237
241
  groupWorkspace: "\u5DE5\u4F5C\u533A",
238
242
  groupBundled: "\u5185\u7F6E\uFF08DSH \u5B98\u65B9\uFF09",
243
+ groupProject: "\u9879\u76EE\u7EA7",
244
+ groupUser: "\u7528\u6237\u7EA7",
245
+ groupPlugin: "\u63D2\u4EF6\u81EA\u5E26",
246
+ groupRuntime: "\u4EE3\u7801\u6CE8\u518C",
247
+ groupRuntimeNote: "\u7531\u63D2\u4EF6\u5728\u8FD0\u884C\u65F6\u6CE8\u518C\uFF0C\u4E0D\u53EF\u5728\u6B64\u7981\u7528",
239
248
  readOnlyBadge: "\u5185\u7F6E\u53EA\u8BFB",
240
249
  noMatch: "\u6CA1\u6709\u5339\u914D\u7684 skill",
241
250
  detailPath: "\u8DEF\u5F84",
@@ -295,6 +304,11 @@ var enDict = {
295
304
  groupGlobal: "Global",
296
305
  groupWorkspace: "Workspace",
297
306
  groupBundled: "Built-in (DSH official)",
307
+ groupProject: "Project",
308
+ groupUser: "User",
309
+ groupPlugin: "Plugin-bundled",
310
+ groupRuntime: "Code-registered",
311
+ groupRuntimeNote: "Registered by a plugin at runtime; cannot be disabled here",
298
312
  readOnlyBadge: "built-in read-only",
299
313
  noMatch: "No matching skills",
300
314
  detailPath: "path",
@@ -835,6 +849,21 @@ function sourceLabel(source, short = false) {
835
849
  const name = pkg.split("/").pop() ?? pkg;
836
850
  return short ? name : `plugin \xB7 ${name}`;
837
851
  }
852
+ var SKILL_GROUP_ORDER = ["project", "user", "plugin", "bundled", "runtime"];
853
+ var SKILL_GROUP_LABEL = {
854
+ project: "groupProject",
855
+ user: "groupUser",
856
+ plugin: "groupPlugin",
857
+ bundled: "groupBundled",
858
+ runtime: "groupRuntime"
859
+ };
860
+ function skillGroupOf(source) {
861
+ if (source === "project-dsh" || source === "project-agents") return "project";
862
+ if (source === "user-dsh" || source === "user-agents") return "user";
863
+ if (source.startsWith("plugin:")) return "plugin";
864
+ if (source === "bundled") return "bundled";
865
+ return "runtime";
866
+ }
838
867
  function skillInNamespace(s, ns) {
839
868
  if (ns === "all") return true;
840
869
  if (ns === "global") {
@@ -927,57 +956,64 @@ function SidebarSkillTab({ visible, cwd }) {
927
956
  k
928
957
  )) })
929
958
  ] }),
930
- /* @__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: [
931
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-srv", title: `${s.description}
932
- ${s.path}`, children: [
933
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-dot${s.modelInvocable ? "" : " idle"}` }),
934
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-name", children: s.name }),
935
- !s.modelInvocable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: t("modelDisabled") }),
936
- !s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: sourceLabel(s.source, true) }),
937
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
938
- "button",
939
- {
940
- type: "button",
941
- className: "smc-btn",
942
- style: { height: 20, padding: "0 6px", fontSize: 11 },
943
- onClick: () => {
944
- openMd(s.path);
945
- },
946
- "aria-label": mdPath === s.path ? t("hideMd") : t("viewMd"),
947
- children: mdPath === s.path ? t("hideMd") : "MD"
948
- }
949
- ),
950
- s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
951
- "button",
952
- {
953
- type: "button",
954
- className: "smc-btn danger",
955
- style: { height: 20, padding: "0 6px", fontSize: 11 },
956
- onClick: () => {
957
- removeSkill(s);
958
- },
959
- "aria-label": t("deleteSkill"),
960
- children: t("remove")
961
- }
962
- ),
963
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
964
- "button",
965
- {
966
- type: "button",
967
- className: `smc-toggle${s.modelInvocable ? " on" : ""}`,
968
- title: t("toggleModelVisible"),
969
- onClick: () => {
970
- void rpc("toggleSkill", { path: s.path }).then(() => {
971
- load();
972
- });
973
- },
974
- "aria-label": s.modelInvocable ? t("disable") : t("enable")
975
- }
976
- )
959
+ /* @__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") }) : SKILL_GROUP_ORDER.map((kind) => ({ kind, items: visibleItems.filter((s) => skillGroupOf(s.source) === kind) })).filter((group) => group.items.length > 0).map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-skill-group", children: [
960
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-skill-group-head", children: [
961
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t(SKILL_GROUP_LABEL[group.kind]) }),
962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-skill-group-count", children: group.items.length })
977
963
  ] }),
978
- s.description !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-srv-desc", children: s.description }),
979
- 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 }) }))
980
- ] }, s.path)) })
964
+ group.kind === "runtime" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-skill-group-note", children: t("groupRuntimeNote") }),
965
+ group.items.map((s) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
966
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "smc-srv", title: `${s.description}
967
+ ${s.path}`, children: [
968
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `smc-dot${s.modelInvocable ? "" : " idle"}` }),
969
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-srv-name", children: s.name }),
970
+ !s.modelInvocable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: t("modelDisabled") }),
971
+ !s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "smc-badge", children: sourceLabel(s.source, true) }),
972
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
973
+ "button",
974
+ {
975
+ type: "button",
976
+ className: "smc-btn",
977
+ style: { height: 20, padding: "0 6px", fontSize: 11 },
978
+ onClick: () => {
979
+ openMd(s.path);
980
+ },
981
+ "aria-label": mdPath === s.path ? t("hideMd") : t("viewMd"),
982
+ children: mdPath === s.path ? t("hideMd") : "MD"
983
+ }
984
+ ),
985
+ s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
986
+ "button",
987
+ {
988
+ type: "button",
989
+ className: "smc-btn danger",
990
+ style: { height: 20, padding: "0 6px", fontSize: 11 },
991
+ onClick: () => {
992
+ removeSkill(s);
993
+ },
994
+ "aria-label": t("deleteSkill"),
995
+ children: t("remove")
996
+ }
997
+ ),
998
+ s.writable && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
999
+ "button",
1000
+ {
1001
+ type: "button",
1002
+ className: `smc-toggle${s.modelInvocable ? " on" : ""}`,
1003
+ title: t("toggleModelVisible"),
1004
+ onClick: () => {
1005
+ void rpc("toggleSkill", { path: s.path }).then(() => {
1006
+ load();
1007
+ });
1008
+ },
1009
+ "aria-label": s.modelInvocable ? t("disable") : t("enable")
1010
+ }
1011
+ )
1012
+ ] }),
1013
+ s.description !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "smc-srv-desc", children: s.description }),
1014
+ 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 }) }))
1015
+ ] }, s.path === "" ? `runtime:${s.name}` : s.path))
1016
+ ] }, group.kind)) })
981
1017
  ] });
982
1018
  }
983
1019
  var inject = ["slots", "connection", "locale"];
package/dist/service.d.ts CHANGED
@@ -163,6 +163,16 @@ export declare class SkillMcpService extends Service {
163
163
  * bundled inside loaded plugin packages, and any configured official roots.
164
164
  */
165
165
  listSkills(cwd?: string): Promise<SkillView[]>;
166
+ /**
167
+ * 补上「运行时注册」的 skill:`ctx.skills.register()` 注册的条目没有 SKILL.md,
168
+ * 磁盘扫描(本项目的主要取数方式)看不到它们——dsh-jubensha 的 DM 手册、office、
169
+ * genui 都属于这一类。用户因此报了「面板少了很多 skill」。
170
+ *
171
+ * skills 服务是**可选**的:web-app 宿主把它关掉(presets 拥有发现,没有全局层可列),
172
+ * 所以这里 feature-detect,拿不到就原样返回空,不改变既有行为。
173
+ * 这些条目没有可写的 SKILL.md,标成 `writable: false`——开关对它们没有意义。
174
+ */
175
+ private runtimeOnlySkills;
166
176
  /** Flip one disk-backed skill's model invocation by rewriting its SKILL.md frontmatter. */
167
177
  toggleSkill(path: string): Promise<SkillView>;
168
178
  /**
package/dist/service.js CHANGED
@@ -268,8 +268,48 @@ export class SkillMcpService extends Service {
268
268
  for (const dir of this.officialSkillDirs) {
269
269
  skills.push(...await scanSkillRoot(dir, 'bundled', false, 'dsh-official'));
270
270
  }
271
+ skills.push(...await this.runtimeOnlySkills(skills));
271
272
  return skills;
272
273
  }
274
+ /**
275
+ * 补上「运行时注册」的 skill:`ctx.skills.register()` 注册的条目没有 SKILL.md,
276
+ * 磁盘扫描(本项目的主要取数方式)看不到它们——dsh-jubensha 的 DM 手册、office、
277
+ * genui 都属于这一类。用户因此报了「面板少了很多 skill」。
278
+ *
279
+ * skills 服务是**可选**的:web-app 宿主把它关掉(presets 拥有发现,没有全局层可列),
280
+ * 所以这里 feature-detect,拿不到就原样返回空,不改变既有行为。
281
+ * 这些条目没有可写的 SKILL.md,标成 `writable: false`——开关对它们没有意义。
282
+ */
283
+ async runtimeOnlySkills(diskBacked) {
284
+ const skills = this.ctx.get('skills');
285
+ if (skills === undefined)
286
+ return [];
287
+ let catalog;
288
+ try {
289
+ catalog = await skills.list();
290
+ }
291
+ catch {
292
+ // 目录服务不可用不该让整个面板失败——磁盘那部分仍然有价值。
293
+ return [];
294
+ }
295
+ const known = new Set(diskBacked.map(skill => skill.name));
296
+ const extra = [];
297
+ for (const entry of catalog) {
298
+ if (known.has(entry.name))
299
+ continue;
300
+ extra.push({
301
+ name: entry.name,
302
+ description: entry.description,
303
+ source: 'runtime',
304
+ provider: entry.provider,
305
+ modelInvocable: entry.invocation.modelInvocable,
306
+ userInvocable: entry.invocation.userInvocable,
307
+ writable: false,
308
+ path: '',
309
+ });
310
+ }
311
+ return extra;
312
+ }
273
313
  /** Flip one disk-backed skill's model invocation by rewriting its SKILL.md frontmatter. */
274
314
  async toggleSkill(path) {
275
315
  let text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-skill-mcp-center",
3
- "version": "0.6.0",
3
+ "version": "0.6.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": {