dsh-skill-hub 0.3.11 → 0.3.13
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/CONTRIBUTING.md +4 -2
- package/README.md +2 -1
- package/README.zh.md +2 -1
- package/lib/client.js +85 -41
- package/lib/client.js.map +1 -1
- package/lib/index.js +132 -2
- package/lib/types/client/grouping.d.ts +18 -1
- package/lib/types/error-text.d.ts +4 -1
- package/lib/types/reconcile.d.ts +24 -0
- package/lib/types/skillfs/scan.d.ts +7 -0
- package/package.json +35 -29
- package/src/client/grouping.test.ts +35 -2
- package/src/client/grouping.ts +38 -1
- package/src/client/panel/PanelDialogs.tsx +8 -2
- package/src/client/panel/SourcesView.tsx +12 -12
- package/src/client/panel/dialogs.tsx +45 -18
- package/src/error-text.test.ts +45 -0
- package/src/error-text.ts +44 -2
- package/src/index.ts +11 -0
- package/src/reconcile.test.ts +76 -0
- package/src/reconcile.ts +62 -0
- package/src/skillfs/scan.ts +38 -0
package/CONTRIBUTING.md
CHANGED
|
@@ -39,7 +39,7 @@ src/client/locales/ dictionaries by view (common/skills/market/sources/detai
|
|
|
39
39
|
```bash
|
|
40
40
|
npm install
|
|
41
41
|
npm run typecheck # tsc --noEmit
|
|
42
|
-
npm test # vitest (
|
|
42
|
+
npm test # vitest (233 tests across 14 suites)
|
|
43
43
|
npm run build # tsc declarations + tsdown bundles (lib/index.js + lib/client.js)
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -68,10 +68,12 @@ npm run build # tsc declarations + tsdown bundles (lib/index.js + lib/clie
|
|
|
68
68
|
```bash
|
|
69
69
|
# after a change:
|
|
70
70
|
npm run build
|
|
71
|
-
cp lib/index.js lib/client.js ~/.dsh/profiles/web/node_modules/dsh-skill-hub/lib/
|
|
72
71
|
# restart the dsh web process, then verify Settings → 技能 and Settings → 插件 → Skill Hub
|
|
73
72
|
```
|
|
74
73
|
|
|
74
|
+
When the web profile installs this repo as a link (`"dsh-skill-hub": "link:/path/to/repo"`, the
|
|
75
|
+
usual local-dev setup), `lib/` is picked up on the next `dsh web` restart — no copy step needed.
|
|
76
|
+
|
|
75
77
|
## Issues
|
|
76
78
|
|
|
77
79
|
- **Bugs**: include the dsh version, Node version, the plugin version, and the exact steps.
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ dsh plugin --profile web add dsh-skill-hub
|
|
|
24
24
|
# restart dsh web → Settings → 技能 → Market → scan → import
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
Requires `Node ^22.19 || >=24` + dsh web (`0.1.
|
|
27
|
+
Requires `Node ^22.19 || >=24` + dsh web (`0.1.6-alpha.1`, `0.1.x` forward compatible).
|
|
28
28
|
|
|
29
29
|
## Features
|
|
30
30
|
|
|
@@ -115,6 +115,7 @@ npm run build # tsc + tsdown → lib/index.js + lib/client.js
|
|
|
115
115
|
|
|
116
116
|
- `duplicate loader entry id: skill-hub` — remove the duplicate install (keep one `dsh plugin add` method).
|
|
117
117
|
- Skill missing — check the diagnostics section (frontmatter / name mismatch / short description).
|
|
118
|
+
- Empty source group — its skills were deleted, or their disabled records were lost (sidecar restored/hand-edited). Startup reconciles `.disabled` files on disk; source groups with no visible member are no longer rendered.
|
|
118
119
|
- Dots missing in `/` menu — dsh internals changed; catalog still works.
|
|
119
120
|
|
|
120
121
|
## Community
|
package/README.zh.md
CHANGED
|
@@ -24,7 +24,7 @@ dsh plugin --profile web add dsh-skill-hub
|
|
|
24
24
|
# 重启 dsh web → 设置 → 技能 → 市场 → 扫描 → 导入
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
要求 `Node ^22.19 || >=24` + dsh web(`0.1.
|
|
27
|
+
要求 `Node ^22.19 || >=24` + dsh web(`0.1.6-alpha.1`,兼容后续 `0.1.x`)。
|
|
28
28
|
|
|
29
29
|
## 功能
|
|
30
30
|
|
|
@@ -115,6 +115,7 @@ npm run build # tsc + tsdown → lib/index.js + lib/client.js
|
|
|
115
115
|
|
|
116
116
|
- `duplicate loader entry id: skill-hub` — 删掉重复安装(只留一种 `dsh plugin add`)。
|
|
117
117
|
- 技能不出现 — 看诊断区(缺 frontmatter / 名称不一致 / 描述过短)。
|
|
118
|
+
- 来源组空壳 — 组内技能已被删除、或禁用记录丢失(状态文件被恢复/手改)。启动时自动对账磁盘上的 `.disabled` 文件补记录;无可见成员的来源组不再渲染。
|
|
118
119
|
- `/` 菜单圆点消失 — dsh 内部触发源变更,目录功能不受影响。
|
|
119
120
|
|
|
120
121
|
## 社区
|
package/lib/client.js
CHANGED
|
@@ -959,6 +959,27 @@ window.__ModuleLoader__.load({
|
|
|
959
959
|
});
|
|
960
960
|
}
|
|
961
961
|
/**
|
|
962
|
+
* Match origin collections against the currently visible enabled skills and
|
|
963
|
+
* disabled records, dropping collections with no visible member. Without
|
|
964
|
+
* this, a stale origin (skill deleted on disk, or a `.disabled` file whose
|
|
965
|
+
* sidecar record was lost) renders a group header with zero rows — an empty
|
|
966
|
+
* shell the sources tab otherwise never shows (project/personal groups
|
|
967
|
+
* already disappear when they have nothing to display).
|
|
968
|
+
*/
|
|
969
|
+
function visibleCollections(collections, visibleSkills, disabledRecords, normalized, sourceFilter, origins) {
|
|
970
|
+
const visible = [];
|
|
971
|
+
for (const collection of collections) {
|
|
972
|
+
const skills = visibleSkills.filter((skill) => collection.skillNames.includes(skill.name));
|
|
973
|
+
const disabledMembers = disabledRecords.filter((record) => collection.skillNames.includes(record.name) && (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)) && (sourceFilter === "all" || (origins[record.name] ?? "private") === sourceFilter));
|
|
974
|
+
if (skills.length > 0 || disabledMembers.length > 0) visible.push({
|
|
975
|
+
collection,
|
|
976
|
+
skills,
|
|
977
|
+
disabledMembers
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
return visible;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
962
983
|
* Sort a skill list in place-safe copy order: name ascending, added
|
|
963
984
|
* descending (newest first, unknown addedAt last), or uses descending
|
|
964
985
|
* (most-called first). Unknown values always trail.
|
|
@@ -3833,10 +3854,12 @@ window.__ModuleLoader__.load({
|
|
|
3833
3854
|
const [topOverKey, setTopOverKey] = (0, react.useState)(null);
|
|
3834
3855
|
/** 重复技能名集合:整表只建一次,行内用 has 取代逐行线性 includes。 */
|
|
3835
3856
|
const duplicateNames = (0, react.useMemo)(() => new Set(catalog?.duplicateNames ?? []), [catalog]);
|
|
3836
|
-
const
|
|
3857
|
+
const sourceFiltered = filterBySource(sorted, sourceFilter, origins);
|
|
3858
|
+
const projectSkillsAll = sourceFiltered.filter((skill) => isProjectSource(skill.source));
|
|
3837
3859
|
const hasProject = projectSkillsAll.length > 0;
|
|
3838
|
-
const
|
|
3839
|
-
const
|
|
3860
|
+
const visible = visibleCollections(groupsState?.collections ?? [], sourceFiltered, catalog?.disabled ?? [], normalized, sourceFilter, origins);
|
|
3861
|
+
const collections = visible.map((entry) => entry.collection);
|
|
3862
|
+
const uncategorized = sourceFiltered.filter((skill) => origins[skill.name] === void 0 && !isProjectSource(skill.source));
|
|
3840
3863
|
const personalDisabled = (catalog?.disabled ?? []).filter((record) => origins[record.name] === void 0).filter((record) => normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)).filter((record) => sourceFilter === "all" || sourceFilter === "private");
|
|
3841
3864
|
const allPersonalNames = [...uncategorized.map((s) => s.name), ...personalDisabled.map((r) => r.name)];
|
|
3842
3865
|
const hasPersonal = allPersonalNames.length > 0;
|
|
@@ -3895,7 +3918,7 @@ window.__ModuleLoader__.load({
|
|
|
3895
3918
|
openDetail: hub.openDetail,
|
|
3896
3919
|
requestDeleteSkill: hub.requestDeleteSkill
|
|
3897
3920
|
};
|
|
3898
|
-
if (skillView === "flat") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children:
|
|
3921
|
+
if (skillView === "flat") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: sourceFiltered.map((skill) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SkillRow, {
|
|
3899
3922
|
skill,
|
|
3900
3923
|
...rowProps
|
|
3901
3924
|
}, skill.name)) });
|
|
@@ -3933,10 +3956,9 @@ window.__ModuleLoader__.load({
|
|
|
3933
3956
|
}, "project");
|
|
3934
3957
|
if (topKey.startsWith("col:")) {
|
|
3935
3958
|
const colName = topKey.slice(4);
|
|
3936
|
-
const
|
|
3937
|
-
if (
|
|
3938
|
-
const
|
|
3939
|
-
const disabledMembers = (catalog?.disabled ?? []).filter((record) => collection.skillNames.includes(record.name) && (normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized)) && (sourceFilter === "all" || (origins[record.name] ?? "private") === sourceFilter));
|
|
3959
|
+
const entry = visible.find((item) => item.collection.name === colName);
|
|
3960
|
+
if (entry === void 0) return null;
|
|
3961
|
+
const { collection, skills, disabledMembers } = entry;
|
|
3940
3962
|
const collapsed = collapsedGroups.has("col:" + collection.name);
|
|
3941
3963
|
const view = groupSwitchView(collection.skillNames, viewNames);
|
|
3942
3964
|
const check = sourceCheck[collection.name];
|
|
@@ -4407,6 +4429,23 @@ window.__ModuleLoader__.load({
|
|
|
4407
4429
|
function VersionChoiceDialog(props) {
|
|
4408
4430
|
const { choice, busy, onSelect, onCustom, onCancel, onConfirm } = props;
|
|
4409
4431
|
const effective = choice.custom.trim() !== "" ? choice.custom.trim() : choice.selected;
|
|
4432
|
+
const branchOptions = choice.branches.filter((branch) => !choice.releases.includes(branch));
|
|
4433
|
+
const [refGroup, setRefGroup] = (0, react.useState)(choice.releases.includes(choice.selected) || choice.branches.length === 0 ? "releases" : "branches");
|
|
4434
|
+
(0, react.useEffect)(() => {
|
|
4435
|
+
if (choice.loading) return;
|
|
4436
|
+
if (choice.releases.includes(choice.selected)) setRefGroup("releases");
|
|
4437
|
+
else if (branchOptions.includes(choice.selected)) setRefGroup("branches");
|
|
4438
|
+
}, [choice.loading, choice.repo]);
|
|
4439
|
+
const activeRefs = refGroup === "releases" ? choice.releases : branchOptions;
|
|
4440
|
+
const listedRefs = [...choice.releases, ...branchOptions];
|
|
4441
|
+
const currentUnlistedRef = choice.selected !== "" && !listedRefs.includes(choice.selected) ? choice.selected : void 0;
|
|
4442
|
+
const selectRefs = currentUnlistedRef !== void 0 ? [currentUnlistedRef, ...activeRefs] : activeRefs;
|
|
4443
|
+
const selectValue = selectRefs.includes(choice.selected) ? choice.selected : selectRefs[0] ?? "";
|
|
4444
|
+
const selectGroup = (next) => {
|
|
4445
|
+
setRefGroup(next);
|
|
4446
|
+
const nextRefs = next === "releases" ? choice.releases : branchOptions;
|
|
4447
|
+
if (nextRefs.length > 0 && !nextRefs.includes(choice.selected)) onSelect(nextRefs[0]);
|
|
4448
|
+
};
|
|
4410
4449
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DialogShell, {
|
|
4411
4450
|
onClose: onCancel,
|
|
4412
4451
|
children: [
|
|
@@ -4422,38 +4461,42 @@ window.__ModuleLoader__.load({
|
|
|
4422
4461
|
className: panel_module_css_default.dialogText,
|
|
4423
4462
|
children: tt("market.scanning")
|
|
4424
4463
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4425
|
-
|
|
4426
|
-
className: panel_module_css_default.
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
|
|
4433
|
-
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
children:
|
|
4439
|
-
},
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4464
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
4465
|
+
className: panel_module_css_default.segmented,
|
|
4466
|
+
role: "group",
|
|
4467
|
+
"aria-label": tt("market.versionTitle"),
|
|
4468
|
+
style: { marginBottom: 8 },
|
|
4469
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4470
|
+
type: "button",
|
|
4471
|
+
className: panel_module_css_default.segBtn + (refGroup === "releases" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
4472
|
+
disabled: choice.releases.length === 0,
|
|
4473
|
+
"aria-pressed": refGroup === "releases",
|
|
4474
|
+
onClick: () => {
|
|
4475
|
+
selectGroup("releases");
|
|
4476
|
+
},
|
|
4477
|
+
children: tt("market.versionReleases")
|
|
4478
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
4479
|
+
type: "button",
|
|
4480
|
+
className: panel_module_css_default.segBtn + (refGroup === "branches" ? " " + panel_module_css_default.segBtnActive : ""),
|
|
4481
|
+
disabled: branchOptions.length === 0,
|
|
4482
|
+
"aria-pressed": refGroup === "branches",
|
|
4483
|
+
onClick: () => {
|
|
4484
|
+
selectGroup("branches");
|
|
4485
|
+
},
|
|
4486
|
+
children: tt("market.versionBranches")
|
|
4487
|
+
})]
|
|
4488
|
+
}),
|
|
4489
|
+
selectValue !== "" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
|
|
4446
4490
|
className: panel_module_css_default.select + " " + panel_module_css_default.dialogSelect,
|
|
4447
|
-
value:
|
|
4491
|
+
value: selectValue,
|
|
4448
4492
|
onChange: (event) => {
|
|
4449
4493
|
onSelect(event.target.value);
|
|
4450
|
-
onCustom("");
|
|
4451
4494
|
},
|
|
4452
|
-
children:
|
|
4453
|
-
value:
|
|
4454
|
-
children:
|
|
4455
|
-
},
|
|
4456
|
-
})
|
|
4495
|
+
children: selectRefs.map((ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
4496
|
+
value: ref,
|
|
4497
|
+
children: ref
|
|
4498
|
+
}, ref))
|
|
4499
|
+
}) : null,
|
|
4457
4500
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
4458
4501
|
className: panel_module_css_default.dialogText,
|
|
4459
4502
|
style: { marginBottom: 4 },
|
|
@@ -5322,14 +5365,15 @@ window.__ModuleLoader__.load({
|
|
|
5322
5365
|
choice: versionDialog,
|
|
5323
5366
|
busy: versionBusy,
|
|
5324
5367
|
onSelect: (selected) => {
|
|
5325
|
-
setVersionDialog({
|
|
5326
|
-
...
|
|
5327
|
-
selected
|
|
5368
|
+
setVersionDialog((previous) => previous === null ? previous : {
|
|
5369
|
+
...previous,
|
|
5370
|
+
selected,
|
|
5371
|
+
custom: ""
|
|
5328
5372
|
});
|
|
5329
5373
|
},
|
|
5330
5374
|
onCustom: (custom) => {
|
|
5331
|
-
setVersionDialog({
|
|
5332
|
-
...
|
|
5375
|
+
setVersionDialog((previous) => previous === null ? previous : {
|
|
5376
|
+
...previous,
|
|
5333
5377
|
custom
|
|
5334
5378
|
});
|
|
5335
5379
|
},
|