dsh-command-palette 0.1.0 → 0.1.2
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.en.md +14 -1
- package/README.md +14 -1
- package/client.js +92 -6
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-command-palette)
|
|
6
6
|
[](https://github.com/chenyangcun/dsh-command-palette)
|
|
7
7
|
|
|
8
|
-
A keyboard-first command palette for standard DeepSeek Harness (DSH). Double-tap Shift to search and open sessions, workspaces, available features, or reusable custom commands.
|
|
8
|
+
A keyboard-first command palette for standard DeepSeek Harness (DSH). Double-tap Shift to search and open sessions, workspaces, settings pages, available features, or reusable custom commands.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -21,6 +21,7 @@ Restart the current DSH Web process after installation, then open **Settings →
|
|
|
21
21
|
- **Recent sessions** — shows the six most recently updated valid sessions by default
|
|
22
22
|
- **Session search** — searches titles, working directories, Agent presets, and session IDs
|
|
23
23
|
- **Workspace switching** — starts a session in the selected workspace directly from the palette
|
|
24
|
+
- **Settings navigation** — discovers registered DSH settings sections and opens the selected page directly
|
|
24
25
|
- **Custom commands** — saves frequently used prompts and either creates a session each time or reuses a dedicated session
|
|
25
26
|
- **Workspace binding** — chooses the workspace used when a custom command creates a session
|
|
26
27
|
- **Provider API** — lets other Client plugins register commands or trigger the palette
|
|
@@ -32,6 +33,8 @@ Restart the current DSH Web process after installation, then open **Settings →
|
|
|
32
33
|
3. Use `↑` / `↓` to select, Enter to run, and Esc to close.
|
|
33
34
|
4. Add, edit, or remove custom commands under **Settings → Command Palette**.
|
|
34
35
|
|
|
36
|
+
The palette reads the current settings-section registry whenever it opens, so settings pages added by other plugins automatically appear in the **Settings** group.
|
|
37
|
+
|
|
35
38
|
When **Reuse dedicated session** is enabled, the first run creates and stores a session binding. Later runs continue in that session. If the bound session is archived or unavailable, the plugin creates a replacement automatically.
|
|
36
39
|
|
|
37
40
|
## Standard DSH and extension API
|
|
@@ -75,6 +78,16 @@ Service API:
|
|
|
75
78
|
|
|
76
79
|
Provider IDs must be unique. Commands require at least `id`, `title`, and `run()`; the provider ID is automatically prefixed to the final command ID.
|
|
77
80
|
|
|
81
|
+
Dynamic providers may also implement `subscribe(invalidate)`. Call `invalidate()` when external state changes to make the palette collect commands again. The disposer returned by `subscribe` runs automatically when the provider is removed:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
{
|
|
85
|
+
id: 'dynamic-plugin',
|
|
86
|
+
collect: () => buildCommands(currentState),
|
|
87
|
+
subscribe: (invalidate) => externalStore.subscribe(() => invalidate())
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
78
91
|
Native Desktop browser tabs, Electron IPC, and native-view coordination are outside this package. A separate Desktop enhancement plugin can add them later through this API.
|
|
79
92
|
|
|
80
93
|
## Data storage
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/dsh-command-palette)
|
|
6
6
|
[](https://github.com/chenyangcun/dsh-command-palette)
|
|
7
7
|
|
|
8
|
-
适用于标准版 DeepSeek Harness(DSH)的键盘优先命令面板。连续快速按两次 Shift
|
|
8
|
+
适用于标准版 DeepSeek Harness(DSH)的键盘优先命令面板。连续快速按两次 Shift,即可搜索和打开会话、工作区、设置页面、已安装功能或可复用的自定义指令。
|
|
9
9
|
|
|
10
10
|
## 安装
|
|
11
11
|
|
|
@@ -21,6 +21,7 @@ dsh plugin --profile web add dsh-command-palette
|
|
|
21
21
|
- **最近会话**——默认展示最近更新的六个有效会话
|
|
22
22
|
- **会话搜索**——按标题、工作目录、Agent 预设或会话 ID 搜索
|
|
23
23
|
- **工作区切换**——从命令面板直接在目标工作区启动会话
|
|
24
|
+
- **设置页跳转**——自动读取 DSH 已注册的设置菜单,搜索后直接打开对应设置页面
|
|
24
25
|
- **自定义指令**——保存常用 Prompt,可选择每次新建会话或复用专属会话
|
|
25
26
|
- **工作区绑定**——为自定义指令的新会话指定目标工作区
|
|
26
27
|
- **扩展 Provider**——其他 Client 插件可以注册自己的命令或触发面板
|
|
@@ -32,6 +33,8 @@ dsh plugin --profile web add dsh-command-palette
|
|
|
32
33
|
3. 使用 `↑` / `↓` 选择,按 Enter 执行,按 Esc 关闭。
|
|
33
34
|
4. 在 **设置 → 命令面板** 中添加、编辑或删除自定义指令。
|
|
34
35
|
|
|
36
|
+
插件会在每次打开面板时读取当前已注册的设置页面,因此其他插件新增的设置菜单也会自动出现在“设置”分组中。
|
|
37
|
+
|
|
35
38
|
自定义指令启用“复用专属会话”后,首次执行会创建会话并保存绑定;后续执行继续向同一会话发送内容。如果绑定会话已经归档或不可用,插件会自动创建新会话。
|
|
36
39
|
|
|
37
40
|
## 标准 DSH 与扩展接口
|
|
@@ -75,6 +78,16 @@ exports.apply = (ctx) => {
|
|
|
75
78
|
|
|
76
79
|
Provider ID 必须唯一。命令至少需要 `id`、`title` 和 `run()`;最终命令 ID 会自动加上 Provider 前缀。
|
|
77
80
|
|
|
81
|
+
动态 Provider 可以额外实现 `subscribe(invalidate)`。当外部状态变化时调用 `invalidate()`,命令面板会重新收集命令;`subscribe` 返回的卸载函数会在 Provider 注销时自动执行:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
{
|
|
85
|
+
id: 'dynamic-plugin',
|
|
86
|
+
collect: () => buildCommands(currentState),
|
|
87
|
+
subscribe: (invalidate) => externalStore.subscribe(() => invalidate())
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
78
91
|
Desktop 原生浏览器标签、Electron IPC 和原生视图协调不属于本包,后续可以由独立 Desktop 增强插件通过上述接口接入。
|
|
79
92
|
|
|
80
93
|
## 数据存储
|
package/client.js
CHANGED
|
@@ -7,8 +7,8 @@ window.__ModuleLoader__.load({
|
|
|
7
7
|
const React = require('react')
|
|
8
8
|
const ReactDOM = require('react-dom')
|
|
9
9
|
|
|
10
|
-
const GROUP_ORDER = ['features', 'browser', 'workspaces', 'sessions']
|
|
11
|
-
const GROUP_LABELS = { features: '功能', browser: '浏览器', workspaces: '工作区', sessions: '会话', recent: '最近会话' }
|
|
10
|
+
const GROUP_ORDER = ['features', 'settings', 'browser', 'workspaces', 'sessions']
|
|
11
|
+
const GROUP_LABELS = { features: '功能', settings: '设置', browser: '浏览器', workspaces: '工作区', sessions: '会话', recent: '最近会话' }
|
|
12
12
|
const SUPPORTED_GROUPS = new Set(GROUP_ORDER)
|
|
13
13
|
|
|
14
14
|
function normalizeCustomCommand(value) {
|
|
@@ -36,7 +36,10 @@ window.__ModuleLoader__.load({
|
|
|
36
36
|
let revision = 0
|
|
37
37
|
const emit = (reason) => {
|
|
38
38
|
const snapshot = { open, revision: ++revision, reason }
|
|
39
|
-
for (const listener of listeners)
|
|
39
|
+
for (const listener of listeners) {
|
|
40
|
+
try { listener(snapshot) }
|
|
41
|
+
catch (error) { console.warn('[dsh-command-palette] subscriber failed:', error) }
|
|
42
|
+
}
|
|
40
43
|
}
|
|
41
44
|
return {
|
|
42
45
|
registerProvider(provider) {
|
|
@@ -45,15 +48,33 @@ window.__ModuleLoader__.load({
|
|
|
45
48
|
}
|
|
46
49
|
const id = provider.id.trim()
|
|
47
50
|
if (providers.has(id)) throw new Error(`Command palette provider already registered: ${id}`)
|
|
48
|
-
|
|
51
|
+
const record = { provider, disposeSubscription: null }
|
|
52
|
+
providers.set(id, record)
|
|
49
53
|
emit('provider-registered')
|
|
54
|
+
if (typeof provider.subscribe === 'function') {
|
|
55
|
+
try {
|
|
56
|
+
const dispose = provider.subscribe(() => emit('provider-updated'))
|
|
57
|
+
if (typeof dispose === 'function') record.disposeSubscription = dispose
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.warn(`[dsh-command-palette] provider ${id} subscription failed:`, error)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let removed = false
|
|
50
63
|
return () => {
|
|
51
|
-
if (
|
|
64
|
+
if (removed) return
|
|
65
|
+
removed = true
|
|
66
|
+
const current = providers.get(id)
|
|
67
|
+
if (!current) return
|
|
68
|
+
providers.delete(id)
|
|
69
|
+
try { current.disposeSubscription?.() }
|
|
70
|
+
catch (error) { console.warn(`[dsh-command-palette] provider ${id} disposer failed:`, error) }
|
|
71
|
+
emit('provider-removed')
|
|
52
72
|
}
|
|
53
73
|
},
|
|
54
74
|
collect(context = {}) {
|
|
55
75
|
const commands = []
|
|
56
|
-
for (const [providerId,
|
|
76
|
+
for (const [providerId, record] of providers) {
|
|
77
|
+
const provider = record.provider
|
|
57
78
|
const commandIds = new Set()
|
|
58
79
|
let provided
|
|
59
80
|
try { provided = provider.collect(context) }
|
|
@@ -106,6 +127,69 @@ window.__ModuleLoader__.load({
|
|
|
106
127
|
return [command.title, command.subtitle, ...(command.keywords || [])].filter(Boolean).join(' ').toLocaleLowerCase()
|
|
107
128
|
}
|
|
108
129
|
|
|
130
|
+
function resolveSettingsLabel(value) {
|
|
131
|
+
try {
|
|
132
|
+
const label = typeof value === 'function' ? value() : value
|
|
133
|
+
return typeof label === 'string' ? label.trim() : ''
|
|
134
|
+
} catch {
|
|
135
|
+
return ''
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function findSettingsNavigationButton(label) {
|
|
140
|
+
for (const dialog of document.querySelectorAll('[role="dialog"]')) {
|
|
141
|
+
const nav = dialog.querySelector('nav')
|
|
142
|
+
if (!nav) continue
|
|
143
|
+
const button = Array.from(nav.querySelectorAll('button')).find((item) => item.textContent?.trim() === label)
|
|
144
|
+
if (button) return button
|
|
145
|
+
}
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function openSettingsSection(label) {
|
|
150
|
+
let button = findSettingsNavigationButton(label)
|
|
151
|
+
if (!button) {
|
|
152
|
+
const trigger = document.querySelector('button[aria-haspopup="dialog"][aria-expanded]')
|
|
153
|
+
if (!trigger) throw new Error('当前 DSH 页面未提供可用的设置入口。')
|
|
154
|
+
trigger.click()
|
|
155
|
+
button = findSettingsNavigationButton(label)
|
|
156
|
+
for (let attempt = 0; !button && attempt < 4; attempt += 1) {
|
|
157
|
+
await new Promise((resolve) => requestAnimationFrame(resolve))
|
|
158
|
+
button = findSettingsNavigationButton(label)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!button) throw new Error(`无法打开设置页面:${label}`)
|
|
162
|
+
button.click()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function createSettingsProvider(ctx) {
|
|
166
|
+
return {
|
|
167
|
+
id: 'settings',
|
|
168
|
+
collect: () => {
|
|
169
|
+
let entries
|
|
170
|
+
try { entries = ctx.slots.entries('settings.section') }
|
|
171
|
+
catch { return [] }
|
|
172
|
+
return Array.from(entries || [])
|
|
173
|
+
.map((entry) => ({
|
|
174
|
+
id: typeof entry?.options?.id === 'string' ? entry.options.id.trim() : '',
|
|
175
|
+
order: typeof entry?.options?.order === 'number' ? entry.options.order : 0,
|
|
176
|
+
title: resolveSettingsLabel(entry?.options?.label)
|
|
177
|
+
}))
|
|
178
|
+
.filter(({ id, title }) => id && title)
|
|
179
|
+
.sort((left, right) => left.order - right.order)
|
|
180
|
+
.map(({ id, title }) => ({
|
|
181
|
+
id,
|
|
182
|
+
group: 'settings',
|
|
183
|
+
icon: '⚙',
|
|
184
|
+
title,
|
|
185
|
+
subtitle: '打开设置页面',
|
|
186
|
+
keywords: ['settings', '设置', id],
|
|
187
|
+
run: () => openSettingsSection(title)
|
|
188
|
+
}))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
109
193
|
function recentTimestamp(value) {
|
|
110
194
|
return typeof value === 'number' ? value : Date.parse(String(value || '')) || 0
|
|
111
195
|
}
|
|
@@ -358,6 +442,7 @@ window.__ModuleLoader__.load({
|
|
|
358
442
|
const commandSettings = ctx.get('settingsScope').bind({ namespace: 'command-palette' })
|
|
359
443
|
const paletteService = createCommandPaletteService()
|
|
360
444
|
ctx.provide('commandPalette', paletteService)
|
|
445
|
+
const removeSettingsProvider = paletteService.registerProvider(createSettingsProvider(ctx))
|
|
361
446
|
installStyles()
|
|
362
447
|
const saveCustomCommands = async (next) => {
|
|
363
448
|
const commands = normalizeCustomCommands(next)
|
|
@@ -399,6 +484,7 @@ window.__ModuleLoader__.load({
|
|
|
399
484
|
ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dsh-command-palette', order: 220, inject: injected }, Palette))
|
|
400
485
|
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'command-palette-settings', order: 90, label: () => '命令面板', inject: injected }, ShortcutSettings))
|
|
401
486
|
ctx.effect(() => () => {
|
|
487
|
+
removeSettingsProvider()
|
|
402
488
|
paletteService.close()
|
|
403
489
|
document.querySelector('style[data-plugin-css="dsh-command-palette"]')?.remove()
|
|
404
490
|
}, 'dsh-command-palette: cleanup')
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-command-palette",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A keyboard-first command palette for DeepSeek Harness sessions, workspaces, features, and reusable custom commands.",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "A keyboard-first command palette for DeepSeek Harness sessions, workspaces, settings, features, and reusable custom commands.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "chenyangcun",
|
|
7
7
|
"type": "module",
|