dsh-skill-hub 0.3.26 → 0.3.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +1 -1
- package/README.md +11 -3
- package/README.zh.md +11 -3
- package/lib/client.js +310 -39
- package/lib/client.js.map +1 -1
- package/lib/index.js +790 -15
- package/lib/types/client/api.d.ts +8 -1
- package/lib/types/client/locales/common.d.ts +4 -0
- package/lib/types/client/locales/market.d.ts +10 -0
- package/lib/types/client/locales/sources.d.ts +3 -0
- package/lib/types/client/locales.d.ts +17 -0
- package/lib/types/client/panel/ForeignImportDialog.d.ts +15 -0
- package/lib/types/client/panel/PanelDialogs.d.ts +2 -0
- package/lib/types/client/panel/dialogs.d.ts +11 -1
- package/lib/types/client/panel/useSkillHub.d.ts +1 -0
- package/lib/types/protocol/api.d.ts +2 -0
- package/lib/types/protocol/catalog.d.ts +39 -1
- package/lib/types/protocol/foreign.d.ts +51 -0
- package/lib/types/protocol/sources.d.ts +13 -0
- package/lib/types/protocol.d.ts +3 -1
- package/lib/types/repo/install.d.ts +4 -0
- package/lib/types/routes/deps.d.ts +11 -0
- package/lib/types/routes/foreign.d.ts +10 -0
- package/lib/types/routes/helpers.d.ts +1 -1
- package/lib/types/skillfs/audit.d.ts +58 -0
- package/lib/types/skillfs/foreign.d.ts +51 -0
- package/lib/types/skillfs/hash.d.ts +24 -0
- package/lib/types/skillfs.d.ts +2 -0
- package/lib/types/store/store.d.ts +6 -0
- package/package.json +23 -21
- package/src/client/api.ts +14 -0
- package/src/client/locales/common.ts +8 -0
- package/src/client/locales/market.ts +20 -0
- package/src/client/locales/sources.ts +6 -0
- package/src/client/panel/ForeignImportDialog.tsx +130 -0
- package/src/client/panel/PanelDialogs.tsx +4 -1
- package/src/client/panel/SkillHubPanel.tsx +34 -4
- package/src/client/panel/SourceStatusBadge.tsx +23 -13
- package/src/client/panel/dialogs.tsx +14 -3
- package/src/client/panel/panel.module.css +6 -0
- package/src/client/panel/useSkillHub.ts +1 -1
- package/src/index.ts +2 -2
- package/src/protocol/api.ts +2 -0
- package/src/protocol/catalog.ts +56 -0
- package/src/protocol/foreign.ts +47 -0
- package/src/protocol/sources.ts +13 -0
- package/src/protocol.test.ts +59 -0
- package/src/protocol.ts +3 -0
- package/src/repo/install.ts +34 -0
- package/src/repo.test.ts +55 -0
- package/src/routes/catalog-data.ts +2 -0
- package/src/routes/deps.ts +15 -0
- package/src/routes/foreign.ts +127 -0
- package/src/routes/helpers.ts +1 -0
- package/src/routes/repo-import.ts +4 -1
- package/src/routes/sources.ts +53 -3
- package/src/routes.test.ts +162 -3
- package/src/routes.ts +2 -0
- package/src/skillfs/audit.test.ts +182 -0
- package/src/skillfs/audit.ts +229 -0
- package/src/skillfs/foreign.test.ts +121 -0
- package/src/skillfs/foreign.ts +198 -0
- package/src/skillfs/hash.test.ts +129 -0
- package/src/skillfs/hash.ts +112 -0
- package/src/skillfs/scan.ts +17 -0
- package/src/skillfs.test.ts +37 -0
- package/src/skillfs.ts +2 -0
- package/src/store/migrate.ts +7 -1
- package/src/store/store.ts +36 -3
- package/src/store.test.ts +24 -0
|
@@ -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 ConfigResponse, type CreateRequest, type CreateResponse, type DiagnosticFixResponse, type GroupsResponse, type MarketCheckResponse, type MarketSourceAddResponse, type MarketSourcesResponse, type MarketSourceVersionsResponse, type MarketStatsResponse, 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 TagSaveRequest, type ToggleBatchResponse, type UpdateCheckResponse } from '../protocol.ts';
|
|
5
|
+
import { type CatalogResponse, type ConfigResponse, type CreateRequest, type CreateResponse, type DiagnosticFixResponse, type ForeignImportResponse, type ForeignListResponse, type GroupsResponse, type MarketCheckResponse, type MarketSourceAddResponse, type MarketSourcesResponse, type MarketSourceVersionsResponse, type MarketStatsResponse, 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 TagSaveRequest, 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);
|
|
@@ -93,6 +93,13 @@ export declare class SkillHubApi {
|
|
|
93
93
|
reorderSourceGroups(orderedKeys: string[]): Promise<string[]>;
|
|
94
94
|
/** 来源列表 + 派生 origin 映射 + 集合组 + 回收站。 */
|
|
95
95
|
sources(): Promise<SourcesResponse>;
|
|
96
|
+
/** 列出其他 agent 用户级技能目录里的可收编技能(只读扫描)。 */
|
|
97
|
+
listForeignSkills(): Promise<ForeignListResponse>;
|
|
98
|
+
/** 收编所选外部技能到 ~/.dsh/skills。 */
|
|
99
|
+
importForeignSkills(items: Array<{
|
|
100
|
+
agent: string;
|
|
101
|
+
name: string;
|
|
102
|
+
}>): Promise<ForeignImportResponse>;
|
|
96
103
|
/** 检查指定(或全部)来源的上游更新。 */
|
|
97
104
|
checkSources(repo?: string): Promise<SourceCheckResponse>;
|
|
98
105
|
/** 同步一个来源的所选(或全部)技能到上游最新版本。 */
|
|
@@ -17,6 +17,10 @@ export declare const zhCommon: {
|
|
|
17
17
|
readonly 'legend.hint': "每个技能最多一个圆点;无圆点 = 不可被调用。";
|
|
18
18
|
readonly 'panel.disabled': "已禁用";
|
|
19
19
|
readonly 'panel.diagnostics': "发现诊断";
|
|
20
|
+
readonly 'panel.auditBadge': "审计";
|
|
21
|
+
readonly 'panel.announcement': "公告预算 {chars} 字符 ≈ {tokens} tokens · {skills} 个技能";
|
|
22
|
+
readonly 'panel.announcementOver': "超出建议上限 {budget} 字符,排在后面的技能可能不触发";
|
|
23
|
+
readonly 'panel.announcementHint': "技能目录的第一层(name + description)常驻在 system prompt 里。建议不超 {budget} 字符 —— 超出后客户端会截断尾部条目,表现为「装了却不触发」。";
|
|
20
24
|
readonly 'panel.empty': "没有匹配的技能。";
|
|
21
25
|
readonly 'panel.emptyAll': "本地还没有技能。点击「新建技能」创建第一个。";
|
|
22
26
|
readonly 'panel.loading': "读取中…";
|
|
@@ -79,5 +79,15 @@ export declare const zhMarket: {
|
|
|
79
79
|
readonly 'repo.root.skills': "skills";
|
|
80
80
|
readonly 'repo.root.designTemplates': "design-templates";
|
|
81
81
|
readonly 'repo.root.generic': "{root}";
|
|
82
|
+
readonly 'foreign.open': "从其他 Agent 导入";
|
|
83
|
+
readonly 'foreign.title': "从其他 Agent 导入";
|
|
84
|
+
readonly 'foreign.text': "读取 Claude Code / Codex / Cursor 等工具的用户级技能目录(只读),把所选技能复制到 ~/.dsh/skills。原目录不受影响。";
|
|
85
|
+
readonly 'foreign.empty': "没有在这些工具的技能目录里发现技能。";
|
|
86
|
+
readonly 'foreign.exists': "已存在";
|
|
87
|
+
readonly 'foreign.import': "导入所选({count})";
|
|
88
|
+
readonly 'foreign.importing': "导入中…";
|
|
89
|
+
readonly 'foreign.skipped': "{count} 个因同名已存在被跳过。";
|
|
90
|
+
readonly 'foreign.importedBanner': "已导入 {count} 个技能。";
|
|
91
|
+
readonly 'foreign.importedReplacedBanner': "已导入 {count} 个技能,另有 {replaced} 个同名占位被移入回收站(可恢复)。";
|
|
82
92
|
};
|
|
83
93
|
export declare const enMarket: Record<keyof typeof zhMarket, string>;
|
|
@@ -11,6 +11,9 @@ export declare const zhSources: {
|
|
|
11
11
|
readonly 'source.checking': "检查中…";
|
|
12
12
|
readonly 'source.upToDate': "已是最新";
|
|
13
13
|
readonly 'source.updated': "有更新 {count} 个";
|
|
14
|
+
readonly 'source.localModified': "本地改动 {count} 个";
|
|
15
|
+
readonly 'source.localModifiedBadge': "本地已改";
|
|
16
|
+
readonly 'source.localModifiedHint': "这些技能的磁盘内容与导入/同步时不一致(你自己改过)。同步会覆盖它们。";
|
|
14
17
|
readonly 'source.deleted': "上游已删除 {count} 个";
|
|
15
18
|
readonly 'source.error': "检查失败";
|
|
16
19
|
readonly 'source.throttled': "刚刚检查过";
|
|
@@ -63,6 +63,9 @@ export declare const zh: {
|
|
|
63
63
|
readonly 'source.checking': "检查中…";
|
|
64
64
|
readonly 'source.upToDate': "已是最新";
|
|
65
65
|
readonly 'source.updated': "有更新 {count} 个";
|
|
66
|
+
readonly 'source.localModified': "本地改动 {count} 个";
|
|
67
|
+
readonly 'source.localModifiedBadge': "本地已改";
|
|
68
|
+
readonly 'source.localModifiedHint': "这些技能的磁盘内容与导入/同步时不一致(你自己改过)。同步会覆盖它们。";
|
|
66
69
|
readonly 'source.deleted': "上游已删除 {count} 个";
|
|
67
70
|
readonly 'source.error': "检查失败";
|
|
68
71
|
readonly 'source.throttled': "刚刚检查过";
|
|
@@ -162,6 +165,16 @@ export declare const zh: {
|
|
|
162
165
|
readonly 'repo.root.skills': "skills";
|
|
163
166
|
readonly 'repo.root.designTemplates': "design-templates";
|
|
164
167
|
readonly 'repo.root.generic': "{root}";
|
|
168
|
+
readonly 'foreign.open': "从其他 Agent 导入";
|
|
169
|
+
readonly 'foreign.title': "从其他 Agent 导入";
|
|
170
|
+
readonly 'foreign.text': "读取 Claude Code / Codex / Cursor 等工具的用户级技能目录(只读),把所选技能复制到 ~/.dsh/skills。原目录不受影响。";
|
|
171
|
+
readonly 'foreign.empty': "没有在这些工具的技能目录里发现技能。";
|
|
172
|
+
readonly 'foreign.exists': "已存在";
|
|
173
|
+
readonly 'foreign.import': "导入所选({count})";
|
|
174
|
+
readonly 'foreign.importing': "导入中…";
|
|
175
|
+
readonly 'foreign.skipped': "{count} 个因同名已存在被跳过。";
|
|
176
|
+
readonly 'foreign.importedBanner': "已导入 {count} 个技能。";
|
|
177
|
+
readonly 'foreign.importedReplacedBanner': "已导入 {count} 个技能,另有 {replaced} 个同名占位被移入回收站(可恢复)。";
|
|
165
178
|
readonly 'edit.start': "编辑";
|
|
166
179
|
readonly 'edit.hint': "开启后显示删除与排序操作";
|
|
167
180
|
readonly 'edit.done': "完成";
|
|
@@ -238,6 +251,10 @@ export declare const zh: {
|
|
|
238
251
|
readonly 'legend.hint': "每个技能最多一个圆点;无圆点 = 不可被调用。";
|
|
239
252
|
readonly 'panel.disabled': "已禁用";
|
|
240
253
|
readonly 'panel.diagnostics': "发现诊断";
|
|
254
|
+
readonly 'panel.auditBadge': "审计";
|
|
255
|
+
readonly 'panel.announcement': "公告预算 {chars} 字符 ≈ {tokens} tokens · {skills} 个技能";
|
|
256
|
+
readonly 'panel.announcementOver': "超出建议上限 {budget} 字符,排在后面的技能可能不触发";
|
|
257
|
+
readonly 'panel.announcementHint': "技能目录的第一层(name + description)常驻在 system prompt 里。建议不超 {budget} 字符 —— 超出后客户端会截断尾部条目,表现为「装了却不触发」。";
|
|
241
258
|
readonly 'panel.empty': "没有匹配的技能。";
|
|
242
259
|
readonly 'panel.emptyAll': "本地还没有技能。点击「新建技能」创建第一个。";
|
|
243
260
|
readonly 'panel.loading': "读取中…";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从其他 agent 导入:列出 Claude Code / Codex / Cursor / Gemini CLI 等技能目录里的
|
|
3
|
+
* 技能,勾选后复制到本地可写根。
|
|
4
|
+
*
|
|
5
|
+
* 只读扫描 + 复制,不改动别家目录。已存在的同名技能不可选(导入会覆盖,
|
|
6
|
+
* 那是用户在别处没预期的事)。
|
|
7
|
+
*/
|
|
8
|
+
import type { SkillHubApi } from '../api.ts';
|
|
9
|
+
export interface ForeignImportDialogProps {
|
|
10
|
+
api: SkillHubApi;
|
|
11
|
+
onClose: () => void;
|
|
12
|
+
/** 导入成功后回调,由面板负责刷新目录与提示(replaced = 被移入回收站的同名占位)。 */
|
|
13
|
+
onImported: (imported: string[], replaced: number) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function ForeignImportDialog(props: ForeignImportDialogProps): React.JSX.Element;
|
|
@@ -18,6 +18,8 @@ export interface PanelDialogsProps {
|
|
|
18
18
|
confirmDialog: SkillHubState['confirmDialog'];
|
|
19
19
|
setConfirmDialog: SkillHubState['setConfirmDialog'];
|
|
20
20
|
runConfirmed: SkillHubState['runConfirmed'];
|
|
21
|
+
/** 来源检查结果(同步确认里标出本地改动过的技能)。 */
|
|
22
|
+
sourceCheck: SkillHubState['sourceCheck'];
|
|
21
23
|
/** 无 release 的市场源分支选择。 */
|
|
22
24
|
branchChoice: SkillHubState['branchChoice'];
|
|
23
25
|
branchBusy: SkillHubState['branchBusy'];
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* market sync selection). Every dialog closes on outside click and keeps
|
|
5
5
|
* the same role/aria shell; the panel owns all dialog state.
|
|
6
6
|
*/
|
|
7
|
-
import { type JSX } from 'react';
|
|
7
|
+
import { type JSX, type ReactNode } from 'react';
|
|
8
8
|
import type { CollectionGroup, SkillTag } from '../../protocol.ts';
|
|
9
9
|
/** A group-close conflict waiting for the user's decision. */
|
|
10
10
|
export interface ConflictDialogState {
|
|
@@ -44,12 +44,22 @@ export interface MarketSyncDialogState {
|
|
|
44
44
|
skills: string[];
|
|
45
45
|
selected: ReadonlySet<string>;
|
|
46
46
|
}
|
|
47
|
+
/** Overlay + centered alert-dialog shell; outside click or Escape cancels. */
|
|
48
|
+
export declare function DialogShell(props: {
|
|
49
|
+
onClose: () => void;
|
|
50
|
+
children: ReactNode;
|
|
51
|
+
}): JSX.Element;
|
|
47
52
|
/** Simple confirm dialog: title + text + optional skill list + cancel/confirm. */
|
|
48
53
|
export declare function ConfirmDialog(props: {
|
|
49
54
|
title: string;
|
|
50
55
|
text: string;
|
|
51
56
|
/** Optional skill names listed between the text and the buttons. */
|
|
52
57
|
items?: readonly string[];
|
|
58
|
+
/**
|
|
59
|
+
* 这些条目在磁盘上已被本地改过。列出来时逐个标记 —— 「会覆盖本地修改」
|
|
60
|
+
* 这句话只有在能指出是哪几个时才有用。
|
|
61
|
+
*/
|
|
62
|
+
marked?: readonly string[];
|
|
53
63
|
confirmLabel: string;
|
|
54
64
|
/** Danger-styled confirm button (destructive actions). */
|
|
55
65
|
danger?: boolean;
|
|
@@ -48,4 +48,6 @@ export declare const SKILL_HUB_API: {
|
|
|
48
48
|
readonly diagnosticFix: "/api/skill-hub/diagnostic/fix";
|
|
49
49
|
readonly marketSourceVersions: "/api/skill-hub/market/source/versions";
|
|
50
50
|
readonly marketStats: "/api/skill-hub/market/stats";
|
|
51
|
+
readonly foreign: "/api/skill-hub/foreign";
|
|
52
|
+
readonly foreignImport: "/api/skill-hub/foreign/import";
|
|
51
53
|
};
|
|
@@ -50,7 +50,43 @@ export interface DiagnosticEntry {
|
|
|
50
50
|
reason: string;
|
|
51
51
|
/** Whether this diagnostic can be auto-fixed (e.g. unquoted colon). */
|
|
52
52
|
fixable?: boolean;
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* 条目来源。缺省 = 发现诊断(provider 跳过了这个文件);`security` = 内容审计
|
|
55
|
+
* (文件能被加载,但正文里有值得看一眼的东西)。两者共用同一块 UI,但
|
|
56
|
+
* security 条目不能「修复」,只能说清问题在哪一行。
|
|
57
|
+
*/
|
|
58
|
+
kind?: 'security';
|
|
59
|
+
/** security 条目的严重度;发现诊断条目省略。 */
|
|
60
|
+
severity?: 'block' | 'warn';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 技能目录在 system prompt 里占用的公告预算。渐进加载的第一层只放
|
|
64
|
+
* name + description,但技能多了这一层本身就会顶到客户端的上限而被截断,
|
|
65
|
+
* 表现为「装了却不触发」—— 面板必须能看见这个数字。
|
|
66
|
+
*/
|
|
67
|
+
export interface AnnouncementBudget {
|
|
68
|
+
/** 参与公告的技能数(已启用且 modelInvocable)。 */
|
|
69
|
+
skills: number;
|
|
70
|
+
/** name + description + whenToUse 的总字符数。 */
|
|
71
|
+
chars: number;
|
|
72
|
+
/** 粗略 token 估算(按 3 字符/token)。 */
|
|
73
|
+
approxTokens: number;
|
|
74
|
+
/** 建议预算(字符),超出即 overBudget。 */
|
|
75
|
+
budget: number;
|
|
76
|
+
overBudget: boolean;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 技能目录的建议字符预算。取 8000:这是 Codex 在上下文窗口未知时对初始
|
|
80
|
+
* 技能清单的硬上限,也是目前公开客户端里最保守的一档。超出后尾部条目会被
|
|
81
|
+
* 静默丢弃(技能看起来装了,实际不触发)。
|
|
82
|
+
*/
|
|
83
|
+
export declare const ANNOUNCEMENT_CHAR_BUDGET = 8000;
|
|
84
|
+
/**
|
|
85
|
+
* 按「已启用且模型可调用」的技能估算公告成本。只统计一层渐进加载真正注入的
|
|
86
|
+
* 字段(name + description + whenToUse),不含正文 —— 正文是激活后才载入的,
|
|
87
|
+
* 不占常驻预算。
|
|
88
|
+
*/
|
|
89
|
+
export declare function computeAnnouncementBudget(skills: readonly CatalogSkill[]): AnnouncementBudget;
|
|
54
90
|
/** POST /api/skill-hub/diagnostic/fix — repair a fixable diagnostic in place. */
|
|
55
91
|
export interface DiagnosticFixRequest {
|
|
56
92
|
path: string;
|
|
@@ -72,6 +108,8 @@ export interface CatalogResponse {
|
|
|
72
108
|
disabled: DisabledSkill[];
|
|
73
109
|
/** Files in the writable roots the provider ignores, with reasons. */
|
|
74
110
|
diagnostics: DiagnosticEntry[];
|
|
111
|
+
/** 技能目录占用的公告预算(见 AnnouncementBudget)。 */
|
|
112
|
+
announcement: AnnouncementBudget;
|
|
75
113
|
/** Skill names that appeared in multiple roots (first wins, others hidden). Mirrors codex name_counts. */
|
|
76
114
|
duplicateNames?: string[];
|
|
77
115
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 agent 技能(收编)的线格式。
|
|
3
|
+
*
|
|
4
|
+
* 发现源是别的 agent 的用户级技能目录(Claude Code / Codex / Cursor / …),
|
|
5
|
+
* 它们一律只读:这里只描述「看到了什么」和「要收编哪一个」。
|
|
6
|
+
*/
|
|
7
|
+
/** 一个被扫描的外部技能根。 */
|
|
8
|
+
export interface ForeignRootView {
|
|
9
|
+
agent: string;
|
|
10
|
+
dir: string;
|
|
11
|
+
/** 该目录在本机是否存在(不存在时 skills 里不会出现它的条目)。 */
|
|
12
|
+
present: boolean;
|
|
13
|
+
}
|
|
14
|
+
/** 一条可收编的外部技能。 */
|
|
15
|
+
export interface ForeignSkillView {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
agent: string;
|
|
19
|
+
/** 发现文件绝对路径(只读展示)。 */
|
|
20
|
+
path: string;
|
|
21
|
+
kind: 'directory' | 'flat';
|
|
22
|
+
/** 目标根已有同名技能,不可导入。 */
|
|
23
|
+
existing: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** GET /api/skill-hub/foreign */
|
|
26
|
+
export interface ForeignListResponse {
|
|
27
|
+
ok: true;
|
|
28
|
+
roots: ForeignRootView[];
|
|
29
|
+
skills: ForeignSkillView[];
|
|
30
|
+
}
|
|
31
|
+
/** POST /api/skill-hub/foreign/import — 按 agent + 名字收编(不信任前端给的路径)。 */
|
|
32
|
+
export interface ForeignImportRequest {
|
|
33
|
+
items: Array<{
|
|
34
|
+
agent: string;
|
|
35
|
+
name: string;
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export interface ForeignImportResponse {
|
|
39
|
+
ok: true;
|
|
40
|
+
imported: string[];
|
|
41
|
+
/** 导入前被移入回收站的同名占位(已禁用的发现文件 / 空壳目录),可恢复。 */
|
|
42
|
+
replaced?: string[];
|
|
43
|
+
skipped: Array<{
|
|
44
|
+
name: string;
|
|
45
|
+
reason: string;
|
|
46
|
+
}>;
|
|
47
|
+
failed: Array<{
|
|
48
|
+
name: string;
|
|
49
|
+
error: string;
|
|
50
|
+
}>;
|
|
51
|
+
}
|
|
@@ -14,6 +14,12 @@ export interface SourceRecord {
|
|
|
14
14
|
skills: string[];
|
|
15
15
|
/** 仓库内路径 → 文件大小 的基线清单(差异检测用;缺失 = 无基线)。 */
|
|
16
16
|
manifest?: Record<string, number>;
|
|
17
|
+
/**
|
|
18
|
+
* 技能名 → 本地内容哈希(导入/同步落盘后立刻采集)。用于区分「上游变了」
|
|
19
|
+
* 与「本地被改过」:没有它,「全部更新」只能盲覆盖。缺失 = 该技能还没采集过
|
|
20
|
+
* (旧记录),比对时按「未知」处理,不误报为已修改。
|
|
21
|
+
*/
|
|
22
|
+
localHashes?: Record<string, string>;
|
|
17
23
|
}
|
|
18
24
|
/** 回收站条目:上游删除后跟进删除(移入 .trash)的技能。 */
|
|
19
25
|
export interface TrashEntry {
|
|
@@ -72,6 +78,11 @@ export interface SourceCheckResult {
|
|
|
72
78
|
throttled?: boolean;
|
|
73
79
|
/** 该来源此前没有快照(迁移/旧记录),本次已回填 commit,尚未有差异基线。 */
|
|
74
80
|
unverified?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* 本地磁盘内容与上次导入/同步时不一致的技能(用户自己改过)。只在检查
|
|
83
|
+
* 时本地能读到文件才判定;哈希未采集过(旧记录)的技能不出现在这里。
|
|
84
|
+
*/
|
|
85
|
+
localModified?: string[];
|
|
75
86
|
}
|
|
76
87
|
/** POST /api/skill-hub/sources/check */
|
|
77
88
|
export interface SourceCheckResponse {
|
|
@@ -92,6 +103,8 @@ export interface SourceSyncResponse {
|
|
|
92
103
|
commitSha: string;
|
|
93
104
|
/** 成功同步的技能。 */
|
|
94
105
|
synced: string[];
|
|
106
|
+
/** 本次同步覆盖掉的、本地有改动的技能(改前哈希 ≠ 记录哈希)。 */
|
|
107
|
+
overwritten?: string[];
|
|
95
108
|
/** 失败的技能。 */
|
|
96
109
|
failed: Array<{
|
|
97
110
|
name: string;
|
package/lib/types/protocol.d.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* 新代码可按需直引 `from './protocol/<domain>.ts'`。
|
|
9
9
|
*/
|
|
10
10
|
export { SKILL_HUB_API, SKILL_HUB_API_ROOT } from './protocol/api.ts';
|
|
11
|
-
export type { WritableRoot, HubInvocation, CatalogSkill, DisabledSkill, DiagnosticEntry, DiagnosticFixRequest, DiagnosticFixResponse, CatalogResponse, SkillDetail, SkillDetailResponse, SkillDeleteRequest, SkillDeleteResponse, ToggleRequest, ToggleResponse, ToggleBatchRequest, ToggleBatchResponse, CreateRequest, CreateResponse, } from './protocol/catalog.ts';
|
|
11
|
+
export type { WritableRoot, HubInvocation, CatalogSkill, DisabledSkill, DiagnosticEntry, AnnouncementBudget, DiagnosticFixRequest, DiagnosticFixResponse, CatalogResponse, SkillDetail, SkillDetailResponse, SkillDeleteRequest, SkillDeleteResponse, ToggleRequest, ToggleResponse, ToggleBatchRequest, ToggleBatchResponse, CreateRequest, CreateResponse, } from './protocol/catalog.ts';
|
|
12
|
+
export { ANNOUNCEMENT_CHAR_BUDGET, computeAnnouncementBudget } from './protocol/catalog.ts';
|
|
12
13
|
export type { SkillStat, StatsResponse, SkillStatsCheckpoint } from './protocol/stats.ts';
|
|
13
14
|
export type { ErrorResponse, HubConfig, HubSettingsValue, RedactedHubConfig, ConfigResponse, ConfigRequest } from './protocol/config.ts';
|
|
14
15
|
export { HUB_CONFIG_DEFAULTS, HUB_ENTRY_ID, HEX_COLOR_RE, GITHUB_TOKEN_RE, resolveHubConfig, redactGithubToken } from './protocol/config.ts';
|
|
@@ -17,3 +18,4 @@ export type { RepoSkillEntry, RepoRoot, RepoDiscoverResponse, RepoImportRequest,
|
|
|
17
18
|
export type { SkillTag, CollectionGroup, GroupsResponse, TagSaveRequest, TagSaveResponse, TagDeleteRequest, TagDeleteResponse, TagMembersRequest, TagMembersResponse, TagReorderRequest, TagReorderResponse, CollectionReorderRequest, CollectionReorderResponse, SourceGroupReorderRequest, SourceGroupReorderResponse, } from './protocol/groups.ts';
|
|
18
19
|
export type { SourceRecord, TrashEntry, SourcesResponse, SourceCheckRequest, SourceCheckResult, SourceCheckResponse, SourceSyncRequest, SourceSyncResponse, SourceDeleteRequest, SourceDeleteResponse, SourceRestoreRequest, SourceRestoreResponse, SourceTrashClearResponse, } from './protocol/sources.ts';
|
|
19
20
|
export { isProjectSource } from './protocol/sources.ts';
|
|
21
|
+
export type { ForeignRootView, ForeignSkillView, ForeignListResponse, ForeignImportRequest, ForeignImportResponse } from './protocol/foreign.ts';
|
|
@@ -16,6 +16,10 @@ export declare function downloadGitHubFile(repo: string, ref: string, path: stri
|
|
|
16
16
|
* Download a full skill directory into a temporary dir, validate SKILL.md,
|
|
17
17
|
* then atomically rename it into place. The target root is created when missing.
|
|
18
18
|
* Callers pass a target root and a skill entry plus its collected files.
|
|
19
|
+
*
|
|
20
|
+
* 写盘前有两道闸:体积/数量上限(预检用 tree 的 size,落盘前再用实际字节复核)
|
|
21
|
+
* 和内容审计(隐藏字符、危险命令、凭据路径)。审计的 `block` 级发现直接拒绝
|
|
22
|
+
* 导入 —— 一个能藏住指令的技能不该靠用户「看得仔细」来兜底。
|
|
19
23
|
*/
|
|
20
24
|
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<{
|
|
21
25
|
targetDir: string;
|
|
@@ -31,6 +31,12 @@ export interface SkillHubRouteDeps {
|
|
|
31
31
|
fence?: RouteFence;
|
|
32
32
|
/** DSH home override (tests isolate the writable roots; defaults to ~/.dsh). */
|
|
33
33
|
home?: string;
|
|
34
|
+
/**
|
|
35
|
+
* 用户主目录覆盖(其他 agent 的 `~/.claude/skills` 等相对它解析)。
|
|
36
|
+
* 与 `home` 是两个概念:`home` 是 DSH 自己的状态根(`~/.dsh`),
|
|
37
|
+
* 这个才是 `/Users/<user>`。测试用它把外部扫描圈在临时目录里。
|
|
38
|
+
*/
|
|
39
|
+
userHome?: string;
|
|
34
40
|
/** Invalidate the registry catalog cache after hub-driven mutations. */
|
|
35
41
|
invalidate?: () => void;
|
|
36
42
|
/** Optional invocation-count reader; absent means the stats route reports unavailable. */
|
|
@@ -55,6 +61,11 @@ export declare function savedOf(deps: SkillHubRouteDeps): Partial<HubConfig>;
|
|
|
55
61
|
export declare function disabledGate(deps: SkillHubRouteDeps, res: ServerResponse): boolean;
|
|
56
62
|
/** Resolve the home used for writable-root operations. */
|
|
57
63
|
export declare function homeOf(deps: SkillHubRouteDeps): string;
|
|
64
|
+
/**
|
|
65
|
+
* Resolve the user's home directory — the base every other agent's user-level
|
|
66
|
+
* skill folder hangs off. Distinct from `homeOf` (the DSH state root).
|
|
67
|
+
*/
|
|
68
|
+
export declare function userHomeOf(deps: SkillHubRouteDeps): string;
|
|
58
69
|
/** A skill resolved as a hub-writable user-level file. */
|
|
59
70
|
export interface WritableSkill {
|
|
60
71
|
skill: SkillDefinition;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 agent 技能域路由:发现(只读扫描别家目录)与收编(复制进本地可写根)。
|
|
3
|
+
*
|
|
4
|
+
* 两条约束:
|
|
5
|
+
* 1. 外部目录只读 —— 扫描不会 mkdir,也不会写任何东西到别家路径下;
|
|
6
|
+
* 2. 导入不信任前端给的路径:只接受 `agent + name`,服务端重新扫一遍定位,
|
|
7
|
+
* 否则一个拼出来的绝对路径就能把任意文件复制进技能目录。
|
|
8
|
+
*/
|
|
9
|
+
import { type RouteSpec, type SkillHubRouteDeps } from './helpers.ts';
|
|
10
|
+
export declare function foreignRoutes(deps: SkillHubRouteDeps): RouteSpec[];
|
|
@@ -10,7 +10,7 @@ import type { WebRoute, WebRouteKind } from '@deepseek-ai/dsh-host-webserver';
|
|
|
10
10
|
import { type SkillHubRouteDeps } from './deps.ts';
|
|
11
11
|
export { MAX_JSON_BODY_BYTES, STORE_ERROR_STATUS, isLoopbackRequest, loopbackOnlyFence, pathExists, queryParam, readJsonBody, readString, readStrings, writeError, writeJson, writeRouteError, } from './http.ts';
|
|
12
12
|
export type { RouteFence } from './http.ts';
|
|
13
|
-
export { configOf, disabledGate, homeOf, isWritableSource, resolveWritableSkill, savedOf, } from './deps.ts';
|
|
13
|
+
export { configOf, disabledGate, homeOf, isWritableSource, resolveWritableSkill, savedOf, userHomeOf, } from './deps.ts';
|
|
14
14
|
export type { SkillHubRouteDeps, SkillLookupLike, WritableSkill, WritableSkillRefusal, WritableSkillResult, } from './deps.ts';
|
|
15
15
|
export { buildCollections, buildGroups } from './collection.ts';
|
|
16
16
|
export { applyInterface, buildCatalog, knownSkillNames, toDetail, workspaceEntries, } from './catalog-data.ts';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 技能内容审计:安装前拦截与常驻体检共用的一套纯函数规则。
|
|
3
|
+
*
|
|
4
|
+
* 两条设计约束:
|
|
5
|
+
* 1. 零依赖、纯字符串处理。宿主每 5 秒轮询一次 /catalog,扫描必须廉价,
|
|
6
|
+
* 否则面板响应时间会随技能数量线性变慢(与 scanDiagnostics 同一理由)。
|
|
7
|
+
* 2. 规则只认结构特征(不可见字符、命令管道、凭据路径),不做语义判断。
|
|
8
|
+
* 语义/LLM 判断误报率高且要模型调用,不属于一个管理器的职责边界;
|
|
9
|
+
* 这里的目标是「让藏起来的东西显形」,不是「判定作者意图」。
|
|
10
|
+
*
|
|
11
|
+
* 两级严重度:
|
|
12
|
+
* - `block`:安装阶段拒绝落盘。只用于「没有任何正当用途」的特征 ——
|
|
13
|
+
* 裸 ESC 控制字符、Unicode 标签字符、双向控制符、以及体积越界。
|
|
14
|
+
* - `warn`:落盘但标记。零宽字符、危险命令、凭据路径、超长描述都可能
|
|
15
|
+
* 出现在正常技能里(比如一个教人用 curl 的技能),交给人判断。
|
|
16
|
+
*/
|
|
17
|
+
/** 规则 id:稳定,测试与文案都按它断言。 */
|
|
18
|
+
export type AuditRuleId = 'ansi-escape' | 'unicode-tag' | 'bidi-control' | 'zero-width' | 'dangerous-command' | 'credential-path' | 'file-too-large' | 'bundle-too-large' | 'too-many-files' | 'description-too-long';
|
|
19
|
+
export type AuditSeverity = 'block' | 'warn';
|
|
20
|
+
/** 一条审计发现。`file` 相对技能目录,便于在面板上直接定位。 */
|
|
21
|
+
export interface AuditFinding {
|
|
22
|
+
rule: AuditRuleId;
|
|
23
|
+
severity: AuditSeverity;
|
|
24
|
+
file: string;
|
|
25
|
+
/** 1 起行号;整文件级规则(体积、文件数)省略。 */
|
|
26
|
+
line?: number;
|
|
27
|
+
message: string;
|
|
28
|
+
}
|
|
29
|
+
/** 单文件字节上限:技能是纯文本加少量脚本,2 MiB 已远超正常体积。 */
|
|
30
|
+
export declare const MAX_SKILL_FILE_BYTES: number;
|
|
31
|
+
/** 单个技能总字节上限。 */
|
|
32
|
+
export declare const MAX_SKILL_BUNDLE_BYTES: number;
|
|
33
|
+
/** 单个技能文件数上限。 */
|
|
34
|
+
export declare const MAX_SKILL_FILES = 500;
|
|
35
|
+
/** agentskills.io 规范对 description 的硬上限。 */
|
|
36
|
+
export declare const MAX_DESCRIPTION_CHARS = 1024;
|
|
37
|
+
/**
|
|
38
|
+
* 审计一段技能文本(SKILL.md 正文、脚本、参考文档都适用)。
|
|
39
|
+
* 纯函数:同样的输入永远给同样的结果,便于单测与「改动前后对比」。
|
|
40
|
+
*/
|
|
41
|
+
export declare function auditSkillText(relativePath: string, text: string): AuditFinding[];
|
|
42
|
+
/** description 超出规范上限:部分客户端会截断,技能因此静默失去自动激活能力。 */
|
|
43
|
+
export declare function auditDescription(file: string, description: string): AuditFinding[];
|
|
44
|
+
/** 一个待落盘/待检查的技能文件。`text` 只在能当作文本读时提供。 */
|
|
45
|
+
export interface AuditBundleFile {
|
|
46
|
+
/** 相对技能目录的路径。 */
|
|
47
|
+
path: string;
|
|
48
|
+
size: number;
|
|
49
|
+
/** 文本内容;二进制或未下载时省略。 */
|
|
50
|
+
text?: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 审计一个完整技能目录:体积/数量上限 + 每个文本文件的规则扫描。
|
|
54
|
+
* `block` 级发现(体积越界)意味着不该落盘;文本级 `block` 由调用方决定。
|
|
55
|
+
*/
|
|
56
|
+
export declare function auditSkillBundle(files: readonly AuditBundleFile[], description?: string): AuditFinding[];
|
|
57
|
+
/** 阻断性发现的单行摘要(导入失败时的错误文本)。 */
|
|
58
|
+
export declare function blockingReason(findings: readonly AuditFinding[]): string | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 agent 技能的只读发现与收编。
|
|
3
|
+
*
|
|
4
|
+
* SKILL.md 已经是跨 agent 的事实标准(Claude Code / Codex / Cursor / Gemini CLI
|
|
5
|
+
* 等 45+ 产品读同一份文件),但**发现路径各家不同**。于是「我在 Claude Code 里
|
|
6
|
+
* 调好的技能」在 dsh 里看不见,用户只能手工复制目录。
|
|
7
|
+
*
|
|
8
|
+
* 这里只做两件事:读(列出别处有什么)和复制(把选中的收编到本地可写根)。
|
|
9
|
+
* 外部目录一律**只读**,绝不改写 —— 那是别的 agent 的资产。
|
|
10
|
+
*/
|
|
11
|
+
/** 一个外部 agent 的技能根。`agent` 是展示用的稳定标识。 */
|
|
12
|
+
export interface ForeignRoot {
|
|
13
|
+
agent: string;
|
|
14
|
+
/** 绝对路径;不存在时扫描直接返回空。 */
|
|
15
|
+
dir: string;
|
|
16
|
+
}
|
|
17
|
+
/** 一条可收编的外部技能。 */
|
|
18
|
+
export interface ForeignSkill {
|
|
19
|
+
/** 技能名(目录名或扁平文件名,与 frontmatter 一致时才算有效)。 */
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
/** 来源 agent 标识。 */
|
|
23
|
+
agent: string;
|
|
24
|
+
/** 发现文件绝对路径。 */
|
|
25
|
+
path: string;
|
|
26
|
+
/** 资源基目录(目录包本身,或扁平文件的父目录)。 */
|
|
27
|
+
directory: string;
|
|
28
|
+
kind: 'directory' | 'flat';
|
|
29
|
+
/** 目标根已有同名技能(不可导入)。 */
|
|
30
|
+
existing: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 常见 agent 的用户级技能目录。`~/.agents/skills` 不在此列 —— 那是 hub 自己的
|
|
34
|
+
* 可写根(user-agents),重复列会让同一个技能出现两次。
|
|
35
|
+
* 路径取自 vercel-labs/skills 的 agent 注册表,只保留桌面端常见的几个。
|
|
36
|
+
*/
|
|
37
|
+
export declare function foreignRoots(userHome: string): ForeignRoot[];
|
|
38
|
+
/**
|
|
39
|
+
* 扫描所有外部根。解析不出 frontmatter 的条目直接跳过 —— 它们在任何 agent 里
|
|
40
|
+
* 都不会生效,列出来只会让人以为「导入就好了」。
|
|
41
|
+
*/
|
|
42
|
+
export declare function scanForeignSkills(userHome: string, existing?: ReadonlySet<string>): Promise<ForeignSkill[]>;
|
|
43
|
+
/**
|
|
44
|
+
* 把一个外部技能收编到 `targetRoot`(通常是 `~/.dsh/skills`)。
|
|
45
|
+
*
|
|
46
|
+
* 与市场导入同一套闸:体积/数量上限 + 内容审计,命中阻断级直接拒绝;
|
|
47
|
+
* 落盘仍是「临时目录 → 校验 → 原子 rename」,失败不留半成品。
|
|
48
|
+
* 扁平技能统一落成目录包 —— hub 的所有写入路径都建目录包,两种形态并存会
|
|
49
|
+
* 触发同名不可达(issue #16 那一类)。
|
|
50
|
+
*/
|
|
51
|
+
export declare function importForeignSkill(skill: ForeignSkill, targetRoot: string): Promise<string>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 技能内容哈希:给「本地这份技能到底有没有被改过」一个确定答案。
|
|
3
|
+
*
|
|
4
|
+
* 现有来源记录只存上游的 commit 与按路径的字节数基线,能回答「上游变没变」,
|
|
5
|
+
* 回答不了「磁盘上这份还是不是我当初导入的那份」。于是「一键全更」只能盲覆盖,
|
|
6
|
+
* 用户唯一的保护是弹窗里那句他自己也判断不了的「会覆盖本地修改」。
|
|
7
|
+
*
|
|
8
|
+
* 哈希的粒度是**整个技能**(目录包 = 目录下全部文件;扁平技能 = 那一个文件),
|
|
9
|
+
* 而不是单文件:用户改的是「这个技能」,不是「这个技能里的第 3 个文件」。
|
|
10
|
+
*
|
|
11
|
+
* 边界(与体积审计同一套取舍):技能是纯文本加少量脚本,正常体积在几十 KB。
|
|
12
|
+
* 走查上限存在的意义是「一个恶意或事故性的巨大目录不能把 /catalog 拖死」,
|
|
13
|
+
* 不是为了支持大包。
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 一个技能目录的内容哈希。目录不可读时返回 undefined(调用方据此跳过比对,
|
|
17
|
+
* 而不是把它当成「改过了」—— 读不到不等于变了)。
|
|
18
|
+
*/
|
|
19
|
+
export declare function hashSkillDirectory(dir: string): Promise<string | undefined>;
|
|
20
|
+
/**
|
|
21
|
+
* 从一个发现文件推断技能形态并算哈希:`SKILL.md`(含 `.disabled` 变体)是
|
|
22
|
+
* 目录包,其余 `<name>.md` 是扁平技能。两种形态共用同一个返回契约。
|
|
23
|
+
*/
|
|
24
|
+
export declare function hashSkillAt(discoveryPath: string): Promise<string | undefined>;
|
package/lib/types/skillfs.d.ts
CHANGED
|
@@ -15,6 +15,8 @@ import type { TrashEntry, WritableRoot } from './protocol.ts';
|
|
|
15
15
|
export * from './skillfs/frontmatter.ts';
|
|
16
16
|
export * from './skillfs/paths.ts';
|
|
17
17
|
export * from './skillfs/scan.ts';
|
|
18
|
+
export * from './skillfs/hash.ts';
|
|
19
|
+
export * from './skillfs/foreign.ts';
|
|
18
20
|
/**
|
|
19
21
|
* Scaffold a directory-bundle skill: <root>/<name>/SKILL.md with a frontmatter
|
|
20
22
|
* template. Refuses non-kebab-case names.
|
|
@@ -101,6 +101,12 @@ export declare class SkillHubStore {
|
|
|
101
101
|
removeSkillFromSources(name: string): Promise<void>;
|
|
102
102
|
/** Update a source's commit snapshot. */
|
|
103
103
|
setSourceCommit(repo: string, commitSha: string): Promise<void>;
|
|
104
|
+
/**
|
|
105
|
+
* Record one skill's local content hash (sampled right after the bytes land).
|
|
106
|
+
* This is the only thing that can tell "the user edited this" apart from
|
|
107
|
+
* "upstream moved", so it is written on every import and every sync.
|
|
108
|
+
*/
|
|
109
|
+
setSourceSkillHash(repo: string, skillName: string, hash: string): Promise<void>;
|
|
104
110
|
/** Update a source's pinned ref (release tag / branch) when the market syncs. */
|
|
105
111
|
setSourceRef(repo: string, ref: string): Promise<void>;
|
|
106
112
|
/**
|