dsh-sooyo:把多个信息源拼成一个 bento 主页

前言

在 DSH 里做个人主页,常见做法是写一个固定页面,或者把 Markdown 笔记当成首页。前者要自己写前端和抓取逻辑,改一个源就要动一次代码;后者只能呈现已经整理好的内容,没法把外部信息源按卡片摆进去。

下面介绍的 dsh-sooyo 走的是另一种思路:插件在侧栏加一个「sooyo daily」,页面是一块 bento 网格,每格是一个 Widget,每个 Widget 可以挂不同的信息源。维护者是 CaiZongyuan,许可证 MIT,GitHub 仓库 CaiZongyuan/dsh-sooyo 当前 0 星,属于客户端类插件。

它要解决的问题是:日常要盯的几个信息源(AI新闻、论文、Product Hunt、YouTube 频道)和本地的任务、笔记、习惯,分散在各处,打开 DSH 时看不到「今天要做什么、有什么值得读、项目走到哪了」。

一句话定位

sooyo daily 是一个可组合、本地优先的个人空间:Board 装着 Widget,Widget 是用户摆上去的一个实例,Source 只负责供内容。

模型只有这一句,其它都是它的展开。

核心设计

这个插件把界面拆成三层,避免「一个源对应一张固定卡片」的耦合:

层 职责
Board 页面分段,总览、工作、生活、阅读四个开箱即用,也可以自己建
Widget 用户摆上去的一个实例;同一个 Widget Type 可以放好几份,各自选来源、呈现和尺寸
Source 只供 Item,外加「建议」一个默认呈现;不声明卡片

Size 用 S / M / L / Wide 表示四种信息层级,不是把一张卡拉伸。每种 Widget 只声明它真正支持的尺寸。

订阅 Widget 一个类型包五种呈现:图文、榜单、论文、产品、视频。加一个源不写客户端代码。

卡片流(Deck)一次一条,可拖动:右滑稍后读、左滑跳过,论文卡可以翻面看摘要,S 尺寸自动轮播,方向键也能用。

关注词命中标题、摘要、一句话总结或推荐理由的条目会带标记,开启「关注优先」排序后排到前面。

Widget 库

Widget 尺寸 说明
任务 S / M / L 今天要做的事,直接勾选完成;可以只看一个 Project
订阅 S / M / L / Wide 一个或多个来源的最新内容,可选五种呈现与卡片流
快速记录 M / Wide 一行记下想法,回车进收件箱;#note 开头存为 Note
书签 S / M / Wide 常去的网站一键直达
项目 S / M / L 按任务完成度算进度,不连外部服务
稍后读 S / M / L 存下来、晚点再读的文章
Agent 收件箱 S / M / L 后台审阅给出的建议,逐条批准或拒绝
习惯 S / M 每天打卡,看连续天数
专注计时 S / M 番茄钟,可关联一个任务,倒计时由宿主保存
日程 / 天气 / 简报 / 嵌入 — 需要一个数据源,尚未接上;标为「即将推出」,不显示假数据

内置信息源

源 默认呈现 封面来源 频率
AIHOT 精选 图文 + 评分与推荐理由 原文页 og:image 10 分钟
AIHOT 热榜 榜单(多源热点 Top 10) 无(编号即视觉) 30 分钟
HuggingFace 论文 论文真实封面 + 一句话总结 + 关键词 + 赞数/评论 HuggingFace CDN 或论文配图 6 小时
Product Hunt 今日 字母徽标 + 一句话 产品页 og:image 3 小时
YouTube · Dwarkesh Patel 完整节目 16:9,Shorts 竖屏货架 YouTube 缩略图 3 小时

AIHOT 和 Product Hunt 的接口本身不带图片,插件会去条目链接的页面读 og:image(也吃 twitter:image 和 JSON-LD)。每个页面只读一次,只读前 160KB 就断开,结果永久缓存;读不到图的页面会被记下来,不反复请求。

安装与启用

前置条件:DeepSeek Harness >=0.2.0-rc.1,Node.js >=20。插件自身版本 0.2.0,纯 JavaScript,package.json 里没有 dependencies,没有打包步骤。

从 GitHub 直接装(Web profile):

dsh plugin --profile web add github:CaiZongyuan/dsh-sooyo

桌面端可以在「设置 → 插件」里安装;本地开发时也可以用插件管理器指向本目录。

装好之后重启一次 DSH。宿主插件是按 specifier 缓存的,改动要新进程才生效。重启并刷新页面后,侧栏就会出现「sooyo daily」。

配置

配置写在 cordis.patch.yml 的 config 下:

config:
  mode: selected                 # AIHOT:selected 为编辑精选,all 为全部公开动态
  sources: []                    # 留空表示全部启用;只启用列出的源 id
  refreshMinutes: null           # 覆盖所有源的抓取节奏
  thumbnailBatch: 6              # 每轮补封面的页面上限,最大 20
  sync:
    role: standalone
    tokens: []
    context: true
