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.
@@ -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 { JSX } from 'react';
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 { JSX } from 'react';
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, RepoImportResponse, SkillDetail, SkillTag, SourceCheckResult, SourcesResponse, UpdateCheckResponse, WritableRoot } from '../../protocol.ts';
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: RepoImportResponse | null;
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>;
@@ -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/ or design-templates/. */
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
- /** Supported skill roots in a GitHub repo. */
388
- export type RepoRoot = 'skills' | 'design-templates';
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
- /** 系统集合组(按 origin 聚合,名称排序)。 */
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。 */
@@ -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. The two supported roots mirror the repositories we care about:
6
- * `skills/**` (standard skill bundles) and `design-templates/**` (template
7
- * skill bundles in nexu-io/open-design).
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
- /** Supported skill roots in a GitHub repo. */
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<RepoRoot>, root: RepoRoot): string;
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: RepoRoot;
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: RepoRoot;
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: RepoRoot, repo: string): RepoSkillEntry;
140
+ export declare function repoSkillEntry(name: string, root: string, repo: string): RepoSkillEntry;
@@ -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, RepoRoot, SkillStatsCheckpoint, SkillTag, SourceRecord, TrashEntry } from './protocol.ts';
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: RepoRoot, commitSha: string, ref: string | undefined, skillName: string): Promise<void>;
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.2.6",
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)
@@ -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',