dsh-skill-hub 0.0.1 → 0.2.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.
Files changed (88) hide show
  1. package/CONTRIBUTING.md +56 -0
  2. package/README.md +355 -20
  3. package/cordis.patch.yml +12 -0
  4. package/lib/client.js +4514 -0
  5. package/lib/client.js.map +1 -0
  6. package/lib/index.js +3119 -0
  7. package/lib/invariant.js +6 -0
  8. package/lib/types/client/SkillHubSettingsCard.d.ts +53 -0
  9. package/lib/types/client/api-config-scope.d.ts +48 -0
  10. package/lib/types/client/api.d.ts +64 -0
  11. package/lib/types/client/grouping.d.ts +64 -0
  12. package/lib/types/client/helpers.d.ts +11 -0
  13. package/lib/types/client/index.d.ts +42 -0
  14. package/lib/types/client/locales.d.ts +216 -0
  15. package/lib/types/client/market-catalog.d.ts +14 -0
  16. package/lib/types/client/panel/DisabledRow.d.ts +12 -0
  17. package/lib/types/client/panel/GroupSummary.d.ts +10 -0
  18. package/lib/types/client/panel/MarketView.d.ts +11 -0
  19. package/lib/types/client/panel/ScenesView.d.ts +11 -0
  20. package/lib/types/client/panel/SkillDetailView.d.ts +31 -0
  21. package/lib/types/client/panel/SkillHubPanel.d.ts +18 -0
  22. package/lib/types/client/panel/SkillRow.d.ts +11 -0
  23. package/lib/types/client/panel/SourceStatusBadge.d.ts +18 -0
  24. package/lib/types/client/panel/SourcesView.d.ts +10 -0
  25. package/lib/types/client/panel/TagEditorView.d.ts +29 -0
  26. package/lib/types/client/panel/dialogs.d.ts +73 -0
  27. package/lib/types/client/panel/format.d.ts +14 -0
  28. package/lib/types/client/panel/useSkillHub.d.ts +183 -0
  29. package/lib/types/client/settings-card.d.ts +81 -0
  30. package/lib/types/client/settings-form.d.ts +95 -0
  31. package/lib/types/client/settings-nav-icon.d.ts +8 -0
  32. package/lib/types/index.d.ts +36 -0
  33. package/lib/types/invariant.d.ts +3 -0
  34. package/lib/types/protocol.d.ts +571 -0
  35. package/lib/types/provider.d.ts +47 -0
  36. package/lib/types/repo.d.ts +133 -0
  37. package/lib/types/routes.d.ts +51 -0
  38. package/lib/types/skillfs.d.ts +95 -0
  39. package/lib/types/stats.d.ts +49 -0
  40. package/lib/types/store.d.ts +142 -0
  41. package/lib/types/update.d.ts +20 -0
  42. package/package.json +115 -11
  43. package/src/client/SkillHubSettingsCard.tsx +161 -0
  44. package/src/client/api-config-scope.test.ts +79 -0
  45. package/src/client/api-config-scope.ts +119 -0
  46. package/src/client/api.ts +327 -0
  47. package/src/client/css-modules.d.ts +5 -0
  48. package/src/client/grouping.test.ts +109 -0
  49. package/src/client/grouping.ts +120 -0
  50. package/src/client/helpers.ts +23 -0
  51. package/src/client/index.tsx +104 -0
  52. package/src/client/locales.ts +432 -0
  53. package/src/client/market-catalog.ts +22 -0
  54. package/src/client/panel/DisabledRow.tsx +32 -0
  55. package/src/client/panel/GroupSummary.tsx +27 -0
  56. package/src/client/panel/MarketView.tsx +173 -0
  57. package/src/client/panel/ScenesView.tsx +75 -0
  58. package/src/client/panel/SkillDetailView.tsx +95 -0
  59. package/src/client/panel/SkillHubPanel.tsx +347 -0
  60. package/src/client/panel/SkillRow.tsx +54 -0
  61. package/src/client/panel/SourceStatusBadge.tsx +61 -0
  62. package/src/client/panel/SourcesView.tsx +104 -0
  63. package/src/client/panel/TagEditorView.tsx +89 -0
  64. package/src/client/panel/dialogs.tsx +185 -0
  65. package/src/client/panel/format.ts +31 -0
  66. package/src/client/panel/panel.module.css +219 -0
  67. package/src/client/panel/useSkillHub.ts +797 -0
  68. package/src/client/settings-card.module.css +52 -0
  69. package/src/client/settings-card.tsx +237 -0
  70. package/src/client/settings-form.ts +227 -0
  71. package/src/client/settings-nav-icon.ts +42 -0
  72. package/src/index.ts +181 -0
  73. package/src/invariant.ts +4 -0
  74. package/src/protocol.ts +622 -0
  75. package/src/provider.test.ts +112 -0
  76. package/src/provider.ts +168 -0
  77. package/src/repo.test.ts +212 -0
  78. package/src/repo.ts +528 -0
  79. package/src/routes.test.ts +786 -0
  80. package/src/routes.ts +1252 -0
  81. package/src/skillfs.test.ts +290 -0
  82. package/src/skillfs.ts +306 -0
  83. package/src/stats.test.ts +168 -0
  84. package/src/stats.ts +124 -0
  85. package/src/store.test.ts +253 -0
  86. package/src/store.ts +628 -0
  87. package/src/update.test.ts +73 -0
  88. package/src/update.ts +136 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * GitHub repository skill discovery/import helpers.
