dsh-plugin-palette-board
本项目为 DeepSeek Harness web 控制台带来一块 2D 调色盘应用板。唤出悬浮面板,即时搜索、分类过滤、全键盘导航,把散落在侧栏与浏览器里的插件页面收进一张可自定义的卡片网格。
- Stars
- 0
- Language
- TypeScript
- Created
- Aug 31, 2026
- Updated
- Aug 31, 2026
Introduction
dsh-plugin-palette-board
Raycast / Launchpad 风格的 2D 应用调色盘:⌘K 一键唤出的全键盘悬浮应用网格,DeepSeek Harness (dsh) web 控制台插件。
简体中文 | English
📖 简介
dsh-plugin-palette-board 为 DSH (DeepSeek Harness) web 控制台带来一块 2D 调色盘应用板:⌘K / Ctrl+K / Alt+Space 唤出玻璃拟态悬浮面板,即时搜索、分类过滤、全键盘导航,把散落在侧栏与浏览器里的插件页面收进一张可自定义的卡片网格。
卡片目录固化在本地 JSON 文件(~/.dsh/storages/palette-board.json),不播种、不预知任何插件——用面板上的「注册」手写卡片、点「扫描」一键收编侧栏入口,或让第三方插件经 ctx.paletteHub 服务入驻。调色盘服务面向所有 client 插件开放,软依赖接入、一行注册。
📸 界面预览
插件经宿主 Slot 系统在侧栏底部注册入口(lib/client.js 为轻量零依赖浏览器 bundle),面板本体作为全屏覆盖层直接挂载 document.body。
主面板(fresh 纸感主题)

即时搜索 + 动态分类过滤 + 卡片网格:悬停「⋯」溢出菜单置顶 / 调序 / 编辑 / 删除,头部按钮循环三档尺寸、一键切换双主题,状态条实时显示目录同步健康度。
扫描收编 —— 把侧栏入口变成卡片

扫描按钮读取侧栏 footer 动作 slot 的注册表,列出尚未收编的入口,一键转为点击即触发的自定义卡片。
注册自定义插件 —— 零代码收编任意页面

运行时注册 / 编辑弹窗:填一个「页面链接」即可把任何插件的 web 页面收进调色盘,或手写 footerTrigger 卡片转触发侧栏按钮。
暗色玻璃主题(glass)

