@michengai/dsh-skills-manager 1.0.0 → 1.0.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/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  [简体中文](CHANGELOG.zh-CN.md)
4
4
 
5
+ ## 1.0.1 - 2026-09-21
6
+
7
+ This maintenance release improves source development, packaging verification and screenshot privacy while retaining the existing plugin runtime interfaces.
8
+
9
+ ### Development and packaging
10
+
11
+ - Migrate ten source modules to strict TypeScript, retaining the existing plugin architecture and JavaScript runtime entry points. Add domain, host and client contracts, plus positive and negative type tests; include type checking in the main test command.
12
+ - Stop tracking `lib` in Git. Run type checks and build automatically before packing, and verify that a clean directory without `lib` produces a package with all runtime files and a loadable server entry point.
13
+ - Cover the exact minimum Node.js version 22.19.0 alongside Node.js 24 in CI. Code changes continue to trigger the supported-host compatibility matrix.
14
+ - Document supported installation via the npm registry or locally built `.tgz` packages; direct Git/GitHub dependency installation is not supported. Align the English and Chinese development instructions.
15
+
16
+ ### Fixes
17
+
18
+ - Restore modal focus after an asynchronous form submission disables the focused button, so Escape after an error closes only the plugin dialog instead of the entire host settings page.
19
+
20
+ ### Documentation and privacy
21
+
22
+ - Crop all nine screenshots to their feature panels, redact local paths, and remove real filenames and project directories from the background. Reduce total screenshot size by 81% with lossless compression.
23
+ - Correct the English screenshot caption and align image display sizes with the cropped files.
24
+
5
25
  ## 1.0.0 - 2026-09-21
6
26
 
7
27
  ### 1.0 release scope
@@ -2,6 +2,26 @@
2
2
 
3
3
  [English](CHANGELOG.md)
4
4
 
5
+ ## 1.0.1 - 2026-09-21
6
+
7
+ 本次维护版本完善源码开发、打包验证与截图隐私处理,保留现有插件运行接口。
8
+
9
+ ### 开发与打包
10
+
11
+ - 十个源码模块迁移到严格 TypeScript,保留现有插件架构和 JavaScript 运行入口;新增领域、宿主和客户端契约以及类型正反例测试,将类型检查纳入主测试命令。
12
+ - `lib` 不再纳入 Git;打包前自动检查类型并构建,验证没有 `lib` 的干净目录仍可生成包含全部运行产物、服务端入口可加载的安装包。
13
+ - CI 同时覆盖精确最低 Node.js 22.19.0 与 Node.js 24;代码改动继续自动触发受支持宿主兼容矩阵。
14
+ - 明确支持 npm registry 和本地构建 `.tgz` 安装,不支持 Git/GitHub 依赖直装;对齐中英文开发说明。
15
+
16
+ ### 修复
17
+
18
+ - 修复异步表单提交禁用按钮后焦点丢失的问题;错误后按 Escape 仅关闭插件弹窗,不再连带关闭宿主设置页。
19
+
20
+ ### 文档与隐私
21
+
22
+ - 将全部九张截图裁到功能面板,遮盖本机路径并移除背景中的真实文件名和项目目录;无损压缩后总体积减少 81%。
23
+ - 修正英文截图说明,并按裁切后的尺寸调整文档展示。
24
+
5
25
  ## 1.0.0 - 2026-09-21
6
26
 
7
27
  ### 1.0 正式版范围
package/README.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  > DSH Skills Manager is a community-maintained DeepSeek Harness (DSH) plugin, not an official DeepSeek AI product.
21
21
 
