@sparkelf/dsh-client-ui-skill-center 0.2.1-alpha.29 → 0.2.1-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/plus/ui-skill-center/README.md
5
- README.md: 0402b32b8f3fd4b1e290a3036d335ba119db970c
6
- README.zh.md: 4a1ea41110d11bf7d3de4b91878c27b1d61fa8dc
5
+ README.md: ffac1f7d3aeb0e73c23f99b728b65f5a3e776884
6
+ README.zh.md: 70e530cb1f95d6b365358f8fb96a514ab5379770
package/README.md CHANGED
@@ -14,7 +14,8 @@ A skill center for the dsh Web sidebar: browses loaded skills grouped by source,
14
14
  ## What it does
15
15
 
16
16
  - **Sidebar entry** "Skill Center" opens a panel, registered through the official `sidebar.panellist` slot and the keyed `main` page slot — the same slots the Plugins panel uses, so the shell owns the row, label, and selected state.
17
- - **Skills list**: skills grouped by discovery source (bundled / runtime / `~/.agents/skills` / `~/.dsh/skills` / project `.agents/skills` / project `.dsh/skills` / custom roots), with a search box that filters by name, description, or when-to-use.
17
+ - **Skills list**: skills grouped by discovery source (bundled / runtime / `~/.agents/skills` / `~/.dsh/skills` / project `.agents/skills` / project `.dsh/skills` / custom roots). A card carries the skill's summary — its translated one when a translation sits beside the skill, otherwise the registry description — and a search box filters by name, description, summary, or when-to-use.
18
+ - **Details**: a card opens the skill's own `SKILL.md` body in a scrolling block, under the metadata row. The body is read when the dialog opens, so a long skill costs nothing until it is asked for.
18
19
  - **Enable / disable**: rewrites `disable-model-invocation` in the skill's YAML frontmatter. The field is edited in place rather than re-serialized, so comments, key order, and the body survive.
19
20
  - **Create**: writes a standard `SKILL.md` under the user or project root.
20
21
  - **Delete**: moves the skill into a `.trash` sibling directory, where it can be restored.
@@ -33,7 +34,7 @@ A skill center for the dsh Web sidebar: browses loaded skills grouped by source,
33
34
 
34
35
  Reading goes through the official `ctx.skills` registry, so the catalog and its hot reload are the harness's rather than a second scanner's. The registry's summaries deliberately omit file paths — they are invocation-neutral metadata — so a write resolves the path through `ctx.skills.get()` when an action actually needs to touch a file. `collect()` caches the catalog those calls share, so the resolve is not a second scan.
35
36
 
36
- The three writes have no official equivalent; the registry is read-only.
37
+ The three writes have no official equivalent; the registry is read-only. The content route resolves the file the panel listed first, then through `ctx.skills.get()`, then through the configured `translationRoot`: a bundled skill serves its body from inside an installed package, where the registry reports a path or a provider raises, so one source is never the only answer.
37
38
 
38
39
  Every colour, border, and label weight comes from the shared alias tokens (`--dsw-alias-*`), and the sidebar glyph is `IconSkillOutline16` from `@deepseek-ai/dsh-client-ui-primitives`, so the panel and its row match the official panels beside them.
39
40
 
@@ -54,7 +55,7 @@ None; no string this package owns reaches a model request, so a request's cachea
54
55
  No runtime invariant companion is published. The package owns no durable package-local state: the route handlers read the skill registry and write skill files, and every observable outcome is covered by the route tests.
55
56
 
56
57
  - **Deletion refuses a symlinked skill**: a skill discovered through a symlink has no single owning directory to move, so the delete action is withheld for it.
57
- - **The list omits file paths**: whether a skill can be deleted is therefore not known until the delete is attempted, which then reports it.
58
+ - **The list omits file paths**: a registry-served row has no path, so the panel can neither offer its deletion nor read its body by path; whether either applies is known only when the action is attempted.
58
59
  - **Grouping follows the discovery source label, not the file path**: a provider that reports an unrecognized source lands in the custom group.
59
60
 
60
61
  <a id="dev-note"></a>
package/README.zh.md CHANGED
@@ -16,7 +16,8 @@ dsh Web 侧边栏的技能中心:按来源分组浏览已加载的技能、启
16
16
  ## 功能
17
17
 
18
18
  - **侧边栏入口**「技能中心」打开一个面板,它通过官方 `sidebar.panellist` 槽位与键控的 `main` 页面槽位注册 —— 与「插件」面板用的是同一批槽位,因此行、标签与选中态由外壳负责。
19
- - **技能列表**:按发现来源分组(系统内置 / 运行时 / `~/.agents/skills` / `~/.dsh/skills` / 项目 `.agents/skills` / 项目 `.dsh/skills` / 自定义目录),并有一个搜索框按名称、描述或何时使用过滤。
19
+ - **技能列表**:按发现来源分组(系统内置 / 运行时 / `~/.agents/skills` / `~/.dsh/skills` / 项目 `.agents/skills` / 项目 `.dsh/skills` / 自定义目录)。卡片只带一句简介 —— 技能旁有翻译时用译文,否则用注册表的描述 —— 搜索框按名称、描述、简介或何时使用过滤。
20
+ - **详情**:卡片打开后,在元数据行下方以滚动区块展示该技能自己的 `SKILL.md` 正文。正文在弹窗打开时才读取,因此长技能在没人点开前不产生任何开销。
20
21
  - **启用 / 停用**:改写技能 YAML 前置块里的 `disable-model-invocation`。该字段是就地编辑而非重新序列化,因此注释、键顺序与正文都原样保留。
21
22
  - **新建**:在用户级或项目级根目录下写入一个标准的 `SKILL.md`。
22
23
  - **删除**:把技能移入同级的 `.trash` 目录,可从中恢复。
@@ -37,7 +38,7 @@ dsh Web 侧边栏的技能中心:按来源分组浏览已加载的技能、启
37
38
 
38
39
  读取走官方的 `ctx.skills` 注册表,因此目录与其热更新属于 harness 本身,而不是第二套扫描器。注册表的摘要有意省略文件路径 —— 它们是调用无关元数据 —— 所以写操作在真正需要触碰文件时通过 `ctx.skills.get()` 解析路径。这些调用共享的目录由 `collect()` 缓存,因此该解析不是第二次全量扫描。
39
40
 
40
- 三个写操作在官方没有等价物;注册表是只读的。
41
+ 三个写操作在官方没有等价物;注册表是只读的。正文路由先解析面板列出的文件,再走 `ctx.skills.get()`,最后走配置的 `translationRoot`:内置技能的正文安装在已安装的包内,注册表要么报告路径、要么提供者抛错,因此任何单一来源都不能是唯一答案。
41
42
 
42
43
  每一个颜色、边框与标签字重都来自共享别名 token(`--dsw-alias-*`),侧边栏图标是 `@deepseek-ai/dsh-client-ui-primitives` 的 `IconSkillOutline16`,因此面板与其行与旁边的官方面板一致。
43
44
 
@@ -61,7 +62,7 @@ host 半边挂载 `/api/dsh-skill-center` 路由族;client 半边注册侧边
61
62
  No runtime invariant companion is published. 本包不拥有任何持久化的包内状态:路由处理器读取技能注册表并写入技能文件,每一个可观察结果都由路由测试覆盖。
62
63
 
63
64
  - **删除会拒绝符号链接的技能**:通过符号链接发现的技能没有唯一归属目录可移动,因此对它不提供删除操作。
64
- - **列表省略文件路径**:因此某个技能能否删除,要等到删除被尝试时才知道,那时才会报告。
65
+ - **列表省略文件路径**:注册表提供的行没有路径,因此面板既无法为它提供删除,也无法按路径读取它的正文;这两者是否适用,只有真正执行该操作时才知道。
65
66
  - **分组跟随发现来源标签,而不是文件路径**:报告了无法识别的来源的提供者会落到自定义分组。
66
67
 
67
68
  <a id="dev-note"></a>