fresh(清新纸感,默认)与 glass(暗色玻璃拟态)双主题,头部按钮切换、localStorage 记忆,CSS 变量驱动零重渲染成本。
挂载点一览
| 挂载点 | 位置 | 展示内容 |
|---|---|---|
sidebar.footer.action | 侧边栏底部操作区 | 调色盘常驻唤出按钮(rail 收起态为圆形图标) |
document.body portal | 全屏覆盖层 | 调色盘主面板(搜索 / 分类 / 网格 / 注册 / 扫描) |
| 右下角浮动球 | 常驻 meta 入口 | 可拖拽换位、位置记忆、面板打开时自动隐藏 |
host 静态路由 /palette-board/guide | 新标签页 | 内置接入指南(双语) |
✨ 核心特性
- ⌨️ 全键盘交互:
⌘K/Ctrl+K/Alt+Space唤出,Esc关闭,↓进入网格,方向键 /Home/End在卡片间移动(roving tabindex),回车执行;IME 组合期间不抢键。 - 🗂️ 目录固化本地 JSON:
~/.dsh/storages/palette-board.json双段结构(entries用户手写卡片 +layout插件卡片布局),编辑 / 删除 / 新增 / 置顶 / 调序实时回写,刷新重启原样恢复,可直接手工编辑或备份。 - 🛡️ 故障告警三分列:PUT 回写失败、启动读取失败(文件不动,绝不覆盖没读到过的文件)、超上限拒收(200 卡 / 256KB,宿主存储层裁定并映射 HTTP 413)——状态条分列提示,删减卡片后自动恢复。
- 🔌 paletteHub 服务化插拔:client 半以 cordis 服务发布
ctx.paletteHub,其他插件软依赖接入、一行注册卡片;palette-board 停用时各方安然无恙。 - 🔍 扫描收编:读取
sidebar.footer.actionslot 注册表,一键收编现存侧栏入口为自定义卡片;口径与 footerTrigger 触发区同源。 - 🧹 筛模式:漏斗开关隐藏失效卡(触发按钮不在侧栏、链接 404/410),只影响显示不动目录。
- 📏 尺寸三档:紧凑 / 标准 / 宽大循环切换,面板与网格随档伸缩,localStorage 记忆。
- 🌗 双主题:
fresh纸感 /glass玻璃一键切换;界面 zh / en 双语跟随 dsh 语言偏好。 - ♿ 模态可访问性:
role="dialog"+aria-modal+ Tab 焦点陷阱 + Esc 关闭 + 焦点归还。 - ⚡ 克隆即用:
lib/构建产物随仓库提交、运行时零依赖——克隆后无需 install / build 即可挂载。
🚀 快速上手
1. 环境准备
- Node.js >=
20 - dsh web >=
0.1.2-alpha.1(新版宿主,见「架构备注 · 版本基线」)
2. 克隆并挂载
# 1. 克隆。lib/ 构建产物随仓库提交、运行时零依赖——克隆即可挂载,
# 普通使用者不需要 install / build;仅当你改动 src/ 时才需要:
git clone https://github.com/zhm20001/dsh-plugin-palette-board.git
cd dsh-plugin-palette-board
# 2. 挂载进 dsh web profile:profile package.json 的 dependencies 加
# "dsh-plugin-palette-board": "link:/path/to/dsh-plugin-palette-board"
# 并把 "dsh-plugin-palette-board" 加入 dsh.profile.bundles 数组,然后:
cd ~/.dsh/profiles/web && pnpm install
# 3. 重启 dsh web(host 半变更需要重启;client 半改动热加载,硬刷新浏览器即可)
bundle 通道会自动合并本包 cordis.patch.yml 的 insert 行,无需改 profile 自己的 patch 文件。手动通道(把 insert 行写进 profile 的 cordis.patch.yml)也可以,但两种通道不要同时用。
3. 首次使用
浏览器里 ⌘K / Ctrl+K / Alt+Space 唤出调色盘(侧栏设置按钮上方的调色盘入口、右下角浮动球同样可以)。目录一开始是空的——用面板上的「注册」手写卡片、点「扫描」收编侧栏入口,或让第三方插件经 ctx.paletteHub 入驻(见下文「第三方插件接入」)。
⚙️ 目录文件与存储
| 类别 | 存储位置 | 说明 |
|---|---|---|
| 卡片目录(唯一真源) | ~/.dsh/storages/palette-board.json | { entries, layout } 双段:entries 为用户手写卡片,layout 为插件卡片布局记录 |
| 主题 / 尺寸 / 浮动球位置 | 浏览器 localStorage | 纯 UI 偏好,不入目录文件 |
💡 目录可直接手工编辑或备份;损坏时 host 先备份
.bak再重置为空目录,v1.6–v1.7 纯数组旧格式自动迁移。写入仅经回环 same-origin 的 entries API,带 Host 围栏。
🔌 第三方插件接入(paletteHub,软依赖)
调色盘的全部注册面都在浏览器侧。务必用软依赖——把 'paletteHub' 写进模块级 inject 是硬契约,palette-board 停用时你的插件会永远 pending 并让整个 web GUI 启动失败:
// my-plugin/src/client/index.tsx
import type {} from 'dsh-plugin-palette-board/client' // 纯类型导入:触发 ctx.paletteHub 类型合并
import type { Context } from '@deepseek-ai/cordis'
export function apply(ctx: Context): void {
// 软依赖:palette-board 未启用时本插件照常激活,回调零开销跳过
ctx.inject(['paletteHub'], (sctx) => {
const unregister = sctx.paletteHub.register({
id: 'my-plugin:telemetry',
title: 'Container Live Telemetry',
description: 'Real-time Docker & GPU kernel metrics inspector.',
category: 'Developer Tools',
color: 'emerald',
badge: 'v1.4',
actionLabel: 'Inspect',
onClick: () => { /* 打开你的 UI / 调用你的服务 */ },
})
sctx.effect(() => unregister, 'my-plugin: palette entry') // 停用 / HMR 时自动注销
})
}
完整示例见 examples/demo-plugin.tsx。要点:
ctx.inject(['paletteHub'], …)的子作用域在服务出现时执行回调、palette-board 停用时整体销毁——两种状态都安全,不要改成模块级inject = ['paletteHub']。register的 disposer 务必包在sctx.effect里,否则 fiber 卸载后注册残留,重激活会重复。import type {}编译期擦除:不产生运行时依赖,不触碰 client bundle 纯度门(跨插件协作只走 cordis 服务,禁止值导入)。icon收任意ComponentType<{ className?: string }>(24x24 线性风格最佳);不传则用自带 Sparkles。- 零改动通道:不想写代码?Register 弹窗填「页面链接」即可收编任何插件的 web 页面;或点扫描按钮一键收编侧栏入口。
- 插件卡片本体是会话级的:刷新后由插件重新激活时再次注册(插件还在就自动回来);你的置顶 / 调序以布局记录落盘固化,插件回来原地生效。想要永久入口,用 Register 弹窗、扫描收编或手工编辑目录文件。
⌨️ 快捷键
| 按键 | 行为 |
|---|---|
⌘K / Ctrl+K | 唤出 / 收起调色盘 |
Alt+Space | 唤出 / 收起调色盘 |
Esc | 关闭(IME 组合期间与可编辑元素内不抢;弹窗内由弹窗自监听关闭) |
↓(搜索框) | 进入卡片网格 |
↑ ↓ ← → / Home / End | 网格内移动焦点(首行再 ↑ 回搜索框) |
Enter | 执行焦点卡片 |
🛠️ 本地开发
pnpm install # 仅开发需要(运行时零依赖,克隆即用)
pnpm build # tsc 声明 → lib/types,tsdown → lib/index.js + lib/client.js
pnpm typecheck # tsc --noEmit
pnpm test # node:test 单测(宿主存储层 / 扫描纯函数,无外部依赖)
pnpm watch # 监听构建(配合 dsh 宿主 client HMR)
⚠️ 改动
src/后记得pnpm build并把lib/产物一并提交——仓库以 lib/ 产物直接分发。
dsh-plugin-palette-board/
├── package.json # dsh.client 声明 + exports["./client"] + dsh.bundle.patch
├── cordis.patch.yml # bundle patch:insert 挂载行(id: palette-board)
├── src/
│ ├── index.ts # host 半:cordis 插件 + guide / entries 路由
│ ├── entries-store.ts # 卡片目录 JSON 文件存储(校验/迁移/原子写/备份)
│ ├── guide.ts # 接入指南静态页(双语,host 半服务)
│ ├── types.ts # PaletteItem / PaletteHubService 共享类型面
│ └── client/ # client 半:面板 / 注册 / 扫描 / 主题 / i18n / paletteHub 服务
│ ├── index.tsx # 入口:provide 服务 + locale 词典 + 快捷键 + portal + 侧栏入口
│ ├── PaletteBoard.tsx # 主面板(搜索/分类/网格/键盘导航/筛模式/状态条)
│ └── … # 完整清单见 CONTEXT.md 领域术语表
├── test/ # node:test 单测
└── lib/ # 构建产物(随仓库提交)
🧩 架构备注
- 双半结构:遵循真实 dsh web 插件规范——Node 侧 cordis 插件(
inject = ['webServer', 'webRuntime'])+ 浏览器侧__ModuleLoader__client bundle,经dsh.client声明 +exports["./client"]+dsh.bundle.patch挂载。 - 产物契约:
lib/index.js(host 半,Node ESM);lib/client.js(client 半,浏览器 CJS 闭包工厂),require只允许平台模块表词(本插件只用到react/react/jsx-runtime/react-dom/client),其余全部内联。 paletteHub只在 client 半:host 半承担插件身份、/palette-board/guide静态路由与 entries API。host 半不要 default 导出(loader 的unwrapExports会丢掉模块级name/inject声明)。- store 纪律:
createPaletteHubStore()每激活创建一实例(禁止模块级单例);快照不可变、引用稳定,配useSyncExternalStore消费。 - portal 挂载:调色盘是全屏覆盖层,不进 shell slot 布局——直接向
document.body挂 root(data-dsh-palette-board锚点),附 body 重组守卫与根级错误边界。 - 版本基线:按 dsh
0.1.2-alpha.1的模块表与扫描约定实现,仅适配新版本 dsh,不做老版本兼容——扫描收编依赖宿主 slots 服务的公开枚举面,老版本宿主上守卫会让扫描结果为空(其余功能不受影响);如遇扫描始终为空,请先把 dsh 更新到当前版本。
📄 License
本项目基于 MIT License 开源。host / client 双半源码、构建产物(lib/)、接入指南页与全部文档同许可。