22
- This README describes **1.0.0**. See the [changelog](CHANGELOG.md) and [bilingual 1.0.0 release notes](https://github.com/MichengAI/dsh-skills-manager/blob/master/docs/02-%E4%BA%A7%E5%93%81%E4%B8%8E%E4%B8%9A%E5%8A%A1/04-1.0.0%E5%8F%91%E8%A1%8C%E8%AF%B4%E6%98%8E.md).
22
+ This README describes **1.0.1**. See the [changelog](CHANGELOG.md) and [release notes](https://github.com/MichengAI/dsh-skills-manager/releases/latest).
23
23
 
24
24
  ## Features
25
25
 
@@ -45,12 +45,14 @@ Skill repositories:
45
45
 
46
46
  ![Browse and install repository skills](assets/screenshots/skill-repositories.webp)
47
47
 
48
- *Screenshots show the pre-release 0.1.54 local build used to prepare 1.0.0. The final version adds refresh animation and progress feedback. Screenshots use lossless WebP at original resolution.*
48
+ *Screenshots show the pre-release 0.1.54 local build used to prepare 1.0.0. The final version adds refresh animation and progress feedback. Screenshots show only the feature panels, with real filenames and project directories cropped out of the background, and use lossless WebP.*
49
49
 
50
50
  ## Installation
51
51
 
52
52
  Requires a working DeepSeek Harness installation. The commands below use the `web` profile; replace it with yours if needed.
53
53
 
54
+ Supported plugin installation sources are the npm registry and a locally built `.tgz` package. Direct Git/GitHub dependency installation (such as `github:MichengAI/dsh-skills-manager`) is not supported. For source development, install development dependencies and build the project before loading it into DSH.
55
+
54
56
  ### Ask an agent to install it
55
57
 
56
58
  Send this to an agent that can use your local terminal:
@@ -69,7 +71,7 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
69
71
 
70
72
  Restart DSH, refresh the page, and open **Settings → Skills**. To update, select **Check for updates** or run the installation command again.
71
73
 
72
- - Plugin `1.0.0` is tested with DeepSeek Harness `0.1.0-rc.8`, `0.1.1-rc.2`, `0.1.2-rc.1`, `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`.
74
+ - Plugin `1.0.1` is tested with DeepSeek Harness `0.1.0-rc.8`, `0.1.1-rc.2`, `0.1.2-rc.1`, `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.6-alpha.1`, `0.1.6-alpha.2`.
73
75
 
74
76
  ## Usage
75
77
 
@@ -142,7 +144,9 @@ For a desktop workbench, download [DSH Codex Desktop](https://github.com/Micheng
142
144
 
143
145
  [Open an issue](https://github.com/MichengAI/dsh-skills-manager/issues) for bugs or suggestions. Include your DSH and plugin versions, reproduction steps, and a full screenshot for UI issues.
144
146
 
145
- Source code lives in `src`. Contributions are welcome.
147
+ Source code lives in `src` and uses strict TypeScript while retaining the existing plugin architecture. Run `npm run typecheck` for type checks, `npm run build` to generate runtime files, and `npm run verify` for the full regression and package checks. `lib` is not tracked in Git; packing automatically checks types and builds the JavaScript files required by the npm package. CI runs full verification on Node.js 22.19.0 and 24, and code changes also trigger the supported-host compatibility matrix. Contributions are welcome.
148
+
149
+ To test a source checkout, install dependencies using the pinned pnpm version, run `npm run verify`, then run `npm pack`. Install the resulting `.tgz` with `dsh plugin --profile web add <package.tgz>`, restart DSH and refresh the page. Do not use `--ignore-scripts` when packing: it skips the build hook.
146
150
 
147
151
  ## License
148
152
 
package/README.zh-CN.md CHANGED
@@ -19,7 +19,7 @@
19
19
 
20
20
  > DSH Skills Manager 是社区维护的 DeepSeek Harness(DSH)插件,并非 DeepSeek AI 官方产品。
21
21
 
22
- 本文对应 **1.0.0**。完整变更见[更新日志](CHANGELOG.zh-CN.md),中英文发行说明见 [1.0.0 发行说明](https://github.com/MichengAI/dsh-skills-manager/blob/master/docs/02-%E4%BA%A7%E5%93%81%E4%B8%8E%E4%B8%9A%E5%8A%A1/04-1.0.0%E5%8F%91%E8%A1%8C%E8%AF%B4%E6%98%8E.md)。
22
+ 本文对应 **1.0.1**。完整变更见[更新日志](CHANGELOG.zh-CN.md)[最新发行说明](https://github.com/MichengAI/dsh-skills-manager/releases/latest)。
23
23
 
24
24
  ## 功能概览
25
25
 
@@ -45,12 +45,14 @@
45
45
 
46
46
  ![技能仓库浏览与安装](assets/screenshots/skill-repositories.webp)
47
47
 
48
- *截图来自 1.0.0 发布前的 0.1.54 本地测试版,展示全局技能和技能仓库;最终版补充了刷新动画及进度提示。截图采用原尺寸无损 WebP。*
48
+ *截图来自 1.0.0 发布前的 0.1.54 本地测试版,展示全局技能和技能仓库;最终版补充了刷新动画及进度提示。截图仅保留功能面板,已裁去背景中的真实文件名与项目目录,采用无损 WebP。*
49
49
 
50
50
  ## 安装
51
51
 
52
52
  需要已安装并能正常运行 DeepSeek Harness。以下命令使用 `web` profile,请按实际环境替换。
53
53
 
54
+ 插件安装支持 npm registry 和本地构建的 `.tgz` 包,不支持直接以 Git/GitHub 依赖安装(例如 `github:MichengAI/dsh-skills-manager`)。从源码开发时,请先安装开发依赖并构建,再加载到 DSH。
55
+
54
56
  ### 让 Agent 帮你安装
55
57
 
56
58
  把这段话发给能够操作本机终端的 Agent:
@@ -69,7 +71,7 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
69
71
 
70
72
  安装后重启 DSH 并刷新页面,打开「设置 → 技能」即可使用。更新时可点击「检查更新」,或重新执行安装命令。
71
73
 
72
- - `1.0.0` 已验证兼容 DeepSeek Harness `0.1.0-rc.8`、`0.1.1-rc.2`、`0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`。
74
+ - `1.0.1` 已验证兼容 DeepSeek Harness `0.1.0-rc.8`、`0.1.1-rc.2`、`0.1.2-rc.1`、`0.1.5-rc.1`、`0.1.5-rc.2`、`0.1.6-alpha.1`、`0.1.6-alpha.2`。
73
75
 
74
76
  ## 使用说明
75
77
 
@@ -142,7 +144,9 @@ dsh plugin --profile web add @michengai/dsh-skills-manager@latest --registry=htt
142
144
 
143
145
  遇到问题或有建议,欢迎[提交 Issue](https://github.com/MichengAI/dsh-skills-manager/issues)。请附上 DSH 与插件版本、复现步骤;界面问题可附完整截图。
144
146
 
145
- 源码位于 `src`,欢迎提交改进。
147
+ 源码位于 `src`,使用严格 TypeScript,保留现有插件架构。开发时执行 `npm run typecheck` 检查类型,`npm run build` 生成运行产物,`npm run verify` 执行完整回归与打包验证。`lib` 不纳入 Git 提交;打包前会自动检查类型并构建,npm 安装包仍包含所需 JavaScript 产物。CI 在 Node.js 22.19.0 和 24 上执行完整验证,代码改动也会自动触发受支持宿主兼容矩阵。欢迎提交改进。
148
+
149
+ 从源码制作测试包时,使用项目固定版本的 pnpm 安装依赖,执行 `npm run verify`,再执行 `npm pack`。用 `dsh plugin --profile web add <package.tgz>` 安装生成的 `.tgz`,重启 DSH 并刷新页面。打包时不要使用 `--ignore-scripts`,否则会跳过构建钩子。
146
150
 
147
151
  ## 许可证
148
152
 
Binary file
package/lib/client.js CHANGED
@@ -1,5 +1,6 @@
1
+ "use strict";
1
2
  (() => {
2
- // src/repository-ui.js
3
+ // src/repository-ui.ts
3
4
  var repositoryLocales = {
4
5
  zh: {
5
6
  "repo.refreshing": "\u6B63\u5728\u68C0\u67E5\u66F4\u65B0\u2026",
@@ -164,8 +165,9 @@
164
165
  if (mounted.current) setRepos(data.repositories || []);
165
166
  }
166
167
  function message(error) {
167
- if (t("format.locale") === "zh-CN" && error.code === "error.repo.invalid") return error.error || error.message || t(error.code);
168
- return translateError(t, error) || error.message || String(error);
168
+ const item = error;
169
+ if (t("format.locale") === "zh-CN" && item.code === "error.repo.invalid") return item.error || item.message || t(item.code);
170
+ return translateError(t, error) || item.message || String(error);
169
171
  }
170
172
  function feedbackText(value) {
171
173
  return value.payload ? message(value.payload) : (value.key ? t(value.key) : value.text || "") + (value.name ? " \xB7 " + value.name : "");
@@ -180,7 +182,8 @@
180
182
  try {
181
183
  await task();
182
184
  if (reload) await load();
183
- } catch (error) {
185
+ } catch (caught) {
186
+ const error = caught;
184
187
  if (mounted.current) setFeedback({ error: true, payload: error });
185
188
  } finally {
186
189
  locked.current = false;
@@ -188,7 +191,7 @@
188
191
  }
189
192
  }
190
193
  function post(action, body) {
191
- return api("/repositories/" + action, { method: "POST", headers, body: JSON.stringify(body) });
194
+ return api(`/repositories/${action}`, { method: "POST", headers, body: JSON.stringify(body) });
192
195
  }
193
196
  async function refreshRepo(id) {
194
197
  const result = await post("refresh", { id });
@@ -220,7 +223,8 @@
220
223
  try {
221
224
  const detail = await post("detail", { id: repo.id, path: skill.path });
222
225
  if (mounted.current) setModal({ type: "detail", detail, repo });
223
- } catch (error) {
226
+ } catch (caught) {
227
+ const error = caught;
224
228
  if (mounted.current) setModal({ type: "detail", repo, failed: true });
225
229
  throw error;
226
230
  }
@@ -250,7 +254,8 @@
250
254
  setInstallation({ ...target, pending: true, key: "repo.installProgress" });
251
255
  try {
252
256
  await post("install", target);
253
- } catch (error) {
257
+ } catch (caught) {
258
+ const error = caught;
254
259
  if (mounted.current) setInstallation({ ...target, error: true, payload: error });
255
260
  throw error;
256
261
  }
@@ -267,13 +272,15 @@
267
272
  });
268
273
  }
269
274
  function update() {
275
+ if (!modal || modal.type !== "review" && modal.type !== "rollback") return;
270
276
  const selected = modal, rollback = selected.type === "rollback";
271
277
  return perform(async () => {
272
278
  const target = { id: selected.repo.id, path: selected.skill.path };
273
279
  setInstallation({ ...target, pending: true, key: "repo.updateProgress" });
274
280
  try {
275
281
  await post(rollback ? "rollback" : "update", { ...target, token: selected.detail.token, overwrite: selected.detail.localModified });
276
- } catch (error) {
282
+ } catch (caught) {
283
+ const error = caught;
277
284
  if (mounted.current) setInstallation({ ...target, error: true, payload: error });
278
285
  throw error;
279
286
  }
@@ -350,7 +357,7 @@
350
357
  "div",
351
358
  { className: "dssm-repo-panel" },
352
359
  h("div", { className: "dssm-name" }, modal.skill.name),
353
- h("div", { className: "dssm-repo-metadata" }, modal.repo.owner + "/" + modal.repo.name + " \xB7 " + modal.detail.commit.slice(0, 7)),
360
+ h("div", { className: "dssm-repo-metadata" }, modal.repo.owner + "/" + modal.repo.name + " \xB7 " + modal.detail.commit?.slice(0, 7)),
354
361
  modal.detail.localModified ? h("p", { className: "dssm-feedback dssm-warning", role: "alert" }, t("repo.modified")) : null,
355
362
  modal.detail.changes.length ? h("ul", { style: { maxHeight: "240px", overflow: "auto", overflowWrap: "anywhere", paddingLeft: "20px" } }, modal.detail.changes.map((change) => h("li", { key: change.path }, t("repo." + (change.kind === "modified" ? "modifiedFile" : change.kind)) + " \xB7 " + change.path))) : h("p", null, t("repo.noChanges")),
356
363
  busy ? h("div", { role: "status" }, t("repo.updateProgress")) : null,
@@ -430,7 +437,7 @@
430
437
  };
431
438
  }
432
439
 
433
- // src/plugin-update-ui.js
440
+ // src/plugin-update-ui.ts
434
441
  var UPDATE_HEADER = "x-michengai-plugin-update";
435
442
  var STYLE_ID = "michengai-plugin-update-ui";
436
443
  var CSS2 = `
@@ -512,7 +519,7 @@
512
519
  ...signalOption
513
520
  });
514
521
  const value = await response.json();
515
- if (!response.ok || !validPayload(value)) throw new Error(typeof value.error === "string" ? value.error : strings().failed);
522
+ if (!response.ok || !validPayload(value)) throw new Error(value && typeof value === "object" && "error" in value && typeof value.error === "string" ? value.error : strings().failed);
516
523
  return value;
517
524
  }
518
525
  function manualPluginUpdateCommand(profileName, packageName, version) {
@@ -637,7 +644,7 @@
637
644
  update.disabled = busy || payload?.canAutoUpdate !== true || payload.updateAvailable !== true;
638
645
  if (payload === void 0) setMessage(() => text.checking);
639
646
  else if (payload.latestCheckFailed) setMessage(() => text.failed, "error");
640
- else if (payload.updateAvailable) setMessage(() => `${text.found}: v${payload.latestVersion ?? text.unknown}`);
647
+ else if (payload.updateAvailable) setMessage(() => `${text.found}: v${payload?.latestVersion ?? text.unknown}`);
641
648
  else setMessage(() => text.latest, "success");
642
649
  if (payload !== void 0 && !payload.canAutoUpdate && payload.updateAvailable) setMessage(() => text.unavailable);
643
650
  };
@@ -649,7 +656,8 @@
649
656
  await load();
650
657
  setBusy(false);
651
658
  render();
652
- } catch (error) {
659
+ } catch (caught) {
660
+ const error = caught;
653
661
  setBusy(false);
654
662
  setMessage(error instanceof Error ? error.message : () => text.failed, "error");
655
663
  }
@@ -664,8 +672,9 @@
664
672
  payload = await requestStatus(options.endpoint, "POST", controller.signal);
665
673
  applyControls();
666
674
  render();
667
- setMessage(() => payload.autoReload === true ? text.restarting : text.restart, "success");
668
- } catch (error) {
675
+ setMessage(() => payload?.autoReload === true ? text.restarting : text.restart, "success");
676
+ } catch (caught) {
677
+ const error = caught;
669
678
  setMessage(error instanceof Error ? error.message : () => text.failed, "error");
670
679
  } finally {
671
680
  updating = false;
@@ -748,7 +757,7 @@
748
757
  };
749
758
  }
750
759
 
751
- // src/source-select.js
760
+ // src/source-select.ts
752
761
  function createSourceSelect(react, primitives) {
753
762
  const h = react.createElement;
754
763
  return function SourceSelect(props) {
@@ -796,12 +805,12 @@
796
805
  setOpen(true);
797
806
  }
798
807
  }
799
- }, h("span", { className: "dssm-select-value" }, props.action ? props.label : selected?.label || props.label), props.action ? null : h(primitives.IconChevronDownOutline14, { className: "dssm-select-chevron", "aria-hidden": true }))
808
+ }, h("span", { className: "dssm-select-value" }, props.action ? props.label : selected?.label || props.label), props.action ? null : h(primitives.IconChevronDownOutline14, { className: "dssm-select-chevron", ...{ "aria-hidden": true } }))
800
809
  });
801
810
  };
802
811
  }
803
812
 
804
- // src/client.js
813
+ // src/client.ts
805
814
  window.__ModuleLoader__.load({
806
815
  id: "@michengai/dsh-skills-manager",
807
816
  factory: (require2) => {
@@ -1287,14 +1296,15 @@
1287
1296
  function translateError(t, payload) {
1288
1297
  if (payload instanceof Error && !payload.code) return payload.message;
1289
1298
  if (payload && typeof payload === "object") {
1290
- if (payload.code) {
1291
- var params = Object.assign({}, payload.params || {});
1292
- if (params.action) params.action = translateOrFallback(t, "action." + params.action, params.action);
1293
- var translated = t(payload.code, params);
1294
- if (typeof translated === "string" && translated !== payload.code) return translated;
1299
+ const item = payload;
1300
+ if (item.code) {
1301
+ var params = Object.assign({}, item.params || {});
1302
+ if (params.action) params.action = translateOrFallback(t, "action." + params.action, String(params.action));
1303
+ var translated = t(item.code, params);
1304
+ if (typeof translated === "string" && translated !== item.code) return translated;
1295
1305
  }
1296
- if (payload.error !== void 0) return translateError(t, payload.error);
1297
- if (payload.message !== void 0) return String(payload.message);
1306
+ if (item.error !== void 0) return translateError(t, item.error);
1307
+ if (item.message !== void 0) return String(item.message);
1298
1308
  }
1299
1309
  return String(payload == null ? "" : payload);
1300
1310
  }
@@ -1304,7 +1314,8 @@
1304
1314
  error.code = "error.proto.nonJson";
1305
1315
  error.params = { status: response.status };
1306
1316
  throw error;
1307
- }).then(function(payload) {
1317
+ }).then(function(value) {
1318
+ const payload = value;
1308
1319
  if (!response.ok || payload.ok === false) throw payload;
1309
1320
  return payload.data;
1310
1321
  });
@@ -1314,7 +1325,7 @@
1314
1325
  }
1315
1326
  function isSkillEnabled(skill) {
1316
1327
  if (skill.enabled !== void 0) return skill.enabled === true;
1317
- return skill.invocationPolicyValid && skill.modelInvocable && skill.userInvocable && skill.managerEnabled !== false;
1328
+ return !!(skill.invocationPolicyValid && skill.modelInvocable && skill.userInvocable && skill.managerEnabled !== false);
1318
1329
  }
1319
1330
  function countKey(key, count) {
1320
1331
  return key + (Number(count) === 1 ? ".one" : ".other");
@@ -1403,7 +1414,7 @@
1403
1414
  return String(file && (file._dssmPath || file.webkitRelativePath || file.name) || "").replace(/\\/g, "/");
1404
1415
  }
1405
1416
  function inspectUploadSelection(files) {
1406
- var list = Array.prototype.slice.call(files || []);
1417
+ var list = Array.from(files || []);
1407
1418
  if (!list.length) return null;
1408
1419
  if (list.length > MAX_UPLOAD_ENTRIES) return { error: { code: "error.upload.tooMany", params: { limit: MAX_UPLOAD_ENTRIES } } };
1409
1420
  var relative = list.some(function(file) {
@@ -1432,7 +1443,7 @@
1432
1443
  }
1433
1444
  function bytesToBase64(buffer) {
1434
1445
  var bytes = new Uint8Array(buffer), binary = "", chunk = 32768;
1435
- for (var i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode.apply(null, bytes.subarray(i, Math.min(i + chunk, bytes.length)));
1446
+ for (var i = 0; i < bytes.length; i += chunk) binary += String.fromCharCode(...bytes.subarray(i, Math.min(i + chunk, bytes.length)));
1436
1447
  return btoa(binary);
1437
1448
  }
1438
1449
  function buildUploadPayload(selection) {
@@ -1460,7 +1471,9 @@
1460
1471
  if (!batch.length) {
1461
1472
  Promise.all(children.map(function(child) {
1462
1473
  return readDroppedEntry(child, prefix + entry.name + "/", output);
1463
- })).then(resolve, reject);
1474
+ })).then(function() {
1475
+ resolve();
1476
+ }, reject);
1464
1477
  return;
1465
1478
  }
1466
1479
  children = children.concat(batch);
@@ -1471,10 +1484,10 @@
1471
1484
  });
1472
1485
  }
1473
1486
  function droppedFiles(dataTransfer) {
1474
- var items = Array.prototype.slice.call(dataTransfer && dataTransfer.items || []), entries = items.map(function(item) {
1487
+ var items = Array.from(dataTransfer && dataTransfer.items || []), entries = items.map(function(item) {
1475
1488
  return item.webkitGetAsEntry && item.webkitGetAsEntry();
1476
- }).filter(Boolean);
1477
- if (!entries.length) return Promise.resolve(Array.prototype.slice.call(dataTransfer && dataTransfer.files || []));
1489
+ }).filter((entry) => !!entry);
1490
+ if (!entries.length) return Promise.resolve(Array.from(dataTransfer && dataTransfer.files || []));
1478
1491
  var files = [];
1479
1492
  return Promise.all(entries.map(function(entry) {
1480
1493
  return readDroppedEntry(entry, "", files);
@@ -1509,11 +1522,17 @@
1509
1522
  function GithubMark16() {
1510
1523
  return h("svg", { viewBox: "0 0 16 16", width: 16, height: 16, "aria-hidden": true, focusable: "false" }, h("path", { fill: "currentColor", d: "M8 0a8 8 0 0 0-2.53 15.59c.4.074.547-.173.547-.385 0-.19-.007-.693-.01-1.36-2.226.484-2.695-1.073-2.695-1.073-.364-.924-.89-1.17-.89-1.17-.726-.496.055-.486.055-.486.803.056 1.225.824 1.225.824.714 1.223 1.872.87 2.328.665.072-.517.28-.87.508-1.07-1.777-.202-3.645-.888-3.645-3.956 0-.874.31-1.588.823-2.148-.083-.202-.357-1.017.078-2.12 0 0 .672-.215 2.2.82A7.65 7.65 0 0 1 8 4.8c.68.003 1.365.092 2.004.27 1.527-1.035 2.197-.82 2.197-.82.437 1.103.162 1.918.08 2.12.513.56.822 1.274.822 2.148 0 3.076-1.872 3.752-3.654 3.95.288.248.544.735.544 1.482 0 1.07-.01 1.932-.01 2.195 0 .214.144.463.55.384A8.001 8.001 0 0 0 8 0Z" }));
1511
1524
  }
1525
+ function recoverModalFocus(modal, active, body) {
1526
+ if (modal && active === body) modal.focus();
1527
+ }
1512
1528
  function Modal(props) {
1513
1529
  var ref = react.useRef(null);
1514
1530
  react.useEffect(function() {
1515
1531
  if (ref.current) ref.current.focus();
1516
1532
  }, []);
1533
+ react.useLayoutEffect(function() {
1534
+ recoverModalFocus(ref.current, document.activeElement, document.body);
1535
+ });
1517
1536
  return h("div", { className: "dssm-mask", onMouseDown: function(e) {
1518
1537
  if (e.target === e.currentTarget) props.onClose();
1519
1538
  } }, h("div", { ref, tabIndex: -1, className: "dssm-modal" + (props.wide ? " dssm-modal-wide" : "") + (props.className ? " " + props.className : ""), role: "dialog", "aria-modal": "true", onKeyDown: function(e) {
@@ -1542,7 +1561,7 @@
1542
1561
  function SkillManagerView(props) {
1543
1562
  var t = props.t;
1544
1563
  function sessionApi(path, options2) {
1545
- var headers = Object.assign({}, options2 && options2.headers);
1564
+ var headers = Object.fromEntries(new Headers(options2?.headers).entries());
1546
1565
  if (props.sessionId) headers["x-dsh-skills-session"] = props.sessionId;
1547
1566
  return callApi(path, Object.assign({}, options2, { headers }));
1548
1567
  }
@@ -1655,7 +1674,7 @@
1655
1674
  }).catch(function() {
1656
1675
  });
1657
1676
  }
1658
- var data = snapshot.data || { roots: [], projects: [], trash: [] }, allRoots = data.roots || [], projects = data.projects || [];
1677
+ var data = snapshot.data || { roots: [], projects: [], trash: [], warnings: [] }, allRoots = data.roots || [], projects = data.projects || [];
1659
1678
  var activeProject = projects.length === 1 ? projects[0].root : "";
1660
1679
  var filterKey = scope === "project" ? "project:" + activeProject : "user", filters = savedFilters[filterKey] || { query: "", source: "", status: "" }, query = filters.query, source = filters.source;
1661
1680
  function updateFilter(key, value) {
@@ -1684,7 +1703,7 @@
1684
1703
  return item.root && item.root.scope === "project" ? t("root.projectDsh") + " \xB7 " + (item.root.projectName || item.root.projectRoot) : t("root.dsh");
1685
1704
  }
1686
1705
  var options = [{ value: "", label: t("filter.all") }].concat(roots.map(function(root) {
1687
- return { value: root.key, label: t("filter.option", { name: rootDisplayName(t, root), count: root.count == null ? root.skills.length : root.count }) };
1706
+ return { value: root.key, label: t("filter.option", { name: rootDisplayName(t, root), count: root.skills.length }) };
1688
1707
  }));
1689
1708
  function renderFallback(skill) {
1690
1709
  if (!skill.fallbackTo) return null;
@@ -1797,7 +1816,7 @@
1797
1816
  event.preventDefault();
1798
1817
  var next = nextScopeTab(value, event.key);
1799
1818
  setScope(next);
1800
- document.getElementById("dssm-tab-" + next).focus();
1819
+ document.getElementById("dssm-tab-" + next)?.focus();
1801
1820
  } }, t("scope." + value), value === "trash" && (data.trash || []).length > 0 ? h("span", { className: "dssm-trash-count" }, (data.trash || []).length) : null);
1802
1821
  }));
1803
1822
  var skillControls = [h("div", { key: "summary", className: "dssm-summary" }, [["total", "summary.total"], ["enabled", "summary.enabled"], ["disabled", "summary.disabled"], ["invalid", "summary.invalid"]].map(function(item) {
@@ -1886,7 +1905,7 @@
1886
1905
  } }, detail ? h(react.Fragment, null, h("div", { className: "dssm-detail-path" }, detail.path), h("div", { className: "dssm-detail-section" }, h("div", { className: "dssm-detail-title" }, t("detail.diagnostics")), detail.diagnostics.length ? detail.diagnostics.map(function(item, index) {
1887
1906
  return h("div", { key: index, className: "dssm-diag" }, t(item.code, item.params || {}));
1888
1907
  }) : h("div", { className: "dssm-note" }, t("detail.noIssues"))), h("div", { className: "dssm-detail-section" }, h("div", { className: "dssm-detail-title" }, t("detail.body")), h("pre", { className: "dssm-code" }, detail.body || "")), h("div", { className: "dssm-detail-section" }, h("div", { className: "dssm-detail-title" }, t("detail.frontmatter")), h("pre", { className: "dssm-code" }, JSON.stringify(detail.frontmatter, null, 2)))) : h("div", { className: "dssm-empty" }, t("loading"))));
1889
- if (modal && modal.type === "trash-confirm") content.push(h(Modal, { key: "trash-confirm", title: t("confirm.trash.title"), closeLabel: t("btn.close"), onClose: function() {
1908
+ if (modal && typeof modal === "object" && modal.type === "trash-confirm") content.push(h(Modal, { key: "trash-confirm", title: t("confirm.trash.title"), closeLabel: t("btn.close"), onClose: function() {
1890
1909
  setModal(null);
1891
1910
  } }, h("p", { className: "dssm-desc" }, t("confirm.trash.desc", { name: modal.name })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", onClick: function() {
1892
1911
  setModal(null);
@@ -1896,7 +1915,7 @@
1896
1915
  }).catch(function() {
1897
1916
  });
1898
1917
  } }, t("btn.trash")))));
1899
- if (modal && modal.type === "delete-confirm") content.push(h(Modal, { key: "delete-confirm", title: t("confirm.delete.title"), closeLabel: t("btn.close"), onClose: function() {
1918
+ if (modal && typeof modal === "object" && modal.type === "delete-confirm") content.push(h(Modal, { key: "delete-confirm", title: t("confirm.delete.title"), closeLabel: t("btn.close"), onClose: function() {
1900
1919
  setModal(null);
1901
1920
  } }, h("p", { className: "dssm-desc" }, t("confirm.delete.desc", { name: modal.name })), h("div", { className: "dssm-modal-actions" }, h("button", { className: "dssm-btn dssm-btn-secondary", onClick: function() {
1902
1921
  setModal(null);
@@ -1940,6 +1959,7 @@
1940
1959
  module.exports.nextScopeTab = nextScopeTab;
1941
1960
  module.exports.currentSessionId = currentSessionId;
1942
1961
  module.exports.canToggleSource = canToggleSource;
1962
+ module.exports.recoverModalFocus = recoverModalFocus;
1943
1963
  module.exports.trapModalFocus = trapModalFocus;
1944
1964
  module.exports.handleModalEscape = handleModalEscape;
1945
1965
  module.exports.inspectUploadSelection = inspectUploadSelection;
package/lib/core.js CHANGED
@@ -398,7 +398,8 @@ async function browseDirectories(inputPath) {
398
398
  try {
399
399
  canonical = await fs.realpath(target);
400
400
  directory = await fs.stat(canonical);
401
- } catch (error) {
401
+ } catch (caught) {
402
+ const error = caught;
402
403
  return {
403
404
  ok: false,
404
405
  error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${target}`,
@@ -437,7 +438,8 @@ async function browseDirectories(inputPath) {
437
438
  hidden: item.name.startsWith(".")
438
439
  });
439
440
  }
440
- } catch (error) {
441
+ } catch (caught) {
442
+ const error = caught;
441
443
  return {
442
444
  ok: false,
443
445
  error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${canonical}`,
@@ -671,8 +673,9 @@ async function renameWithRetry(source, destination, options = {}) {
671
673
  for (let attempt = 1; ; attempt++) {
672
674
  try {
673
675
  return await rename(source, destination);
674
- } catch (error) {
675
- if (!TRANSIENT_RENAME_CODES.has(error && error.code) || attempt >= maxAttempts)
676
+ } catch (caught) {
677
+ const error = caught;
678
+ if (!TRANSIENT_RENAME_CODES.has(error && error.code || "") || attempt >= maxAttempts)
676
679
  throw error;
677
680
  await new Promise(
678
681
  (resolvePromise) => setTimeout(resolvePromise, delayMs * attempt)
@@ -691,7 +694,8 @@ async function movePathWithFallback(source, destination, options = {}) {
691
694
  try {
692
695
  await renameWithRetry(source, destination, options);
693
696
  return { copied: false, cleanupError: null };
694
- } catch (error) {
697
+ } catch (caught) {
698
+ const error = caught;
695
699
  if (!error || error.code !== "EXDEV") throw error;
696
700
  }
697
701
  const quarantine = join(
@@ -707,14 +711,16 @@ async function movePathWithFallback(source, destination, options = {}) {
707
711
  verbatimSymlinks: true
708
712
  });
709
713
  await renameWithRetry(source, quarantine, options);
710
- } catch (error) {
714
+ } catch (caught) {
715
+ const error = caught;
711
716
  await removeMovedPath(destination).catch(() => void 0);
712
717
  throw error;
713
718
  }
714
719
  let cleanupError = null;
715
720
  try {
716
721
  await removeMovedPath(quarantine);
717
- } catch (error) {
722
+ } catch (caught) {
723
+ const error = caught;
718
724
  cleanupError = error;
719
725
  }
720
726
  return { copied: true, cleanupError, quarantine };
@@ -727,7 +733,8 @@ async function writeFileAtomically(path, content) {
727
733
  try {
728
734
  await fs.writeFile(temp, content, "utf8");
729
735
  await fs.rename(temp, path);
730
- } catch (error) {
736
+ } catch (caught) {
737
+ const error = caught;
731
738
  await fs.rm(temp, { force: true }).catch(() => void 0);
732
739
  throw error;
733
740
  }
@@ -746,11 +753,11 @@ async function resolveEntry(root, name) {
746
753
  (entry) => entry.name === name
747
754
  ) || null;
748
755
  }
749
- root = definition ? definition.path : root;
756
+ const directory = definition ? definition.path : root;
750
757
  try {
751
- const bundlePath = entryPath(root, name);
758
+ const bundlePath = entryPath(directory, name);
752
759
  if (bundlePath === null) return null;
753
- const rootPath = resolve(root);
760
+ const rootPath = resolve(directory);
754
761
  const rootStat = await lstatOrNull(rootPath);
755
762
  if (!rootStat || !rootStat.isDirectory() || rootStat.isSymbolicLink())
756
763
  return null;
@@ -856,38 +863,38 @@ async function scanEntries(root, options = {}) {
856
863
  }
857
864
  return { ...discovered, entries: entries2 };
858
865
  }
859
- root = definition ? definition.path : root;
860
- const rootStat = await lstatOrNull(resolve(root));
866
+ const directory = definition ? definition.path : root;
867
+ const rootStat = await lstatOrNull(resolve(directory));
861
868
  if (!rootStat || !rootStat.isDirectory() || rootStat.isSymbolicLink())
862
869
  return { exists: false, entries: [] };
863
870
  let items;
864
871
  try {
865
- items = await fs.readdir(root, { withFileTypes: true });
872
+ items = await fs.readdir(directory, { withFileTypes: true });
866
873
  } catch {
867
874
  return { exists: false, entries: [] };
868
875
  }
869
876
  const byName = /* @__PURE__ */ new Map();
870
- const rootReal = await resolvedPath(root);
877
+ const rootReal = await resolvedPath(directory);
871
878
  for (const it of items) {
872
879
  try {
873
880
  if (it.isSymbolicLink()) continue;
874
- if (it.isDirectory() && entryPath(root, it.name) !== null) {
875
- const docPath = join(root, it.name, "SKILL.md");
881
+ if (it.isDirectory() && entryPath(directory, it.name) !== null) {
882
+ const docPath = join(directory, it.name, "SKILL.md");
876
883
  const st = await lstatOrNull(docPath);
877
884
  if (!st || !st.isFile() || st.isSymbolicLink() || !await isInsideResolvedRoot(rootReal, docPath))
878
885
  continue;
879
886
  const doc = options.metadataOnly ? { map: {}, hasFrontmatter: false } : parseSkillDoc(await fs.readFile(docPath, "utf8"));
880
887
  byName.set(it.name, {
881
888
  ...entryOf(it.name, "bundle", docPath, doc),
882
- entryPath: join(root, it.name),
889
+ entryPath: join(directory, it.name),
883
890
  realDocPath: await fs.realpath(docPath),
884
- realEntryPath: await fs.realpath(join(root, it.name)),
891
+ realEntryPath: await fs.realpath(join(directory, it.name)),
885
892
  linked: false
886
893
  });
887
- } else if (it.isFile() && it.name.toLowerCase().endsWith(".md") && it.name.toLowerCase() !== "skill.md" && entryPath(root, it.name.slice(0, -3)) !== null) {
894
+ } else if (it.isFile() && it.name.toLowerCase().endsWith(".md") && it.name.toLowerCase() !== "skill.md" && entryPath(directory, it.name.slice(0, -3)) !== null) {
888
895
  const skillName = it.name.slice(0, -3);
889
896
  if (byName.has(skillName)) continue;
890
- const docPath = join(root, it.name);
897
+ const docPath = join(directory, it.name);
891
898
  if (!await isInsideResolvedRoot(rootReal, docPath)) continue;
892
899
  const doc = options.metadataOnly ? { map: {}, hasFrontmatter: false } : parseSkillDoc(await fs.readFile(docPath, "utf8"));
893
900
  const realDocPath = await fs.realpath(docPath);
@@ -909,7 +916,7 @@ async function scanEntries(root, options = {}) {
909
916
  }
910
917
  async function scanDeduplicatedRoots(roots = userRoots(), options = {}) {
911
918
  const results = await Promise.all(
912
- roots.map(async (root) => [root, await scanEntries(root, options)])
919
+ roots.map(async (root) => [root, options.metadataOnly ? await scanEntries(root, { metadataOnly: true }) : await scanEntries(root)])
913
920
  );
914
921
  const scans = /* @__PURE__ */ new Map();
915
922
  const groups = /* @__PURE__ */ new Map();
@@ -1068,7 +1075,8 @@ async function readManagerState() {
1068
1075
  warning: null,
1069
1076
  writable: true
1070
1077
  };
1071
- } catch (error) {
1078
+ } catch (caught) {
1079
+ const error = caught;
1072
1080
  if (error && error.code === "ENOENT")
1073
1081
  return { state: defaultManagerState(), warning: null, writable: true };
1074
1082
  return {
@@ -1293,8 +1301,9 @@ async function publishTrashStage(stage, finalPath, metadata, renameOptions) {
1293
1301
  try {
1294
1302
  await renameWithRetry(stage, finalPath, renameOptions);
1295
1303
  return { fallback: false, cleanupError: null };
1296
- } catch (error) {
1297
- if (!TRANSIENT_RENAME_CODES.has(error && error.code)) throw error;
1304
+ } catch (caught) {
1305
+ const error = caught;
1306
+ if (!TRANSIENT_RENAME_CODES.has(error && error.code || "")) throw error;
1298
1307
  }
1299
1308
  await fs.mkdir(finalPath);
1300
1309
  try {
@@ -1312,14 +1321,16 @@ async function publishTrashStage(stage, finalPath, metadata, renameOptions) {
1312
1321
  `,
1313
1322
  "utf8"
1314
1323
  );
1315
- } catch (error) {
1324
+ } catch (caught) {
1325
+ const error = caught;
1316
1326
  await fs.rm(finalPath, { recursive: true, force: true }).catch(() => void 0);
1317
1327
  throw error;
1318
1328
  }
1319
1329
  let cleanupError = null;
1320
1330
  try {
1321
1331
  await fs.rm(stage, { recursive: true, force: true });
1322
- } catch (error) {
1332
+ } catch (caught) {
1333
+ const error = caught;
1323
1334
  cleanupError = error;
1324
1335
  }
1325
1336
  return { fallback: true, cleanupError };
@@ -1423,7 +1434,8 @@ async function deleteSkill(root, name, log, options = {}) {
1423
1434
  );
1424
1435
  if (log) log("trash", `\u79FB\u5230\u56DE\u6536\u7AD9 ${name} -> ${finalPath}`);
1425
1436
  return { id, name, deletedAt: metadata.deletedAt, root: metadata.root };
1426
- } catch (error) {
1437
+ } catch (caught) {
1438
+ const error = caught;
1427
1439
  const rollbackFailures = [];
1428
1440
  for (const item of moved.reverse()) {
1429
1441
  try {
@@ -1432,7 +1444,8 @@ async function deleteSkill(root, name, log, options = {}) {
1432
1444
  item.path,
1433
1445
  options.renameOptions
1434
1446
  );
1435
- } catch (rollbackError) {
1447
+ } catch (caught2) {
1448
+ const rollbackError = caught2;
1436
1449
  rollbackFailures.push({
1437
1450
  path: item.destination,
1438
1451
  error: String(
@@ -1506,7 +1519,8 @@ async function restoreTrash(id, log, options = {}) {
1506
1519
  await fs.rm(itemRoot, { recursive: true, force: true });
1507
1520
  if (log) log("restore", `\u4ECE\u56DE\u6536\u7AD9\u6062\u590D ${metadata.name} -> ${root}`);
1508
1521
  return { id, name: metadata.name, root: trashRootMetadata(definition) };
1509
- } catch (error) {
1522
+ } catch (caught) {
1523
+ const error = caught;
1510
1524
  for (const item of moved.reverse())
1511
1525
  await movePathWithFallback(
1512
1526
  item.destination,
@@ -1680,7 +1694,8 @@ async function preflightCandidates(pending, conflicts, failed) {
1680
1694
  const candidate = group[i];
1681
1695
  try {
1682
1696
  await assertNoSymbolicLinks(candidate.source);
1683
- } catch (error) {
1697
+ } catch (caught) {
1698
+ const error = caught;
1684
1699
  failed.push(
1685
1700
  attachCode(
1686
1701
  {
@@ -1704,7 +1719,8 @@ async function copyToTemporary(source, target, isDir) {
1704
1719
  else await fs.copyFile(source, temp);
1705
1720
  await assertNoSymbolicLinks(temp);
1706
1721
  return temp;
1707
- } catch (error) {
1722
+ } catch (caught) {
1723
+ const error = caught;
1708
1724
  await fs.rm(temp, { recursive: true, force: true }).catch(() => void 0);
1709
1725
  throw error;
1710
1726
  }
@@ -1719,13 +1735,15 @@ async function replaceWithCopy(source, dest, isDir, existing = []) {
1719
1735
  backups.push({ path, backup });
1720
1736
  }
1721
1737
  await fs.rename(stage, dest);
1722
- } catch (error) {
1738
+ } catch (caught) {
1739
+ const error = caught;
1723
1740
  await fs.rm(stage, { recursive: true, force: true }).catch(() => void 0);
1724
1741
  const rollbackFailures = [];
1725
1742
  for (const item of backups.reverse()) {
1726
1743
  try {
1727
1744
  await fs.rename(item.backup, item.path);
1728
- } catch (rollbackError) {
1745
+ } catch (caught2) {
1746
+ const rollbackError = caught2;
1729
1747
  rollbackFailures.push(item.backup);
1730
1748
  }
1731
1749
  }
@@ -1743,7 +1761,8 @@ async function replaceWithCopy(source, dest, isDir, existing = []) {
1743
1761
  for (const item of backups) {
1744
1762
  try {
1745
1763
  await fs.rm(item.backup, { recursive: true, force: true });
1746
- } catch (error) {
1764
+ } catch (caught) {
1765
+ const error = caught;
1747
1766
  warnings.push({
1748
1767
  code: "warning.backupUncleaned",
1749
1768
  params: {
@@ -1787,7 +1806,8 @@ async function importSkill(source, log, options = {}) {
1787
1806
  } else {
1788
1807
  try {
1789
1808
  candidates = await collectCandidates(source);
1790
- } catch (error) {
1809
+ } catch (caught) {
1810
+ const error = caught;
1791
1811
  return attachCode(
1792
1812
  {
1793
1813
  ok: false,
@@ -1883,7 +1903,8 @@ async function importSkill(source, log, options = {}) {
1883
1903
  const warnings = await replaceWithCopy(p.source, p.dest, p.isDir);
1884
1904
  imported.push({ name: p.name, overwritten: false, warnings });
1885
1905
  if (log) log("import", `\u5BFC\u5165 ${p.source} -> ${p.dest}`);
1886
- } catch (e) {
1906
+ } catch (caught) {
1907
+ const e = caught;
1887
1908
  failed.push(
1888
1909
  attachCode(
1889
1910
  { source: p.source, error: String(e && e.message ? e.message : e) },
@@ -1904,7 +1925,8 @@ async function importSkill(source, log, options = {}) {
1904
1925
  );
1905
1926
  imported.push({ name: c.name, overwritten: true, warnings });
1906
1927
  if (log) log("import-overwrite", `\u8986\u76D6\u5BFC\u5165 ${c.source} -> ${dest}`);
1907
- } catch (e) {
1928
+ } catch (caught) {
1929
+ const e = caught;
1908
1930
  failed.push(
1909
1931
  attachCode(
1910
1932
  { source: c.source, error: String(e && e.message ? e.message : e) },
@@ -2050,7 +2072,8 @@ async function writeUploadedZip(contentRoot, encoded) {
2050
2072
  return !normalized.directory;
2051
2073
  }
2052
2074
  });
2053
- } catch (error) {
2075
+ } catch (caught) {
2076
+ const error = caught;
2054
2077
  if (error && /^error\./.test(String(error.code || ""))) throw error;
2055
2078
  throw codedError(
2056
2079
  `ZIP \u65E0\u6CD5\u89E3\u538B: ${String(error && error.message ? error.message : error)}`,
@@ -2095,7 +2118,8 @@ async function importUploadedSkill(input, log, options = {}) {
2095
2118
  await fs.mkdir(sessionRoot, { recursive: true });
2096
2119
  const source = await prepareUploadedSource(sessionRoot, input || {});
2097
2120
  return await importSkill(source, log, options);
2098
- } catch (error) {
2121
+ } catch (caught) {
2122
+ const error = caught;
2099
2123
  return uploadError(error);
2100
2124
  } finally {
2101
2125
  await fs.rm(sessionRoot, { recursive: true, force: true }).catch(() => void 0);
@@ -2156,7 +2180,8 @@ ${body}
2156
2180
  `;
2157
2181
  await fs.writeFile(join(stage, "SKILL.md"), content, "utf8");
2158
2182
  await fs.rename(stage, target);
2159
- } catch (error) {
2183
+ } catch (caught) {
2184
+ const error = caught;
2160
2185
  await fs.rm(stage, { recursive: true, force: true }).catch(() => void 0);
2161
2186
  throw error;
2162
2187
  }
@@ -2363,6 +2388,7 @@ async function state(options = {}) {
2363
2388
  const result = {
2364
2389
  roots: [],
2365
2390
  projects: [],
2391
+ summary: { total: 0, enabled: 0, disabled: 0, issues: 0 },
2366
2392
  trash,
2367
2393
  warnings: [
2368
2394
  ...policyResult.warning ? [policyResult.warning] : [],
package/lib/index.js CHANGED
@@ -73,7 +73,8 @@ function readBody(req, limit = 1 << 20) {
73
73
  const body = raw ? JSON.parse(raw) : {};
74
74
  settled = true;
75
75
  resolve(body);
76
- } catch (e) {
76
+ } catch (caught) {
77
+ const e = caught;
77
78
  const error = new Error(`invalid JSON body: ${e.message}`);
78
79
  error.statusCode = 400;
79
80
  error.code = "error.proto.invalidJson";
@@ -142,7 +143,7 @@ function validateMutationRequest(req, trustedHosts) {
142
143
  if (contentType !== "application/json") return { statusCode: 415, code: "error.proto.contentType", error: "content-type must be application/json" };
143
144
  return null;
144
145
  }
145
- function json(res, code, payload, headOnly) {
146
+ function json(res, code, payload, headOnly = false) {
146
147
  if (!res || res.writableEnded || res.destroyed) return;
147
148
  res.once("error", () => {
148
149
  });
@@ -156,9 +157,9 @@ function json(res, code, payload, headOnly) {
156
157
  } catch {
157
158
  }
158
159
  }
159
- function run(res, task, afterSuccess, headOnly) {
160
+ function run(res, task, afterSuccess, headOnly = false) {
160
161
  return Promise.resolve().then(task).then((r) => {
161
- if (r && r.ok === false) json(res, 400, r, headOnly);
162
+ if (r && typeof r === "object" && "ok" in r && r.ok === false) json(res, 400, r, headOnly);
162
163
  else {
163
164
  try {
164
165
  if (afterSuccess) afterSuccess();
@@ -324,7 +325,8 @@ function apply(ctx) {
324
325
  try {
325
326
  const sources = await repositories.sources();
326
327
  for (const root of snapshot.roots) if (root.key === "dsh") for (const skill of root.skills || []) skill.installSource = sources[skill.name] || null;
327
- } catch (error) {
328
+ } catch (caught) {
329
+ const error = caught;
328
330
  snapshot.warnings.push({ code: "error.repo.state", error: error.message });
329
331
  }
330
332
  return snapshot;
@@ -415,7 +417,8 @@ function apply(ctx) {
415
417
  json(res, 404, { ok: false, code: "error.proto.unknownAction", error: `unknown action: ${path}` });
416
418
  }
417
419
  });
418
- } catch (e) {
420
+ } catch (caught) {
421
+ const e = caught;
419
422
  json(res, Number.isInteger(e && e.statusCode) ? e.statusCode : 500, {
420
423
  ok: false,
421
424
  ...e && e.code ? { code: e.code } : {},
@@ -240,7 +240,8 @@ function registerPluginUpdater(ctx, options) {
240
240
  if (notifyParent) setTimeout(() => {
241
241
  process.send?.(PLUGIN_UPDATE_IPC);
242
242
  }, 150).unref?.();
243
- } catch (error) {
243
+ } catch (caught) {
244
+ const error = caught;
244
245
  ctx.logger.warn(`plugin updater failed: ${String(error)}`);
245
246
  json(response, 503, { error: publicError(error) });
246
247
  }
@@ -34,7 +34,8 @@ async function discoverReadonlyEntries(root) {
34
34
  let docStat;
35
35
  try {
36
36
  docStat = await fs.lstat(docPath);
37
- } catch (error) {
37
+ } catch (caught) {
38
+ const error = caught;
38
39
  if (error.code !== "ENOENT") throw error;
39
40
  }
40
41
  if (docStat?.isFile() && !docStat.isSymbolicLink()) {
@@ -16,7 +16,8 @@ function safePath(value, allowEmpty = false) {
16
16
  if (parts.length > 64 || parts.some((p) => !p || p === "." || p === ".." || /[\\:<>"|?*\x00-\x1f]/.test(p) || /[. ]$/.test(p) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i.test(p))) throw failure("\u4ED3\u5E93\u8DEF\u5F84\u4E0D\u5B89\u5168");
17
17
  return value;
18
18
  }
19
- function validateStoredState(value) {
19
+ function validateStoredState(input) {
20
+ const value = input;
20
21
  if (!value || value.version !== 1 || !Array.isArray(value.repositories) || value.repositories.length > 30 || !Array.isArray(value.installs)) throw failure("\u4ED3\u5E93\u72B6\u6001\u683C\u5F0F\u65E0\u6548");
21
22
  for (const repo of value.repositories) {
22
23
  if (!repo || typeof repo.id !== "string" || !Array.isArray(repo.skills) || repo.skills.length > 500) throw failure("\u4ED3\u5E93\u72B6\u6001\u683C\u5F0F\u65E0\u6548");
@@ -28,7 +29,7 @@ function validateStoredState(value) {
28
29
  }
29
30
  }
30
31
  for (const record of value.installs) {
31
- if (!record || typeof record.id !== "string" || typeof record.complete !== "boolean" || !Array.isArray(record.files) || !record.files.length || record.files.length > 1e3 || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(record.commit)) throw failure("\u4ED3\u5E93\u5B89\u88C5\u8BB0\u5F55\u65E0\u6548");
32
+ if (!record || typeof record.id !== "string" || typeof record.complete !== "boolean" || !Array.isArray(record.files) || !record.files.length || record.files.length > 1e3 || !/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(record.commit || "")) throw failure("\u4ED3\u5E93\u5B89\u88C5\u8BB0\u5F55\u65E0\u6548");
32
33
  safePath(record.path, true);
33
34
  safePath(record.name);
34
35
  if (record.name.includes("/")) throw failure("\u4ED3\u5E93\u5B89\u88C5\u540D\u79F0\u65E0\u6548");
@@ -122,7 +123,8 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
122
123
  const value = JSON.parse(await fs.readFile(file, "utf8"));
123
124
  validateStoredState(value);
124
125
  return value;
125
- } catch (error) {
126
+ } catch (caught) {
127
+ const error = caught;
126
128
  if (error.code === "ENOENT") return { version: 1, repositories: [], installs: [] };
127
129
  throw failure("\u4ED3\u5E93\u72B6\u6001\u6587\u4EF6\u635F\u574F\u6216\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u8BF7\u4FDD\u7559\u6587\u4EF6\u5E76\u4FEE\u590D\u540E\u91CD\u8BD5", "error.repo.state");
128
130
  }
@@ -151,7 +153,8 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
151
153
  try {
152
154
  const response = await fetchImpl(url, { redirect: "error", signal: AbortSignal.timeout(6e4), headers: { Accept: "application/zip", "User-Agent": "dsh-skills-manager" } });
153
155
  return await readResponse(response, LIMIT);
154
- } catch (error) {
156
+ } catch (caught) {
157
+ const error = caught;
155
158
  if (typeof error.code === "string" && error.code.startsWith("error.repo.")) throw error;
156
159
  throw failure("\u4ED3\u5E93\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5", "error.repo.network");
157
160
  }
@@ -199,10 +202,12 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
199
202
  for (let i = 0; i < refs.length; i++) {
200
203
  try {
201
204
  return await request(`https://codeload.github.com/${repo.owner}/${repo.name}/zip/${refs[i].split("/").map(encodeURIComponent).join("/")}`);
202
- } catch (error) {
205
+ } catch (caught) {
206
+ const error = caught;
203
207
  if (error.httpStatus !== 404 || i === refs.length - 1) throw error;
204
208
  }
205
209
  }
210
+ throw failure("\u4ED3\u5E93\u5206\u652F\u4E0D\u5B58\u5728");
206
211
  }
207
212
  async function matches(record) {
208
213
  try {
@@ -289,14 +294,16 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
289
294
  }
290
295
  const snapshot = await cacheArchive(repo, bytes);
291
296
  Object.assign(repo, { skills, commit: snapshot, refreshedAt: (/* @__PURE__ */ new Date()).toISOString(), error: null });
292
- } catch (error) {
297
+ } catch (caught) {
298
+ const error = caught;
293
299
  repo.error = { code: error.code || "error.repo.invalid", error: error.message };
294
300
  }
295
301
  await write(data);
296
302
  if (!repo.error) {
297
303
  try {
298
304
  await pruneCache(repo);
299
- } catch (error) {
305
+ } catch (caught) {
306
+ const error = caught;
300
307
  if (log) await log("repository.cache.cleanup.failed", { repository: repo.id, error: error.message });
301
308
  else console.warn("\u4ED3\u5E93\u65E7\u7F13\u5B58\u672A\u6E05\u7406\uFF1A" + error.message);
302
309
  }
@@ -326,7 +333,8 @@ function createRepositoryManager({ fetchImpl = globalThis.fetch, log } = {}) {
326
333
  try {
327
334
  const result = await importUploadedSkill({ name: skill.name, entries }, log, { conflict: "skip" });
328
335
  if (result.ok === false || !result.imported?.length) throw failure(result.error || "\u6280\u80FD\u5B89\u88C5\u5931\u8D25\u6216\u9047\u5230\u540C\u540D\u51B2\u7A81", "error.repo.conflict");
329
- } catch (error) {
336
+ } catch (caught) {
337
+ const error = caught;
330
338
  data.installs = data.installs.filter((item) => item !== record);
331
339
  try {
332
340
  await write(data);
@@ -101,7 +101,8 @@ function createRepositoryUpdater({ read, write, repository, download, serialize,
101
101
  replaced = true;
102
102
  data.installs[data.installs.indexOf(record)] = nextRecord;
103
103
  await write(data);
104
- } catch (error) {
104
+ } catch (caught) {
105
+ const error = caught;
105
106
  if (moved) {
106
107
  try {
107
108
  if (replaced) await fs.rename(target, stage);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-skills-manager",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "DSH Skills Manager — 在 DeepSeek Harness 中统一加载并安全管理本机 Agent Skills · Load and safely manage local Agent Skills in DSH",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -40,11 +40,13 @@
40
40
  },
41
41
  "scripts": {
42
42
  "build": "node scripts/build.mjs",
43
- "test": "npm run build && node test/build-test.mjs && node test/repositories.test.mjs && node test/repository-ui.test.mjs && node test/theme-select.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/readonly-discovery.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
44
- "verify": "node scripts/sync-hosts.mjs && npm test && npm run pack:check && node scripts/check-generated.mjs",
43
+ "test": "npm run typecheck && npm run build && node test/build-test.mjs && node test/repositories.test.mjs && node test/repository-ui.test.mjs && node test/theme-select.test.mjs && node test/repository-routes.test.mjs && node test/core-test.mjs && node test/readonly-discovery.test.mjs && node test/project-sources.test.mjs && node test/locale-test.mjs && node --test test/plugin-updater.test.mjs && node test/plugin-update-locale.test.mjs && node test/client-scope.test.mjs && node test/compatibility-options-test.mjs",
44
+ "verify": "node scripts/sync-hosts.mjs && npm test && npm run pack:check && node test/pack-test.mjs",
45
45
  "pack:check": "npm pack --dry-run --json",
46
+ "prepack": "npm run typecheck && npm run build",
46
47
  "prepublishOnly": "npm run verify",
47
- "test:compat": "node scripts/test-host-compatibility.mjs"
48
+ "test:compat": "node scripts/test-host-compatibility.mjs",
49
+ "typecheck": "tsc --noEmit"
48
50
  },
49
51
  "publishConfig": {
50
52
  "registry": "https://registry.npmjs.org/",
@@ -68,12 +70,15 @@
68
70
  "@deepseek-ai/dsh-client-ui-primitives": "0.1.6-alpha.2",
69
71
  "@deepseek-ai/dsh-client-ui-slots": "0.1.6-alpha.2",
70
72
  "@deepseek-ai/dsh-host-webserver": "0.1.6-alpha.2",
71
- "@deepseek-ai/dsh-session": "0.1.6-alpha.2",
72
73
  "@deepseek-ai/dsh-scope": "0.1.6-alpha.2",
74
+ "@deepseek-ai/dsh-session": "0.1.6-alpha.2",
73
75
  "@deepseek-ai/dsh-skill": "0.1.6-alpha.2",
74
76
  "@deepseek-ai/dsh-tools": "0.1.6-alpha.2",
75
77
  "@deepseek-ai/dsh-web-app": "0.1.6-alpha.2",
76
- "esbuild": "0.25.0"
78
+ "@types/node": "22.20.4",
79
+ "@types/react": "18.3.31",
80
+ "esbuild": "0.25.0",
81
+ "typescript": "5.9.3"
77
82
  },
78
83
  "engines": {
79
84
  "node": "^22.19.0 || >=24.0.0"