package/lib/client.js CHANGED
@@ -8,7 +8,7 @@ window.__ModuleLoader__.load({
8
8
  let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
9
9
  let react_jsx_runtime = require("react/jsx-runtime");
10
10
  //#region \0dsh-css:/root/projects/deepseek-harness-plus/packages/plus/ui-skill-center/src/client/SkillCenterPage.module.css.mjs
11
- const css = ".DGiSta_page{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);flex-direction:column;align-items:center;gap:32px;padding:28px clamp(24px,4vw,48px) 48px;display:flex;overflow:auto}.DGiSta_page>*{width:100%;max-width:960px}.DGiSta_pageHead{justify-content:space-between;align-items:flex-start;gap:16px;display:flex}.DGiSta_pageTitle{margin:0;font-size:20px;font-weight:500;line-height:28px}.DGiSta_pageIntro{color:var(--dsw-alias-label-secondary);margin:4px 0 0;font-size:13px;line-height:20px}.DGiSta_toolbar{justify-content:flex-end;align-items:center;gap:16px;display:flex}.DGiSta_iconButton{width:28px;height:28px;color:var(--dsw-alias-label-caption);cursor:pointer;background:0 0;border:0;border-radius:28px;justify-content:center;align-items:center;display:inline-flex}.DGiSta_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.DGiSta_iconButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_iconWrap{display:inline-flex}.DGiSta_button{border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);cursor:pointer;background:0 0;border-radius:8px;padding:0 12px;font-size:13px}.DGiSta_button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.DGiSta_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_search{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);width:100%;height:36px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:10px;padding:0 12px;font-size:13px}.DGiSta_search::placeholder{color:var(--dsw-alias-label-tertiary)}.DGiSta_group{margin-top:28px}.DGiSta_groupTitle{align-items:baseline;gap:8px;margin:0 0 12px;font-size:15px;font-weight:500;line-height:22px;display:flex}.DGiSta_groupCount{color:var(--dsw-alias-label-tertiary);font-size:13px;font-weight:400}.DGiSta_groupHint{color:var(--dsw-alias-label-caption);font-size:12px;font-weight:400}.DGiSta_cards{grid-template-columns:repeat(2,minmax(0,1fr));align-items:start;gap:10px;margin:0;padding:0;list-style:none;display:grid}.DGiSta_card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:12px;flex-direction:column;align-self:start;gap:12px;min-width:0;padding:12px 14px;display:flex}.DGiSta_cardHead{align-items:center;gap:10px;min-width:0;display:flex}.DGiSta_cardIcon{background:var(--dsw-alias-bg-layer-2);width:28px;height:28px;color:var(--dsw-alias-label-secondary);border-radius:50%;flex:none;place-items:center;display:grid}.DGiSta_cardForeign{opacity:.72}.DGiSta_workspaceBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);vertical-align:middle;border-radius:4px;margin-left:8px;padding:1px 6px;font-size:11px;font-weight:400;line-height:16px}.DGiSta_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:15px;font-weight:600;line-height:22px;overflow:hidden}.DGiSta_cardTitleRow{flex:1;align-items:center;gap:8px;min-width:0;display:flex}.DGiSta_cardDesc{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}.DGiSta_cardWhenToUse{color:var(--dsw-alias-label-caption);font-size:12px;line-height:18px}.DGiSta_cardMarks{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.DGiSta_mark{border:1px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-secondary);white-space:nowrap;border-radius:4px;padding:1px 6px;font-size:11px;line-height:16px}.DGiSta_markOn{border-color:var(--dsw-alias-state-business-primary);color:var(--dsw-alias-state-business-primary)}.DGiSta_markOff{border-color:var(--dsw-alias-border-l4);color:var(--dsw-alias-label-caption)}.DGiSta_markDanger{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}.DGiSta_cardEnd{flex:none;justify-content:space-between;align-items:center;gap:8px;margin-top:auto;padding-top:2px;display:flex}.DGiSta_cardBody{flex-direction:column;gap:6px;min-width:0;display:flex}.DGiSta_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:10px;width:36px;height:20px;transition:background .12s;position:relative}.DGiSta_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_switchOn{background:var(--dsw-alias-state-success-primary)}.DGiSta_switch:after{content:\"\";background:var(--dsw-alias-bg-layer-1);border-radius:50%;width:16px;height:16px;transition:transform .12s;position:absolute;top:2px;left:2px}.DGiSta_switchOn:after{transform:translate(16px)}.DGiSta_dialog{width:min(560px,100%)}.DGiSta_form{flex-direction:column;gap:12px;display:flex}.DGiSta_field{flex-direction:column;gap:4px;display:flex}.DGiSta_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.DGiSta_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;padding:0 10px;font-size:13px}.DGiSta_textarea{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);min-height:80px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-code-block-small);resize:vertical;background:0 0;border-radius:8px;padding:8px 10px;font-size:12px}.DGiSta_status{text-align:center;color:var(--dsw-alias-label-tertiary);padding:32px 0;font-size:13px}.DGiSta_statusError{color:var(--dsw-alias-state-error-primary)}";
11
+ const css = ".DGiSta_page{box-sizing:border-box;height:100%;color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);flex-direction:column;align-items:center;gap:16px;padding:24px clamp(24px,4vw,40px) 40px;display:flex;overflow:auto}.DGiSta_page>*{width:100%;max-width:960px}.DGiSta_pageHead{justify-content:space-between;align-items:center;gap:16px;display:flex}.DGiSta_pageTitle{margin:0;font-size:20px;font-weight:500;line-height:28px}.DGiSta_toolbar{justify-content:flex-end;align-items:center;gap:16px;display:flex}.DGiSta_iconButton{width:28px;height:28px;color:var(--dsw-alias-label-caption);cursor:pointer;background:0 0;border:0;border-radius:28px;justify-content:center;align-items:center;display:inline-flex}.DGiSta_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}.DGiSta_iconButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_iconWrap{display:inline-flex}.DGiSta_search{width:100%}.DGiSta_group{min-width:0}.DGiSta_groupTitle{align-items:baseline;gap:8px;margin:0 0 8px;font-size:14px;font-weight:500;line-height:20px;display:flex}.DGiSta_groupCount{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400}.DGiSta_cards{grid-template-columns:repeat(auto-fill,minmax(272px,1fr));align-items:start;gap:12px;margin:0;padding:0;list-style:none;display:grid}.DGiSta_card{border:.5px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:10px;flex-direction:column;align-self:start;gap:10px;min-width:0;padding:12px;display:flex}.DGiSta_cardHead{align-items:center;gap:10px;min-width:0;display:flex}.DGiSta_cardIcon{corner-shape:round;background:var(--dsw-alias-bg-layer-2);width:28px;height:28px;color:var(--dsw-alias-label-secondary);border-radius:50%;flex:none;place-items:center;display:grid}.DGiSta_cardForeign{opacity:.72}.DGiSta_workspaceBadge{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);vertical-align:middle;border-radius:4px;margin-left:8px;padding:1px 6px;font-size:11px;font-weight:400;line-height:16px}.DGiSta_cardTitle{text-overflow:ellipsis;white-space:nowrap;font-size:14px;font-weight:600;line-height:20px;overflow:hidden}.DGiSta_cardTitleRow{flex:1;align-items:center;gap:8px;min-width:0;display:flex}.DGiSta_cardMarks{flex-wrap:wrap;gap:6px;margin-top:2px;display:flex}.DGiSta_mark{color:var(--dsw-alias-state-business-primary);white-space:nowrap;background:#ecf1fc;border:0;border-radius:4px;padding:2px 7px;font-size:11px;font-weight:500;line-height:16px}.DGiSta_markOff,.DGiSta_markDanger{background:#ecf1fc}.DGiSta_detail{flex-direction:column;gap:12px;margin:0;display:flex}.DGiSta_cardActions{flex:none;align-items:center;gap:4px;display:inline-flex}.DGiSta_detailRow{flex-direction:column;gap:2px;display:flex}.DGiSta_detailLabel{color:var(--dsw-alias-label-caption);font-size:11px;line-height:16px}.DGiSta_detailValue{color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere;margin:0;font-size:12px;line-height:18px}.DGiSta_cardEnd{flex:none;justify-content:space-between;align-items:center;gap:8px;margin-top:auto;padding-top:2px;display:flex}.DGiSta_cardBody{flex-direction:column;gap:6px;min-width:0;display:flex}.DGiSta_switch{background:var(--dsw-alias-border-l3);cursor:pointer;border:none;border-radius:10px;width:36px;height:20px;transition:background .12s;position:relative}.DGiSta_switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}.DGiSta_switchOn{background:var(--dsw-alias-state-success-primary)}.DGiSta_switch:after{content:\"\";corner-shape:round;background:var(--dsw-alias-bg-layer-1);border-radius:50%;width:16px;height:16px;transition:transform .12s;position:absolute;top:2px;left:2px}.DGiSta_switchOn:after{transform:translate(16px)}.DGiSta_dialog{width:min(560px,100%)}.DGiSta_form{flex-direction:column;gap:12px;display:flex}.DGiSta_field{flex-direction:column;gap:4px;display:flex}.DGiSta_fieldLabel{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}.DGiSta_input{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);height:32px;color:var(--dsw-alias-label-primary);background:0 0;border-radius:8px;padding:0 10px;font-size:13px}.DGiSta_textarea{box-sizing:border-box;border:.5px solid var(--dsw-alias-border-l3);min-height:80px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-code-block-small);resize:vertical;background:0 0;border-radius:8px;padding:8px 10px;font-size:12px}.DGiSta_status{text-align:center;color:var(--dsw-alias-label-tertiary);padding:32px 0;font-size:13px}.DGiSta_statusError{color:var(--dsw-alias-state-error-primary)}.DGiSta_cardSummary{color:var(--dsw-alias-label-secondary);-webkit-line-clamp:2;-webkit-box-orient:vertical;font-size:12px;line-height:18px;display:-webkit-box;overflow:hidden}.DGiSta_body{flex-direction:column;gap:4px;min-width:0;margin-top:16px;display:flex}.DGiSta_bodyLabel{color:var(--dsw-alias-label-caption);margin:0;font-size:11px;font-weight:500;line-height:16px}.DGiSta_bodyPre{background:var(--dsw-alias-markdown-code-block);max-height:46vh;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-markdown-code-block-small-font-family);font-size:var(--dsw-font-markdown-code-block-small-font-size);line-height:var(--dsw-font-markdown-code-block-small-line-height);white-space:pre-wrap;overflow-wrap:anywhere;--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:8px;margin:0;padding:12px;overflow:auto}.DGiSta_bodyStatus{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:18px}.DGiSta_bodyError{color:var(--dsw-alias-state-error-primary);}.DGiSta_progressRow{align-items:center;gap:8px;margin-bottom:12px;display:flex}.DGiSta_progressLabel{color:var(--dsw-alias-label-secondary);white-space:nowrap;font-size:12px;line-height:18px}.DGiSta_progressTrack{background:var(--dsw-alias-border-l2);border-radius:2px;flex:1;height:4px;overflow:hidden}.DGiSta_progressFill{background:var(--dsw-alias-label-primary);border-radius:2px;height:100%;transition:width .2s;display:block}.DGiSta_progressCount{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;white-space:nowrap;font-size:12px;line-height:18px}";
12
12
  const tagId = "@sparkelf/dsh-client-ui-skill-center/SkillCenterPage.module.css";
13
13
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
14
14
  const tag = document.createElement("style");
@@ -18,26 +18,33 @@ window.__ModuleLoader__.load({
18
18
  document.head.appendChild(tag);
19
19
  }
20
20
  var SkillCenterPage_module_css_default = {
21
- "button": "DGiSta_button",
21
+ "body": "DGiSta_body",
22
+ "bodyError": "DGiSta_bodyError",
23
+ "bodyLabel": "DGiSta_bodyLabel",
24
+ "bodyPre": "DGiSta_bodyPre",
25
+ "bodyStatus": "DGiSta_bodyStatus",
22
26
  "card": "DGiSta_card",
27
+ "cardActions": "DGiSta_cardActions",
23
28
  "cardBody": "DGiSta_cardBody",
24
- "cardDesc": "DGiSta_cardDesc",
25
29
  "cardEnd": "DGiSta_cardEnd",
26
30
  "cardForeign": "DGiSta_cardForeign",
27
31
  "cardHead": "DGiSta_cardHead",
28
32
  "cardIcon": "DGiSta_cardIcon",
29
33
  "cardMarks": "DGiSta_cardMarks",
34
+ "cardSummary": "DGiSta_cardSummary",
30
35
  "cardTitle": "DGiSta_cardTitle",
31
36
  "cardTitleRow": "DGiSta_cardTitleRow",
32
- "cardWhenToUse": "DGiSta_cardWhenToUse",
33
37
  "cards": "DGiSta_cards",
38
+ "detail": "DGiSta_detail",
39
+ "detailLabel": "DGiSta_detailLabel",
40
+ "detailRow": "DGiSta_detailRow",
41
+ "detailValue": "DGiSta_detailValue",
34
42
  "dialog": "DGiSta_dialog",
35
43
  "field": "DGiSta_field",
36
44
  "fieldLabel": "DGiSta_fieldLabel",
37
45
  "form": "DGiSta_form",
38
46
  "group": "DGiSta_group",
39
47
  "groupCount": "DGiSta_groupCount",
40
- "groupHint": "DGiSta_groupHint",
41
48
  "groupTitle": "DGiSta_groupTitle",
42
49
  "iconButton": "DGiSta_iconButton",
43
50
  "iconWrap": "DGiSta_iconWrap",
@@ -45,11 +52,14 @@ window.__ModuleLoader__.load({
45
52
  "mark": "DGiSta_mark",
46
53
  "markDanger": "DGiSta_markDanger",
47
54
  "markOff": "DGiSta_markOff",
48
- "markOn": "DGiSta_markOn",
49
55
  "page": "DGiSta_page",
50
56
  "pageHead": "DGiSta_pageHead",
51
- "pageIntro": "DGiSta_pageIntro",
52
57
  "pageTitle": "DGiSta_pageTitle",
58
+ "progressCount": "DGiSta_progressCount",
59
+ "progressFill": "DGiSta_progressFill",
60
+ "progressLabel": "DGiSta_progressLabel",
61
+ "progressRow": "DGiSta_progressRow",
62
+ "progressTrack": "DGiSta_progressTrack",
53
63
  "search": "DGiSta_search",
54
64
  "status": "DGiSta_status",
55
65
  "statusError": "DGiSta_statusError",
@@ -72,7 +82,9 @@ window.__ModuleLoader__.load({
72
82
  list: "/api/dsh-skill-center/list",
73
83
  setEnabled: "/api/dsh-skill-center/set-enabled",
74
84
  create: "/api/dsh-skill-center/create",
75
- remove: "/api/dsh-skill-center/delete"
85
+ remove: "/api/dsh-skill-center/delete",
86
+ content: "/api/dsh-skill-center/content",
87
+ translationProgress: "/api/dsh-skill-center/translation-progress"
76
88
  };
77
89
  /** One thrown API error carrying the host's message. */
78
90
  var ApiError = class extends Error {};
@@ -111,6 +123,27 @@ window.__ModuleLoader__.load({
111
123
  }
112
124
  });
113
125
  }
126
+ /**
127
+ * One skill's instruction body, without the YAML frontmatter the card already
128
+ * shows as metadata.
129
+ *
130
+ * The path the list served travels back with the request: a row from another
131
+ * workspace is read from the file that was listed rather than from whatever the
132
+ * viewing workspace's registry resolves by name.
133
+ */
134
+ async content(name, path, cwd) {
135
+ const params = new URLSearchParams({ name });
136
+ if (path !== void 0 && path !== "") params.set("path", path);
137
+ if (cwd !== void 0 && cwd !== "") params.set("cwd", cwd);
138
+ return this.request(API.content + "?" + params.toString());
139
+ }
140
+ /**
141
+ * The translated share of the skill set, or undefined when the host has not
142
+ * been told where the translations live (the route then answers `{}`).
143
+ */
144
+ async translationProgress() {
145
+ return (await this.request(API.translationProgress)).progress;
146
+ }
114
147
  async request(path, options = {}) {
115
148
  const init = { method: options.method ?? "GET" };
116
149
  if (options.body !== void 0) {
@@ -157,13 +190,59 @@ window.__ModuleLoader__.load({
157
190
  });
158
191
  }
159
192
  /**
160
- * One skill row.
161
- * @param props - the entry, its toggle, and its delete action.
193
+ * One skill row, with the dialog that shows the skill's own Markdown.
194
+ * @param props - the entry, the API client and viewing context its body is read
195
+ * with, its toggle, and its delete action.
162
196
  * @returns the row.
163
197
  */
164
- function SkillCard({ skill, t, onToggle, onDelete }) {
198
+ function SkillCard({ skill, api, cwd, t, onToggle, onDelete }) {
199
+ const [open, setOpen] = (0, react.useState)(false);
200
+ const [body, setBody] = (0, react.useState)({ kind: "loading" });
201
+ (0, react.useEffect)(() => {
202
+ if (!open) return;
203
+ let cancelled = false;
204
+ setBody({ kind: "loading" });
205
+ api.content(skill.name, skill.path, cwd).then((payload) => {
206
+ if (!cancelled) setBody({
207
+ kind: "ready",
208
+ content: payload.content
209
+ });
210
+ }).catch((error) => {
211
+ if (!cancelled) setBody({
212
+ kind: "failed",
213
+ message: error instanceof Error ? error.message : String(error)
214
+ });
215
+ });
216
+ return () => {
217
+ cancelled = true;
218
+ };
219
+ }, [
220
+ open,
221
+ api,
222
+ skill.name,
223
+ skill.path,
224
+ cwd
225
+ ]);
226
+ const foreign = skill.isActiveWorkspace === false && skill.workspaceName !== void 0;
227
+ const details = [];
228
+ if (skill.whenToUse !== void 0) details.push({
229
+ key: "detail.whenToUse",
230
+ value: skill.whenToUse
231
+ });
232
+ details.push({
233
+ key: "detail.source",
234
+ value: skill.level
235
+ });
236
+ if (skill.provider !== void 0) details.push({
237
+ key: "detail.provider",
238
+ value: skill.provider
239
+ });
240
+ if (skill.path !== void 0) details.push({
241
+ key: "detail.path",
242
+ value: skill.path
243
+ });
165
244
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
166
- className: skill.isActiveWorkspace === false && skill.workspaceName !== void 0 ? SkillCenterPage_module_css_default.card + " " + SkillCenterPage_module_css_default.cardForeign : SkillCenterPage_module_css_default.card,
245
+ className: foreign ? SkillCenterPage_module_css_default.card + " " + SkillCenterPage_module_css_default.cardForeign : SkillCenterPage_module_css_default.card,
167
246
  children: [
168
247
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
169
248
  className: SkillCenterPage_module_css_default.cardHead,
@@ -174,7 +253,7 @@ window.__ModuleLoader__.load({
174
253
  className: SkillCenterPage_module_css_default.cardTitleRow,
175
254
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
176
255
  className: SkillCenterPage_module_css_default.cardTitle,
177
- children: skill.name
256
+ children: skill.i18n?.title ?? skill.name
178
257
  }), skill.workspaceName === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
179
258
  className: SkillCenterPage_module_css_default.workspaceBadge,
180
259
  children: skill.workspaceName
@@ -183,33 +262,26 @@ window.__ModuleLoader__.load({
183
262
  }),
184
263
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
185
264
  className: SkillCenterPage_module_css_default.cardBody,
186
- children: [
187
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
188
- className: SkillCenterPage_module_css_default.cardDesc,
189
- children: skill.description
190
- }),
191
- skill.whenToUse === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
192
- className: SkillCenterPage_module_css_default.cardWhenToUse,
193
- children: skill.whenToUse
194
- }),
195
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
196
- className: SkillCenterPage_module_css_default.cardMarks,
197
- children: [
198
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
199
- className: skill.modelInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
200
- children: t("skill.modelInvocable")
201
- }),
202
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
203
- className: skill.userInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
204
- children: t("skill.userInvocable")
205
- }),
206
- skill.linked === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
207
- className: SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markDanger,
208
- children: t("skill.linked")
209
- }) : null
210
- ]
211
- })
212
- ]
265
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
266
+ className: SkillCenterPage_module_css_default.cardSummary,
267
+ children: skill.i18n?.summary ?? skill.description
268
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
269
+ className: SkillCenterPage_module_css_default.cardMarks,
270
+ children: [
271
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
272
+ className: skill.modelInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
273
+ children: t("skill.modelInvocable")
274
+ }),
275
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
276
+ className: skill.userInvocable ? SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOn : SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markOff,
277
+ children: t("skill.userInvocable")
278
+ }),
279
+ skill.linked === true ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
280
+ className: SkillCenterPage_module_css_default.mark + " " + SkillCenterPage_module_css_default.markDanger,
281
+ children: t("skill.linked")
282
+ }) : null
283
+ ]
284
+ })]
213
285
  }),
