dsh-skill-hub 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -50
- package/README.zh.md +38 -48
- package/lib/client.js +2209 -1820
- package/lib/client.js.map +1 -1
- package/lib/index.js +653 -419
- package/lib/types/client/SkillHubSettingsCard.d.ts +1 -0
- package/lib/types/client/api.d.ts +12 -6
- package/lib/types/client/helpers.d.ts +0 -1
- package/lib/types/client/locales/common.d.ts +52 -0
- package/lib/types/client/locales/detail.d.ts +16 -0
- package/lib/types/client/locales/market.d.ts +73 -0
- package/lib/types/client/locales/settings.d.ts +41 -0
- package/lib/types/client/locales/skills.d.ts +65 -0
- package/lib/types/client/locales/sources.d.ts +35 -0
- package/lib/types/client/locales.d.ts +124 -118
- package/lib/types/client/panel/MarketSourceRow.d.ts +12 -0
- package/lib/types/client/panel/MarketView.d.ts +1 -0
- package/lib/types/client/panel/RepoScanCard.d.ts +10 -0
- package/lib/types/client/panel/hooks/shared.d.ts +72 -0
- package/lib/types/client/panel/hooks/useCatalogFlow.d.ts +62 -0
- package/lib/types/client/panel/hooks/useGroupFlow.d.ts +41 -0
- package/lib/types/client/panel/hooks/useMarketFlow.d.ts +73 -0
- package/lib/types/client/panel/hooks/useMetaFlow.d.ts +18 -0
- package/lib/types/client/panel/hooks/useSourceFlow.d.ts +44 -0
- package/lib/types/client/panel/market-format.d.ts +6 -0
- package/lib/types/client/panel/useSkillHub.d.ts +45 -73
- package/lib/types/client/settings-card.d.ts +20 -0
- package/lib/types/client/settings-form.d.ts +6 -0
- package/lib/types/protocol/api.d.ts +38 -0
- package/lib/types/protocol/catalog.d.ts +155 -0
- package/lib/types/protocol/config.d.ts +113 -0
- package/lib/types/protocol/groups.d.ts +95 -0
- package/lib/types/protocol/market.d.ts +94 -0
- package/lib/types/protocol/repo.d.ts +107 -0
- package/lib/types/protocol/sources.d.ts +139 -0
- package/lib/types/protocol/stats.d.ts +46 -0
- package/lib/types/protocol.d.ts +13 -743
- package/lib/types/repo/github-client.d.ts +42 -0
- package/lib/types/repo.d.ts +6 -17
- package/lib/types/routes/catalog.d.ts +7 -0
- package/lib/types/routes/config.d.ts +7 -0
- package/lib/types/routes/groups.d.ts +7 -0
- package/lib/types/routes/helpers.d.ts +145 -0
- package/lib/types/routes/market.d.ts +8 -0
- package/lib/types/routes/repo-import.d.ts +7 -0
- package/lib/types/routes/route-state.d.ts +55 -0
- package/lib/types/routes/sources.d.ts +8 -0
- package/lib/types/routes.d.ts +5 -34
- package/lib/types/stats.d.ts +3 -2
- package/lib/types/store/errors.d.ts +10 -0
- package/lib/types/store/migrate.d.ts +44 -0
- package/lib/types/store/paths.d.ts +32 -0
- package/lib/types/store/store.d.ts +133 -0
- package/lib/types/store.d.ts +7 -146
- package/package.json +28 -27
- package/src/client/SkillHubSettingsCard.tsx +17 -3
- package/src/client/api.ts +101 -195
- package/src/client/helpers.ts +10 -1
- package/src/client/locales/common.ts +102 -0
- package/src/client/locales/detail.ts +30 -0
- package/src/client/locales/market.ts +144 -0
- package/src/client/locales/settings.ts +80 -0
- package/src/client/locales/skills.ts +128 -0
- package/src/client/locales/sources.ts +68 -0
- package/src/client/locales.ts +26 -512
- package/src/client/panel/MarketSourceRow.tsx +108 -0
- package/src/client/panel/MarketView.tsx +12 -253
- package/src/client/panel/RepoScanCard.tsx +171 -0
- package/src/client/panel/hooks/shared.ts +81 -0
- package/src/client/panel/hooks/useCatalogFlow.ts +211 -0
- package/src/client/panel/hooks/useGroupFlow.ts +240 -0
- package/src/client/panel/hooks/useMarketFlow.ts +421 -0
- package/src/client/panel/hooks/useMetaFlow.ts +58 -0
- package/src/client/panel/hooks/useSourceFlow.ts +193 -0
- package/src/client/panel/market-format.ts +22 -0
- package/src/client/panel/useSkillHub.ts +141 -1032
- package/src/client/settings-card.tsx +51 -0
- package/src/client/settings-form.ts +18 -0
- package/src/index.ts +14 -4
- package/src/protocol/api.ts +38 -0
- package/src/protocol/catalog.ts +168 -0
- package/src/protocol/config.ts +145 -0
- package/src/protocol/groups.ts +106 -0
- package/src/protocol/market.ts +100 -0
- package/src/protocol/repo.ts +104 -0
- package/src/protocol/sources.ts +146 -0
- package/src/protocol/stats.ts +42 -0
- package/src/protocol.ts +83 -809
- package/src/repo/github-client.ts +131 -0
- package/src/repo.test.ts +24 -1
- package/src/repo.ts +50 -170
- package/src/routes/catalog.ts +309 -0
- package/src/routes/config.ts +119 -0
- package/src/routes/groups.ts +133 -0
- package/src/routes/helpers.ts +489 -0
- package/src/routes/market.ts +255 -0
- package/src/routes/repo-import.ts +267 -0
- package/src/routes/route-state.ts +89 -0
- package/src/routes/sources.ts +318 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +21 -1699
- package/src/stats.test.ts +58 -3
- package/src/stats.ts +45 -16
- package/src/store/errors.ts +14 -0
- package/src/store/migrate.ts +272 -0
- package/src/store/paths.ts +43 -0
- package/src/store/store.ts +532 -0
- package/src/store.test.ts +18 -0
- package/src/store.ts +7 -737
|
@@ -197,6 +197,57 @@ export function ColorField(props: ColorFieldProps): ReactElement {
|
|
|
197
197
|
)
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
/**
|
|
201
|
+
* One staged secret field: a password input that never echoes the stored
|
|
202
|
+
* value. Shows a "set" state via the overridden badge; Reset unsets it.
|
|
203
|
+
*/
|
|
204
|
+
export interface SecretFieldProps {
|
|
205
|
+
id: string
|
|
206
|
+
label: string
|
|
207
|
+
hint: string
|
|
208
|
+
/** Placeholder shown while empty (never the stored token). */
|
|
209
|
+
placeholder: string
|
|
210
|
+
overriddenLabel: string
|
|
211
|
+
resetLabel: string
|
|
212
|
+
disabled: boolean
|
|
213
|
+
text: string
|
|
214
|
+
overridden: boolean
|
|
215
|
+
invalid?: boolean
|
|
216
|
+
onEdit: (text: string) => void
|
|
217
|
+
onReset: () => void
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function SecretField(props: SecretFieldProps): ReactElement {
|
|
221
|
+
return (
|
|
222
|
+
<div className={css.field}>
|
|
223
|
+
<div className={css.head}>
|
|
224
|
+
<label className={css.label} htmlFor={props.id}>{props.label}</label>
|
|
225
|
+
{props.overridden ? (
|
|
226
|
+
<span className={css.badges}>
|
|
227
|
+
<span className={css.badge}>{props.overriddenLabel}</span>
|
|
228
|
+
<button type='button' className={css.reset} disabled={props.disabled} onClick={props.onReset}>
|
|
229
|
+
{props.resetLabel}
|
|
230
|
+
</button>
|
|
231
|
+
</span>
|
|
232
|
+
) : null}
|
|
233
|
+
</div>
|
|
234
|
+
<input
|
|
235
|
+
id={props.id}
|
|
236
|
+
type='password'
|
|
237
|
+
autoComplete='off'
|
|
238
|
+
spellCheck={false}
|
|
239
|
+
className={css.input}
|
|
240
|
+
value={props.text}
|
|
241
|
+
disabled={props.disabled}
|
|
242
|
+
placeholder={props.placeholder}
|
|
243
|
+
aria-invalid={props.invalid === true}
|
|
244
|
+
onChange={(event) => { props.onEdit(event.target.value) }}
|
|
245
|
+
/>
|
|
246
|
+
<p className={css.hint}>{props.hint}</p>
|
|
247
|
+
</div>
|
|
248
|
+
)
|
|
249
|
+
}
|
|
250
|
+
|
|
200
251
|
/** One staged numeric field: a numeric draft text input with inherit/reset semantics. */
|
|
201
252
|
export interface NumberFieldProps {
|
|
202
253
|
id: string
|
|
@@ -50,6 +50,24 @@ export function colorField(field: string): FieldSpec {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* A secret field (API token): the stored value is never echoed back into the
|
|
55
|
+
* draft (format always yields ''), so typing is always a fresh set and Reset
|
|
56
|
+
* always unsets. An empty draft stages nothing.
|
|
57
|
+
*/
|
|
58
|
+
export function secretField(field: string): FieldSpec {
|
|
59
|
+
return {
|
|
60
|
+
field,
|
|
61
|
+
format: () => '',
|
|
62
|
+
parse: (text) => {
|
|
63
|
+
const trimmed = text.trim()
|
|
64
|
+
if (trimmed === '') return undefined
|
|
65
|
+
if (!/^[A-Za-z0-9_-]{8,255}$/.test(trimmed)) return undefined
|
|
66
|
+
return { kind: 'set', value: trimmed }
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
53
71
|
/**
|
|
54
72
|
* A numeric field with range clamping. Integer by default: a fractional draft
|
|
55
73
|
* is truncated so "5.5 分钟" cannot sneak past an integer-only schema.
|
package/src/index.ts
CHANGED
|
@@ -21,7 +21,7 @@ import { SkillHubProvider } from './provider.ts'
|
|
|
21
21
|
import { makeRoutes } from './routes.ts'
|
|
22
22
|
import { createSkillStatsReader, type SkillStatsReader } from './stats.ts'
|
|
23
23
|
import { SkillHubStore } from './store.ts'
|
|
24
|
-
import { cleanupLeftoverImportDirs } from './repo.ts'
|
|
24
|
+
import { cleanupLeftoverImportDirs, setGithubToken } from './repo.ts'
|
|
25
25
|
import { dshHome } from './store.ts'
|
|
26
26
|
import { join } from 'node:path'
|
|
27
27
|
import { homedir } from 'node:os'
|
|
@@ -78,6 +78,7 @@ export const HubSettingsSchema: z<HubSettingsValue> = z.object({
|
|
|
78
78
|
showGroupSummary: z.boolean().default(HUB_CONFIG_DEFAULTS.showGroupSummary),
|
|
79
79
|
dotModelColor: z.string().pattern(HEX_COLOR_RE),
|
|
80
80
|
dotUserColor: z.string().pattern(HEX_COLOR_RE),
|
|
81
|
+
githubToken: z.string(),
|
|
81
82
|
statsWindowDays: z.number().min(0).max(3650).default(HUB_CONFIG_DEFAULTS.statsWindowDays),
|
|
82
83
|
statsScanMinutes: z.number().min(1).max(1440).default(HUB_CONFIG_DEFAULTS.statsScanMinutes),
|
|
83
84
|
})
|
|
@@ -162,6 +163,10 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
162
163
|
disposeProvider = undefined
|
|
163
164
|
}
|
|
164
165
|
const value = current()
|
|
166
|
+
// GitHub auth for market/source API calls: env GITHUB_TOKEN/GH_TOKEN is the
|
|
167
|
+
// fallback (read at module load); an explicit settings value wins live and
|
|
168
|
+
// applies without a restart via the settings watcher below.
|
|
169
|
+
setGithubToken(value.githubToken)
|
|
165
170
|
if (value.announceToAgent) {
|
|
166
171
|
disposeSection = ctx.systemPrompt.section({
|
|
167
172
|
name: 'plugin:dsh-skill-hub',
|
|
@@ -249,8 +254,8 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
249
254
|
// runs).
|
|
250
255
|
ctx.inject(['sessionQuery'], (sctx) => {
|
|
251
256
|
// 恢复 sidecar 里的增量扫描检查点后再建 reader(异步、不阻塞注入回调):
|
|
252
|
-
//
|
|
253
|
-
//
|
|
257
|
+
// 重启后无需重新解压全部历史日志;每次扫描完都落盘(含上次总数),所以
|
|
258
|
+
// 重启后面板秒出旧数、后台重扫。扫描间隔与滚动窗口都从设置命名空间
|
|
254
259
|
// 实时读取——卡片里改完即生效,无需重启。
|
|
255
260
|
void (async () => {
|
|
256
261
|
const saved = await store.getSkillStatsState().catch(() => undefined)
|
|
@@ -262,7 +267,7 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
262
267
|
const value = current().statsWindowDays
|
|
263
268
|
return typeof value === 'number' && value >= 0 ? Math.floor(value) : HUB_CONFIG_DEFAULTS.statsWindowDays
|
|
264
269
|
}
|
|
265
|
-
|
|
270
|
+
const reader = createSkillStatsReader(sctx.sessionQuery, () => scanMinutes() * 60_000, {
|
|
266
271
|
checkpoint: saved,
|
|
267
272
|
windowDays,
|
|
268
273
|
onCheckpoint: (next) => {
|
|
@@ -271,7 +276,12 @@ export function apply(ctx: Context, config?: Config): void {
|
|
|
271
276
|
})
|
|
272
277
|
},
|
|
273
278
|
})
|
|
279
|
+
stats = reader
|
|
274
280
|
sync()
|
|
281
|
+
// 冷启动预热:检查点无总数时,全量扫描(数十 MB 会话日志)在启动时就
|
|
282
|
+
// 开始,而不是等面板第一次打开才起跑;有总数时这次调用命中内存缓存,
|
|
283
|
+
// 开销可忽略。失败由 reader 内部吞掉(保留旧总数),不影响启动。
|
|
284
|
+
void reader()
|
|
275
285
|
})()
|
|
276
286
|
})
|
|
277
287
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Browser-facing base paths of the skill-hub API family. */
|
|
2
|
+
export const SKILL_HUB_API = {
|
|
3
|
+
catalog: '/api/skill-hub/catalog',
|
|
4
|
+
skill: '/api/skill-hub/skill',
|
|
5
|
+
skillDelete: '/api/skill-hub/skill/delete',
|
|
6
|
+
toggle: '/api/skill-hub/toggle',
|
|
7
|
+
toggleBatch: '/api/skill-hub/toggle-batch',
|
|
8
|
+
create: '/api/skill-hub/create',
|
|
9
|
+
stats: '/api/skill-hub/stats',
|
|
10
|
+
config: '/api/skill-hub/config',
|
|
11
|
+
market: '/api/skill-hub/market',
|
|
12
|
+
marketSource: '/api/skill-hub/market/source',
|
|
13
|
+
marketSourceDelete: '/api/skill-hub/market/source/delete',
|
|
14
|
+
marketSourceRef: '/api/skill-hub/market/source/ref',
|
|
15
|
+
marketCheck: '/api/skill-hub/market/check',
|
|
16
|
+
marketSync: '/api/skill-hub/market/source/sync',
|
|
17
|
+
repo: '/api/skill-hub/repo',
|
|
18
|
+
repoImport: '/api/skill-hub/repo/import',
|
|
19
|
+
repoImportProgress: '/api/skill-hub/repo/import/progress',
|
|
20
|
+
repoImportCancel: '/api/skill-hub/repo/import/cancel',
|
|
21
|
+
update: '/api/skill-hub/update',
|
|
22
|
+
groups: '/api/skill-hub/groups',
|
|
23
|
+
tag: '/api/skill-hub/tag',
|
|
24
|
+
tagDelete: '/api/skill-hub/tag/delete',
|
|
25
|
+
tagMembers: '/api/skill-hub/tag/members',
|
|
26
|
+
tagReorder: '/api/skill-hub/tag/reorder',
|
|
27
|
+
collectionReorder: '/api/skill-hub/collections/reorder',
|
|
28
|
+
sourceGroupReorder: '/api/skill-hub/source-groups/reorder',
|
|
29
|
+
sources: '/api/skill-hub/sources',
|
|
30
|
+
sourceCheck: '/api/skill-hub/sources/check',
|
|
31
|
+
sourceSync: '/api/skill-hub/sources/sync',
|
|
32
|
+
sourceDelete: '/api/skill-hub/sources/delete',
|
|
33
|
+
sourceRestore: '/api/skill-hub/sources/restore',
|
|
34
|
+
sourceTrashClear: '/api/skill-hub/sources/trash/clear',
|
|
35
|
+
diagnosticFix: '/api/skill-hub/diagnostic/fix',
|
|
36
|
+
marketSourceVersions: '/api/skill-hub/market/source/versions',
|
|
37
|
+
marketStats: '/api/skill-hub/market/stats',
|
|
38
|
+
} as const
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/** User-level roots the hub may write to (matches dsh-skill-filesystem ranks 400/500). */
|
|
2
|
+
export type WritableRoot = 'user-dsh' | 'user-agents'
|
|
3
|
+
|
|
4
|
+
/** Invocation policy resolved by the registry, re-spelled for the wire. */
|
|
5
|
+
export interface HubInvocation {
|
|
6
|
+
/** Whether model-facing catalogs may load this skill. */
|
|
7
|
+
modelInvocable: boolean
|
|
8
|
+
/** Whether human-facing command catalogs may load this skill. */
|
|
9
|
+
userInvocable: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** One enabled skill row in the catalog. */
|
|
13
|
+
export interface CatalogSkill {
|
|
14
|
+
name: string
|
|
15
|
+
description: string
|
|
16
|
+
whenToUse?: string
|
|
17
|
+
invocation: HubInvocation
|
|
18
|
+
provider: string
|
|
19
|
+
/** Whether the hub may toggle this skill (user-level filesystem skills only). */
|
|
20
|
+
writable: boolean
|
|
21
|
+
/** 技能来源标识(user-dsh/user-agents/project-dsh/project-agents/...)。 */
|
|
22
|
+
source: string
|
|
23
|
+
/** SKILL.md creation time (epoch ms); used for "added" sorting. Absent when unknown. */
|
|
24
|
+
addedAt?: number
|
|
25
|
+
/** SKILL.md last-modified time (epoch ms); used for "updated" display. Absent when unknown. */
|
|
26
|
+
updatedAt?: number
|
|
27
|
+
/** 项目技能的所属工作区路径(仅 project-dsh/project-agents 来源携带)。 */
|
|
28
|
+
workspace?: string
|
|
29
|
+
/** 工作区显示标题(来自 workspace.json;无则回退为路径)。 */
|
|
30
|
+
workspaceTitle?: string
|
|
31
|
+
/** UI metadata from agents/openai.yaml (mirrors codex SkillInterface). */
|
|
32
|
+
displayName?: string
|
|
33
|
+
shortDescription?: string
|
|
34
|
+
brandColor?: string
|
|
35
|
+
iconSmall?: string
|
|
36
|
+
iconLarge?: string
|
|
37
|
+
defaultPrompt?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** One disabled skill tracked by the hub sidecar (SKILL.md renamed away). */
|
|
41
|
+
export interface DisabledSkill {
|
|
42
|
+
name: string
|
|
43
|
+
description: string
|
|
44
|
+
/** Absolute path of the renamed file (SKILL.md.disabled / <name>.md.disabled). */
|
|
45
|
+
path: string
|
|
46
|
+
root: WritableRoot
|
|
47
|
+
disabledAt: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** One discovery diagnostic: a file the filesystem provider skips, with the reason. */
|
|
51
|
+
export interface DiagnosticEntry {
|
|
52
|
+
path: string
|
|
53
|
+
root: string
|
|
54
|
+
reason: string
|
|
55
|
+
/** Whether this diagnostic can be auto-fixed (e.g. unquoted colon). */
|
|
56
|
+
fixable?: boolean
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** POST /api/skill-hub/diagnostic/fix — repair a fixable diagnostic in place. */
|
|
60
|
+
export interface DiagnosticFixRequest {
|
|
61
|
+
path: string
|
|
62
|
+
}
|
|
63
|
+
export interface DiagnosticFixResponse {
|
|
64
|
+
ok: true
|
|
65
|
+
path: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** GET /api/skill-hub/catalog */
|
|
69
|
+
export interface CatalogResponse {
|
|
70
|
+
ok: true
|
|
71
|
+
/** 已安装插件自身的版本号(package.json version),面板标题旁显示。 */
|
|
72
|
+
pluginVersion: string
|
|
73
|
+
/** Whether discovery completed within a stable catalog revision. */
|
|
74
|
+
complete: boolean
|
|
75
|
+
/** Sorted winning summaries of every enabled skill (all roots + providers). */
|
|
76
|
+
skills: CatalogSkill[]
|
|
77
|
+
/** Skills the hub has toggled off (kept outside provider discovery). */
|
|
78
|
+
disabled: DisabledSkill[]
|
|
79
|
+
/** Files in the writable roots the provider ignores, with reasons. */
|
|
80
|
+
diagnostics: DiagnosticEntry[]
|
|
81
|
+
/** Skill names that appeared in multiple roots (first wins, others hidden). Mirrors codex name_counts. */
|
|
82
|
+
duplicateNames?: string[]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** GET /api/skill-hub/skill */
|
|
86
|
+
export interface SkillDetail {
|
|
87
|
+
name: string
|
|
88
|
+
description: string
|
|
89
|
+
whenToUse?: string
|
|
90
|
+
invocation: HubInvocation
|
|
91
|
+
provider: string
|
|
92
|
+
/** Absolute file path when the skill came from disk. */
|
|
93
|
+
path?: string
|
|
94
|
+
/** SKILL.md creation time (epoch ms); absent when the file is unreadable. */
|
|
95
|
+
addedAt?: number
|
|
96
|
+
/** SKILL.md last-modified time (epoch ms); absent when the file is unreadable. */
|
|
97
|
+
updatedAt?: number
|
|
98
|
+
/** Markdown instruction body. */
|
|
99
|
+
content: string
|
|
100
|
+
displayName?: string
|
|
101
|
+
shortDescription?: string
|
|
102
|
+
brandColor?: string
|
|
103
|
+
iconSmall?: string
|
|
104
|
+
iconLarge?: string
|
|
105
|
+
defaultPrompt?: string
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface SkillDetailResponse {
|
|
109
|
+
ok: true
|
|
110
|
+
skill: SkillDetail
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** POST /api/skill-hub/skill/delete — 把单个技能移入回收站(可恢复)。 */
|
|
114
|
+
export interface SkillDeleteRequest {
|
|
115
|
+
name: string
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** POST /api/skill-hub/skill/delete */
|
|
119
|
+
export interface SkillDeleteResponse {
|
|
120
|
+
ok: true
|
|
121
|
+
name: string
|
|
122
|
+
/** 移入回收站后的路径(目录或文件)。 */
|
|
123
|
+
path: string
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** POST /api/skill-hub/toggle */
|
|
127
|
+
export interface ToggleRequest {
|
|
128
|
+
/** Kebab-case skill name. */
|
|
129
|
+
name: string
|
|
130
|
+
/** true re-enables a hub-disabled skill; false disables an enabled one. */
|
|
131
|
+
enabled: boolean
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface ToggleResponse {
|
|
135
|
+
ok: true
|
|
136
|
+
/** Fresh catalog after the mutation (the filesystem provider may lag a beat). */
|
|
137
|
+
catalog: CatalogResponse
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** POST /api/skill-hub/toggle-batch — one group of skills, one write. */
|
|
141
|
+
export interface ToggleBatchRequest {
|
|
142
|
+
names: string[]
|
|
143
|
+
enabled: boolean
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** POST /api/skill-hub/toggle-batch */
|
|
147
|
+
export interface ToggleBatchResponse {
|
|
148
|
+
ok: true
|
|
149
|
+
catalog: CatalogResponse
|
|
150
|
+
/** Per-name failures (unknown/read-only skills); empty means all landed. */
|
|
151
|
+
failures: Array<{ name: string; error: string }>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** POST /api/skill-hub/create */
|
|
155
|
+
export interface CreateRequest {
|
|
156
|
+
/** Kebab-case skill name (validated with the official isSkillName grammar). */
|
|
157
|
+
name: string
|
|
158
|
+
/** Optional one-line routing description for the frontmatter. */
|
|
159
|
+
description?: string
|
|
160
|
+
/** Target user root; defaults to user-dsh (~/.dsh/skills). */
|
|
161
|
+
root?: WritableRoot
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface CreateResponse {
|
|
165
|
+
ok: true
|
|
166
|
+
path: string
|
|
167
|
+
root: WritableRoot
|
|
168
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/** JSON error body shared by every route. */
|
|
2
|
+
export interface ErrorResponse {
|
|
3
|
+
error: string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** The skill hub's own runtime configuration, edited from the settings card. */
|
|
7
|
+
export interface HubConfig {
|
|
8
|
+
/** Master switch: routes, provider, and announcement all go live with this. */
|
|
9
|
+
enabled: boolean
|
|
10
|
+
/** When true, a system-prompt section announces the hub to every agent. */
|
|
11
|
+
announceToAgent: boolean
|
|
12
|
+
/** Model-invocable dot color (#rrggbb); absent means the panel default. */
|
|
13
|
+
dotModelColor?: string
|
|
14
|
+
/** User-invocable dot color (#rrggbb); absent means the panel default. */
|
|
15
|
+
dotUserColor?: string
|
|
16
|
+
/** Show per-skill invocation count chip. Default true. */
|
|
17
|
+
showUseCount?: boolean
|
|
18
|
+
/** Show per-skill last-used relative time. Default true. */
|
|
19
|
+
showUseTime?: boolean
|
|
20
|
+
/** Show group-header usage summaries (count + last used). Default true. */
|
|
21
|
+
showGroupSummary?: boolean
|
|
22
|
+
/** 统计滚动窗口天数:只统计最近 N 天的使用;0 = 全部历史。默认 0。 */
|
|
23
|
+
statsWindowDays?: number
|
|
24
|
+
/** 自动统计扫描间隔(分钟,最小 1)。默认 5。 */
|
|
25
|
+
statsScanMinutes?: number
|
|
26
|
+
/**
|
|
27
|
+
* GitHub personal access token for market/source API calls (anonymous quota
|
|
28
|
+
* is 60 req/h, authed 5000/hr). Stored in the local settings doc; absent
|
|
29
|
+
* means anonymous. Never logged.
|
|
30
|
+
*/
|
|
31
|
+
githubToken?: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The resolved shape of the hub's settings namespace (schema defaults, then
|
|
36
|
+
* the composition base, then the user layer). Kept as a type alias so the
|
|
37
|
+
* browser-side settings scope snapshot is index-compatible with the card
|
|
38
|
+
* form's record shape.
|
|
39
|
+
*/
|
|
40
|
+
export type HubSettingsValue = {
|
|
41
|
+
enabled: boolean
|
|
42
|
+
announceToAgent: boolean
|
|
43
|
+
showUseCount: boolean
|
|
44
|
+
showUseTime: boolean
|
|
45
|
+
showGroupSummary: boolean
|
|
46
|
+
/** Model-invocable dot color (#rrggbb); absent means the panel default. */
|
|
47
|
+
dotModelColor?: string
|
|
48
|
+
/** User-invocable dot color (#rrggbb); absent means the panel default. */
|
|
49
|
+
dotUserColor?: string
|
|
50
|
+
/** 统计滚动窗口天数(0 = 全部历史)。 */
|
|
51
|
+
statsWindowDays?: number
|
|
52
|
+
/** 自动统计扫描间隔(分钟)。 */
|
|
53
|
+
statsScanMinutes?: number
|
|
54
|
+
/** GitHub token(明文存本地设置文档,仅回环可读;缺省为匿名)。 */
|
|
55
|
+
githubToken?: string
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Hub config defaults — the single source every layer reads: the cordis
|
|
60
|
+
* schema (index.ts), the host's saved-override merge, and the routes'
|
|
61
|
+
* fallback view. Changing a default here changes all three.
|
|
62
|
+
*/
|
|
63
|
+
export const HUB_CONFIG_DEFAULTS = {
|
|
64
|
+
enabled: true,
|
|
65
|
+
announceToAgent: true,
|
|
66
|
+
showUseCount: true,
|
|
67
|
+
showUseTime: true,
|
|
68
|
+
showGroupSummary: true,
|
|
69
|
+
statsWindowDays: 14,
|
|
70
|
+
statsScanMinutes: 5,
|
|
71
|
+
} as const
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the effective hub config: saved sidecar overrides win over the
|
|
75
|
+
* cordis composition entry (the web card owns runtime config), missing
|
|
76
|
+
* booleans fall back to HUB_CONFIG_DEFAULTS, and dot colors pass through
|
|
77
|
+
* (saved first, then base) only when set. Numeric stats knobs are clamped to
|
|
78
|
+
* their sane ranges (window ≥ 0, scan interval ≥ 1 minute).
|
|
79
|
+
*/
|
|
80
|
+
export function resolveHubConfig(saved: Partial<HubConfig>, base: Partial<HubConfig> = {}): HubConfig {
|
|
81
|
+
const dotModelColor = saved.dotModelColor !== undefined ? saved.dotModelColor : base.dotModelColor
|
|
82
|
+
const dotUserColor = saved.dotUserColor !== undefined ? saved.dotUserColor : base.dotUserColor
|
|
83
|
+
const githubToken = saved.githubToken !== undefined ? saved.githubToken : base.githubToken
|
|
84
|
+
const windowDays = clampNumber(saved.statsWindowDays ?? base.statsWindowDays, 0) ?? HUB_CONFIG_DEFAULTS.statsWindowDays
|
|
85
|
+
const scanMinutes = clampNumber(saved.statsScanMinutes ?? base.statsScanMinutes, 1) ?? HUB_CONFIG_DEFAULTS.statsScanMinutes
|
|
86
|
+
return {
|
|
87
|
+
enabled: saved.enabled ?? base.enabled ?? HUB_CONFIG_DEFAULTS.enabled,
|
|
88
|
+
announceToAgent: saved.announceToAgent ?? base.announceToAgent ?? HUB_CONFIG_DEFAULTS.announceToAgent,
|
|
89
|
+
showUseCount: saved.showUseCount ?? base.showUseCount ?? HUB_CONFIG_DEFAULTS.showUseCount,
|
|
90
|
+
showUseTime: saved.showUseTime ?? base.showUseTime ?? HUB_CONFIG_DEFAULTS.showUseTime,
|
|
91
|
+
showGroupSummary: saved.showGroupSummary ?? base.showGroupSummary ?? HUB_CONFIG_DEFAULTS.showGroupSummary,
|
|
92
|
+
statsWindowDays: windowDays,
|
|
93
|
+
statsScanMinutes: scanMinutes,
|
|
94
|
+
...(dotModelColor !== undefined ? { dotModelColor } : {}),
|
|
95
|
+
...(dotUserColor !== undefined ? { dotUserColor } : {}),
|
|
96
|
+
...(githubToken !== undefined ? { githubToken } : {}),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Clamp a numeric override into a valid value; undefined/invalid stays undefined. */
|
|
101
|
+
function clampNumber(value: unknown, min: number): number | undefined {
|
|
102
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < min) return undefined
|
|
103
|
+
return Math.floor(value)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** HEX color validation shared by host routes and the settings card. */
|
|
107
|
+
export const HEX_COLOR_RE = /^#[0-9a-f]{6}$/i
|
|
108
|
+
|
|
109
|
+
/** GET /api/skill-hub/config */
|
|
110
|
+
export interface ConfigResponse {
|
|
111
|
+
ok: true
|
|
112
|
+
/** 已安装插件自身的版本号(package.json version),设置卡标题旁显示。 */
|
|
113
|
+
pluginVersion: string
|
|
114
|
+
/** Effective configuration (saved overrides merged over the defaults). */
|
|
115
|
+
config: HubConfig
|
|
116
|
+
/** Raw user overrides persisted in the sidecar (absent fields inherit defaults). */
|
|
117
|
+
saved: Partial<HubConfig>
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** POST /api/skill-hub/config — a partial patch; omitted fields keep their values. */
|
|
121
|
+
export interface ConfigRequest {
|
|
122
|
+
/** Set the field; null clears the saved override so it re-inherits the default. */
|
|
123
|
+
enabled?: boolean | null
|
|
124
|
+
/** Set the field; null clears the saved override so it re-inherits the default. */
|
|
125
|
+
announceToAgent?: boolean | null
|
|
126
|
+
/** Set the field; null clears the saved override so it re-inherits the default. */
|
|
127
|
+
showUseCount?: boolean | null
|
|
128
|
+
/** Set the field; null clears the saved override so it re-inherits the default. */
|
|
129
|
+
showUseTime?: boolean | null
|
|
130
|
+
/** Set the field; null clears the saved override so it re-inherits the default. */
|
|
131
|
+
showGroupSummary?: boolean | null
|
|
132
|
+
/** Set the dot color; null clears the saved override so it re-inherits the default. */
|
|
133
|
+
dotModelColor?: string | null
|
|
134
|
+
/** Set the dot color; null clears the saved override so it re-inherits the default. */
|
|
135
|
+
dotUserColor?: string | null
|
|
136
|
+
/** 统计滚动窗口天数(0 = 全部历史);null 清除覆盖回默认。 */
|
|
137
|
+
statsWindowDays?: number | null
|
|
138
|
+
/** 自动统计扫描间隔(分钟);null 清除覆盖回默认。 */
|
|
139
|
+
statsScanMinutes?: number | null
|
|
140
|
+
/** Set the GitHub token; null/empty clears it back to anonymous. */
|
|
141
|
+
githubToken?: string | null
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** GitHub token validation shared by host routes and the settings card. */
|
|
145
|
+
export const GITHUB_TOKEN_RE = /^[A-Za-z0-9_\-]{8,255}$/
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/** 用户自定义 tag 分组:纯组织视图,不改技能文件。 */
|
|
2
|
+
export interface SkillTag {
|
|
3
|
+
/** 稳定 ID(新建时由宿主生成)。 */
|
|
4
|
+
id: string
|
|
5
|
+
/** 显示名。 */
|
|
6
|
+
name: string
|
|
7
|
+
/** 成员技能名(集合技能与独立技能都可加入)。 */
|
|
8
|
+
skillNames: string[]
|
|
9
|
+
/** 默认场景(「通用」):系统预置、不可删除、新技能自动归入。 */
|
|
10
|
+
default?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 系统集合组:按 origin(来源集合标识)自动聚合。 */
|
|
14
|
+
export interface CollectionGroup {
|
|
15
|
+
/** 集合名(origin 值,如 "superpowers" / "anthropics/skills")。 */
|
|
16
|
+
name: string
|
|
17
|
+
/** 该集合下的技能名,按名称排序。 */
|
|
18
|
+
skillNames: string[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** GET /api/skill-hub/groups */
|
|
22
|
+
export interface GroupsResponse {
|
|
23
|
+
ok: true
|
|
24
|
+
/** 用户自定义 tag 分组(按拖拽顺序)。 */
|
|
25
|
+
tags: SkillTag[]
|
|
26
|
+
/** 系统集合组(按拖拽顺序,兜底按名称)。 */
|
|
27
|
+
collections: CollectionGroup[]
|
|
28
|
+
/** skillName → 集合名 的完整映射。 */
|
|
29
|
+
origins: Record<string, string>
|
|
30
|
+
/** 来源分组整体顺序(project / col:xxx / uncategorized-source),用于 SourcesView 顶层排序 */
|
|
31
|
+
sourceGroupOrder?: string[]
|
|
32
|
+
/** 兼容旧 collectionOrder 字段 */
|
|
33
|
+
collectionOrder?: string[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** POST /api/skill-hub/tag — 新建(缺省 id)或重命名(带 id)。 */
|
|
37
|
+
export interface TagSaveRequest {
|
|
38
|
+
/** 已有 tag 的 id;缺省表示新建。 */
|
|
39
|
+
id?: string
|
|
40
|
+
/** 显示名(去空格后非空)。 */
|
|
41
|
+
name: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** POST /api/skill-hub/tag */
|
|
45
|
+
export interface TagSaveResponse {
|
|
46
|
+
ok: true
|
|
47
|
+
/** 变更后的全部 tag。 */
|
|
48
|
+
tags: SkillTag[]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** POST /api/skill-hub/tag/delete */
|
|
52
|
+
export interface TagDeleteRequest {
|
|
53
|
+
id: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** POST /api/skill-hub/tag/delete */
|
|
57
|
+
export interface TagDeleteResponse {
|
|
58
|
+
ok: true
|
|
59
|
+
tags: SkillTag[]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** POST /api/skill-hub/tag/members — 直接设置某 tag 的完整成员列表(幂等)。 */
|
|
63
|
+
export interface TagMembersRequest {
|
|
64
|
+
id: string
|
|
65
|
+
/** 目标成员技能名;后端只保留目录中存在的名字。 */
|
|
66
|
+
skillNames: string[]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** POST /api/skill-hub/tag/members */
|
|
70
|
+
export interface TagMembersResponse {
|
|
71
|
+
ok: true
|
|
72
|
+
tags: SkillTag[]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** POST /api/skill-hub/tag/reorder — 拖拽重排场景分组 */
|
|
76
|
+
export interface TagReorderRequest {
|
|
77
|
+
/** 按新顺序排列的 tag id 列表(需包含全部 id) */
|
|
78
|
+
orderedIds: string[]
|
|
79
|
+
}
|
|
80
|
+
/** POST /api/skill-hub/tag/reorder */
|
|
81
|
+
export interface TagReorderResponse {
|
|
82
|
+
ok: true
|
|
83
|
+
tags: SkillTag[]
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** POST /api/skill-hub/collections/reorder — 拖拽重排来源集合 */
|
|
87
|
+
export interface CollectionReorderRequest {
|
|
88
|
+
/** 按新顺序排列的集合名列表(需包含全部 name) */
|
|
89
|
+
orderedNames: string[]
|
|
90
|
+
}
|
|
91
|
+
/** POST /api/skill-hub/collections/reorder */
|
|
92
|
+
export interface CollectionReorderResponse {
|
|
93
|
+
ok: true
|
|
94
|
+
collections: CollectionGroup[]
|
|
95
|
+
order: string[]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** POST /api/skill-hub/source-groups/reorder — 拖拽重排来源顶层分组(project / collections / personal) */
|
|
99
|
+
export interface SourceGroupReorderRequest {
|
|
100
|
+
/** 按新顺序排列的顶层分组 key 列表(project / col:xxx / uncategorized-source) */
|
|
101
|
+
orderedKeys: string[]
|
|
102
|
+
}
|
|
103
|
+
export interface SourceGroupReorderResponse {
|
|
104
|
+
ok: true
|
|
105
|
+
order: string[]
|
|
106
|
+
}
|