dsh-skill-hub 0.2.5 → 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.
@@ -5,7 +5,7 @@
5
5
  * Registered into the official `settings.plugin.item` slot keyed by that
6
6
  * namespace, so the plugin shows up in Settings → 插件 on dsh rc.7+.
7
7
  */
8
- import type { ReactElement } from 'react';
8
+ import { type ReactElement } from 'react';
9
9
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
10
10
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
11
11
  import { type CardShell, type FieldState, type FormScope } from './settings-form.ts';
@@ -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': "清空回收站";
@@ -180,7 +193,7 @@ export declare const zh: {
180
193
  readonly 'form.success': "已创建:";
181
194
  readonly 'form.error': "创建失败:";
182
195
  readonly 'err.dismiss': "知道了";
183
- readonly 'settings.title': "Skill Hub";
196
+ readonly 'settings.title': "技能中枢";
184
197
  readonly 'settings.description': "管理 dsh 本地技能目录:浏览、分组、来源同步与新建。";
185
198
  readonly 'settings.enabled': "启用插件";
186
199
  readonly 'settings.enabledHint': "关闭后技能中枢的路由、入口与公告全部下线。";
@@ -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>;
@@ -16,6 +16,8 @@ export interface PluginSettingsCardProps {
16
16
  titleKey: HubKey;
17
17
  /** Locale key of the card description. */
18
18
  descriptionKey: HubKey;
19
+ /** 插件自身版本(可选):显示在标题旁的小徽标;缺省不渲染。 */
20
+ version?: string;
19
21
  /** The form shell state. */
20
22
  state: CardShell;
21
23
  onSave: () => 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";
@@ -278,6 +283,8 @@ export declare const HEX_COLOR_RE: RegExp;
278
283
  /** GET /api/skill-hub/config */
279
284
  export interface ConfigResponse {
280
285
  ok: true;
286
+ /** 已安装插件自身的版本号(package.json version),设置卡标题旁显示。 */
287
+ pluginVersion: string;
281
288
  /** Effective configuration (saved overrides merged over the defaults). */
282
289
  config: HubConfig;
283
290
  /** Raw user overrides persisted in the sidecar (absent fields inherit defaults). */
@@ -363,7 +370,7 @@ export interface MarketSyncResponse {
363
370
  /** Local skills tracked from this repo (candidates for a batch update). */
364
371
  skills: string[];
365
372
  }
366
- /** 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/). */
367
374
  export interface RepoSkillEntry {
368
375
  /** Kebab-case skill name (directory basename). */
369
376
  name: string;
@@ -382,8 +389,8 @@ export interface RepoSkillEntry {
382
389
  /** Whether a same-named skill already exists locally. */
383
390
  existing: boolean;
384
391
  }
385
- /** Supported skill roots in a GitHub repo. */
386
- 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;
387
394
  /** GET /api/skill-hub/repo — discover importable skills in a GitHub repo. */
388
395
  export interface RepoDiscoverResponse {
389
396
  ok: true;
@@ -406,8 +413,56 @@ export interface RepoImportRequest {
406
413
  /** The ref the discovery ran against; the import must use the same one. */
407
414
  ref?: string;
408
415
  }
409
- /** POST /api/skill-hub/repo/import */
416
+ /** POST /api/skill-hub/repo/import — now creates an async job (B方案) */
410
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 {
411
466
  ok: true;
412
467
  imported: Array<{
413
468
  name: string;
@@ -458,12 +513,16 @@ export interface CollectionGroup {
458
513
  /** GET /api/skill-hub/groups */
459
514
  export interface GroupsResponse {
460
515
  ok: true;
461
- /** 用户自定义 tag 分组(按创建顺序)。 */
516
+ /** 用户自定义 tag 分组(按拖拽顺序)。 */
462
517
  tags: SkillTag[];
463
- /** 系统集合组(按 origin 聚合,名称排序)。 */
518
+ /** 系统集合组(按拖拽顺序,兜底按名称)。 */
464
519
  collections: CollectionGroup[];
465
520
  /** skillName → 集合名 的完整映射。 */
466
521
  origins: Record<string, string>;
522
+ /** 来源分组整体顺序(project / col:xxx / uncategorized-source),用于 SourcesView 顶层排序 */
523
+ sourceGroupOrder?: string[];
524
+ /** 兼容旧 collectionOrder 字段 */
525
+ collectionOrder?: string[];
467
526
  }
468
527
  /** POST /api/skill-hub/tag — 新建(缺省 id)或重命名(带 id)。 */
469
528
  export interface TagSaveRequest {
@@ -498,6 +557,36 @@ export interface TagMembersResponse {
498
557
  ok: true;
499
558
  tags: SkillTag[];
500
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
+ }
501
590
  /** 来源跟踪记录:一组来自同一上游仓库根目录的技能。 */
502
591
  export interface SourceRecord {
503
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.5",
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": {
@@ -6,9 +6,10 @@
6
6
  * namespace, so the plugin shows up in Settings → 插件 on dsh rc.7+.
7
7
  */
8
8
 
9
- import type { ReactElement } from 'react'
9
+ import { useEffect, useState, type ReactElement } from 'react'
10
10
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
11
11
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
12
+ import { SkillHubApi } from './api.ts'
12
13
  import { ColorField, NumberField, PluginSettingsCard, SwitchField } from './settings-card.tsx'
13
14
  import { booleanField, CardForm, colorField, numberField, type CardShell, type FieldState, type FormScope } from './settings-form.ts'
14
15
  // Single source for the TS-side dot defaults (the panel CSS mirrors these).
@@ -91,6 +92,15 @@ export class SkillHubSettingsCardController {
91
92
  export function SkillHubSettingsCard(props: SkillHubSettingsCardProps): ReactElement {
92
93
  const { t } = props
93
94
  const state = props.useSkillHubSettingsCard((snapshot) => snapshot)
95
+ // 插件自身版本:挂载时从 config 路由取一次(与面板标题徽标同源),失败静默不显示。
96
+ const [version, setVersion] = useState<string | undefined>(undefined)
97
+ useEffect(() => {
98
+ let cancelled = false
99
+ void new SkillHubApi().config()
100
+ .then((res) => { if (!cancelled && res.ok) setVersion(res.pluginVersion) })
101
+ .catch(() => { /* 版本徽标是装饰性的,失败不提示 */ })
102
+ return () => { cancelled = true }
103
+ }, [])
94
104
  const disabled = !state.writable
95
105
  const fieldProps = {
96
106
  overriddenLabel: t('settings.overridden'),
@@ -102,6 +112,7 @@ export function SkillHubSettingsCard(props: SkillHubSettingsCardProps): ReactEle
102
112
  t={t}
103
113
  titleKey='settings.title'
104
114
  descriptionKey='settings.description'
115
+ version={version}
105
116
  state={state}
106
117
  onSave={props.save}
107
118
  onDiscard={props.discard}
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)