214
286
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
215
287
  className: SkillCenterPage_module_css_default.cardEnd,
@@ -221,13 +293,77 @@ window.__ModuleLoader__.load({
221
293
  onClick: () => {
222
294
  onToggle(skill, !skill.modelInvocable);
223
295
  }
224
- }), skill.path === void 0 || skill.linked === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
225
- type: "button",
226
- className: SkillCenterPage_module_css_default.button,
296
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
297
+ className: SkillCenterPage_module_css_default.cardActions,
298
+ children: [skill.path === void 0 || skill.linked === true ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
299
+ variant: "outline",
300
+ size: "sm",
301
+ onClick: () => {
302
+ onDelete(skill);
303
+ },
304
+ children: t("action.delete")
305
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
306
+ variant: "outline",
307
+ size: "sm",
308
+ onClick: () => {
309
+ setOpen(true);
310
+ },
311
+ children: t("action.showDetail")
312
+ })]
313
+ })]
314
+ }),
315
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_deepseek_ai_dsh_client_ui_primitives.Modal, {
316
+ open,
317
+ onClose: () => {
318
+ setOpen(false);
319
+ },
320
+ title: skill.name,
321
+ closeLabel: t("action.close"),
322
+ className: SkillCenterPage_module_css_default.dialog,
323
+ footer: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
324
+ variant: "outline",
227
325
  onClick: () => {
228
- onDelete(skill);
326
+ setOpen(false);
229
327
  },
230
- children: t("action.delete")
328
+ children: t("action.close")
329
+ }),
330
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
331
+ className: SkillCenterPage_module_css_default.detail,
332
+ children: details.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
333
+ className: SkillCenterPage_module_css_default.detailRow,
334
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
335
+ className: SkillCenterPage_module_css_default.detailLabel,
336
+ children: t(item.key)
337
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", {
338
+ className: SkillCenterPage_module_css_default.detailValue,
339
+ children: item.value
340
+ })]
341
+ }, item.key))
342
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
343
+ className: SkillCenterPage_module_css_default.body,
344
+ children: [
345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
346
+ className: SkillCenterPage_module_css_default.bodyLabel,
347
+ children: t("detail.content")
348
+ }),
349
+ body.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
350
+ className: SkillCenterPage_module_css_default.bodyStatus,
351
+ children: t("content.loading")
352
+ }) : null,
353
+ body.kind === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
354
+ className: SkillCenterPage_module_css_default.bodyError,
355
+ children: [
356
+ t("content.failed"),
357
+ ": ",
358
+ body.message
359
+ ]
360
+ }) : null,
361
+ body.kind === "ready" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
362
+ className: SkillCenterPage_module_css_default.bodyPre,
363
+ "data-skill-body": skill.name,
364
+ children: body.content
365
+ }) : null
366
+ ]
231
367
  })]
