dsh-skill-hub 0.2.6 → 0.3.0
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.md +3 -1
- package/README.zh.md +3 -1
- package/lib/client.js +1249 -407
- package/lib/client.js.map +1 -1
- package/lib/index.js +439 -81
- package/lib/types/client/api.d.ts +12 -2
- package/lib/types/client/locales.d.ts +13 -0
- package/lib/types/client/panel/ScenesView.d.ts +1 -1
- package/lib/types/client/panel/SourcesView.d.ts +1 -1
- package/lib/types/client/panel/useSkillHub.d.ts +20 -2
- package/lib/types/protocol.d.ts +93 -6
- package/lib/types/repo.d.ts +18 -11
- package/lib/types/store.d.ts +14 -2
- package/package.json +1 -1
- package/src/client/api.ts +61 -1
- package/src/client/locales.ts +26 -0
- package/src/client/panel/MarketView.tsx +201 -47
- package/src/client/panel/ScenesView.tsx +27 -2
- package/src/client/panel/SkillHubPanel.tsx +18 -19
- package/src/client/panel/SkillRow.tsx +8 -8
- package/src/client/panel/SourcesView.tsx +203 -26
- package/src/client/panel/panel.module.css +17 -6
- package/src/client/panel/useSkillHub.ts +219 -12
- package/src/index.ts +18 -0
- package/src/protocol.ts +89 -6
- package/src/repo.test.ts +61 -19
- package/src/repo.ts +86 -23
- package/src/routes.test.ts +18 -4
- package/src/routes.ts +269 -47
- package/src/skillfs.ts +17 -4
- package/src/store.ts +72 -4
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Browser-side API client for the /api/skill-hub route family. The only
|
|
3
3
|
* data access path the panel uses — plain fetch, same origin.
|
|
4
4
|
*/
|
|
5
|
-
import { type CatalogResponse, type ConfigRequest, type ConfigResponse, type CreateRequest, type CreateResponse, type MarketCheckResponse, type MarketSourcesResponse, type MarketSyncResponse, type RepoDiscoverResponse, type RepoImportResponse, type SkillDetail, type SkillDeleteResponse, type SourceCheckResponse, type SourceDeleteResponse, type SourceRestoreResponse, type SourceTrashClearResponse, type SourcesResponse, type SourceSyncResponse, type StatsResponse, type ToggleBatchResponse, type UpdateCheckResponse } from '../protocol.ts';
|
|
5
|
+
import { type CatalogResponse, type CollectionGroup, type ConfigRequest, type ConfigResponse, type CreateRequest, type CreateResponse, type MarketCheckResponse, type MarketSourcesResponse, type MarketSyncResponse, type RepoDiscoverResponse, type RepoImportCancelResponse, type RepoImportProgressResponse, type RepoImportResponse, type SkillDetail, type SkillDeleteResponse, type SkillTag, type SourceCheckResponse, type SourceDeleteResponse, type SourceRestoreResponse, type SourceTrashClearResponse, type SourcesResponse, type SourceSyncResponse, type StatsResponse, type ToggleBatchResponse, type UpdateCheckResponse } from '../protocol.ts';
|
|
6
6
|
/** Error carrying the route's JSON error message. */
|
|
7
7
|
export declare class SkillHubApiError extends Error {
|
|
8
8
|
constructor(message: string);
|
|
@@ -41,8 +41,12 @@ export declare class SkillHubApi {
|
|
|
41
41
|
updateCheck(): Promise<UpdateCheckResponse>;
|
|
42
42
|
/** Discover importable skills in a GitHub repo. */
|
|
43
43
|
repoDiscover(repo: string): Promise<RepoDiscoverResponse>;
|
|
44
|
-
/** Install selected repo skills into the user-dsh root (records the source). */
|
|
44
|
+
/** Install selected repo skills into the user-dsh root (records the source). Returns jobId for polling. */
|
|
45
45
|
repoImport(repo: string, paths: string[], ref?: string): Promise<RepoImportResponse>;
|
|
46
|
+
/** Poll import job progress (B方案轮询) */
|
|
47
|
+
repoImportProgress(jobId: string): Promise<RepoImportProgressResponse>;
|
|
48
|
+
/** Cancel a running import job (选项2:唯有取消才停) */
|
|
49
|
+
repoImportCancel(jobId: string): Promise<RepoImportCancelResponse>;
|
|
46
50
|
/** Read the hub's runtime config (effective values + saved overrides). */
|
|
47
51
|
config(): Promise<ConfigResponse>;
|
|
48
52
|
/** Patch the hub's runtime config (null clears a saved override). */
|
|
@@ -55,6 +59,12 @@ export declare class SkillHubApi {
|
|
|
55
59
|
deleteTag(id: string): Promise<import('../protocol.ts').SkillTag[]>;
|
|
56
60
|
/** 直接设置某 tag 的完整成员列表;返回全部 tag。 */
|
|
57
61
|
setTagMembers(id: string, skillNames: string[]): Promise<import('../protocol.ts').SkillTag[]>;
|
|
62
|
+
/** 拖拽重排场景分组 */
|
|
63
|
+
reorderTags(orderedIds: string[]): Promise<SkillTag[]>;
|
|
64
|
+
/** 拖拽重排来源集合 */
|
|
65
|
+
reorderCollections(orderedNames: string[]): Promise<CollectionGroup[]>;
|
|
66
|
+
/** 拖拽重排来源顶层分组(project / collections / personal) */
|
|
67
|
+
reorderSourceGroups(orderedKeys: string[]): Promise<string[]>;
|
|
58
68
|
/** 来源列表 + 派生 origin 映射 + 集合组 + 回收站。 */
|
|
59
69
|
sources(): Promise<SourcesResponse>;
|
|
60
70
|
/** 检查指定(或全部)来源的上游更新。 */
|
|
@@ -52,6 +52,11 @@ export declare const zh: {
|
|
|
52
52
|
readonly 'market.scan': "扫描";
|
|
53
53
|
readonly 'market.scanning': "扫描中…";
|
|
54
54
|
readonly 'market.check': "检查";
|
|
55
|
+
readonly 'market.checkUpdate': "检查更新";
|
|
56
|
+
readonly 'market.checking': "检查中…";
|
|
57
|
+
readonly 'market.upToDate': "已是最新";
|
|
58
|
+
readonly 'market.update': "更新";
|
|
59
|
+
readonly 'market.updateCount': "更新 {count} 个";
|
|
55
60
|
readonly 'market.sync': "同步";
|
|
56
61
|
readonly 'market.syncing': "同步中…";
|
|
57
62
|
readonly 'market.updated': "有更新";
|
|
@@ -75,8 +80,12 @@ export declare const zh: {
|
|
|
75
80
|
readonly 'repo.imported': "导入成功 {count} 个";
|
|
76
81
|
readonly 'repo.skippedExisting': "跳过已存在 {count} 个";
|
|
77
82
|
readonly 'repo.failed': "失败 {count} 个";
|
|
83
|
+
readonly 'repo.cancel': "取消";
|
|
84
|
+
readonly 'repo.cancelled': "已取消 · 已导入 {imported}/{total} 已保留,临时文件已清理";
|
|
85
|
+
readonly 'repo.importingCurrent': "正在导入: {name} ({done}/{total})";
|
|
78
86
|
readonly 'repo.root.skills': "技能 skills";
|
|
79
87
|
readonly 'repo.root.designTemplates': "模板 design-templates";
|
|
88
|
+
readonly 'repo.root.generic': "{root}";
|
|
80
89
|
readonly 'update.check': "检查更新";
|
|
81
90
|
readonly 'update.checking': "检查中…";
|
|
82
91
|
readonly 'update.upToDate': "已是最新版本 v{version}";
|
|
@@ -163,6 +172,10 @@ export declare const zh: {
|
|
|
163
172
|
readonly 'source.followDelete': "跟进删除";
|
|
164
173
|
readonly 'source.deleteConfirmTitle': "跟进上游删除?";
|
|
165
174
|
readonly 'source.deleteConfirmText': "以下技能已从上游仓库移除,本地目录将移入回收站(可恢复):";
|
|
175
|
+
readonly 'source.deleteGroup': "删除整组";
|
|
176
|
+
readonly 'source.deleteGroupHint': "删除该分组下的全部 {count} 个技能";
|
|
177
|
+
readonly 'source.deleteGroupTitle': "删除整组?";
|
|
178
|
+
readonly 'source.deleteGroupText': "将把分组“{name}”下的全部 {count} 个技能移入回收站(可恢复),只读技能会跳过:";
|
|
166
179
|
readonly 'source.trash': "回收站";
|
|
167
180
|
readonly 'source.restore': "恢复";
|
|
168
181
|
readonly 'source.clearTrash': "清空回收站";
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* enable/disable units (e.g. a Godot scene vs a Java scene); upstream repos
|
|
5
5
|
* are managed in the sources tab.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type JSX } from 'react';
|
|
8
8
|
import type { SkillHubState } from './useSkillHub.ts';
|
|
9
9
|
export declare function ScenesView(props: {
|
|
10
10
|
hub: SkillHubState;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* follow-delete actions and the tri-state switch, plus the uncategorized
|
|
6
6
|
* "personal" card (project skills never count as personal).
|
|
7
7
|
*/
|
|
8
|
-
import type
|
|
8
|
+
import { type JSX } from 'react';
|
|
9
9
|
import type { SkillHubState } from './useSkillHub.ts';
|
|
10
10
|
export declare function SourcesView(props: {
|
|
11
11
|
hub: SkillHubState;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* and tag-editor views without violating the rules of hooks.
|
|
7
7
|
*/
|
|
8
8
|
import { type FormEvent } from 'react';
|
|
9
|
-
import type { CatalogResponse, CatalogSkill, DisabledSkill, GroupsResponse, HubConfig, MarketSourceRecord, RepoDiscoverResponse,
|
|
9
|
+
import type { CatalogResponse, CatalogSkill, DisabledSkill, GroupsResponse, HubConfig, MarketSourceRecord, RepoDiscoverResponse, RepoImportProgressResponse, SkillDetail, SkillTag, SourceCheckResult, SourcesResponse, UpdateCheckResponse, WritableRoot } from '../../protocol.ts';
|
|
10
10
|
import type { SkillHubApi } from '../api.ts';
|
|
11
11
|
import { type SortKey, type GroupSwitchState } from '../grouping.ts';
|
|
12
12
|
import type { BranchChoiceState, ConfirmDialogState, ConflictDialogState, MarketSyncDialogState } from './dialogs.tsx';
|
|
@@ -51,7 +51,8 @@ export declare function useSkillHub(api: SkillHubApi): {
|
|
|
51
51
|
scanningRepo: string | null;
|
|
52
52
|
repoSelected: ReadonlySet<string>;
|
|
53
53
|
repoImporting: boolean;
|
|
54
|
-
repoResult:
|
|
54
|
+
repoResult: RepoImportProgressResponse | null;
|
|
55
|
+
importJobId: string | null;
|
|
55
56
|
search: string;
|
|
56
57
|
workspace: string;
|
|
57
58
|
detail: SkillDetail | null;
|
|
@@ -100,6 +101,10 @@ export declare function useSkillHub(api: SkillHubApi): {
|
|
|
100
101
|
conflictDialog: ConflictDialogState | null;
|
|
101
102
|
confirmDialog: ConfirmDialogState | null;
|
|
102
103
|
deleteSkillDialog: string | null;
|
|
104
|
+
deleteGroupDialog: {
|
|
105
|
+
name: string;
|
|
106
|
+
skillNames: string[];
|
|
107
|
+
} | null;
|
|
103
108
|
confirmClearTrash: boolean;
|
|
104
109
|
updateAllDialog: boolean;
|
|
105
110
|
editingTag: SkillTag | null;
|
|
@@ -111,6 +116,7 @@ export declare function useSkillHub(api: SkillHubApi): {
|
|
|
111
116
|
collapsedGroups: ReadonlySet<string>;
|
|
112
117
|
subdividedProjects: ReadonlySet<string>;
|
|
113
118
|
showLegend: boolean;
|
|
119
|
+
editMode: boolean;
|
|
114
120
|
actionNames: Set<string>;
|
|
115
121
|
viewNames: Set<string>;
|
|
116
122
|
normalized: string;
|
|
@@ -142,6 +148,10 @@ export declare function useSkillHub(api: SkillHubApi): {
|
|
|
142
148
|
setConflictDialog: import("react").Dispatch<import("react").SetStateAction<ConflictDialogState | null>>;
|
|
143
149
|
setConfirmDialog: import("react").Dispatch<import("react").SetStateAction<ConfirmDialogState | null>>;
|
|
144
150
|
setDeleteSkillDialog: import("react").Dispatch<import("react").SetStateAction<string | null>>;
|
|
151
|
+
setDeleteGroupDialog: import("react").Dispatch<import("react").SetStateAction<{
|
|
152
|
+
name: string;
|
|
153
|
+
skillNames: string[];
|
|
154
|
+
} | null>>;
|
|
145
155
|
setConfirmClearTrash: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
146
156
|
setUpdateAllDialog: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
147
157
|
setEditingTag: import("react").Dispatch<import("react").SetStateAction<SkillTag | null>>;
|
|
@@ -150,6 +160,7 @@ export declare function useSkillHub(api: SkillHubApi): {
|
|
|
150
160
|
setNewTagName: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
151
161
|
setEditSearch: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
152
162
|
setShowLegend: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
163
|
+
setEditMode: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
153
164
|
toggleGroupCollapse: (key: string) => void;
|
|
154
165
|
toggleSubdivide: (key: string) => void;
|
|
155
166
|
checkUpdate: () => Promise<void>;
|
|
@@ -168,9 +179,14 @@ export declare function useSkillHub(api: SkillHubApi): {
|
|
|
168
179
|
clearTrash: () => Promise<void>;
|
|
169
180
|
requestDeleteSkill: (name: string) => void;
|
|
170
181
|
runDeleteSkill: () => Promise<void>;
|
|
182
|
+
requestDeleteGroup: (name: string, skillNames: string[]) => void;
|
|
183
|
+
runDeleteGroup: () => Promise<void>;
|
|
171
184
|
createTag: (event: FormEvent) => Promise<void>;
|
|
172
185
|
deleteTag: (id: string) => Promise<void>;
|
|
173
186
|
saveTag: (id: string, name: string, memberNames: string[] | null) => Promise<void>;
|
|
187
|
+
reorderTags: (orderedIds: string[]) => Promise<void>;
|
|
188
|
+
reorderCollections: (orderedNames: string[]) => Promise<void>;
|
|
189
|
+
reorderSourceGroups: (orderedKeys: string[]) => Promise<void>;
|
|
174
190
|
addSource: (input: string) => Promise<void>;
|
|
175
191
|
addMarketSource: () => Promise<void>;
|
|
176
192
|
removeMarketSource: (repo: string) => Promise<void>;
|
|
@@ -178,6 +194,8 @@ export declare function useSkillHub(api: SkillHubApi): {
|
|
|
178
194
|
confirmBranchChoice: () => Promise<void>;
|
|
179
195
|
toggleRepoSelected: (path: string, checked: boolean) => void;
|
|
180
196
|
importRepo: () => Promise<void>;
|
|
197
|
+
cancelImport: () => Promise<void>;
|
|
198
|
+
clearScan: () => void;
|
|
181
199
|
checkMarket: () => Promise<void>;
|
|
182
200
|
syncMarketSource: (repo: string) => Promise<void>;
|
|
183
201
|
confirmMarketSync: () => Promise<void>;
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -22,11 +22,16 @@ export declare const SKILL_HUB_API: {
|
|
|
22
22
|
readonly marketSync: "/api/skill-hub/market/source/sync";
|
|
23
23
|
readonly repo: "/api/skill-hub/repo";
|
|
24
24
|
readonly repoImport: "/api/skill-hub/repo/import";
|
|
25
|
+
readonly repoImportProgress: "/api/skill-hub/repo/import/progress";
|
|
26
|
+
readonly repoImportCancel: "/api/skill-hub/repo/import/cancel";
|
|
25
27
|
readonly update: "/api/skill-hub/update";
|
|
26
28
|
readonly groups: "/api/skill-hub/groups";
|
|
27
29
|
readonly tag: "/api/skill-hub/tag";
|
|
28
30
|
readonly tagDelete: "/api/skill-hub/tag/delete";
|
|
29
31
|
readonly tagMembers: "/api/skill-hub/tag/members";
|
|
32
|
+
readonly tagReorder: "/api/skill-hub/tag/reorder";
|
|
33
|
+
readonly collectionReorder: "/api/skill-hub/collections/reorder";
|
|
34
|
+
readonly sourceGroupReorder: "/api/skill-hub/source-groups/reorder";
|
|
30
35
|
readonly sources: "/api/skill-hub/sources";
|
|
31
36
|
readonly sourceCheck: "/api/skill-hub/sources/check";
|
|
32
37
|
readonly sourceSync: "/api/skill-hub/sources/sync";
|
|
@@ -365,7 +370,7 @@ export interface MarketSyncResponse {
|
|
|
365
370
|
/** Local skills tracked from this repo (candidates for a batch update). */
|
|
366
371
|
skills: string[];
|
|
367
372
|
}
|
|
368
|
-
/** A skill discovered in a GitHub repo under skills
|
|
373
|
+
/** A skill discovered in a GitHub repo under any top-level root (e.g. skills/, templates/). */
|
|
369
374
|
export interface RepoSkillEntry {
|
|
370
375
|
/** Kebab-case skill name (directory basename). */
|
|
371
376
|
name: string;
|
|
@@ -384,8 +389,8 @@ export interface RepoSkillEntry {
|
|
|
384
389
|
/** Whether a same-named skill already exists locally. */
|
|
385
390
|
existing: boolean;
|
|
386
391
|
}
|
|
387
|
-
/**
|
|
388
|
-
export type RepoRoot =
|
|
392
|
+
/** Skill root in a GitHub repo: the top-level directory that contains skills (e.g. skills, design-templates, templates). Auto-derived from SKILL.md locations, not hard-coded. */
|
|
393
|
+
export type RepoRoot = string;
|
|
389
394
|
/** GET /api/skill-hub/repo — discover importable skills in a GitHub repo. */
|
|
390
395
|
export interface RepoDiscoverResponse {
|
|
391
396
|
ok: true;
|
|
@@ -408,8 +413,56 @@ export interface RepoImportRequest {
|
|
|
408
413
|
/** The ref the discovery ran against; the import must use the same one. */
|
|
409
414
|
ref?: string;
|
|
410
415
|
}
|
|
411
|
-
/** POST /api/skill-hub/repo/import */
|
|
416
|
+
/** POST /api/skill-hub/repo/import — now creates an async job (B方案) */
|
|
412
417
|
export interface RepoImportResponse {
|
|
418
|
+
ok: true;
|
|
419
|
+
jobId: string;
|
|
420
|
+
total: number;
|
|
421
|
+
totalBytes: number;
|
|
422
|
+
}
|
|
423
|
+
/** GET /api/skill-hub/repo/import/progress?jobId=xxx */
|
|
424
|
+
export interface RepoImportProgressResponse {
|
|
425
|
+
ok: true;
|
|
426
|
+
jobId: string;
|
|
427
|
+
status: 'running' | 'done' | 'cancelled' | 'error';
|
|
428
|
+
total: number;
|
|
429
|
+
done: number;
|
|
430
|
+
/** 当前正在下载的 skill 名,done < total 时有效 */
|
|
431
|
+
current?: string;
|
|
432
|
+
/** 当前正在下载的文件路径(相对 skill dir) */
|
|
433
|
+
currentFile?: string;
|
|
434
|
+
/** 字节级进度 */
|
|
435
|
+
totalBytes: number;
|
|
436
|
+
downloadedBytes: number;
|
|
437
|
+
/** 下载速度 bytes/sec,running 时有效 */
|
|
438
|
+
bytesPerSecond?: number;
|
|
439
|
+
imported: Array<{
|
|
440
|
+
name: string;
|
|
441
|
+
origin: string;
|
|
442
|
+
path: string;
|
|
443
|
+
}>;
|
|
444
|
+
skipped: Array<{
|
|
445
|
+
name: string;
|
|
446
|
+
reason: 'exists';
|
|
447
|
+
}>;
|
|
448
|
+
failed: Array<{
|
|
449
|
+
name: string;
|
|
450
|
+
error: string;
|
|
451
|
+
}>;
|
|
452
|
+
/** error 状态时的原因 */
|
|
453
|
+
error?: string;
|
|
454
|
+
}
|
|
455
|
+
/** POST /api/skill-hub/repo/import/cancel */
|
|
456
|
+
export interface RepoImportCancelRequest {
|
|
457
|
+
jobId: string;
|
|
458
|
+
}
|
|
459
|
+
export interface RepoImportCancelResponse {
|
|
460
|
+
ok: true;
|
|
461
|
+
jobId: string;
|
|
462
|
+
status: 'cancelled' | 'done';
|
|
463
|
+
}
|
|
464
|
+
/** @deprecated 旧同步响应,保留供类型兼容,实际已由 Progress 代替 */
|
|
465
|
+
export interface RepoImportLegacyResponse {
|
|
413
466
|
ok: true;
|
|
414
467
|
imported: Array<{
|
|
415
468
|
name: string;
|
|
@@ -460,12 +513,16 @@ export interface CollectionGroup {
|
|
|
460
513
|
/** GET /api/skill-hub/groups */
|
|
461
514
|
export interface GroupsResponse {
|
|
462
515
|
ok: true;
|
|
463
|
-
/** 用户自定义 tag
|
|
516
|
+
/** 用户自定义 tag 分组(按拖拽顺序)。 */
|
|
464
517
|
tags: SkillTag[];
|
|
465
|
-
/**
|
|
518
|
+
/** 系统集合组(按拖拽顺序,兜底按名称)。 */
|
|
466
519
|
collections: CollectionGroup[];
|
|
467
520
|
/** skillName → 集合名 的完整映射。 */
|
|
468
521
|
origins: Record<string, string>;
|
|
522
|
+
/** 来源分组整体顺序(project / col:xxx / uncategorized-source),用于 SourcesView 顶层排序 */
|
|
523
|
+
sourceGroupOrder?: string[];
|
|
524
|
+
/** 兼容旧 collectionOrder 字段 */
|
|
525
|
+
collectionOrder?: string[];
|
|
469
526
|
}
|
|
470
527
|
/** POST /api/skill-hub/tag — 新建(缺省 id)或重命名(带 id)。 */
|
|
471
528
|
export interface TagSaveRequest {
|
|
@@ -500,6 +557,36 @@ export interface TagMembersResponse {
|
|
|
500
557
|
ok: true;
|
|
501
558
|
tags: SkillTag[];
|
|
502
559
|
}
|
|
560
|
+
/** POST /api/skill-hub/tag/reorder — 拖拽重排场景分组 */
|
|
561
|
+
export interface TagReorderRequest {
|
|
562
|
+
/** 按新顺序排列的 tag id 列表(需包含全部 id) */
|
|
563
|
+
orderedIds: string[];
|
|
564
|
+
}
|
|
565
|
+
/** POST /api/skill-hub/tag/reorder */
|
|
566
|
+
export interface TagReorderResponse {
|
|
567
|
+
ok: true;
|
|
568
|
+
tags: SkillTag[];
|
|
569
|
+
}
|
|
570
|
+
/** POST /api/skill-hub/collections/reorder — 拖拽重排来源集合 */
|
|
571
|
+
export interface CollectionReorderRequest {
|
|
572
|
+
/** 按新顺序排列的集合名列表(需包含全部 name) */
|
|
573
|
+
orderedNames: string[];
|
|
574
|
+
}
|
|
575
|
+
/** POST /api/skill-hub/collections/reorder */
|
|
576
|
+
export interface CollectionReorderResponse {
|
|
577
|
+
ok: true;
|
|
578
|
+
collections: CollectionGroup[];
|
|
579
|
+
order: string[];
|
|
580
|
+
}
|
|
581
|
+
/** POST /api/skill-hub/source-groups/reorder — 拖拽重排来源顶层分组(project / collections / personal) */
|
|
582
|
+
export interface SourceGroupReorderRequest {
|
|
583
|
+
/** 按新顺序排列的顶层分组 key 列表(project / col:xxx / uncategorized-source) */
|
|
584
|
+
orderedKeys: string[];
|
|
585
|
+
}
|
|
586
|
+
export interface SourceGroupReorderResponse {
|
|
587
|
+
ok: true;
|
|
588
|
+
order: string[];
|
|
589
|
+
}
|
|
503
590
|
/** 来源跟踪记录:一组来自同一上游仓库根目录的技能。 */
|
|
504
591
|
export interface SourceRecord {
|
|
505
592
|
/** GitHub owner/repo。 */
|
package/lib/types/repo.d.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* GitHub repository skill discovery/import helpers.
|
|
3
3
|
*
|
|
4
4
|
* Kept dependency-free and mostly pure so the root/origin rules are easy to
|
|
5
|
-
* test.
|
|
6
|
-
* `
|
|
7
|
-
* skill
|
|
5
|
+
* test. Roots are auto-derived: any top-level directory that contains a
|
|
6
|
+
* `**\/SKILL.md` (e.g. `skills/**`, `design-templates/**`, `templates/**`,
|
|
7
|
+
* `workflows/**`) is treated as a skill root. No hard-coded allowlist.
|
|
8
8
|
*/
|
|
9
9
|
import type { RepoRoot, RepoSkillEntry } from './protocol.ts';
|
|
10
|
-
/**
|
|
10
|
+
/** Preferred display order for known roots; unknown roots sort alphabetically after these. */
|
|
11
11
|
export declare const REPO_ROOTS: readonly RepoRoot[];
|
|
12
12
|
/** Parsed GitHub repository reference. */
|
|
13
13
|
export interface RepoRef {
|
|
@@ -61,27 +61,34 @@ export declare function loadRepoTree(repo: string, ref?: string, fetchImpl?: typ
|
|
|
61
61
|
/** Collect every file inside a skill directory, including SKILL.md itself. */
|
|
62
62
|
export declare function collectRepoSkillFiles(tree: readonly RepoTreeItem[], dir: string): RepoFile[];
|
|
63
63
|
/** Compute an origin collection name. Multiple roots split by root, one root keeps the repo slug. */
|
|
64
|
-
export declare function originForRoot(repo: string, rootsPresent: ReadonlySet<
|
|
65
|
-
/** Discover importable skills from a repo tree. Invalid names are ignored. */
|
|
64
|
+
export declare function originForRoot(repo: string, rootsPresent: ReadonlySet<string>, root: string): string;
|
|
65
|
+
/** Discover importable skills from a repo tree. Invalid names are ignored. Roots are auto-derived from the top-level directory of each SKILL.md. */
|
|
66
66
|
export declare function discoverRepoEntries(tree: readonly RepoTreeItem[], repo: string, existingNames?: ReadonlySet<string>): RepoSkillEntry[];
|
|
67
67
|
/** Run an async worker over items with a bounded concurrency. */
|
|
68
68
|
export declare function mapConcurrent<T, R>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
|
|
69
|
+
/** Whether an error is an abort (AbortError / signal). */
|
|
70
|
+
export declare function isAbortError(error: unknown): boolean;
|
|
69
71
|
/**
|
|
70
72
|
* Download one GitHub file as a buffer. Tries raw.githubusercontent.com
|
|
71
73
|
* first (no API quota); on any failure falls back to the api.github.com
|
|
72
74
|
* contents endpoint with the raw media type (rate-limited, but reachable
|
|
73
75
|
* from networks that block the raw host).
|
|
74
76
|
*/
|
|
75
|
-
export declare function downloadGitHubFile(repo: string, ref: string, path: string, fetchImpl?: typeof fetch): Promise<Buffer>;
|
|
77
|
+
export declare function downloadGitHubFile(repo: string, ref: string, path: string, fetchImpl?: typeof fetch, signal?: AbortSignal): Promise<Buffer>;
|
|
76
78
|
/**
|
|
77
79
|
* Download a full skill directory into a temporary dir, validate SKILL.md,
|
|
78
80
|
* then atomically rename it into place. The target root is created when missing.
|
|
79
81
|
* Callers pass a target root and a skill entry plus its collected files.
|
|
80
82
|
*/
|
|
81
|
-
export declare function downloadRepoSkill(repo: string, ref: string, entry: RepoSkillEntry, files: readonly RepoFile[], targetRoot: string, fetchImpl?: typeof fetch): Promise<{
|
|
83
|
+
export declare function downloadRepoSkill(repo: string, ref: string, entry: RepoSkillEntry, files: readonly RepoFile[], targetRoot: string, fetchImpl?: typeof fetch, signal?: AbortSignal, onProgress?: (bytes: number, file: string) => void): Promise<{
|
|
82
84
|
targetDir: string;
|
|
83
85
|
skillPath: string;
|
|
84
86
|
}>;
|
|
87
|
+
/**
|
|
88
|
+
* 启动时扫描并清理残留的 `.*.import-*` 临时目录(Issue #3 第4点)。
|
|
89
|
+
* 越积越多的点前缀目录不会显示为 skill,但会占空间,尽早回收。
|
|
90
|
+
*/
|
|
91
|
+
export declare function cleanupLeftoverImportDirs(targetRoot: string): Promise<number>;
|
|
85
92
|
/**
|
|
86
93
|
* The latest non-prerelease release tag of a repo, or undefined when the
|
|
87
94
|
* repo has no releases (GitHub's /releases/latest skips prereleases/drafts).
|
|
@@ -113,7 +120,7 @@ export declare function skillManifest(tree: readonly RepoTreeItem[], dir: string
|
|
|
113
120
|
* resilient to incomplete manifests and upstream moves.
|
|
114
121
|
*/
|
|
115
122
|
export declare function skillDirOf(source: {
|
|
116
|
-
root:
|
|
123
|
+
root: string;
|
|
117
124
|
manifest?: Record<string, number>;
|
|
118
125
|
}, name: string, treePaths?: readonly string[]): string;
|
|
119
126
|
/**
|
|
@@ -122,7 +129,7 @@ export declare function skillDirOf(source: {
|
|
|
122
129
|
* differs, or no baseline exists — treated as changed). Pure over the tree.
|
|
123
130
|
*/
|
|
124
131
|
export declare function diffRemoteSkills(tree: readonly RepoTreeItem[], source: {
|
|
125
|
-
root:
|
|
132
|
+
root: string;
|
|
126
133
|
skills: readonly string[];
|
|
127
134
|
manifest?: Record<string, number>;
|
|
128
135
|
}): {
|
|
@@ -130,4 +137,4 @@ export declare function diffRemoteSkills(tree: readonly RepoTreeItem[], source:
|
|
|
130
137
|
deleted: string[];
|
|
131
138
|
};
|
|
132
139
|
/** Minimal RepoSkillEntry for a tracked skill name (sync re-downloads by name). */
|
|
133
|
-
export declare function repoSkillEntry(name: string, root:
|
|
140
|
+
export declare function repoSkillEntry(name: string, root: string, repo: string): RepoSkillEntry;
|
package/lib/types/store.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* State file: $DSH_HOME/dsh-skill-hub.json — a small JSON document written
|
|
12
12
|
* atomically (tmp file + rename).
|
|
13
13
|
*/
|
|
14
|
-
import type { DisabledSkill, HubConfig, MarketSourceRecord,
|
|
14
|
+
import type { DisabledSkill, HubConfig, MarketSourceRecord, SkillStatsCheckpoint, SkillTag, SourceRecord, TrashEntry } from './protocol.ts';
|
|
15
15
|
/** 默认场景名(系统预置的兜底场景,新技能自动归入)。 */
|
|
16
16
|
export declare const DEFAULT_SCENE_NAME = "\u901A\u7528";
|
|
17
17
|
/** Resolve the DSH home directory (the filesystem provider's user-dsh root base). */
|
|
@@ -40,6 +40,8 @@ export declare class SkillHubStore {
|
|
|
40
40
|
private marketSources;
|
|
41
41
|
private trashByName;
|
|
42
42
|
private skillStats;
|
|
43
|
+
private collectionOrder;
|
|
44
|
+
private sourceGroupOrder;
|
|
43
45
|
private loaded;
|
|
44
46
|
/** Serializes persist runs: concurrent mutators must not let an earlier
|
|
45
47
|
* snapshot overwrite a later one (rename is atomic, ordering is not). */
|
|
@@ -89,6 +91,16 @@ export declare class SkillHubStore {
|
|
|
89
91
|
setTagMembers(id: string, skillNames: readonly string[]): Promise<SkillTag | undefined>;
|
|
90
92
|
/** Remove one skill from every tag group (used when the skill is deleted). */
|
|
91
93
|
removeSkillFromTags(name: string): Promise<void>;
|
|
94
|
+
/** Reorder tag groups by orderedIds (drag-and-drop). */
|
|
95
|
+
reorderTags(orderedIds: string[]): Promise<SkillTag[]>;
|
|
96
|
+
/** Collection order for 来源分组拖拽 */
|
|
97
|
+
getCollectionOrder(): Promise<string[]>;
|
|
98
|
+
/** Reorder collections by orderedNames (drag-and-drop). */
|
|
99
|
+
reorderCollections(orderedNames: string[]): Promise<string[]>;
|
|
100
|
+
/** Source top-level group order for 来源分组(project / col:xxx / personal) */
|
|
101
|
+
getSourceGroupOrder(): Promise<string[]>;
|
|
102
|
+
/** Reorder source top-level groups by orderedKeys */
|
|
103
|
+
reorderSourceGroups(orderedKeys: string[]): Promise<string[]>;
|
|
92
104
|
/** All source records, sorted by repo. */
|
|
93
105
|
listSources(): Promise<SourceRecord[]>;
|
|
94
106
|
/** One source record by repo (undefined when absent). */
|
|
@@ -101,7 +113,7 @@ export declare class SkillHubStore {
|
|
|
101
113
|
* Upsert one skill into a source record. When the repo has no record yet a
|
|
102
114
|
* new one is created (root + commit snapshot from the caller).
|
|
103
115
|
*/
|
|
104
|
-
addSourceSkill(repo: string, root:
|
|
116
|
+
addSourceSkill(repo: string, root: string, commitSha: string, ref: string | undefined, skillName: string): Promise<void>;
|
|
105
117
|
/** Replace a source's skill list (used after sync/confirm-delete). */
|
|
106
118
|
setSourceSkills(repo: string, skills: readonly string[]): Promise<SourceRecord | undefined>;
|
|
107
119
|
/** Remove one skill from every source record (used when the skill is deleted). */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-skill-hub",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "In-GUI skill hub for DeepSeek Harness (dsh): browse the full local skill catalog from the official ctx.skills registry (every root + third-party providers), toggle skills on/off, inspect bodies, surface frontmatter diagnostics, and scaffold new skills — plus a codex-style skill market (built-in catalog, upstream update checks, one-click update-all) with tracked source sync. The full manager beyond the read-only dsh-skill-manager browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
package/src/client/api.ts
CHANGED
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
import {
|
|
6
6
|
SKILL_HUB_API,
|
|
7
7
|
type CatalogResponse,
|
|
8
|
+
type CollectionGroup,
|
|
8
9
|
type ConfigRequest,
|
|
9
10
|
type ConfigResponse,
|
|
10
11
|
type CreateRequest,
|
|
11
12
|
type CreateResponse,
|
|
13
|
+
type CollectionReorderRequest,
|
|
14
|
+
type CollectionReorderResponse,
|
|
15
|
+
type SourceGroupReorderRequest,
|
|
16
|
+
type SourceGroupReorderResponse,
|
|
12
17
|
type MarketCheckResponse,
|
|
13
18
|
type MarketSourceRefRequest,
|
|
14
19
|
type MarketSourceRequest,
|
|
@@ -16,12 +21,15 @@ import {
|
|
|
16
21
|
type MarketSourcesResponse,
|
|
17
22
|
type MarketSyncResponse,
|
|
18
23
|
type RepoDiscoverResponse,
|
|
24
|
+
type RepoImportCancelResponse,
|
|
25
|
+
type RepoImportProgressResponse,
|
|
19
26
|
type RepoImportRequest,
|
|
20
27
|
type RepoImportResponse,
|
|
21
28
|
type SkillDetail,
|
|
22
29
|
type SkillDetailResponse,
|
|
23
30
|
type SkillDeleteRequest,
|
|
24
31
|
type SkillDeleteResponse,
|
|
32
|
+
type SkillTag,
|
|
25
33
|
type SourceCheckRequest,
|
|
26
34
|
type SourceCheckResponse,
|
|
27
35
|
type SourceDeleteRequest,
|
|
@@ -33,6 +41,8 @@ import {
|
|
|
33
41
|
type SourceSyncRequest,
|
|
34
42
|
type SourceSyncResponse,
|
|
35
43
|
type StatsResponse,
|
|
44
|
+
type TagReorderRequest,
|
|
45
|
+
type TagReorderResponse,
|
|
36
46
|
type ToggleBatchRequest,
|
|
37
47
|
type ToggleBatchResponse,
|
|
38
48
|
type UpdateCheckResponse,
|
|
@@ -206,7 +216,7 @@ export class SkillHubApi {
|
|
|
206
216
|
return readJson<RepoDiscoverResponse>(response)
|
|
207
217
|
}
|
|
208
218
|
|
|
209
|
-
/** Install selected repo skills into the user-dsh root (records the source). */
|
|
219
|
+
/** Install selected repo skills into the user-dsh root (records the source). Returns jobId for polling. */
|
|
210
220
|
async repoImport(repo: string, paths: string[], ref?: string): Promise<RepoImportResponse> {
|
|
211
221
|
const payload: RepoImportRequest = { repo, paths, ...(ref !== undefined && ref !== '' ? { ref } : {}) }
|
|
212
222
|
const response = await fetchWithTimeout(SKILL_HUB_API.repoImport, {
|
|
@@ -217,6 +227,23 @@ export class SkillHubApi {
|
|
|
217
227
|
return readJson<RepoImportResponse>(response)
|
|
218
228
|
}
|
|
219
229
|
|
|
230
|
+
/** Poll import job progress (B方案轮询) */
|
|
231
|
+
async repoImportProgress(jobId: string): Promise<RepoImportProgressResponse> {
|
|
232
|
+
const url = SKILL_HUB_API.repoImportProgress + '?jobId=' + encodeURIComponent(jobId)
|
|
233
|
+
const response = await fetchWithTimeout(url)
|
|
234
|
+
return readJson<RepoImportProgressResponse>(response)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Cancel a running import job (选项2:唯有取消才停) */
|
|
238
|
+
async repoImportCancel(jobId: string): Promise<RepoImportCancelResponse> {
|
|
239
|
+
const response = await fetchWithTimeout(SKILL_HUB_API.repoImportCancel, {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: { 'content-type': 'application/json' },
|
|
242
|
+
body: JSON.stringify({ jobId }),
|
|
243
|
+
})
|
|
244
|
+
return readJson<RepoImportCancelResponse>(response)
|
|
245
|
+
}
|
|
246
|
+
|
|
220
247
|
/** Read the hub's runtime config (effective values + saved overrides). */
|
|
221
248
|
async config(): Promise<ConfigResponse> {
|
|
222
249
|
const response = await fetchWithTimeout(SKILL_HUB_API.config)
|
|
@@ -272,6 +299,39 @@ export class SkillHubApi {
|
|
|
272
299
|
return body.tags
|
|
273
300
|
}
|
|
274
301
|
|
|
302
|
+
/** 拖拽重排场景分组 */
|
|
303
|
+
async reorderTags(orderedIds: string[]): Promise<SkillTag[]> {
|
|
304
|
+
const response = await fetchWithTimeout(SKILL_HUB_API.tagReorder, {
|
|
305
|
+
method: 'POST',
|
|
306
|
+
headers: { 'content-type': 'application/json' },
|
|
307
|
+
body: JSON.stringify({ orderedIds } satisfies TagReorderRequest),
|
|
308
|
+
})
|
|
309
|
+
const body = await readJson<TagReorderResponse>(response)
|
|
310
|
+
return body.tags
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** 拖拽重排来源集合 */
|
|
314
|
+
async reorderCollections(orderedNames: string[]): Promise<CollectionGroup[]> {
|
|
315
|
+
const response = await fetchWithTimeout(SKILL_HUB_API.collectionReorder, {
|
|
316
|
+
method: 'POST',
|
|
317
|
+
headers: { 'content-type': 'application/json' },
|
|
318
|
+
body: JSON.stringify({ orderedNames } satisfies CollectionReorderRequest),
|
|
319
|
+
})
|
|
320
|
+
const body = await readJson<CollectionReorderResponse>(response)
|
|
321
|
+
return body.collections
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** 拖拽重排来源顶层分组(project / collections / personal) */
|
|
325
|
+
async reorderSourceGroups(orderedKeys: string[]): Promise<string[]> {
|
|
326
|
+
const response = await fetchWithTimeout(SKILL_HUB_API.sourceGroupReorder, {
|
|
327
|
+
method: 'POST',
|
|
328
|
+
headers: { 'content-type': 'application/json' },
|
|
329
|
+
body: JSON.stringify({ orderedKeys } satisfies SourceGroupReorderRequest),
|
|
330
|
+
})
|
|
331
|
+
const body = await readJson<SourceGroupReorderResponse>(response)
|
|
332
|
+
return body.order
|
|
333
|
+
}
|
|
334
|
+
|
|
275
335
|
/** 来源列表 + 派生 origin 映射 + 集合组 + 回收站。 */
|
|
276
336
|
async sources(): Promise<SourcesResponse> {
|
|
277
337
|
const response = await fetchWithTimeout(SKILL_HUB_API.sources)
|
package/src/client/locales.ts
CHANGED
|
@@ -53,6 +53,11 @@ export const zh = {
|
|
|
53
53
|
'market.scan': '扫描',
|
|
54
54
|
'market.scanning': '扫描中…',
|
|
55
55
|
'market.check': '检查',
|
|
56
|
+
'market.checkUpdate': '检查更新',
|
|
57
|
+
'market.checking': '检查中…',
|
|
58
|
+
'market.upToDate': '已是最新',
|
|
59
|
+
'market.update': '更新',
|
|
60
|
+
'market.updateCount': '更新 {count} 个',
|
|
56
61
|
'market.sync': '同步',
|
|
57
62
|
'market.syncing': '同步中…',
|
|
58
63
|
'market.updated': '有更新',
|
|
@@ -76,8 +81,12 @@ export const zh = {
|
|
|
76
81
|
'repo.imported': '导入成功 {count} 个',
|
|
77
82
|
'repo.skippedExisting': '跳过已存在 {count} 个',
|
|
78
83
|
'repo.failed': '失败 {count} 个',
|
|
84
|
+
'repo.cancel': '取消',
|
|
85
|
+
'repo.cancelled': '已取消 · 已导入 {imported}/{total} 已保留,临时文件已清理',
|
|
86
|
+
'repo.importingCurrent': '正在导入: {name} ({done}/{total})',
|
|
79
87
|
'repo.root.skills': '技能 skills',
|
|
80
88
|
'repo.root.designTemplates': '模板 design-templates',
|
|
89
|
+
'repo.root.generic': '{root}',
|
|
81
90
|
'update.check': '检查更新',
|
|
82
91
|
'update.checking': '检查中…',
|
|
83
92
|
'update.upToDate': '已是最新版本 v{version}',
|
|
@@ -164,6 +173,10 @@ export const zh = {
|
|
|
164
173
|
'source.followDelete': '跟进删除',
|
|
165
174
|
'source.deleteConfirmTitle': '跟进上游删除?',
|
|
166
175
|
'source.deleteConfirmText': '以下技能已从上游仓库移除,本地目录将移入回收站(可恢复):',
|
|
176
|
+
'source.deleteGroup': '删除整组',
|
|
177
|
+
'source.deleteGroupHint': '删除该分组下的全部 {count} 个技能',
|
|
178
|
+
'source.deleteGroupTitle': '删除整组?',
|
|
179
|
+
'source.deleteGroupText': '将把分组“{name}”下的全部 {count} 个技能移入回收站(可恢复),只读技能会跳过:',
|
|
167
180
|
'source.trash': '回收站',
|
|
168
181
|
'source.restore': '恢复',
|
|
169
182
|
'source.clearTrash': '清空回收站',
|
|
@@ -266,6 +279,11 @@ export const en: Record<HubKey, string> = {
|
|
|
266
279
|
'market.scan': 'Scan',
|
|
267
280
|
'market.scanning': 'Scanning…',
|
|
268
281
|
'market.check': 'Check',
|
|
282
|
+
'market.checkUpdate': 'Check for updates',
|
|
283
|
+
'market.checking': 'Checking…',
|
|
284
|
+
'market.upToDate': 'Up to date',
|
|
285
|
+
'market.update': 'Update',
|
|
286
|
+
'market.updateCount': 'Update {count}',
|
|
269
287
|
'market.sync': 'Sync',
|
|
270
288
|
'market.syncing': 'Syncing…',
|
|
271
289
|
'market.updated': 'Updates available',
|
|
@@ -289,8 +307,12 @@ export const en: Record<HubKey, string> = {
|
|
|
289
307
|
'repo.imported': 'Imported {count}',
|
|
290
308
|
'repo.skippedExisting': 'Skipped existing {count}',
|
|
291
309
|
'repo.failed': 'Failed {count}',
|
|
310
|
+
'repo.cancel': 'Cancel',
|
|
311
|
+
'repo.cancelled': 'Cancelled · {imported}/{total} kept, temp files cleaned',
|
|
312
|
+
'repo.importingCurrent': 'Importing: {name} ({done}/{total})',
|
|
292
313
|
'repo.root.skills': 'Skills',
|
|
293
314
|
'repo.root.designTemplates': 'Templates design-templates',
|
|
315
|
+
'repo.root.generic': '{root}',
|
|
294
316
|
'update.check': 'Check for updates',
|
|
295
317
|
'update.checking': 'Checking…',
|
|
296
318
|
'update.upToDate': 'Up to date: v{version}',
|
|
@@ -377,6 +399,10 @@ export const en: Record<HubKey, string> = {
|
|
|
377
399
|
'source.followDelete': 'Follow deletion',
|
|
378
400
|
'source.deleteConfirmTitle': 'Follow upstream deletion?',
|
|
379
401
|
'source.deleteConfirmText': 'These skills were removed upstream; their local directories move to trash (restorable):',
|
|
402
|
+
'source.deleteGroup': 'Delete group',
|
|
403
|
+
'source.deleteGroupHint': 'Delete all {count} skills in this group',
|
|
404
|
+
'source.deleteGroupTitle': 'Delete group?',
|
|
405
|
+
'source.deleteGroupText': 'All {count} skills in group "{name}" will be moved to trash (restorable); read-only skills will be skipped:',
|
|
380
406
|
'source.trash': 'Trash',
|
|
381
407
|
'source.restore': 'Restore',
|
|
382
408
|
'source.clearTrash': 'Empty trash',
|