字段 默认 说明
mode selected AIHOT 取 selected(编辑精选)还是 all(全部公开动态)
sources 全部 只启用列出的源 id
refreshMinutes — 覆盖所有源的抓取节奏
thumbnailBatch 6 每轮补封面的页面上限(最大 20)
sync.role standalone standalone / hub / client
sync.tokens / sync.token 环境变量 Hub 的设备令牌 / Client 的令牌,只存 Host 侧
sync.allowLoopbackOnly true Hub 是否只接受本机连接
sync.context true 是否在真人对话时注入 Sooyo Context

不写 sync 就是 standalone,老配置不用改。

多设备同步

三种角色:

角色 谁持有数据 抓取信源 适合
standalone(默认) 本机 本机 单机使用
hub 本机 本机 常驻服务器,对外提供受令牌保护的同步 API
client Hub 不抓 笔记本、手机等其它设备

Hub 侧配置:

sync:
  role: hub
  tokens:
    - { token: '至少-16-字符的随机串', device: macbook, access: write }

Client 侧配置:

sync:
  role: client
  endpoint: https://sooyo.example.com
  # 令牌建议走环境变量 SOOYO_SYNC_TOKEN

Client 缺 endpoint 或缺令牌时会明确报错,不会悄悄起一个空的本地库。切换到 client 之前先备份,切换本身不删除任何本地数据。

备份与迁移走 HTTP 接口:

GET  /sooyo/export                       # 导出 JSON(不含抓取到的条目)
POST /sooyo/import                       # 不带 apply:dry-run,逐表列新建/相同/冲突/拒绝
POST /sooyo/import  { apply: true }      # 真正写入;冲突仍然不覆盖
POST /sooyo/import  { overwrite: true, apply: true }   # 显式覆盖

导入永不删除、永不覆盖未确认的记录,中断后重跑不会产生重复实体。

Agent 工具

插件注册了若干工具,读与写分开:

工具 读/写 用途
sooyo_context_get 只读 今日/未完成/收件箱/最近笔记/待批建议的实时摘要
sooyo_task_list 只读 按列表、完成状态、Project 过滤任务
sooyo_task_create 写入 建任务(可带 Origin)
sooyo_task_update 写入 改任务,必须带 revision;被别的设备改过会返回 conflict
sooyo_note_capture 写入 记笔记,来源标记为 agent
sooyo_inbox_list 只读 看待审批的建议
sooyo_proposal_create 写入建议 写一条待批准的建议,不直接改数据
sooyo_review_run 只读+游标 后台审阅的确定性入口:先免费扫描,再 ack 看过的条目

保存 Todo 不调用任何模型。sooyo_action 这种能传任意命令名的通用工具是故意不做的。

后台审阅

只在 Hub 上跑一条 DSH Schedule,时间取自 GET /sooyo/review/schedule 的 spec。第一步是 sooyo_review_run 的确定性扫描:没有新增或改动过的事项时提示模型立刻结束这一轮,不产出任何内容。

建议不会直接改数据,而是进 Agent 收件箱等人工批准。同步 API 拒绝批准操作,所以模型无法批准自己写的建议。审批后按条执行,失败的标为 failed,不假装成功。

加一个源

源只供 Item,建议一个默认呈现,布局和尺寸由用户决定:

{
  id: 'my-source',
  label: '我的源',
  cadenceMinutes: 180,
  suggests: { pres: '图文' },
  async load(signal, previous) {
    // 返回 { items: [{ nativeId, title, url, publishedAt, summary, score, category, origin }], etag }
  },
}

想关掉某个源,编辑本包的 SOURCES 注册表把对应项注掉即可,这是唯一的信息源扩展点。

出网与安全

插件自带出网层:每源独立节奏、条件请求(ETag/304)、全局并发上限、失败按 1/2/4… 分钟递增重试,一个源坏掉不影响其它源。

抓到的第三方文本一律视为资料,不作为指令执行;每条内容都带来源与原文链接。AIHOT 明确允许个人非商业使用,插件保留 attribution 与原文链接;请同时遵守各站点的 robots 与使用条款。

适用场景与注意

适合已经在用 DSH、想要一个常驻侧栏的个人主页,并且能接受把任务、笔记、稍后读、习惯也放进插件里统一管理的用户。东西都是个人数据,落在 DSH 存储域上,Board、布局、任务、Note、稍后读、关注词、习惯、番茄钟都持久化。

前提是插件运行在当前 DSH 进程的权限范围内,能读到本机的配置和存储,也能按配置联网抓取信源。安装前应当打开源码过一遍,包括 cordis.patch.yml、SOURCES 注册表和自带的工具注册逻辑;许可证是 MIT,仓库公开,但社区目录是独立站点,与 DeepSeek / 幻方没有官方从属关系,它也不是官方应用商店。

仓库 0 星、0 fork,属于早期个人项目,接口和 Widget 形态后续都可能调整。当前版本 0.2.0 要求 DSH >=0.2.0-rc.1。

小结

dsh-sooyo 做的事不复杂:把 Board/Widget/Source 三层拆开,让信息源只生产 Item,让卡片形态和布局变成用户数据而不是代码。零依赖、零构建,装完重启就能用;需要多设备时再切 hub/client 角色。

目录页:https://www.skillhub.cn/plugins/CaiZongyuan/dsh-sooyo
仓库:https://github.com/CaiZongyuan/dsh-sooyo

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

小夜