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

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

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

小夜