232
368
  })
233
369
  ]
@@ -242,6 +378,7 @@ window.__ModuleLoader__.load({
242
378
  const api = (0, react.useMemo)(() => new SkillApi(), []);
243
379
  const [state, setState] = (0, react.useState)({ kind: "loading" });
244
380
  const [query, setQuery] = (0, react.useState)("");
381
+ const [progress, setProgress] = (0, react.useState)(void 0);
245
382
  const [creating, setCreating] = (0, react.useState)(false);
246
383
  const [draft, setDraft] = (0, react.useState)({
247
384
  name: "",
@@ -271,13 +408,22 @@ window.__ModuleLoader__.load({
271
408
  (0, react.useEffect)(() => {
272
409
  load();
273
410
  }, [load]);
411
+ (0, react.useEffect)(() => {
412
+ let cancelled = false;
413
+ api.translationProgress().then((p) => {
414
+ if (!cancelled) setProgress(p);
415
+ }).catch(() => {});
416
+ return () => {
417
+ cancelled = true;
418
+ };
419
+ }, [api]);
274
420
  const groups = (0, react.useMemo)(() => {
275
421
  if (state.kind !== "ready") return [];
276
422
  const q = query.trim().toLowerCase();
277
423
  if (q === "") return state.payload.groups;
278
424
  return state.payload.groups.map((g) => ({
279
425
  ...g,
280
- skills: g.skills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || (s.whenToUse ?? "").toLowerCase().includes(q))
426
+ skills: g.skills.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || (s.i18n?.title ?? "").toLowerCase().includes(q) || (s.i18n?.description ?? "").toLowerCase().includes(q) || (s.i18n?.summary ?? "").toLowerCase().includes(q) || (s.whenToUse ?? "").toLowerCase().includes(q))
281
427
  })).filter((g) => g.skills.length > 0);
282
428
  }, [state, query]);
283
429
  const toggle = (0, react.useCallback)(async (skill, enabled) => {
@@ -352,13 +498,10 @@ window.__ModuleLoader__.load({
352
498
  children: [
353
499
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
354
500
  className: SkillCenterPage_module_css_default.pageHead,
355
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
501
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
356
502
  className: SkillCenterPage_module_css_default.pageTitle,
357
503
  children: t("panel")
358
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
359
- className: SkillCenterPage_module_css_default.pageIntro,
360
- children: t("panel.intro")
361
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
504
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
362
505
  className: SkillCenterPage_module_css_default.toolbar,
363
506
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
364
507
  type: "button",
@@ -464,14 +607,40 @@ window.__ModuleLoader__.load({
464
607
  ]
465
608
  })
466
609
  }),
467
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
610
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
468
611
  className: SkillCenterPage_module_css_default.search,
612
+ icon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconSearchOutlineRegular, {}),
469
613
  placeholder: t("search.placeholder"),
614
+ "aria-label": t("search.placeholder"),
470
615
  value: query,
471
616
  onChange: (e) => {
472
617
  setQuery(e.target.value);
473
618
  }
474
619
  }),
620
+ progress === void 0 || progress.done >= progress.total ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
621
+ className: SkillCenterPage_module_css_default.progressRow,
622
+ children: [
623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
624
+ className: SkillCenterPage_module_css_default.progressLabel,
625
+ children: t("translate.label")
626
+ }),
627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
628
+ className: SkillCenterPage_module_css_default.progressTrack,
629
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
630
+ className: SkillCenterPage_module_css_default.progressFill,
631
+ style: { width: String(progress.total === 0 ? 0 : Math.round(progress.done / progress.total * 100)) + "%" }
632
+ })
633
+ }),
634
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
635
+ className: SkillCenterPage_module_css_default.progressCount,
636
+ children: [
637
+ progress.done,
638
+ " / ",
639
+ progress.total
640
+ ]
641
+ })
642
+ ]
643
+ }),
475
644
  state.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
