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/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
@@ -722,3 +722,5 @@ export interface SourceTrashClearResponse {
722
722
  error: string;
723
723
  }>;
724
724
  }
725
+ /** 项目级技能来源(它们有 workspace 归属,不属于「个人」组)。 */
726
+ export declare function isProjectSource(source: string): boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-skill-hub",
3
- "version": "0.3.2",
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": {
@@ -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
- const options = document.querySelectorAll('[role="option"]')
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
- if (!modelByName.has(name) && nameEl.textContent !== name) continue // 保守:仅处理已知名
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
- // 插在名称前;若存在 icon 槽则插在其后,兼容旧版结构
151
- const iconEl = opt.querySelector('[class*="itemIcon"]')
152
- if (iconEl !== null && iconEl.nextSibling !== null) {
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)的项目技能