3
+ *
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).
8
+ */
9
+ import type { RepoRoot, RepoSkillEntry } from './protocol.ts';
10
+ /** Supported skill roots in a GitHub repo. */
11
+ export declare const REPO_ROOTS: readonly RepoRoot[];
12
+ /** Parsed GitHub repository reference. */
13
+ export interface RepoRef {
14
+ owner: string;
15
+ repo: string;
16
+ /** Explicit branch/tag when supplied; undefined means use the default branch. */
17
+ ref?: string;
18
+ }
19
+ /** One file inside a GitHub repo tree. */
20
+ export interface RepoTreeItem {
21
+ path: string;
22
+ type: 'blob' | 'tree';
23
+ size?: number;
24
+ }
25
+ /** One file belonging to a skill directory. */
26
+ export interface RepoFile {
27
+ path: string;
28
+ size: number;
29
+ }
30
+ /** Fetch failure carrying a useful HTTP status for the route layer. */
31
+ export declare class RepoFetchError extends Error {
32
+ readonly status: number;
33
+ constructor(message: string, status?: number);
34
+ }
35
+ /** Override the GitHub token at runtime ('' falls back to the env var). */
36
+ export declare function setGithubToken(token: string | undefined): void;
37
+ /** Authorization header for api.github.com / raw.githubusercontent.com calls. */
38
+ export declare function githubAuthHeaders(): Record<string, string>;
39
+ /**
40
+ * Build a RepoFetchError; when the response shows an exhausted rate limit,
41
+ * report the reset time instead of a bare HTTP status.
42
+ */
43
+ export declare function fetchError(context: string, response: Response, fallbackStatus?: number): RepoFetchError;
44
+ /** `owner/repo` slug for a parsed reference. */
45
+ export declare function repoSlug(ref: RepoRef): string;
46
+ /**
47
+ * Normalize a GitHub URL or `owner/repo`/`owner/repo@ref` input.
48
+ * Only github.com URLs are accepted in v1.
49
+ */
50
+ export declare function normalizeRepoInput(input: string): RepoRef | null;
51
+ /**
52
+ * Load repo metadata and the recursive git tree at the given ref. The meta
53
+ * request always resolves the default branch (used when ref is absent); the
54
+ * tree is fetched at `ref ?? default_branch` so an explicit branch/tag scans
55
+ * the exact same content a later download would fetch.
56
+ */
57
+ export declare function loadRepoTree(repo: string, ref?: string, fetchImpl?: typeof fetch): Promise<{
58
+ ref: string;
59
+ tree: RepoTreeItem[];
60
+ }>;
61
+ /** Collect every file inside a skill directory, including SKILL.md itself. */
62
+ export declare function collectRepoSkillFiles(tree: readonly RepoTreeItem[], dir: string): RepoFile[];
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. */
66
+ export declare function discoverRepoEntries(tree: readonly RepoTreeItem[], repo: string, existingNames?: ReadonlySet<string>): RepoSkillEntry[];
67
+ /** Run an async worker over items with a bounded concurrency. */
68
+ export declare function mapConcurrent<T, R>(items: readonly T[], limit: number, worker: (item: T, index: number) => Promise<R>): Promise<R[]>;
69
+ /**
70
+ * Download one GitHub file as a buffer. Tries raw.githubusercontent.com
71
+ * first (no API quota); on any failure falls back to the api.github.com
72
+ * contents endpoint with the raw media type (rate-limited, but reachable
73
+ * from networks that block the raw host).
74
+ */
75
+ export declare function downloadGitHubFile(repo: string, ref: string, path: string, fetchImpl?: typeof fetch): Promise<Buffer>;
76
+ /**
77
+ * Download a full skill directory into a temporary dir, validate SKILL.md,
78
+ * then atomically rename it into place. The target root is created when missing.
79
+ * Callers pass a target root and a skill entry plus its collected files.
80
+ */
81
+ export declare function downloadRepoSkill(repo: string, ref: string, entry: RepoSkillEntry, files: readonly RepoFile[], targetRoot: string, fetchImpl?: typeof fetch): Promise<{
82
+ targetDir: string;
83
+ skillPath: string;
84
+ }>;
85
+ /**
86
+ * The latest non-prerelease release tag of a repo, or undefined when the
87
+ * repo has no releases (GitHub's /releases/latest skips prereleases/drafts).
88
+ */
89
+ export declare function getLatestReleaseTag(repo: string, fetchImpl?: typeof fetch): Promise<string | undefined>;
90
+ /** Branch names of a repo, default branch first (one GitHub API request). */
91
+ export declare function listRepoBranches(repo: string, fetchImpl?: typeof fetch): Promise<string[]>;
92
+ /**
93
+ * Fetch the latest commit of a repo (explicit ref or default branch) with
94
+ * its tree SHA. One GitHub API request; the tree SHA is used to diff the
95
+ * upstream tree on change (one extra request).
96
+ */
97
+ export declare function getLatestCommit(repo: string, ref?: string, fetchImpl?: typeof fetch): Promise<{
98
+ commitSha: string;
99
+ treeSha: string;
100
+ }>;
101
+ /** Load the recursive git tree at an explicit tree SHA (one API request). */
102
+ export declare function loadRepoTreeAt(repo: string, treeSha: string, fetchImpl?: typeof fetch): Promise<RepoTreeItem[]>;
103
+ /** Build a path→size manifest for one skill directory from a repo tree. */
104
+ export declare function skillManifest(tree: readonly RepoTreeItem[], dir: string): Record<string, number>;
105
+ /**
106
+ * The real upstream directory of one tracked skill. Source records keep only
107
+ * the short name and top-level root (e.g. root "skills", name "ask-matt"),
108
+ * but upstream repos may nest skills under category directories
109
+ * (skills/engineering/ask-matt/) or move them between categories. Resolution
110
+ * order: exact match under the top-level root in the upstream tree, then the
111
+ * manifest's recorded blob path, then any same-name skill in the tree, then
112
+ * the flat root/name fallback. Passing the tree paths makes the lookup
113
+ * resilient to incomplete manifests and upstream moves.
114
+ */
115
+ export declare function skillDirOf(source: {
116
+ root: RepoRoot;
117
+ manifest?: Record<string, number>;
118
+ }, name: string, treePaths?: readonly string[]): string;
119
+ /**
120
+ * Diff one source record against an upstream tree at treeSha: which tracked
121
+ * skills disappeared (no SKILL.md blob) and which changed (manifest baseline
122
+ * differs, or no baseline exists — treated as changed). Pure over the tree.
123
+ */
124
+ export declare function diffRemoteSkills(tree: readonly RepoTreeItem[], source: {
125
+ root: RepoRoot;
126
+ skills: readonly string[];
127
+ manifest?: Record<string, number>;
128
+ }): {
129
+ updated: string[];
130
+ deleted: string[];
131
+ };
132
+ /** Minimal RepoSkillEntry for a tracked skill name (sync re-downloads by name). */
133
+ export declare function repoSkillEntry(name: string, root: RepoRoot, repo: string): RepoSkillEntry;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * The /api/skill-hub route family: full catalog (enabled skills from the
3
+ * official registry + hub-disabled skills + discovery diagnostics), skill
4
+ * detail, enable/disable toggle, new-skill scaffold, user groups (tags +
5
+ * origin collections), and upstream source tracking (check/sync/follow
6
+ * upstream deletion into a restorable trash). Every route carries a
7
+ * loopback-only trust fence — these endpoints rename files under the user's
8
+ * skill roots, so LAN-exposed dsh web deployments must not serve them.
9
+ *
10
+ * Every handler is declared through the local `route()` wrapper, which owns
11
+ * the shared request fences (loopback trust, HTTP method, master switch,
12
+ * JSON body) exactly once, so a new route cannot forget them.
13
+ */
14
+ import type { WebRoute } from '@deepseek-ai/dsh-host-webserver';
15
+ import type { SkillDefinition, SkillSummary } from '@deepseek-ai/dsh-skill';
16
+ import { type HubConfig } from './protocol.ts';
17
+ import { type SkillHubStore } from './store.ts';
18
+ import type { SkillStatsReader } from './stats.ts';
19
+ /** Lookup options the hub forwards to the registry (cwd selects project roots). */
20
+ export interface SkillLookupLike {
21
+ cwd?: string;
22
+ }
23
+ /** Route family dependencies (narrow structural view of ctx.skills for tests). */
24
+ export interface SkillHubRouteDeps {
25
+ skills: {
26
+ snapshot(options?: SkillLookupLike): Promise<{
27
+ skills: SkillSummary[];
28
+ complete: boolean;
29
+ }>;
30
+ get(name: string, options?: SkillLookupLike): Promise<SkillDefinition | undefined>;
31
+ };
32
+ store: SkillHubStore;
33
+ /** DSH home override (tests isolate the writable roots; defaults to ~/.dsh). */
34
+ home?: string;
35
+ /** Invalidate the registry catalog cache after hub-driven mutations. */
36
+ invalidate?: () => void;
37
+ /** Optional invocation-count reader; absent means the stats route reports unavailable. */
38
+ stats?: SkillStatsReader;
39
+ /** Resolves the current plugin config; business routes honour the master switch. */
40
+ config?: () => HubConfig;
41
+ /** Resolves the raw saved config layer (fields the user explicitly overrode). */
42
+ saved?: () => Partial<HubConfig>;
43
+ /** Persist a config patch and re-sync plugin surfaces; resolves with the fresh config. */
44
+ updateConfig?: (patch: Partial<HubConfig>) => Promise<HubConfig>;
45
+ }
46
+ /**
47
+ * Build every /api/skill-hub route.
48
+ * @param deps - skill registry view + sidecar store.
49
+ * @returns the exact-path routes.
50
+ */
51
+ export declare function makeRoutes(deps: SkillHubRouteDeps): WebRoute[];
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Skill filesystem operations for the writable roots and the hub provider's
3
+ * discovery. Toggling works by renaming the discovery file (SKILL.md inside
4
+ * a directory bundle, or the flat <name>.md) to a .disabled name; the
5
+ * watcher (ours or the preset's official filesystem provider) then updates
6
+ * the live catalog by itself.
7
+ *
8
+ * Frontmatter parsing mirrors @deepseek-ai/dsh-skill-filesystem semantics:
9
+ * required name (kebab-case) + description, optional whenToUse, invocation
10
+ * booleans with the same defaults (modelInvocable defaults true,
11
+ * userInvocable defaults true), legacy keys rejected, and the returned
12
+ * content is the body after the frontmatter block, trimmed.
13
+ */
14
+ import type { DiagnosticEntry, TrashEntry, WritableRoot } from './protocol.ts';
15
+ /** Root ids this module may write to. */
16
+ export declare const WRITABLE_ROOTS: readonly WritableRoot[];
17
+ /** Resolve the absolute directory of one writable root. */
18
+ export declare function rootPath(root: WritableRoot, home?: string): string;
19
+ /** Root of an absolute skill file path, or undefined when not user-owned. */
20
+ export declare function rootOfPath(path: string, home?: string): WritableRoot | undefined;
21
+ /**
22
+ * Scaffold a directory-bundle skill: <root>/<name>/SKILL.md with a frontmatter
23
+ * template. Refuses non-kebab-case names.
24
+ * @returns the created SKILL.md path.
25
+ */
26
+ export declare function createSkill(root: WritableRoot, name: string, description: string, home?: string): Promise<string>;
27
+ /**
28
+ * Disable one skill file by renaming it out of discovery shapes. Accepts
29
+ * either the SKILL.md of a directory bundle or a flat .md.
30
+ * @returns the absolute path after renaming.
31
+ */
32
+ export declare function disableSkill(path: string): Promise<string>;
33
+ /** Re-enable a previously disabled file (reverse of disableSkill). */
34
+ export declare function enableSkill(disabledPath: string): Promise<string>;
35
+ /**
36
+ * Move a skill (directory bundle or flat .md file) into the root's trash.
37
+ * The skill stays recoverable: the source is renamed into
38
+ * <root>/.trash/<name>-<timestamp>. A directory bundle is addressed by its
39
+ * SKILL.md path and moved as a whole directory.
40
+ * @returns the trashed path plus the original path that was moved.
41
+ */
42
+ export declare function trashSkill(sourcePath: string): Promise<{
43
+ path: string;
44
+ source: string;
45
+ }>;
46
+ /** Restore a trashed skill (directory or flat file) to its original location. */
47
+ export declare function restoreSkill(entry: TrashEntry, home?: string): Promise<string>;
48
+ /** Permanently delete one trashed skill (directory or flat file). */
49
+ export declare function clearTrash(entry: TrashEntry, home?: string): Promise<string>;
50
+ /** One parsed frontmatter outcome (official provider semantics). */
51
+ export interface FrontmatterValue {
52
+ name: string;
53
+ description: string;
54
+ whenToUse?: string;
55
+ invocation: {
56
+ modelInvocable: boolean;
57
+ userInvocable: boolean;
58
+ };
59
+ /** Instruction body after the frontmatter block, trimmed. */
60
+ content: string;
61
+ }
62
+ /** Parse a SKILL.md the way the official filesystem provider does. */
63
+ export declare function parseFrontmatter(text: string): {
64
+ value: FrontmatterValue;
65
+ } | {
66
+ error: string;
67
+ };
68
+ /** One discoverable skill file in a scanned root. */
69
+ export interface SkillEntry {
70
+ /** Absolute path of the discovery file (SKILL.md or the flat .md). */
71
+ path: string;
72
+ /** Directory serving as the resource base (bundle dir or the root itself). */
73
+ directory: string;
74
+ kind: 'directory' | 'flat';
75
+ }
76
+ /**
77
+ * Scan one skills root for discovery files: directory bundles (SKILL.md)
78
+ * and flat <name>.md files. Hub-disabled files (.disabled) are excluded;
79
+ * dot-prefixed entries (including .trash and .system) are always skipped.
80
+ */
81
+ export declare function scanRoot(base: string): Promise<SkillEntry[]>;
82
+ /** Scan one writable root. */
83
+ export declare function listSkillEntries(root: WritableRoot, home?: string): Promise<SkillEntry[]>;
84
+ /**
85
+ * Walk up from cwd (max 32 levels) for a project marker (.dsh or .git);
86
+ * falls back to cwd itself. The provider roots project skills here.
87
+ */
88
+ export declare function findProjectRoot(cwd: string): Promise<string>;
89
+ /**
90
+ * Scan one writable root for files the provider ignores, so the GUI can
91
+ * show why a skill never appears (the zcode-style diagnostics lesson: a
92
+ * missing frontmatter must be visible, not silent). .disabled files belong
93
+ * to the hub and are skipped.
94
+ */
95
+ export declare function scanDiagnostics(root: WritableRoot, home?: string): Promise<DiagnosticEntry[]>;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Trigger statistics: how often each skill was actually invoked. The count
3
+ * comes from the local session logs, read through the host's `sessionQuery`
4
+ * service (which transparently decompresses the on-disk JSONL.zstd artifacts).
5
+ *
6
+ * A skill is invoked two ways, both recorded in the log:
7
+ * - model-invoked: a `tool/call` event for the `skill` tool, whose raw
8
+ * `arguments` JSON names the requested skill (see @deepseek-ai/dsh-tool-skill);
9
+ * - user-explicit: a `user/message` event whose `source.kind ===
10
+ * 'skill-invocation'` (the MessageSourceMap augmentation from
11
+ * @deepseek-ai/dsh-skill).
12
+ *
13
+ * Counting is per-skill-name, not per-source: a name may resolve to different
14
+ * files across projects, but the model-facing identity is the kebab-case name.
15
+ */
16
+ import type { SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
17
+ import type { SkillStat } from './protocol.ts';
18
+ /** Narrow structural view of the session-query service (kept loose for tests). */
19
+ export interface SessionQueryLike {
20
+ listSessions(signal?: AbortSignal): Promise<Array<{
21
+ header: {
22
+ id: SessionId;
23
+ };
24
+ }>>;
25
+ readSession(id: SessionId): Promise<{
26
+ events: SessionEvent[];
27
+ }>;
28
+ }
29
+ /** Per-skill invocation stats in one session's event log. */
30
+ export interface InvocationStat {
31
+ count: number;
32
+ /** Unix epoch ms of the most recent invocation in this batch. */
33
+ lastUsed: number;
34
+ }
35
+ /** Collect per-skill invocation counts and last-used times from one session. */
36
+ export declare function countSkillInvocations(events: readonly SessionEvent[]): Map<string, InvocationStat>;
37
+ /** Scan every session and total per-skill counts, keeping the latest lastUsed. */
38
+ export declare function readSkillStats(query: SessionQueryLike): Promise<SkillStat[]>;
39
+ /** A memoized stats reader (the panel polls, but logs change slowly). */
40
+ export type SkillStatsReader = () => Promise<SkillStat[]>;
41
+ /**
42
+ * Wrap a query in a stale-while-revalidate cache: responses never wait for a
43
+ * full session-log scan. While the TTL is fresh the cached totals are
44
+ * returned; after expiry the stale totals are returned immediately and a
45
+ * single background rescan refreshes them — the panel's next poll picks the
46
+ * fresh numbers. A full scan decompresses every session log and can take
47
+ * seconds, so it must never sit on the request path.
48
+ */
49
+ export declare function createSkillStatsReader(query: SessionQueryLike, ttlMs?: number): SkillStatsReader;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Hub sidecar store: remembers which skills the hub toggled off and the
3
+ * user's organization/tracking records. Disabling renames the skill's
4
+ * SKILL.md (or flat .md) out of the filesystem provider's discovery shapes,
5
+ * so the provider catalog alone cannot see disabled skills; this store keeps
6
+ * name/path/root so the GUI can list them and re-enable. It also persists
7
+ * user tag groups, upstream source records (repo + commit snapshot for
8
+ * update checks), the market source list, and the trash (skills removed
9
+ * after upstream deletion).
10
+ *
11
+ * State file: $DSH_HOME/dsh-skill-hub.json — a small JSON document written
12
+ * atomically (tmp file + rename).
13
+ */
14
+ import type { DisabledSkill, HubConfig, MarketSourceRecord, RepoRoot, SkillTag, SourceRecord, TrashEntry } from './protocol.ts';
15
+ /** 默认场景名(系统预置的兜底场景,新技能自动归入)。 */
16
+ export declare const DEFAULT_SCENE_NAME = "\u901A\u7528";
17
+ /** Resolve the DSH home directory (the filesystem provider's user-dsh root base). */
18
+ export declare function dshHome(): string;
19
+ /** Resolve the sidecar state path (injectable in tests). */
20
+ export declare function statePath(home?: string): string;
21
+ /** Current sidecar schema version. Bump on breaking shape changes and add a migration below. */
22
+ export declare const STORE_VERSION = 3;
23
+ /**
24
+ * Business-rule failure the routes layer can map onto a 4xx status instead
25
+ * of a blanket 500: user input is invalid (validation → 400), the target
26
+ * does not exist (not-found → 404), or the operation conflicts with an
27
+ * invariant (conflict → 409).
28
+ */
29
+ export declare class StoreError extends Error {
30
+ readonly kind: 'validation' | 'not-found' | 'conflict';
31
+ constructor(kind: StoreError['kind'], message: string);
32
+ }
33
+ /** Sidecar state owner. */
34
+ export declare class SkillHubStore {
35
+ private readonly file;
36
+ private entries;
37
+ private config;
38
+ private tagsById;
39
+ private sourcesByRepo;
40
+ private marketSources;
41
+ private trashByName;
42
+ private loaded;
43
+ /** Serializes persist runs: concurrent mutators must not let an earlier
44
+ * snapshot overwrite a later one (rename is atomic, ordering is not). */
45
+ private writeChain;
46
+ constructor(file?: string);
47
+ private ensureLoaded;
48
+ /**
49
+ * 保证存在默认场景:没有任何 default 标记的 tag 时创建「通用」。
50
+ * 新技能创建后自动归入它;用户可改名,但默认场景不可删除。
51
+ */
52
+ private ensureDefaultTag;
53
+ listDisabled(): Promise<DisabledSkill[]>;
54
+ getDisabled(name: string): Promise<DisabledSkill | undefined>;
55
+ addDisabled(entry: DisabledSkill): Promise<void>;
56
+ removeDisabled(name: string): Promise<void>;
57
+ /** The hub's saved runtime configuration (fields absent here mean "not overridden"). */
58
+ getConfig(): Promise<Partial<HubConfig>>;
59
+ /**
60
+ * Persist a runtime-config patch, merged over the saved values. A field
61
+ * whose patch value is undefined is removed from the saved layer, so the
62
+ * setting re-inherits its default (the web card's "reset" path).
63
+ */
64
+ setConfig(config: Partial<HubConfig>): Promise<void>;
65
+ /** All user-defined tag groups, in creation order. */
66
+ listTags(): Promise<SkillTag[]>;
67
+ /** One tag by id (undefined when absent). */
68
+ getTag(id: string): Promise<SkillTag | undefined>;
69
+ /**
70
+ * Create (no id) or rename (with id) a tag. Returns the saved tag.
71
+ * Creating assigns a fresh UUID; renaming keeps members.
72
+ */
73
+ saveTag(input: {
74
+ id?: string;
75
+ name: string;
76
+ }): Promise<SkillTag>;
77
+ /** Delete a tag by id (no-op when absent). The default scene cannot be deleted. */
78
+ deleteTag(id: string): Promise<void>;
79
+ /** The default scene (「通用」), guaranteed to exist after ensureLoaded. */
80
+ getDefaultTag(): Promise<SkillTag | undefined>;
81
+ /** Append one skill name to a tag (deduplicated; no-op when already a member). */
82
+ addSkillToTag(id: string, name: string): Promise<SkillTag | undefined>;
83
+ /**
84
+ * Replace a tag's member list wholesale (idempotent). Deduplicates and
85
+ * drops blank names; unknown skill names are kept (they may arrive later),
86
+ * the routes layer filters against the live catalog before persisting.
87
+ */
88
+ setTagMembers(id: string, skillNames: readonly string[]): Promise<SkillTag | undefined>;
89
+ /** Remove one skill from every tag group (used when the skill is deleted). */
90
+ removeSkillFromTags(name: string): Promise<void>;
91
+ /** All source records, sorted by repo. */
92
+ listSources(): Promise<SourceRecord[]>;
93
+ /** One source record by repo (undefined when absent). */
94
+ getSource(repo: string): Promise<SourceRecord | undefined>;
95
+ /** The source record that tracks a skill name (undefined when untracked). */
96
+ getSourceForSkill(name: string): Promise<SourceRecord | undefined>;
97
+ /** Remove a source record entirely (no-op when absent). */
98
+ deleteSource(repo: string): Promise<void>;
99
+ /**
100
+ * Upsert one skill into a source record. When the repo has no record yet a
101
+ * new one is created (root + commit snapshot from the caller).
102
+ */
103
+ addSourceSkill(repo: string, root: RepoRoot, commitSha: string, ref: string | undefined, skillName: string): Promise<void>;
104
+ /** Replace a source's skill list (used after sync/confirm-delete). */
105
+ setSourceSkills(repo: string, skills: readonly string[]): Promise<SourceRecord | undefined>;
106
+ /** Remove one skill from every source record (used when the skill is deleted). */
107
+ removeSkillFromSources(name: string): Promise<void>;
108
+ /** Update a source's commit snapshot. */
109
+ setSourceCommit(repo: string, commitSha: string): Promise<void>;
110
+ /** Update a source's pinned ref (release tag / branch) when the market syncs. */
111
+ setSourceRef(repo: string, ref: string): Promise<void>;
112
+ /**
113
+ * Merge per-path manifest entries into a source (incremental imports).
114
+ * When `dir` is given, every baseline path under that skill directory is
115
+ * dropped first, so files the upstream removed never linger in the
116
+ * baseline and skew later update diffs.
117
+ */
118
+ mergeSourceManifest(repo: string, manifest: Record<string, number>, dir?: string): Promise<void>;
119
+ /** skillName → collection name for every recorded origin (derived from sources). */
120
+ listOrigins(): Promise<Record<string, string>>;
121
+ /** The user's market sources, in addition order. */
122
+ listMarketSources(): Promise<MarketSourceRecord[]>;
123
+ /** One market source by repo (undefined when absent). */
124
+ getMarketSource(repo: string): Promise<MarketSourceRecord | undefined>;
125
+ /** Add a repo (deduplicated), optionally with a pinned ref. Returns the fresh list. */
126
+ addMarketSource(repo: string, ref?: string): Promise<MarketSourceRecord[]>;
127
+ /** Remove a repo (no-op when absent). Returns the fresh list. */
128
+ removeMarketSource(repo: string): Promise<MarketSourceRecord[]>;
129
+ /** Pin a market source to an explicit ref (branch/tag). Returns the record. */
130
+ setMarketSourceRef(repo: string, ref: string): Promise<MarketSourceRecord | undefined>;
131
+ /** Record the commit a market source's pinned ref resolved to (update baseline). */
132
+ setMarketSourceCommit(repo: string, commitSha: string): Promise<void>;
133
+ /** All trashed skills, newest first. */
134
+ listTrash(): Promise<TrashEntry[]>;
135
+ /** One trash entry by skill name (undefined when absent). */
136
+ getTrash(name: string): Promise<TrashEntry | undefined>;
137
+ /** Record a trashed skill. */
138
+ addTrash(entry: TrashEntry): Promise<void>;
139
+ /** Remove a trash record (after restore). */
140
+ removeTrash(name: string): Promise<void>;
141
+ private persist;
142
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Self-update check for dsh-skill-hub, mirroring cc-switch's behavior:
3
+ * query GitHub's latest release and compare it to the installed version.
4
+ *
5
+ * This stays dependency-free: Node's global fetch is used and the version
6
+ * comparison is a small semver-subset parser (the plugin only publishes
7
+ * plain x.y.z tags, optionally prefixed with `v`).
8
+ */
9
+ import type { UpdateCheckResponse } from './protocol.ts';
10
+ /** Repository checked for releases. Keep in sync with package.json. */
11
+ export declare const UPDATE_REPO = "cheshireez/dsh-skill-hub";
12
+ export declare const CURRENT_VERSION: string;
13
+ /** Normalize a release tag (`v1.2.3` → `1.2.3`) and parse it numerically. */
14
+ export declare function parseVersion(tag: string): [number, number, number] | null;
15
+ /** Compare two dotted version strings: negative when a < b, 0 equal, positive when a > b. */
16
+ export declare function compareVersions(a: string, b: string): number;
17
+ /** True when latest is strictly newer than current. */
18
+ export declare function isUpdateAvailable(currentVersion: string, latestVersion: string): boolean;
19
+ /** Best-effort GitHub latest-release lookup. Always resolves with ok:true. */
20
+ export declare function checkLatestRelease(repo?: string, fetchImpl?: typeof fetch): Promise<UpdateCheckResponse>;
package/package.json CHANGED
@@ -1,7 +1,101 @@
1
1
  {
2
2
  "name": "dsh-skill-hub",
3
- "version": "0.0.1",
4
- "description": "In-GUI skill hub for DeepSeek Harness (dsh): browse the full local skill catalog (every root + third-party providers), toggle skills on/off, surface frontmatter diagnostics, and manage your skill library from the web GUI — the full manager beyond the read-only dsh-skill-manager browser.",
3
+ "version": "0.2.0",
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
+ "type": "module",
6
+ "engines": {
7
+ "node": "^22.19.0 || >=24.0.0"
8
+ },
9
+ "main": "lib/index.js",
10
+ "types": "lib/types/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./lib/types/index.d.ts",
14
+ "default": "./lib/index.js"
15
+ },
16
+ "./invariant": {
17
+ "types": "./lib/types/invariant.d.ts",
18
+ "default": "./lib/invariant.js"
19
+ },
20
+ "./client": {
21
+ "types": "./lib/types/client/index.d.ts",
22
+ "default": "./lib/client.js"
23
+ },
24
+ "./src/*": "./src/*",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "dsh": {
28
+ "bundle": {
29
+ "patch": "./cordis.patch.yml"
30
+ },
31
+ "client": {
32
+ "inject": [
33
+ "@deepseek-ai/dsh-client-runtime",
34
+ "@deepseek-ai/dsh-client-connection",
35
+ "@deepseek-ai/dsh-client-ui-settings"
36
+ ],
37
+ "platform": "web"
38
+ }
39
+ },
40
+ "dependencies": {
41
+ "js-yaml": "^4.1.0",
42
+ "schemastery": "^3.18.0"
43
+ },
44
+ "peerDependencies": {
45
+ "@deepseek-ai/cordis": "^4.0.1",
46
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
47
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
48
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
49
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
50
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
51
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
52
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
53
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
54
+ "@deepseek-ai/dsh-session-query": "^0.1.0-rc.6",
55
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
56
+ "@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
57
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
58
+ "react": "^18.2.0",
59
+ "react-dom": "^18.2.0"
60
+ },
61
+ "devDependencies": {
62
+ "@deepseek-ai/cordis": "^4.0.1",
63
+ "@deepseek-ai/dsh-client-connection": "^0.1.0-rc.6",
64
+ "@deepseek-ai/dsh-client-locale": "^0.1.0-rc.6",
65
+ "@deepseek-ai/dsh-client-runtime": "^0.1.0-rc.6",
66
+ "@deepseek-ai/dsh-client-ui-primitives": "^0.1.0-rc.6",
67
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.0-rc.6",
68
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.0-rc.6",
69
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.0-rc.6",
70
+ "@deepseek-ai/dsh-host-webserver": "^0.1.0-rc.6",
71
+ "@deepseek-ai/dsh-session": "^0.1.0-rc.6",
72
+ "@deepseek-ai/dsh-session-query": "^0.1.0-rc.6",
73
+ "@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
74
+ "@deepseek-ai/dsh-skill": "^0.1.0-rc.6",
75
+ "@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6",
76
+ "@types/js-yaml": "^4.0.9",
77
+ "@types/node": "^22.20.0",
78
+ "@types/react": "~18.3.1",
79
+ "@types/react-dom": "^18.3.5",
80
+ "lightningcss": "1.32.0",
81
+ "react": "^18.3.1",
82
+ "react-dom": "^18.3.1",
83
+ "schemastery": "^3.18.0",
84
+ "tsdown": "0.22.2",
85
+ "typescript": "~5.7.2",
86
+ "vitest": "^3.0.0"
87
+ },
88
+ "files": [
89
+ "lib/**/*.js",
90
+ "lib/**/*.js.map",
91
+ "lib/**/*.d.ts",
92
+ "lib/**/*.d.ts.map",
93
+ "src",
94
+ "cordis.patch.yml",
95
+ "README.md",
96
+ "LICENSE",
97
+ "CONTRIBUTING.md"
98
+ ],
5
99
  "keywords": [
6
100
  "dsh-plugin",
7
101
  "dsh",
@@ -9,15 +103,25 @@
9
103
  "skill",
10
104
  "skills",
11
105
  "skill-manager",
12
- "agent-skills"
106
+ "agent-skills",
107
+ "skill-hub"
13
108
  ],
14
109
  "license": "MIT",
15
- "type": "module",
16
- "engines": {
17
- "node": "^22.19.0 || >=24.0.0"
110
+ "scripts": {
111
+ "build": "tsc -p tsconfig.build.json && tsdown",
112
+ "bundle": "tsdown",
113
+ "watch": "tsdown --watch",
114
+ "typecheck": "tsc --noEmit",
115
+ "test": "vitest run",
116
+ "test:watch": "vitest",
117
+ "prebuild": "node -e \"require('fs').rmSync('lib',{recursive:true,force:true})\""
18
118
  },
19
- "files": [
20
- "README.md",
21
- "LICENSE"
22
- ]
23
- }
119
+ "repository": {
120
+ "type": "git",
121
+ "url": "git+https://github.com/cheshireez/dsh-skill-hub.git"
122
+ },
123
+ "bugs": {
124
+ "url": "https://github.com/cheshireez/dsh-skill-hub/issues"
125
+ },
126
+ "homepage": "https://github.com/cheshireez/dsh-skill-hub"
127
+ }