Bundle
dsh-plugin-palette-board
2D Palette Board & Application Drawer for DeepSeek Harness (dsh): a Raycast/Launchpad-style floating application grid, with the paletteHub service open to all client plugins
- Source
- zhm20001
- License
- MIT
- Updated
- Updated 6 days ago
Readme
# dsh-plugin-palette-board
> Raycast / Launchpad 风格的 2D 应用调色盘:⌘K 一键唤出的全键盘悬浮应用网格,DeepSeek Harness (dsh) web 控制台插件。
[](https://www.npmjs.com/package/@deepseek-ai/cordis)
[](https://nodejs.org/)
[]()
[](LICENSE)
**简体中文** | [English](README.en.md)
---
## 📖 简介
`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.action` slot 注册表,一键收编现存侧栏入口为自定义卡片;口径与 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. 克隆并挂载
```bash
# 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 启动失败:
```tsx
// 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`](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` | 执行焦点卡片 |
---
## 🛠️ 本地开发
```bash
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/ 产物直接分发。
```text
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](LICENSE) 开源。host / client 双半源码、构建产物(lib/)、接入指南页与全部文档同许可。
Install
dsh plugin --profile web add github:zhm20001/dsh-plugin-palette-board
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-plugin-palette-board from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.