476
645
  className: SkillCenterPage_module_css_default.status,
477
646
  children: t("state.loading")
@@ -496,21 +665,16 @@ window.__ModuleLoader__.load({
496
665
  className: SkillCenterPage_module_css_default.group,
497
666
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
498
667
  className: SkillCenterPage_module_css_default.groupTitle,
499
- children: [
500
- group.title,
501
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
502
- className: SkillCenterPage_module_css_default.groupCount,
503
- children: group.skills.length
504
- }),
505
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
506
- className: SkillCenterPage_module_css_default.groupHint,
507
- children: group.hint
508
- })
509
- ]
668
+ children: [group.title, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
669
+ className: SkillCenterPage_module_css_default.groupCount,
670
+ children: group.skills.length
671
+ })]
510
672
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
511
673
  className: SkillCenterPage_module_css_default.cards,
512
674
  children: group.skills.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillCard, {
513
675
  skill,
676
+ api,
677
+ cwd: state.kind === "ready" ? state.payload.cwd : scope,
514
678
  t,
515
679
  onToggle: (s, enabled) => {
516
680
  toggle(s, enabled);
@@ -540,18 +704,26 @@ window.__ModuleLoader__.load({
540
704
  /** Simplified Chinese dictionary. */
541
705
  const zh = {
542
706
  "panel": "技能中心",
543
- "panel.intro": "浏览已加载的技能,启用或停用模型调用",
544
707
  "search.placeholder": "搜索技能…",
545
- "search.clear": "清除搜索",
546
708
  "search.noMatches": "无匹配技能",
547
709
  "action.refresh": "刷新",
548
710
  "action.create": "新建技能",
549
711
  "action.delete": "删除",
712
+ "action.showDetail": "详情",
713
+ "action.close": "关闭",
714
+ "translate.label": "中文翻译",
715
+ "detail.whenToUse": "何时使用",
716
+ "detail.source": "来源",
717
+ "detail.provider": "提供方",
718
+ "detail.path": "位置",
719
+ "detail.content": "技能内容",
550
720
  "action.cancel": "取消",
551
721
  "action.confirm": "确定",
552
722
  "state.loading": "正在加载技能…",
553
723
  "state.empty": "该分组暂无技能",
554
724
  "state.failed": "技能列表加载失败",
725
+ "content.loading": "正在读取技能正文…",
726
+ "content.failed": "技能正文加载失败",
555
727
  "skill.enabled": "已启用",
556
728
  "skill.disabled": "已停用",
557
729
  "skill.modelInvocable": "模型可调用",
@@ -570,18 +742,26 @@ window.__ModuleLoader__.load({
570
742
  /** English dictionary. */
571
743
  const en = {
572
744
  "panel": "Skill Center",
573
- "panel.intro": "Browse loaded skills and control model invocation",
574
745
  "search.placeholder": "Search skills…",
575
- "search.clear": "Clear search",
576
746
  "search.noMatches": "No matching skills",
577
747
  "action.refresh": "Refresh",
578
748
  "action.create": "New skill",
579
749
  "action.delete": "Delete",
750
+ "action.showDetail": "Details",
751
+ "action.close": "Close",
752
+ "translate.label": "Chinese",
753
+ "detail.whenToUse": "When to use",
754
+ "detail.source": "Source",
755
+ "detail.provider": "Provider",
756
+ "detail.path": "Location",
757
+ "detail.content": "Skill content",
580
758
  "action.cancel": "Cancel",
581
759
  "action.confirm": "Confirm",
582
760
  "state.loading": "Loading skills…",
583
761
  "state.empty": "No skills in this group",
584
762
  "state.failed": "Could not load the skill list",
763
+ "content.loading": "Loading the skill content…",
764
+ "content.failed": "Could not load the skill content",
585
765
  "skill.enabled": "Enabled",
586
766
  "skill.disabled": "Disabled",
587
767
  "skill.modelInvocable": "Model invocable",
package/lib/index.js CHANGED
@@ -2,11 +2,75 @@ import { existsSync, lstatSync } from "node:fs";
2
2
  import { mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
3
3
  import { homedir } from "node:os";
4
4
  import { basename, dirname, join, resolve } from "node:path";
5
+ //#region lib/types/translations.js
6
+ /**
7
+ * Chinese metadata for the skill center, read from each skill's own directory.
8
+ *
9
+ * The panel renders what the official registry routes on, so the registry's English
10
+ * description is what decides whether the model invokes a skill. Replacing it would
11
+ * change that decision, so a translation is carried alongside and applied only at
12
+ * render time: the card shows Chinese, the model keeps reading English.
13
+ *
14
+ * A skill keeps its translation in its own directory (`<skill>/i18n/zh.json`), which
15
+ * lets a skill and its Chinese travel together and ship from one clone. The skill
16
+ * file itself is located through the registry, so the translation is found even when
17
+ * the skill resolves through a symlink into another repository.
18
+ *
19
+ * @module @sparkelf/dsh-client-ui-skill-center/translations
20
+ */
21
+ /**
22
+ * Read one skill's translation from beside its resolved skill file.
23
+ *
24
+ * A skill without a translation, or with an unreadable or malformed one, is returned
25
+ * as undefined: the panel then shows the registry values, so a partial translation
26
+ * set degrades to English rather than to a broken card.
27
+ *
28
+ * @param skillFilePath - absolute path of the skill's SKILL.md, when the registry reports one.
29
+ * @returns the translation, or undefined when none is usable.
30
+ */
31
+ async function readSkillTranslation(skillFilePath) {
32
+ if (skillFilePath === void 0) return void 0;
33
+ let record;
34
+ try {
35
+ record = JSON.parse(await readFile(join(dirname(skillFilePath), "i18n", "zh.json"), "utf8"));
36
+ } catch {
37
+ return;
38
+ }
39
+ const { title, description, summary } = record;
40
+ if (typeof title !== "string" || typeof description !== "string" || typeof summary !== "string") return void 0;
41
+ return {
42
+ title,
43
+ description,
44
+ summary
45
+ };
46
+ }
47
+ /**
48
+ * Read translation progress for the panel's progress bar.
49
+ *
50
+ * @param translationRoot - repository holding the skill set, whose `i18n/.progress.json` the script writes.
51
+ * @returns the progress, or undefined when no translation run has recorded one.
52
+ */
53
+ async function readTranslationProgress(translationRoot) {
54
+ try {
55
+ const raw = JSON.parse(await readFile(join(translationRoot, "i18n", ".progress.json"), "utf8"));
56
+ if (typeof raw.total !== "number" || typeof raw.done !== "number") return void 0;
57
+ return {
58
+ total: raw.total,
59
+ done: raw.done,
60
+ current: typeof raw.current === "string" ? raw.current : null,
61
+ finishedAt: typeof raw.finishedAt === "string" ? raw.finishedAt : null
62
+ };
63
+ } catch {
64
+ return;
65
+ }
66
+ }
67
+ //#endregion
5
68
  //#region lib/types/index.js
6
69
  /**
7
70
  * Skill center — host half. Serves the skill-center data source over the
8
- * `/api/dsh-skill-center` route family: list grouped by source, enable or disable
9
- * model invocation, create, delete into a recoverable trash, and health.
71
+ * `/api/dsh-skill-center` route family: list grouped by source, read one skill's
72
+ * body, enable or disable model invocation, create, delete into a recoverable
73
+ * trash, and health.
10
74
  *
11
75
  * Reading goes through the official `ctx.skills` registry, so the catalog, its
12
76
  * provider precedence, and its hot reload are the harness's rather than a second
@@ -30,7 +94,9 @@ const ROUTES = {
30
94
  setEnabled: "/api/dsh-skill-center/set-enabled",
31
95
  create: "/api/dsh-skill-center/create",
32
96
  delete: "/api/dsh-skill-center/delete",
33
- health: "/api/dsh-skill-center/health"
97
+ content: "/api/dsh-skill-center/content",
98
+ health: "/api/dsh-skill-center/health",
99
+ translationProgress: "/api/dsh-skill-center/translation-progress"
34
100
  };
35
101
  /**
36
102
  * Groups the panel renders, in order. The level a skill resolves to selects its
@@ -39,38 +105,31 @@ const ROUTES = {
39
105
  const SOURCE_GROUPS = [
40
106
  {
41
107
  key: "bundled",
42
- title: "系统内置",
43
- hint: "随 Harness 一同安装,对所有项目生效"
108
+ title: "系统内置"
44
109
  },
45
110
  {
46
111
  key: "project-dsh",
47
- title: "项目 .dsh/skills",
48
- hint: "位于项目目录,仅对该工作区生效"
112
+ title: "项目 .dsh/skills"
49
113
  },
50
114
  {
51
115
  key: "project-agents",
52
- title: "项目 .agents/skills",
53
- hint: "位于项目目录,随项目提交,对协作者共享"
116
+ title: "项目 .agents/skills"
54
117
  },
55
118
  {
56
119
  key: "custom",
57
- title: "自定义目录",
58
- hint: "由本插件的 customSkillDirs 配置"
120
+ title: "自定义目录"
59
121
  },
60
122
  {
61
123
  key: "user-dsh",
62
- title: "用户 ~/.dsh/skills",
63
- hint: "在用户 dsh 目录下,对本机所有项目生效"
124
+ title: "用户 ~/.dsh/skills"
64
125
  },
65
126
  {
66
127
  key: "user-agents",
67
- title: "用户 ~/.agents/skills",
68
- hint: "在用户 agents 目录下,对本机所有项目生效"
128
+ title: "用户 ~/.agents/skills"
69
129
  },
70
130
  {
71
131
  key: "runtime",
72
- title: "运行时注册",
73
- hint: "由已安装的插件在运行时注册"
132
+ title: "运行时注册"
74
133
  }
75
134
  ];
76
135
  /**
@@ -240,6 +299,16 @@ function localSkillRoots(deps, projectRoots, activeRoot) {
240
299
  return roots;
241
300
  }
242
301
  /**
302
+ * Strip the leading YAML frontmatter from a skill file.
303
+ *
304
+ * @param source - the skill file's complete content.
305
+ * @returns the body below the closing delimiter, or the whole file when it declares none.
306
+ */
307
+ function stripFrontmatter(source) {
308
+ const match = /^---\r?\n[\s\S]*?\r?\n---[ \t]*\r?\n?/.exec(source);
309
+ return match === null ? source : source.slice(match[0].length);
310
+ }
311
+ /**
243
312
  * Read `disable-model-invocation` from a skill file.
244
313
  * @param source - the skill file's content.
245
314
  * @returns the flag when the frontmatter declares it, otherwise undefined.
@@ -311,14 +380,43 @@ async function readJson(req, maxBytes) {
311
380
  if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("request body must be a JSON object");
312
381
  return parsed;
313
382
  }
383
+ /**
384
+ * Chinese copy for every skill the list will render, keyed by skill name.
385
+ *
386
+ * The registry omits the skill file path from its summaries, so each one is resolved
387
+ * through `pathOf`, which consults the catalog `collect()` already cached. A skill
388
+ * whose path or translation is missing simply has no entry here and renders in English.
389
+ *
390
+ * @param deps - the routes' dependencies.
391
+ * @param skills - skill names to look up.
392
+ * @param cwd - the viewing context the registry resolves against.
393
+ * @returns the translations found, keyed by name.
394
+ */
395
+ async function translationsFor(deps, skills, cwd) {
396
+ const found = /* @__PURE__ */ new Map();
397
+ const root = deps.translationRoot;
398
+ await Promise.all(skills.map(async ({ name, path: known }) => {
399
+ let path = known;
400
+ if (path === void 0) try {
401
+ path = await deps.pathOf(name, cwd);
402
+ } catch {
403
+ path = void 0;
404
+ }
405
+ const translation = await readSkillTranslation(path) ?? (root === void 0 ? void 0 : await readSkillTranslation(join(root, name, "SKILL.md")));
406
+ if (translation !== void 0) found.set(name, translation);
407
+ }));
408
+ return found;
409
+ }
314
410
  /** The list handler: group the registry snapshot and the local roots together. */
315
411
  async function handleList(deps, cwd) {
316
412
  const projectRoots = [...new Set(deps.activeSessionCwds().map(findProjectRoot).concat(findProjectRoot(cwd)))];
317
413
  const snapshot = await deps.snapshotProject(cwd);
318
414
  const byGroup = new Map(SOURCE_GROUPS.map((g) => [g.key, []]));
319
415
  const seen = /* @__PURE__ */ new Set();
416
+ const translations = await translationsFor(deps, snapshot.skills.map((s) => ({ name: s.name })), cwd);
320
417
  for (const skill of snapshot.skills) {
321
418
  const level = levelOfSource(skill.source, skill.provider);
419
+ const i18n = translations.get(skill.name);
322
420
  const entry = {
323
421
  name: skill.name,
324
422
  description: skill.description,
@@ -326,7 +424,8 @@ async function handleList(deps, cwd) {
326
424
  provider: skill.provider,
327
425
  modelInvocable: skill.invocation.modelInvocable,
328
426
  userInvocable: skill.invocation.userInvocable,
329
- ...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse }
427
+ ...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
428
+ ...i18n === void 0 ? {} : { i18n }
330
429
  };
331
430
  seen.add(level + ":" + skill.name);
332
431
  (byGroup.get(level) ?? byGroup.get("custom"))?.push(entry);
@@ -334,6 +433,7 @@ async function handleList(deps, cwd) {
334
433
  for (const root of localSkillRoots(deps, projectRoots, findProjectRoot(cwd))) for (const skill of await scanSkillRoot(root.path, root.level, root.workspace)) {
335
434
  if (seen.has(root.level + ":" + skill.name)) continue;
336
435
  seen.add(root.level + ":" + skill.name);
436
+ const i18n = await readSkillTranslation(skill.path) ?? (deps.translationRoot === void 0 ? void 0 : await readSkillTranslation(join(deps.translationRoot, skill.name, "SKILL.md")));
337
437
  byGroup.get(root.level)?.push({
338
438
  name: skill.name,
339
439
  description: skill.description,
@@ -345,7 +445,8 @@ async function handleList(deps, cwd) {
345
445
  ...skill.whenToUse === void 0 ? {} : { whenToUse: skill.whenToUse },
346
446
  ...skill.workspaceRoot === void 0 ? {} : { workspaceRoot: skill.workspaceRoot },
347
447
  ...skill.workspaceName === void 0 ? {} : { workspaceName: skill.workspaceName },
348
- ...skill.isActiveWorkspace === void 0 ? {} : { isActiveWorkspace: skill.isActiveWorkspace }
448
+ ...skill.isActiveWorkspace === void 0 ? {} : { isActiveWorkspace: skill.isActiveWorkspace },
449
+ ...i18n === void 0 ? {} : { i18n }
349
450
  });
350
451
  }
351
452
  const groups = [];
@@ -354,7 +455,6 @@ async function handleList(deps, cwd) {
354
455
  if (skills.length > 0) groups.push({
355
456
  key: g.key,
356
457
  title: g.title,
357
- hint: g.hint,
358
458
  skills
359
459
  });
360
460
  }
@@ -365,6 +465,39 @@ async function handleList(deps, cwd) {
365
465
  groups
366
466
  };
367
467
  }
468
+ /**
469
+ * The content handler: one skill's instruction body, without its frontmatter.
470
+ *
471
+ * The registry's summaries carry metadata only, so the body is read from the file
472
+ * the name resolves to. The path the panel listed is preferred — the row already
473
+ * names its own file, and the panel lists skills from workspaces other than the one
474
+ * being viewed — then the registry's resolution, then the configured translation
475
+ * set's own copy, which is what answers for a bundled skill.
476
+ *
477
+ * @param listedPath - the path the listing served with the row, when the caller sent one.
478
+ */
479
+ async function handleContent(deps, name, listedPath, cwd) {
480
+ const candidates = [];
481
+ if (listedPath !== void 0 && listedPath !== "") candidates.push(listedPath);
482
+ try {
483
+ const resolved = await deps.pathOf(name, cwd);
484
+ if (resolved !== void 0 && !candidates.includes(resolved)) candidates.push(resolved);
485
+ } catch {}
486
+ if (deps.translationRoot !== void 0) candidates.push(join(deps.translationRoot, name, SKILL_FILE));
487
+ for (const candidate of candidates) {
488
+ let source;
489
+ try {
490
+ source = await readFile(candidate, "utf8");
491
+ } catch {
492
+ continue;
493
+ }
494
+ return {
495
+ name,
496
+ content: stripFrontmatter(source)
497
+ };
498
+ }
499
+ throw new Error("skill \"" + name + "\" has no readable SKILL.md");
500
+ }
368
501
  /** The set-enabled handler: rewrite the skill's frontmatter flag. */
369
502
  async function handleSetEnabled(deps, body, cwd) {
370
503
  const path = await resolveSkillPath(deps, requireString(body.name, "name"), body, cwd);
@@ -452,6 +585,14 @@ function makeRoutes(deps) {
452
585
  path: ROUTES.health,
453
586
  handler: (_req, res) => writeJson(res, 200, { ok: true })
454
587
  },
588
+ {
589
+ kind: "exact",
590
+ path: ROUTES.translationProgress,
591
+ handler: async (_req, res) => {
592
+ const progress = deps.translationRoot === void 0 ? void 0 : await readTranslationProgress(deps.translationRoot);
593
+ writeJson(res, 200, progress === void 0 ? {} : { progress });
594
+ }
595
+ },
455
596
  {
456
597
  kind: "exact",
457
598
  path: ROUTES.list,
@@ -464,6 +605,21 @@ function makeRoutes(deps) {
464
605
  }
465
606
  }
466
607
  },
608
+ {
609
+ kind: "exact",
610
+ path: ROUTES.content,
611
+ handler: async (req, res) => {
612
+ try {
613
+ const params = new URL(req.url ?? "/", "http://localhost").searchParams;
614
+ const name = params.get("name") ?? "";
615
+ if (name === "") throw new Error("missing name");
616
+ writeJson(res, 200, await handleContent(deps, name, params.get("path") ?? void 0, currentCwd(deps, req)));
617
+ } catch (error) {
618
+ deps.logger.warn(error);
619
+ writeJson(res, 400, { error: error instanceof Error ? error.message : String(error) });
620
+ }
621
+ }
622
+ },
467
623
  {
468
624
  kind: "exact",
469
625
  path: ROUTES.setEnabled,
@@ -522,9 +678,10 @@ function apply(ctx, config = {}) {
522
678
  activeSessionCwds: () => ctx.sessions.list().map((s) => s.header.cwd).filter((c) => typeof c === "string" && c !== ""),
523
679
  logger: { warn: (error) => {
524
680
  ctx.logger.warn(error);
525
- } }
681
+ } },
682
+ ...config.translationRoot === void 0 ? {} : { translationRoot: config.translationRoot }
526
683
  };
527
684
  for (const route of makeRoutes(deps)) ctx.effect(() => ctx.webServer.register(route), "ui-skill-center: route " + route.path);
528
685
  }
529
686
  //#endregion
530
- export { ROUTES, apply, buildSkillContent, findProjectRoot, inject, levelOfSource, localSkillRoots, makeRoutes, name, projectSkillRoot, readDisabledFlag, scanSkillRoot, userSkillRoot, withDisabledFlag };
687
+ export { ROUTES, apply, buildSkillContent, findProjectRoot, inject, levelOfSource, localSkillRoots, makeRoutes, name, projectSkillRoot, readDisabledFlag, scanSkillRoot, stripFrontmatter, userSkillRoot, withDisabledFlag };
@@ -4,6 +4,24 @@
4
4
  *
5
5
  * @module @sparkelf/dsh-client-ui-skill-center/client/api
6
6
  */
7
+ /** Chinese copy for one skill, when a translation file exists beside it. */
8
+ export interface SkillEntryTranslation {
9
+ title: string;
10
+ description: string;
11
+ summary: string;
12
+ }
13
+ /** One skill's instruction body, as the content route serves it. */
14
+ export interface SkillContent {
15
+ name: string;
16
+ content: string;
17
+ }
18
+ /** Translated share of the skill set, as the translation script recorded it. */
19
+ export interface TranslationProgress {
20
+ total: number;
21
+ done: number;
22
+ current: string | null;
23
+ finishedAt: string | null;
24
+ }
7
25
  /** One skill entry as the host serves it. */
8
26
  export interface SkillEntry {
9
27
  name: string;
@@ -19,12 +37,13 @@ export interface SkillEntry {
19
37
  workspaceRoot?: string;
20
38
  workspaceName?: string;
21
39
  isActiveWorkspace?: boolean;
40
+ /** Chinese copy rendered in place of the English values; the registry keeps its own. */
41
+ i18n?: SkillEntryTranslation;
22
42
  }
23
43
  /** One group as the host serves it. */
24
44
  export interface GroupPayload {
25
45
  key: string;
26
46
  title: string;
27
- hint: string;
28
47
  skills: SkillEntry[];
29
48
  }
30
49
  /** One selectable workspace. */
@@ -70,6 +89,20 @@ export declare class SkillApi {
70
89
  ok: true;
71
90
  moved: string;
72
91
  }>;
92
+ /**
93
+ * One skill's instruction body, without the YAML frontmatter the card already
94
+ * shows as metadata.
95
+ *
96
+ * The path the list served travels back with the request: a row from another
97
+ * workspace is read from the file that was listed rather than from whatever the
98
+ * viewing workspace's registry resolves by name.
99
+ */
100
+ content(name: string, path?: string, cwd?: string): Promise<SkillContent>;
101
+ /**
102
+ * The translated share of the skill set, or undefined when the host has not
103
+ * been told where the translations live (the route then answers `{}`).
104
+ */
105
+ translationProgress(): Promise<TranslationProgress | undefined>;
73
106
  private request;
74
107
  }
75
108
  //# sourceMappingURL=api.d.ts.map
@@ -7,18 +7,26 @@
7
7
  /** Every string this plugin renders. */
8
8
  export interface SkillCenterLocale {
9
9
  'panel': string;
10
- 'panel.intro': string;
11
10
  'search.placeholder': string;
12
- 'search.clear': string;
13
11
  'search.noMatches': string;
14
12
  'action.refresh': string;
15
13
  'action.create': string;
16
14
  'action.delete': string;
15
+ 'action.showDetail': string;
16
+ 'action.close': string;
17
+ 'translate.label': string;
18
+ 'detail.whenToUse': string;
19
+ 'detail.source': string;
20
+ 'detail.provider': string;
21
+ 'detail.path': string;
22
+ 'detail.content': string;
17
23
  'action.cancel': string;
18
24
  'action.confirm': string;
19
25
  'state.loading': string;
20
26
  'state.empty': string;
21
27
  'state.failed': string;
28
+ 'content.loading': string;
29
+ 'content.failed': string;
22
30
  'skill.enabled': string;
23
31
  'skill.disabled': string;
24
32
  'skill.modelInvocable': string;
@@ -1,10 +1,12 @@
1
1
  /**
2
- * Owner props a contributed section receives. The slot passes none today; the type exists so a
3
- * consumer can name the slot's owner share without importing the page.
2
+ * Owner props a contributed section receives.
3
+ *
4
+ * Declared here rather than inferred at the call site: the page and the section live in
5
+ * different packages, so the type is the only thing keeping their props in step.
4
6
  */
5
7
  export interface SkillCenterSectionOwnerProps {
6
- /** Reserved for an owner share the panel may add; currently empty. */
7
- readonly reserved?: never;
8
+ /** The page's current search text, unfiltered and untrimmed. */
9
+ readonly query: string;
8
10
  }
9
11
  declare module '@deepseek-ai/dsh-client-ui-slots' {
10
12
  interface SlotMap {
@@ -12,6 +14,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
12
14
  'skill-center.section': {
13
15
  kind: 'list';
14
16
  scope: 'root';
17
+ owner: SkillCenterSectionOwnerProps;
15
18
  };
16
19
  }
17
20
  }
@@ -11,7 +11,9 @@ export declare const ROUTES: {
11
11
  readonly setEnabled: "/api/dsh-skill-center/set-enabled";
12
12
  readonly create: "/api/dsh-skill-center/create";
13
13
  readonly delete: "/api/dsh-skill-center/delete";
14
+ readonly content: "/api/dsh-skill-center/content";
14
15
  readonly health: "/api/dsh-skill-center/health";
16
+ readonly translationProgress: "/api/dsh-skill-center/translation-progress";
15
17
  };
16
18
  /** Plugin configuration. */
17
19
  export interface Config {
@@ -21,6 +23,20 @@ export interface Config {
21
23
  dshHome?: string;
22
24
  /** User agents config root; defaults to `$DSH_AGENTS_HOME` or `~/.agents`. */
23
25
  agentsHome?: string;
26
+ /**
27
+ * Skill set whose `<skill>/i18n/zh.json` files supply the panel's Chinese copy.
28
+ *
29
+ * A skill that resolves through a symlink into another repository keeps its
30
+ * translation in that repository, not in the deployment, so this names the
31
+ * checkout that holds the translated set. Unset means English only.
32
+ */
33
+ translationRoot?: string;
34
+ }
35
+ /** Chinese copy for one skill, rendered in place of the English values. */
36
+ export interface SkillEntryTranslation {
37
+ title: string;
38
+ description: string;
39
+ summary: string;
24
40
  }
25
41
  /** A skill row as the panel receives it. */
26
42
  export interface SkillEntry {
@@ -37,12 +53,18 @@ export interface SkillEntry {
37
53
  workspaceRoot?: string;
38
54
  workspaceName?: string;
39
55
  isActiveWorkspace?: boolean;
56
+ /**
57
+ * Chinese copy, when a translation file exists beside the skill.
58
+ *
59
+ * Kept apart from `description` rather than replacing it: the registry value is
60
+ * what the model routes on, and the panel must stay able to show it.
61
+ */
62
+ i18n?: SkillEntryTranslation;
40
63
  }
41
64
  /** One rendered group. */
42
65
  export interface GroupPayload {
43
66
  key: string;
44
67
  title: string;
45
- hint: string;
46
68
  skills: SkillEntry[];
47
69
  }
48
70
  /** One selectable workspace. */
@@ -148,6 +170,13 @@ export declare function localSkillRoots(deps: Pick<SkillRoutesDeps, 'dshHome' |
148
170
  active: boolean;
149
171
  };
150
172
  }[];
173
+ /**
174
+ * Strip the leading YAML frontmatter from a skill file.
175
+ *
176
+ * @param source - the skill file's complete content.
177
+ * @returns the body below the closing delimiter, or the whole file when it declares none.
178
+ */
179
+ export declare function stripFrontmatter(source: string): string;
151
180
  /**
152
181
  * Read `disable-model-invocation` from a skill file.
153
182
  * @param source - the skill file's content.
@@ -197,6 +226,8 @@ export interface SkillRoutesDeps {
197
226
  logger: {
198
227
  warn(error: unknown): void;
199
228
  };
229
+ /** Skill set holding the `<skill>/i18n/zh.json` files; unset disables translation. */
230
+ translationRoot?: string;
200
231
  }
201
232
  /** The registry summary this route family reads, re-exported for its callers. */
202
233
  export type { SkillSummary };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Chinese metadata for the skill center, read from each skill's own directory.
3
+ *
4
+ * The panel renders what the official registry routes on, so the registry's English
5
+ * description is what decides whether the model invokes a skill. Replacing it would
6
+ * change that decision, so a translation is carried alongside and applied only at
7
+ * render time: the card shows Chinese, the model keeps reading English.
8
+ *
9
+ * A skill keeps its translation in its own directory (`<skill>/i18n/zh.json`), which
10
+ * lets a skill and its Chinese travel together and ship from one clone. The skill
11
+ * file itself is located through the registry, so the translation is found even when
12
+ * the skill resolves through a symlink into another repository.
13
+ *
14
+ * @module @sparkelf/dsh-client-ui-skill-center/translations
15
+ */
16
+ /** One skill's translated metadata. */
17
+ export interface SkillTranslation {
18
+ /** Short Chinese title shown in place of the kebab-case identifier. */
19
+ title: string;
20
+ /** Chinese rendering of the registry description. */
21
+ description: string;
22
+ /** Chinese summary of the skill body. */
23
+ summary: string;
24
+ }
25
+ /** Translation progress, written by the translation script. */
26
+ export interface TranslationProgress {
27
+ total: number;
28
+ done: number;
29
+ current: string | null;
30
+ finishedAt: string | null;
31
+ }
32
+ /**
33
+ * Read one skill's translation from beside its resolved skill file.
34
+ *
35
+ * A skill without a translation, or with an unreadable or malformed one, is returned
36
+ * as undefined: the panel then shows the registry values, so a partial translation
37
+ * set degrades to English rather than to a broken card.
38
+ *
39
+ * @param skillFilePath - absolute path of the skill's SKILL.md, when the registry reports one.
40
+ * @returns the translation, or undefined when none is usable.
41
+ */
42
+ export declare function readSkillTranslation(skillFilePath: string | undefined): Promise<SkillTranslation | undefined>;
43
+ /**
44
+ * Read translation progress for the panel's progress bar.
45
+ *
46
+ * @param translationRoot - repository holding the skill set, whose `i18n/.progress.json` the script writes.
47
+ * @returns the progress, or undefined when no translation run has recorded one.
48
+ */
49
+ export declare function readTranslationProgress(translationRoot: string): Promise<TranslationProgress | undefined>;
50
+ //# sourceMappingURL=translations.d.ts.map
package/package.json CHANGED
@@ -1,20 +1,28 @@
1
1
  {
2
+ "name": "@sparkelf/dsh-client-ui-skill-center",
3
+ "version": "0.2.1-alpha.30",
2
4
  "description": "Skill center for the dsh web client: browses loaded skills grouped by source, enables or disables model invocation, creates skills, and deletes them into a recoverable trash",
3
- "devDependencies": {
4
- "@deepseek-ai/cordis": "^4.0.2",
5
- "@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.1",
6
- "@deepseek-ai/dsh-client-ui-layout": "^0.1.6-alpha.1",
7
- "@deepseek-ai/dsh-client-ui-primitives": "^0.1.6-alpha.1",
8
- "@deepseek-ai/dsh-client-ui-renderer": "^0.1.6-alpha.1",
9
- "@deepseek-ai/dsh-client-ui-sidebar": "^0.1.6-alpha.1",
10
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1",
11
- "@deepseek-ai/dsh-host-webserver": "^0.1.6-alpha.1",
12
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
13
- "@deepseek-ai/dsh-skill": "^0.1.6-alpha.1",
14
- "@types/react": "~18.3.1",
15
- "@types/react-dom": "~18.3.0",
16
- "react": "^18.2.0",
17
- "react-dom": "^18.2.0"
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
11
+ "directory": "packages/plus/ui-skill-center"
12
+ },
13
+ "type": "module",
14
+ "main": "lib/index.js",
15
+ "types": "lib/types/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./lib/types/index.d.ts",
19
+ "default": "./lib/index.js"
20
+ },
21
+ "./client": {
22
+ "types": "./lib/types/client/index.d.ts",
23
+ "default": "./lib/client.js"
24
+ },
25
+ "./package.json": "./package.json"
18
26
  },
19
27
  "dsh": {
20
28
  "client": {
@@ -25,16 +33,9 @@
25
33
  "platform": "web"
26
34
  }
27
35
  },
28
- "exports": {
29
- ".": {
30
- "default": "./lib/index.js",
31
- "types": "./lib/types/index.d.ts"
32
- },
33
- "./client": {
34
- "default": "./lib/client.js",
35
- "types": "./lib/types/client/index.d.ts"
36
- },
37
- "./package.json": "./package.json"
36
+ "scripts": {
37
+ "bundle": "tsdown",
38
+ "watch": "tsdown --watch"
38
39
  },
39
40
  "files": [
40
41
  "lib/index.js",
@@ -42,24 +43,23 @@
42
43
  "lib/types/**/*.d.ts"
43
44
  ],
44
45
  "license": "MIT",
45
- "main": "lib/index.js",
46
- "name": "@sparkelf/dsh-client-ui-skill-center",
47
46
  "peerDependencies": {
48
47
  "@deepseek-ai/cordis": ">=4.0.1"
49
48
  },
50
- "publishConfig": {
51
- "access": "public"
52
- },
53
- "repository": {
54
- "directory": "packages/plus/ui-skill-center",
55
- "type": "git",
56
- "url": "git+https://github.com/deepseek-ai/deepseek-harness.git"
57
- },
58
- "scripts": {
59
- "bundle": "tsdown",
60
- "watch": "tsdown --watch"
61
- },
62
- "type": "module",
63
- "types": "lib/types/index.d.ts",
64
- "version": "0.2.1-alpha.29"
49
+ "devDependencies": {
50
+ "@deepseek-ai/cordis": "workspace:^",
51
+ "@deepseek-ai/dsh-client-locale": "workspace:^",
52
+ "@deepseek-ai/dsh-client-ui-layout": "workspace:^",
53
+ "@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
54
+ "@deepseek-ai/dsh-client-ui-renderer": "workspace:^",
55
+ "@deepseek-ai/dsh-client-ui-sidebar": "workspace:^",
56
+ "@deepseek-ai/dsh-client-ui-slots": "workspace:^",
57
+ "@deepseek-ai/dsh-host-webserver": "workspace:^",
58
+ "@deepseek-ai/dsh-session": "workspace:^",
59
+ "@deepseek-ai/dsh-skill": "workspace:^",
60
+ "@types/react": "~18.3.1",
61
+ "@types/react-dom": "~18.3.0",
62
+ "react": "^18.2.0",
63
+ "react-dom": "^18.2.0"
64
+ }
65
65
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 DeepSeek
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.