dsh-skill-hub 0.3.2 → 0.3.4
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/lib/client.js +37 -39
- package/lib/client.js.map +1 -1
- package/lib/index.js +4 -4
- package/lib/types/client/grouping.d.ts +2 -2
- package/lib/types/protocol.d.ts +2 -0
- package/package.json +1 -1
- package/src/client/grouping.ts +3 -5
- package/src/client/panel/MarketView.tsx +0 -1
- package/src/client/panel/SourcesView.tsx +0 -83
- package/src/client/panel/panel.module.css +1 -4
- package/src/client/slash-dots.tsx +23 -14
- package/src/protocol.ts +5 -0
- package/src/routes.ts +1 -5
package/lib/index.js
CHANGED
|
@@ -93,6 +93,10 @@ function clampNumber(value, min) {
|
|
|
93
93
|
}
|
|
94
94
|
/** HEX color validation shared by host routes and the settings card. */
|
|
95
95
|
const HEX_COLOR_RE = /^#[0-9a-f]{6}$/i;
|
|
96
|
+
/** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
|
|
97
|
+
function isProjectSource(source) {
|
|
98
|
+
return source === "project-dsh" || source === "project-agents";
|
|
99
|
+
}
|
|
96
100
|
//#endregion
|
|
97
101
|
//#region src/store.ts
|
|
98
102
|
/**
|
|
@@ -2022,10 +2026,6 @@ async function workspaceEntries(home) {
|
|
|
2022
2026
|
return [];
|
|
2023
2027
|
}
|
|
2024
2028
|
}
|
|
2025
|
-
/** 项目技能来源(workspace 字段只对它们设置)。 */
|
|
2026
|
-
function isProjectSource(source) {
|
|
2027
|
-
return source === "project-dsh" || source === "project-agents";
|
|
2028
|
-
}
|
|
2029
2029
|
/**
|
|
2030
2030
|
* Build the full catalog response (shared by catalog/toggle/create handlers).
|
|
2031
2031
|
* 显式 cwd 只看该工作区;否则合并所有已知工作区(workspace.json)的项目技能
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* GUI resolves with a dialog; the helpers below compute both sides.
|
|
14
14
|
*/
|
|
15
15
|
import type { CatalogSkill, CollectionGroup, SkillTag } from '../protocol.ts';
|
|
16
|
+
import { isProjectSource } from '../protocol.ts';
|
|
17
|
+
export { isProjectSource };
|
|
16
18
|
/** Grouped switch state derived from member enablement. */
|
|
17
19
|
export type GroupSwitchState = 'on' | 'off' | 'mixed';
|
|
18
20
|
/** The derived switch view of one group. */
|
|
@@ -40,8 +42,6 @@ export declare function conflictsOnClose(members: readonly string[], enabledName
|
|
|
40
42
|
}>): string[];
|
|
41
43
|
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
42
44
|
export declare const PRIVATE_SOURCE = "private";
|
|
43
|
-
/** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
|
|
44
|
-
export declare function isProjectSource(source: string): boolean;
|
|
45
45
|
/**
|
|
46
46
|
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
47
47
|
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
package/lib/types/protocol.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-skill-hub",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "In-GUI skill hub for DeepSeek Harness (dsh): browse the full local skill catalog from the official ctx.skills registry (every root + third-party providers), toggle skills on/off, inspect bodies, surface frontmatter diagnostics, and scaffold new skills — plus a codex-style skill market (built-in catalog, upstream update checks, one-click update-all) with tracked source sync. The full manager beyond the read-only dsh-skill-manager browser.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
package/src/client/grouping.ts
CHANGED
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { CatalogSkill, CollectionGroup, SkillTag } from '../protocol.ts'
|
|
17
|
+
import { isProjectSource } from '../protocol.ts'
|
|
18
|
+
|
|
19
|
+
export { isProjectSource }
|
|
17
20
|
|
|
18
21
|
/** Grouped switch state derived from member enablement. */
|
|
19
22
|
export type GroupSwitchState = 'on' | 'off' | 'mixed'
|
|
@@ -69,11 +72,6 @@ export function conflictsOnClose(
|
|
|
69
72
|
/** Origin-repo filter value: skills with no source record (private skills). */
|
|
70
73
|
export const PRIVATE_SOURCE = 'private'
|
|
71
74
|
|
|
72
|
-
/** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
|
|
73
|
-
export function isProjectSource(source: string): boolean {
|
|
74
|
-
return source === 'project-dsh' || source === 'project-agents'
|
|
75
|
-
}
|
|
76
|
-
|
|
77
75
|
/**
|
|
78
76
|
* Apply the origin filter ('all' or a specific origin repo; skills without a
|
|
79
77
|
* source record count as PRIVATE_SOURCE). The origins map is the store's
|
|
@@ -181,7 +181,6 @@ export function MarketView(props: { hub: SkillHubState }): JSX.Element {
|
|
|
181
181
|
<section className={css.section} style={{ borderLeft: '3px solid var(--hub-model, #2f81f7)', background: 'rgba(47,129,247,0.04)' }}>
|
|
182
182
|
<div className={css.sectionTitle + ' ' + css.sectionHeadRow} style={{ margin: '10px 14px 6px' }}>
|
|
183
183
|
<span className={css.sectionTitleFill} style={{ display:'flex', alignItems:'center', gap:6 }}>
|
|
184
|
-
<span style={{ width:7, height:7, borderRadius:'50%', background:'var(--hub-model, #2f81f7)', display:'inline-block' }} />
|
|
185
184
|
扫描结果
|
|
186
185
|
<span style={{ fontWeight:400, opacity:.75 }}>
|
|
187
186
|
— {repoDiscoverState.status === 'ready' ? repoDiscoverState.data.repo : repoDiscoverState.status === 'error' ? (scanningRepo ?? '') : (scanningRepo ?? '')}
|
|
@@ -16,89 +16,6 @@ import { GroupSummary } from './GroupSummary.tsx'
|
|
|
16
16
|
import type { SkillHubState } from './useSkillHub.ts'
|
|
17
17
|
import css from './panel.module.css'
|
|
18
18
|
|
|
19
|
-
/** 项目级三级树:项目级 → 具体工作区(可折叠、可细分)→ .dsh/.agents。 */
|
|
20
|
-
function ProjectTree(props: { hub: SkillHubState }): JSX.Element | null {
|
|
21
|
-
const { hub } = props
|
|
22
|
-
const { sorted, sourceFilter, origins, collapsedGroups, subdividedProjects, toggleGroupCollapse, toggleSubdivide } = hub
|
|
23
|
-
const projectSkills = filterBySource(sorted, sourceFilter, origins).filter((skill) => isProjectSource(skill.source))
|
|
24
|
-
if (projectSkills.length === 0) return null
|
|
25
|
-
// workspace 缺失(过渡态)时按 source 降级分组,避免空标题。
|
|
26
|
-
const byProject = new Map<string, { title: string; skills: typeof projectSkills }>()
|
|
27
|
-
for (const skill of projectSkills) {
|
|
28
|
-
const key = skill.workspace ?? skill.source
|
|
29
|
-
const entry = byProject.get(key)
|
|
30
|
-
if (entry === undefined) {
|
|
31
|
-
byProject.set(key, {
|
|
32
|
-
title: skill.workspaceTitle ?? skill.workspace ?? tt('groups.project'),
|
|
33
|
-
skills: [skill],
|
|
34
|
-
})
|
|
35
|
-
} else {
|
|
36
|
-
entry.skills.push(skill)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const topCollapsed = collapsedGroups.has('project')
|
|
40
|
-
return (
|
|
41
|
-
<section className={css.section}>
|
|
42
|
-
<div className={css.groupHead}>
|
|
43
|
-
<button type='button' className={css.disclosure} aria-expanded={!topCollapsed} onClick={() => { toggleGroupCollapse('project') }}>
|
|
44
|
-
<span className={css.chevron + (topCollapsed ? ' ' + css.chevronCollapsed : '')} />
|
|
45
|
-
<span className={css.groupTitle}>
|
|
46
|
-
{tt('groups.project')} · {byProject.size}
|
|
47
|
-
</span>
|
|
48
|
-
</button>
|
|
49
|
-
</div>
|
|
50
|
-
{!topCollapsed ? [...byProject.entries()].map(([key, proj]) => {
|
|
51
|
-
const projKey = 'project:' + key
|
|
52
|
-
const projCollapsed = collapsedGroups.has(projKey)
|
|
53
|
-
const subdivided = subdividedProjects.has(key)
|
|
54
|
-
return (
|
|
55
|
-
<div key={projKey} className={css.projectNest}>
|
|
56
|
-
<div className={css.groupHead}>
|
|
57
|
-
<button type='button' className={css.disclosure} aria-expanded={!projCollapsed} onClick={() => { toggleGroupCollapse(projKey) }}>
|
|
58
|
-
<span className={css.chevron + (projCollapsed ? ' ' + css.chevronCollapsed : '')} />
|
|
59
|
-
<span className={css.groupTitle}>
|
|
60
|
-
{proj.title} · {proj.skills.length}
|
|
61
|
-
<GroupSummary members={proj.skills.map((skill) => skill.name)} hub={hub} />
|
|
62
|
-
</span>
|
|
63
|
-
</button>
|
|
64
|
-
<span className={css.groupOps}>
|
|
65
|
-
<button type='button' className={css.opBtn} onClick={(event) => { event.stopPropagation(); toggleSubdivide(key) }}>
|
|
66
|
-
{subdivided ? tt('groups.merge') : tt('groups.subdivide')}
|
|
67
|
-
</button>
|
|
68
|
-
</span>
|
|
69
|
-
</div>
|
|
70
|
-
{!projCollapsed ? (
|
|
71
|
-
subdivided ? (
|
|
72
|
-
<div className={css.projectNest}>
|
|
73
|
-
{(['project-dsh', 'project-agents'] as const).map((source) => {
|
|
74
|
-
const list = proj.skills.filter((skill) => skill.source === source)
|
|
75
|
-
if (list.length === 0) return null
|
|
76
|
-
const srcKey = projKey + ':' + source
|
|
77
|
-
const srcCollapsed = collapsedGroups.has(srcKey)
|
|
78
|
-
return (
|
|
79
|
-
<div key={srcKey} className={css.projectNest}>
|
|
80
|
-
<div className={css.groupHead}>
|
|
81
|
-
<button type='button' className={css.disclosure} aria-expanded={!srcCollapsed} onClick={() => { toggleGroupCollapse(srcKey) }}>
|
|
82
|
-
<span className={css.chevron + (srcCollapsed ? ' ' + css.chevronCollapsed : '')} />
|
|
83
|
-
<span className={css.groupTitle}>
|
|
84
|
-
{tt(('badge.source.' + source) as 'badge.source.project-dsh' | 'badge.source.project-agents')} · {list.length}
|
|
85
|
-
</span>
|
|
86
|
-
</button>
|
|
87
|
-
</div>
|
|
88
|
-
{!srcCollapsed ? list.map((skill) => <SkillRow key={skill.name} skill={skill} hub={hub} />) : null}
|
|
89
|
-
</div>
|
|
90
|
-
)
|
|
91
|
-
})}
|
|
92
|
-
</div>
|
|
93
|
-
) : proj.skills.map((skill) => <SkillRow key={skill.name} skill={skill} hub={hub} />)
|
|
94
|
-
) : null}
|
|
95
|
-
</div>
|
|
96
|
-
)
|
|
97
|
-
}) : null}
|
|
98
|
-
</section>
|
|
99
|
-
)
|
|
100
|
-
}
|
|
101
|
-
|
|
102
19
|
export function SourcesView(props: { hub: SkillHubState }): JSX.Element {
|
|
103
20
|
const { hub } = props
|
|
104
21
|
const { catalog, groupsState, skillView, sourceFilter, origins, sorted, normalized, collapsedGroups, viewNames, sourceCheck, actionNames, checkingSource, syncingSource, batchBusy, busyNames, toggleGroupCollapse, checkSources, requestSync, requestDelete, requestDeleteGroup, toggleGroup, enableDisabled } = hub
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
.sectionTitle { font-size: 13px; font-weight: 700; margin: 14px 16px 2px; }
|
|
25
25
|
.groupHead { display: flex; align-items: center; gap: 8px; padding: 10px 14px; min-height: 44px; box-sizing: border-box; }
|
|
26
26
|
.groupTitle { font-size: 13px; font-weight: 600; opacity: .92; flex: 1; min-width: 0; display: inline-flex; align-items: center; gap: 6px; line-height: 1; }
|
|
27
|
-
.groupOps { flex: none; display: inline-flex; gap: 6px; align-items: center; }
|
|
27
|
+
.groupOps { flex: none; display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
|
28
28
|
|
|
29
29
|
/* Small neutral pill buttons (group-level actions). */
|
|
30
30
|
.opBtn { font-size: 11px; padding: 3px 11px; border-radius: 999px; border: none; background: rgba(128,128,128,.13); color: inherit; cursor: pointer; transition: background .15s ease; }
|
|
@@ -147,9 +147,6 @@
|
|
|
147
147
|
/* Group title inner summary (count + last used) sits inline after the name. */
|
|
148
148
|
.groupTitleInner { display: inline-flex; align-items: baseline; }
|
|
149
149
|
|
|
150
|
-
/* Group ops row wraps on narrow widths; keep controls compact. */
|
|
151
|
-
.groupOps { flex: none; display: inline-flex; gap: 6px; align-items: center; flex-wrap: wrap; }
|
|
152
|
-
|
|
153
150
|
/* Source link inside group titles / rows. */
|
|
154
151
|
.sourceLink { color: inherit; font-weight: 600; text-decoration: none; border-bottom: 1px dotted currentColor; opacity: .9; }
|
|
155
152
|
.sourceLink:hover { opacity: 1; }
|
|
@@ -127,36 +127,45 @@ function dotIcon(color: string): InputTriggerCandidate['icon'] {
|
|
|
127
127
|
function injectDotsViaDOM(modelByName: Map<string, boolean>, modelColor: string, userColor: string): void {
|
|
128
128
|
if (typeof document === 'undefined' || typeof requestAnimationFrame === 'undefined') return
|
|
129
129
|
const run = (): void => {
|
|
130
|
-
|
|
130
|
+
// 仅处理斜杠菜单:通过 MenuView 的哈希类 `_3e4SsG_menu` 定位,避免误伤
|
|
131
|
+
// 面板内市场扫描/分组等其它 `[role="option"]` 列表
|
|
132
|
+
const menu = document.querySelector('[class*="_3e4SsG_menu"]') as HTMLElement | null
|
|
133
|
+
const listbox = menu?.querySelector('[role="listbox"]') ?? document.querySelector('[role="listbox"]')
|
|
134
|
+
// 若菜单未打开则不注入
|
|
135
|
+
if (listbox === null) return
|
|
136
|
+
const scope: ParentNode = listbox
|
|
137
|
+
const options = scope.querySelectorAll('[role="option"]')
|
|
131
138
|
for (const opt of options) {
|
|
132
|
-
// 已注入则跳过(由 data 属性标记)
|
|
133
139
|
if (opt.querySelector('[data-skill-dot]') !== null) continue
|
|
134
|
-
// 技能名在 .itemName 中,取文本匹配 catalog
|
|
135
140
|
const nameEl = opt.querySelector('[class*="itemName"]') as HTMLElement | null
|
|
136
141
|
if (nameEl === null) continue
|
|
137
142
|
const name = (nameEl.textContent ?? '').trim()
|
|
138
143
|
if (name.length === 0) continue
|
|
139
|
-
|
|
144
|
+
// 仅对技能生效:非技能候选(/command、@file 等)不在 catalog,不注点
|
|
145
|
+
if (!modelByName.has(name)) continue
|
|
140
146
|
const color = (modelByName.get(name) ?? true) ? modelColor : userColor
|
|
147
|
+
// 复刻旧版 icon 槽:16×16 容器居中 6px 点,与升级前 `dotIcon` 在 `itemIcon` 内的效果一致
|
|
148
|
+
const wrapper = document.createElement('span')
|
|
149
|
+
wrapper.setAttribute('data-skill-dot', '')
|
|
150
|
+
wrapper.setAttribute('aria-hidden', 'true')
|
|
151
|
+
wrapper.style.width = '16px'
|
|
152
|
+
wrapper.style.height = '16px'
|
|
153
|
+
wrapper.style.display = 'inline-flex'
|
|
154
|
+
wrapper.style.justifyContent = 'center'
|
|
155
|
+
wrapper.style.alignItems = 'center'
|
|
156
|
+
wrapper.style.flex = 'none'
|
|
141
157
|
const dot = document.createElement('span')
|
|
142
|
-
dot.setAttribute('data-skill-dot', '')
|
|
143
|
-
dot.setAttribute('aria-hidden', 'true')
|
|
144
158
|
dot.style.display = 'inline-block'
|
|
145
159
|
dot.style.width = '6px'
|
|
146
160
|
dot.style.height = '6px'
|
|
147
161
|
dot.style.borderRadius = '3px'
|
|
148
162
|
dot.style.background = color
|
|
149
163
|
dot.style.flex = 'none'
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
iconEl.parentElement?.insertBefore(dot, iconEl.nextSibling)
|
|
154
|
-
} else {
|
|
155
|
-
nameEl.parentElement?.insertBefore(dot, nameEl)
|
|
156
|
-
}
|
|
164
|
+
wrapper.appendChild(dot)
|
|
165
|
+
// 新版无 icon 槽,直接插在名称前,与旧版 `itemIcon` 位置一致
|
|
166
|
+
nameEl.parentElement?.insertBefore(wrapper, nameEl)
|
|
157
167
|
}
|
|
158
168
|
}
|
|
159
|
-
// 菜单由 React 异步渲染,需等下一帧
|
|
160
169
|
requestAnimationFrame(() => setTimeout(run, 0))
|
|
161
170
|
}
|
|
162
171
|
|
package/src/protocol.ts
CHANGED
|
@@ -775,3 +775,8 @@ export interface SourceTrashClearResponse {
|
|
|
775
775
|
/** 未能删除的技能(保留在回收站中)。 */
|
|
776
776
|
failed: Array<{ name: string; error: string }>
|
|
777
777
|
}
|
|
778
|
+
|
|
779
|
+
/** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
|
|
780
|
+
export function isProjectSource(source: string): boolean {
|
|
781
|
+
return source === 'project-dsh' || source === 'project-agents'
|
|
782
|
+
}
|
package/src/routes.ts
CHANGED
|
@@ -94,6 +94,7 @@ import {
|
|
|
94
94
|
type ToggleResponse,
|
|
95
95
|
type WritableRoot,
|
|
96
96
|
HEX_COLOR_RE,
|
|
97
|
+
isProjectSource,
|
|
97
98
|
resolveHubConfig,
|
|
98
99
|
} from './protocol.ts'
|
|
99
100
|
import { clearTrash, createSkill, disableSkill, enableSkill, restoreSkill, rootOfPath, rootPath, scanDiagnostics, trashSkill } from './skillfs.ts'
|
|
@@ -350,11 +351,6 @@ async function workspaceEntries(home: string): Promise<WorkspaceEntry[]> {
|
|
|
350
351
|
}
|
|
351
352
|
}
|
|
352
353
|
|
|
353
|
-
/** 项目技能来源(workspace 字段只对它们设置)。 */
|
|
354
|
-
function isProjectSource(source: string): boolean {
|
|
355
|
-
return source === 'project-dsh' || source === 'project-agents'
|
|
356
|
-
}
|
|
357
|
-
|
|
358
354
|
/**
|
|
359
355
|
* Build the full catalog response (shared by catalog/toggle/create handlers).
|
|
360
356
|
* 显式 cwd 只看该工作区;否则合并所有已知工作区(workspace.json)的项目技能
|