前言¶
DSH 的理念是「一切皆插件」。当你要给自己的 Harness 加一个自己用得上的能力时,常见做法是写一个面板,字段自己定义、接口自己拼、样式自己抄。这个插件走的是另一条路:它先有一个独立的本地 Web 应用管着真实数据,再让 Harness 面板只做「读同一份数据 + 同一份操作」。下面介绍的是 ramanasai 维护的 dsh-closet-tracker。
这是什么¶
一个仓库出两种形态:app/ 是一个零依赖的 Bun Web 应用,负责抓取商品链接、存数据、出统计;仓库根目录本身是一个 DSH 插件包,在 Harness 左侧边栏放一个衣架图标,在主列渲染完整的愿望清单面板,读的是同一个 app/data/items.json。
它解决的问题是:两边的数据保持同一份,你在 Web 应用里加的条目会立刻出现在 Harness 面板里,反之亦然。插件在 SkillHub 社区目录中归在「客户端」分类。
核心功能¶
下面这些能力都来自 README:
- 读同一个数据源,两边共用
app/data/items.json - 粘贴链接即可加单品:应用抓取名称、图片、价格、店铺、品牌、分类,条目立刻进网格
- 统计:愿望清单总值、仍需购买件数、已购数量、上次检查以来的降价
- 筛选、搜索、排序:状态页签(All / Wishlist / Bought)、分类筛选、自由文本搜索、按最新 / 价格 / 名称 / 降价幅度排序
- 单品操作:立即查价、愿望清单与已购互切、两步确认删除
- Sync all:重新抓取所有在跟踪的链接并报告降价
- 原生外观:只用主题 token,明暗跟随 Harness;空状态和离线状态会说明该怎么办;本地应用挂掉时每 12 秒静默重试
安装与启用¶
安装分两步:先把 Web 应用跑起来,再装插件包。Web 应用不需要 bun install:
cd app
bun run start # http://localhost:3003
open http://localhost:3003
插件包需要 DeepSeek Harness(面向 0.2.1-rc.5 构建)且已有 profile。官方给出的安装方式是先把仓库 clone 下来,再把 clone 的绝对路径作为 target 传给 plugin_manager install_bundle——仓库根目录就是插件包本身:
git clone https://github.com/ramanasai/dsh-closet-tracker.git
plugin_manager install_bundle target=/absolute/path/to/dsh-closet-tracker
也可以在 Harness 的 Plugin Manager UI 里选「从本地目录安装 bundle」,填同一个路径。
另一个不 clone 的方式:从 Releases 下载 dsh-closet-tracker-1.0.0.tgz,直接把 tarball 路径当 target:
plugin_manager install_bundle target=/absolute/path/to/dsh-closet-tracker-1.0.0.tgz
装完后边栏出现标着 Closet Tracker 的衣架图标。插件只负责界面,数据要应用跑着才显示;如果安装前 Harness 页面已经开着,装完重新加载一次。
配置¶
上游地址写在 cordis.patch.yml,随 bundle 的 patch 插入 profile:
- insert:
- id: closet-tracker
name: dsh-closet-tracker
config:
# The local app. localhost reaches IPv4 or IPv6 loopback listeners.
baseUrl: http://localhost:3003
端口换了的话,应用侧用 PORT=4000 bun run start 启动,再让 baseUrl 对上。默认的 3003 来自应用自身(process.env.PORT || 3003)。
内部结构¶
插件分两半,中间隔着一条代理。index.js 是 Host 端,client.js 是 Client 端。
应用是一个不发 CORS 头的本地进程,Harness 的插件指引又不允许用 iframe 包页面,所以 Host 端注册了一个同源代理路由,Client 端以 /closet-api/api/... 调用。代理的行为比较克制:
- 只转发
/closet-api/api/*,其余路径不代理 - 保留方法、请求体、上游状态码和 content-type
- 上游只收到
host、content-type、accept;Harness 会话 cookie 和Authorization都不转发 - 走 Harness Connection 的浏览器认证,Connection 服务不可用时 fail closed,返回 401
- 上游地址由
config.baseUrl固定,请求路径改不动它;上游超时 45 秒;应用不可达时回结构化的 502 JSON
Client 端用 Harness module table 里的 React 渲染,只用 --dsw-alias-* 主题 token,可见文案都走 Harness locale 服务注册。注册点一共三个:sidebar.panellist(id: closet-tracker,order: 30,衣架图标)、main(key: closet-tracker → 愿望清单面板)、Host 路由前缀 /closet-api。
典型用法¶
装好之后,日常就是三条路。
1、看到喜欢的商品,把链接粘进 Web 应用,应用抓取后把条目写入 items.json;Harness 面板刷新即可看到新条目。
2、在面板里用状态页签切换 All / Wishlist / Bought,用分类筛选和搜索定位,按价格或降价幅度排序。单品操作里可以现在查价(立即请求应用抓一次)、互切愿望清单与已购、两步确认删除。
3、想整体看看降了多少,点 Sync all,应用重新抓取每个在跟踪的链接并回报降价。
测试部分用 Node 自带的 test runner,不需要 Bun 也不需要真实应用——测试会自己起一个桩上游:
npm test # node --test: proxy forwarding, allowlist, gate, header hygiene
node --check index.js client.js # syntax
cd app && bun run start # run the app itself
适用场景与注意¶
适合两类人:一是想要 Harness 里一个隐私可控的购物清单面板、且愿意自己跑一个本地应用的开发者;二是想研究 DSH 插件里 Host / Client 两半怎么配合、代理还要守哪些边界的人——test/proxy.test.mjs 覆盖了转发白名单、认证门、请求头卫生,可以当参考。
几点要注意。插件以当前 DSH 进程的权限运行,所以安装前先读一遍 index.js、client.js 和 cordis.patch.yml,以及它的许可证;许可证是 MIT,仓库里另有一份打包在 app/public/vendor/ 的 jsPDF,同样由其作者以 MIT 授权。应用的 app/data/ 是个人数据目录,已被 gitignore。Web 应用不依赖任何框架,跑在任意浏览器里也能用,不装插件不影响它本身。README 明确说 Amazon 和 Ajio 的链接抓取效果差(两者都挡爬虫),条目仍会加入,需要用应用的编辑对话框手动补全。
常见故障和处理:
- 面板提示 “Closet Tracker is not reachable at http://localhost:3003”:先跑
cd app && bun run start;面板自身每 12 秒会重试,也可以点 Try again - 装完边栏图标不显示:重新加载一次 Harness 页面,让新的 client 模块生成生效
- 面板显示的是别的应用的数据:确认
cordis.patch.yml里的baseUrl与应用端口一致 - 端口占用:
cd app && make stop会清掉占用该PORT(默认 3003)的监听进程;用别的端口启动时要同步改baseUrl。注意make stop是按端口杀的,确认是你要杀的东西,可以用lsof -nP -iTCP:3003 -sTCP:LISTEN查一下
结尾¶
这个插件的价值在于分工清楚:应用管数据和抓取,插件管 Harness 里的呈现,中间用一条带认证和白名单的同源代理打通。两边数据是同一份,改一处就行。目录页与源码在下面。
- SkillHub 目录页:https://www.skillhub.cn/plugins/ramanasai/dsh-closet-tracker
- GitHub:https://github.com/ramanasai/dsh-closet-tracker