dsh-line-bot 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,172 @@
1
- # Temporary Holding Version
1
+ # dsh-line-bot
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <p align="center"><img src="icon.svg" width="96" height="96" alt="dsh-line-bot"></p>
4
+
5
+ **語言 / Languages:** **繁體中文** · [简体中文](README.zh-CN.md) · [English](README.en.md) · [日本語](README.ja.md) · [한국어](README.ko.md)
6
+
7
+ 把 LINE 官方帳號(Messaging API)接進 DeepSeek Harness 的插件。回覆由 DSH 的 Agent 在**同一個行程內**產生,等於在 LINE 上跟你的 DSH 助理聊天;人設有兩種來源:有安裝大肥魚(`dsh-persona-dafeiyu`)時可以直接選它的人設;也可以只用插件內建的自訂人設(不需要任何個性外掛)。
8
+
9
+ 內建:
10
+
11
+ - **Webhook 伺服器**:在本機 `127.0.0.1` 開一個 HTTP server,處理 LINE 的 `X-Line-Signature` 驗章、事件去重、每位使用者各自的佇列。
12
+ - **Cloudflare Tunnel**:一鍵開 quick tunnel(`*.trycloudflare.com`,免 Cloudflare 帳號),或貼上 Cloudflare API Token 建立具名隧道+DNS,取得固定網址;取得網址後自動把 webhook 端點寫回 LINE 並跑一次 `webhook/test`。
13
+ - **LINE 憑證**:填 Channel ID+Channel Secret 就能自動簽發 access token(`/v2/oauth/accessToken`,約 30 天,快到期會自動續簽);也可以直接貼上既有的 Channel Access Token。
14
+ - **人設**:面板可選大肥魚插件裡任何一個內建/自訂人設,或插件內建的自訂人設(存在 `<DSH_HOME>/line-bot/personas.json`,可新增/編輯/匯入)。人設是「逐 session」指定,不會改到你 GUI 裡其他對話的人設。
15
+ - **主動傳訊**:插件內建排程器(不用 launchd),可調機率、間隔、每日/每月上限、最短間隔、安靜時段、對方安靜多久才可打擾、指定對象與提示詞;Agent 覺得不適合時可以回 `SKIP` 跳過。
16
+ - **完整設定面板**:所有設定都在「設定 → LINE Bot」裡,存到 `<DSH_HOME>/line-bot/config.json`(權限 600)。
17
+
18
+ > 關於「用 LINE Business ID 登入、由面板代建 bot」:LINE 自 2024-09 起只允許在 [LINE Official Account Manager](https://manager.line.biz/) 手動建立 Messaging API channel,沒有公開 API;LINE Login 也只是身分驗證,不能取得官方帳號的管理權。所以面板不代建 bot,但會給你正確的入口連結,並把「拿到 Channel ID / Secret 之後」的所有步驟自動化(簽 token、設 webhook、測試)。
19
+
20
+ ## 安裝
21
+
22
+ 需求:DeepSeek Harness **≥ 0.2.0-rc.1**、Node.js **≥ 22**。人設外掛(大肥魚 `dsh-persona-dafeiyu`)是**選用**的——不裝也能用插件內建的自訂人設。
23
+
24
+ ### 方式一:面板安裝(推薦)
25
+
26
+ DSH → **設定 → 外掛 → 安裝 bundle**,輸入 `dsh-line-bot` 後安裝,重新整理頁面。設定左側就會多一個 **LINE Bot**。
27
+
28
+ ### 方式二:npx 一鍵安裝
29
+
30
+ 先完全結束 DSH(桌面版關閉視窗後,確認選單列/程序沒有殘留),再執行:
31
+
32
+ ```bash
33
+ npx dsh-line-bot install
34
+ ```
35
+
36
+ 它會自動判斷要裝進哪個 profile(預設 `desktop`,可用 `--profile <名稱>` 指定),用 DSH 內建的 pnpm 安裝套件、把 bundle 加進 profile,最後提醒你重開 DSH。
37
+
38
+ ```bash
39
+ npx dsh-line-bot status # 看目前裝在哪個 profile、有沒有啟用
40
+ npx dsh-line-bot uninstall # 移除(設定與憑證會保留)
41
+ ```
42
+
43
+ ### 方式三:手動
44
+
45
+ ```bash
46
+ dsh plugin --profile desktop add dsh-line-bot
47
+ ```
48
+
49
+ 再把 `dsh-line-bot` 加進 `~/.dsh/profiles/desktop/package.json` 的 `dsh.profile.bundles` 陣列(可參考 `npx dsh-line-bot install` 的做法),或改用面板安裝。
50
+
51
+ > 還沒發佈到 npm 時,也可以直接從 GitHub 安裝:
52
+ >
53
+ > ```bash
54
+ > npx github:Hanson0901/dsh-line-bot install --spec github:Hanson0901/dsh-line-bot
55
+ > ```
56
+ >
57
+ > 或從原始碼:`git clone https://github.com/Hanson0901/dsh-line-bot.git`,在 DSH 面板「安裝 bundle」選那個資料夾,或 `dsh plugin --profile desktop add /path/to/dsh-line-bot`。
58
+
59
+ ## 第一次上線(5 步)
60
+
61
+ 1. **建立/取得憑證**:到 [LINE Official Account Manager](https://manager.line.biz/) 建立官方帳號 → [LINE Developers Console](https://developers.line.biz/console/) 建立(或選用)Messaging API channel → 在「Messaging API」分頁把 **Use webhook** 打開 → 複製 **Channel ID** 與 **Channel secret**。
62
+ 2. **填入面板**:「LINE Bot → LINE 憑證」貼上 Channel ID、Channel Secret,按「儲存變更」→「驗證並讀取 bot 資訊」。成功會看到 bot 名稱與 Basic ID。
63
+ 3. **啟動 webhook 伺服器**:面板「狀態總覽」按「啟動全部」,或只按「啟動」讓 webhook server 與 tunnel 都起來(預設會自動啟動)。
64
+ 4. **開隧道**:Quick tunnel 直接按「啟動隧道」;要固定網址就在「Cloudflare Tunnel」貼 API Token →「驗證 API Token」→「建立/更新隧道與 DNS」(API Token 權限:Account → Cloudflare Tunnel → Edit、Zone → DNS → Edit、Zone → Zone → Read)。quick tunnel 每次重啟網址都會變(插件會自動把新網址寫回 LINE webhook);具名隧道的網址則固定。
65
+ 5. **自動設定 webhook**:隧道就緒後會自動 PUT 到 LINE;若沒成功,按「把隧道網址套到 webhook」再「測試 webhook」。之後在 LINE 加 bot 好友、傳訊息即可。
66
+
67
+ ## 面板各區
68
+
69
+ 面板最上方是**固定(sticky)儲存列**:捲到任何位置都看得到「儲存變更/重新整理」;儲存列下方是二級快速選單,按一下就會跳到對應區塊(捲動時也會自動高亮目前區塊)。
70
+
71
+ | 區塊 | 內容 |
72
+ | --- | --- |
73
+ | 狀態總覽 | webhook server/隧道/LINE/人設/主動傳訊的綠燈狀態、公開網址、一鍵上線與啟停 |
74
+ | LINE 憑證 | Channel ID、Channel Secret、Access Token、自動簽發、loading 動畫、bot 資訊、webhook 套用/測試、白名單 |
75
+ | Cloudflare Tunnel | quick/API Token 模式、Token、hostname、隧道名稱、Account/Zone ID、下載 cloudflared、啟停、自動設定 webhook、自行提供的公開網址 |
76
+ | 回覆人設 | 選擇人設(大肥魚或插件自訂)、新增/編輯/刪除自訂人設、把目前人設複製成自訂、強制套用、允許使用者自己換人設、切換大肥魚 session 模式、測試 Agent 回覆 |
77
+ | 主動傳訊 | 開關、機率、間隔、每日/每月上限、最短間隔、安靜時段、idle、指定對象、提示詞、立即試傳、統計 |
78
+ | 使用者 | 每位 LINE 使用者對應的 DSH session、專屬工作資料夾、目前人設(可直接改)、訊息數、上次互動、清空記憶/重設 session |
79
+ | 進階設定 | 連接埠、Agent provider/model/effort/cwd/逾時、一人一個工作資料夾(含根目錄)、歡迎/錯誤/空回覆、日誌、開發用 host 重新載入 |
80
+
81
+ ## 設定檔
82
+
83
+ - `<DSH_HOME>/line-bot/config.json`:所有設定,含 LINE secret/Cloudflare token(權限 600,不會回傳明文給面板)。
84
+ - `<DSH_HOME>/line-bot/state.json`:LINE 使用者 ↔ DSH session 對應、訊息數、主動傳訊計數、webhook 狀態。
85
+ - `<DSH_HOME>/line-bot/logs/YYYY-MM-DD.log`:每日日誌(面板也會顯示最近 400 行)。
86
+
87
+ `DSH_HOME` 預設是 `~/.dsh`。
88
+
89
+ ## 運作方式
90
+
91
+ 1. LINE 把訊息 POST 到 Cloudflare 隧道 → 插件 webhook server(驗章後立刻回 200)。
92
+ 2. 依 `userId` 找到(或建立)對應的 DSH session 與工作資料夾,決定這個 session 的人設:選大肥魚時透過它的 HTTP bridge 指定 session 人設;選插件自訂人設時,先請大肥魚在該 session 讓開(`none`),再由插件把自訂人設注入 system prompt。
93
+ 3. 用 `agents.create`/`agents.resume` 在同一個 DSH 行程內建立/接續 Agent,`followup` 一則使用者訊息,等到 turn 結束、子代理收斂、session flush。
94
+ 4. 從 session 事件取出最後一段 assistant 文字,用 LINE reply 回覆;reply token 過期就自動改 push。
95
+ 5. 主動傳訊排程器每分鐘檢查一次(間隔、安靜時段、上限、機率),符合條件就請 Agent 主動說一句,再 push 出去。
96
+
97
+ ## 人設來源
98
+
99
+ - **大肥魚(選用)**:有安裝 `dsh-persona-dafeiyu` 時,面板會列出它的人設(內建+你自訂的),LINE 會逐 session 指定,不會改到 GUI 其他對話。需要它處於 `session` 模式;面板會提示並可一鍵切換。
100
+ - **插件內建自訂人設**:不需要任何個性外掛。面板「回覆人設 → 自訂人設」可新增/編輯/刪除,資料存在 `<DSH_HOME>/line-bot/personas.json`(0600)。選到自訂人設時,插件會請大肥魚在該 session 停用(若它有安裝),再把自訂人設注入這個 agent 的 system prompt。
101
+ - 兩種人設可以在同一個下拉選單裡選;使用者自己用 LINE 指令切換時也一樣(`人設`、`人設 2`、`人設 名字`)。
102
+
103
+ ## 每位使用者各自的記憶與人設
104
+
105
+ - **記憶不共用**:每個 LINE 使用者都有自己的 DSH session,以及自己的 Agent 工作資料夾 `<DSH_HOME>/line-bot/users/<LINE userId>/`。Agent 看到的檔案、`AGENTS.md`、工作區記憶都是該使用者專屬的,不會讀到別人的。
106
+ 可在「進階設定 → 一人一個資料夾」關閉(關閉後全部共用 `agent.cwd`,等於回到舊行為);也能用 `agent.workspaceRoot` 指定放哪個根目錄。
107
+ - **人設可自選**:「回覆人設 → 允許使用者自己換人設」開啟時,LINE 使用者可以傳:
108
+ - `人設`(或 `人格`/`persona`)→ 列出可用人設與目前使用中的人設。
109
+ - `人設 2` → 用清單編號切換;`人設 阿茶` → 用名字切換;`人設 預設` → 回到面板預設。
110
+ 切換只影響該使用者,下一次回覆就會套用;管理員也可以直接在「使用者」表的人設下拉幫他改。
111
+ - **清空記憶**:使用者表按「清空記憶」會刪掉該使用者的 session 與專屬工作資料夾(含裡面的檔案),重建後從空白開始;只按「重設 session」則保留資料夾。
112
+
113
+ ## 貼吧學習自動同步
114
+
115
+ 「刷貼吧」外掛每天最多演化一次自己的語氣(存在 `<DSH_HOME>/tieba-duty/persona.json`),這和 LINE 用的大肥魚人設(`<DSH_HOME>/storages/dsh-persona-dafeiyu/personas.json`)原本是兩套、互不相通。這個外掛可以把它接起來:
116
+
117
+ - 面板「回覆人設 → 貼吧學習同步」可設定開關、同步目標人設(只能選大肥魚的人設;預設關閉,請先選一個)、檢查間隔(預設 60 分鐘)、是否同步性格數值、寫入後是否重載大肥魚外掛。這項功能需要安裝大肥魚外掛。
118
+ - 每次檢查會比對貼吧人設版本;有新版本才動作。寫入時只替換目標人設正文中的 `## 貼吧學來的口氣` 段落(沒有就附加),不會覆蓋其他手寫內容;性格數值(溫柔/毒舌/抽象/話癆/發圖傾向)可一併更新。
119
+ - 寫入前會備份到 `<DSH_HOME>/line-bot/persona-backups/personas-<時間>.json`。
120
+ - 寫入後預設會透過 Cordis loader 重啟 `dsh-persona-dafeiyu`,讓它重新讀取 store;重啟失敗只會記警告,人設文字仍已寫入,下次外掛重載時生效。
121
+ - 貼吧外掛若沒在跑、或貼吧人設不存在,檢查會跳過(`pending` 顯示尚未同步的原因)。
122
+
123
+ ## 貼吧動態(讓角色知道自己剛刷過)
124
+
125
+ 面板「進階設定 → 讓 Agent 知道貼吧動態」(預設開)會在每次回覆前,把「刷貼吧」外掛留下的紀錄整理成一小段背景事實注入 system prompt:
126
+
127
+ - 最近一次刷貼吧的時間(例如「昨天 22:41,5 小時前」)
128
+ - 今天已刷的場次(中午/晚上)
129
+ - 今天的貼吧日報有沒有產生
130
+ - 貼吧人設版本與最後一次回覆時間
131
+
132
+ 使用者問「你剛剛有刷貼吧嗎?」「今天刷過了嗎?」時,角色會照實際時間回答,不會編造。這個功能只讀 `<DSH_HOME>/tieba-duty/` 的檔案,不需要貼吧外掛正在跑,也不會寫入任何東西;讀不到紀錄時就不注入。
133
+
134
+ ## DeepSeek 餘額
135
+
136
+ 面板的「DeepSeek 餘額」卡會讀取 DSH 目前登入的 DeepSeek 帳號錢包餘額(只讀,不會儲值):
137
+
138
+ - 每輪回覆都會把目前餘額放進角色的背景資訊,所以可以直接問它「我還剩多少」。
139
+ - 低於門檻(預設 ¥10,可在面板調整)時,它會用**自己的語氣**傳一則訊息提醒你儲值;內文不是固定模板,是角色當場想的。
140
+ - 提醒有冷卻時間(預設 12 小時),並沿用「主動傳訊」的靜音時段與目標使用者設定。
141
+ - 需要 DSH 已登入 DeepSeek 帳號(deepseek-account)。沒登入或該 profile 沒掛這個服務時,卡片會顯示原因,其餘功能不受影響。
142
+ - 門檻用的是錢包金額,不是 token 數:DeepSeek 平台顯示的是金額,token 換算會隨模型與快取變動。
143
+
144
+ ## 疑難排解
145
+
146
+ - **面板顯示「無法連線到 LINE Bot 執行期」**:webServer 可能還沒就緒,重新整理即可;仍失敗就看 DSH 日誌裡 `[line-bot]` 的訊息。
147
+ - **webhook 測試失敗**:確認 `Use webhook` 已在 LINE console 打開、隧道網址是 https、且本機 webhook server 正在跑(面板綠燈)。
148
+ - **改了 lib/*.mjs 沒生效**:按「進階設定 → 重新載入 host 模組(開發用)」,不用重裝插件。
149
+ - **貼吧同步沒動作**:先看面板同步狀態的 `lastError`;確認貼吧外掛的人設檔存在、目標人設 id 正確。按「預覽差異」可比對目前段落與即將寫入的內容。
150
+ - **cloudflared 找不到**:按「下載 cloudflared」,插件會抓官方 release 放到 `<DSH_HOME>/line-bot/bin/`;也可以自己放一份並在 `tunnel.binaryPath` 指定。
151
+ - **Agent 回覆 403(模型只開放給 Codex)**:在「進階設定 → Agent 模型」把 provider/model 改成 `deepseek-account` / `deepseek-flash`(本機預設已設好),儲存後再測一次。
152
+ - **人設沒套用**:選大肥魚時,它必須是 `session` 模式;面板會提示,按「切換到大肥魚 session 模式」即可(這是它官方支援的設定,只影響「可不可以逐 session 指定人設」)。選插件自訂人設則不受此限制。
153
+ - **Cloudflare 回 530 / error code 1016**:具名隧道的 DNS CNAME 目標必須是 `<tunnel-id>.cfargotunnel.com`(不是 `.cfd.tunnel`);另外該子網域若同時被 A 記錄佔用,邊緣會先回那台主機,請確認 DNS 只留一筆 CNAME。
154
+
155
+ ## 隱私
156
+
157
+ - 這個插件不連任何第三方伺服器(除了你設定的 Cloudflare API、LINE API)。沒有遙測、沒有帳號。
158
+ - 所有設定與憑證只存在本機 `<DSH_HOME>/line-bot/config.json`(權限 600);面板只顯示遮罩後的值。
159
+ - LINE 使用者的 session、工作資料夾與人設選擇都存在本機,不會上傳。
160
+ - 倉庫與 npm 套件不含任何人的憑證、LINE userId、Cloudflare 帳號或網域。
161
+
162
+ ## 安全提醒
163
+
164
+ - 這等同把 DSH Agent 接到公開的 LINE 帳號:拿到 bot 的人可以讓 Agent 使用你電腦上的工具。建議在「白名單使用者 ID」填入自己的 LINE userId,只允許自己使用。
165
+ - 所有憑證都存在本機 `config.json`(600),面板只會看到遮罩後的值;API 只掛在 `127.0.0.1` 的 DSH webServer 上。
166
+ - Cloudflare API Token 建議只給必要權限,並在不用時撤銷。
167
+
168
+ ## 開發
169
+
170
+ - host 邏輯:`lib/*.mjs`(`util`/`config`/`line-api`/`persona`/`agent-runner`/`webhook-server`/`cloudflare`/`tunnel`/`nudge`/`tieba-sync`/`host`)。
171
+ - 面板:`client.js`(`window.__ModuleLoader__` 註冊 `settings.section`)。
172
+ - 進入點:`index.js`(掛 `/api/line-bot`、dev reload、卸載清理)。
@@ -0,0 +1,172 @@
1
+ # dsh-line-bot
2
+
3
+ <p align="center"><img src="icon.svg" width="96" height="96" alt="dsh-line-bot"></p>
4
+
5
+ **语言 / Languages:** [繁體中文](README.md) · [简体中文](README.zh-CN.md) · [English](README.en.md) · [日本語](README.ja.md) · [한국어](README.ko.md)
6
+
7
+ 把 LINE 官方账号(Messaging API)接进 DeepSeek Harness 的插件。回复由 DSH 的 Agent 在**同一个进程内**产生,等于在 LINE 上跟你的 DSH 助理聊天;人设有两种来源:有安装大肥鱼(`dsh-persona-dafeiyu`)时可以直接选它的人设;也可以只用插件内置的自定义人设(不需要任何个性插件)。
8
+
9
+ 内置:
10
+
11
+ - **Webhook 服务器**:在本机 `127.0.0.1` 开一个 HTTP server,处理 LINE 的 `X-Line-Signature` 验签、事件去重、每位用户各自的队列。
12
+ - **Cloudflare Tunnel**:一键开 quick tunnel(`*.trycloudflare.com`,免 Cloudflare 账号),或贴入 Cloudflare API Token 创建具名隧道+DNS,取得固定网址;取得网址后自动把 webhook 端点写回 LINE 并跑一次 `webhook/test`。
13
+ - **LINE 凭证**:填 Channel ID+Channel Secret 就能自动签发 access token(`/v2/oauth/accessToken`,约 30 天,快到期会自动续签);也可以直接贴入既有的 Channel Access Token。
14
+ - **人设**:面板可选大肥鱼插件里任何一个内置/自定义人设,或插件内置的自定义人设(存在 `<DSH_HOME>/line-bot/personas.json`,可新增/编辑/导入)。人设是「逐 session」指定,不会改到你 GUI 里其他对话的人设。
15
+ - **主动传讯**:插件内置排程器(不用 launchd),可调概率、间隔、每日/每月上限、最短间隔、安静时段、对方安静多久才可打扰、指定对象与提示词;Agent 觉得不适合时可以回 `SKIP` 跳过。
16
+ - **完整设置面板**:所有设置都在「设置 → LINE Bot」里,存到 `<DSH_HOME>/line-bot/config.json`(权限 600)。
17
+
18
+ > 关于「用 LINE Business ID 登录、由面板代建 bot」:LINE 自 2024-09 起只允许在 [LINE Official Account Manager](https://manager.line.biz/) 手动创建 Messaging API channel,没有公开 API;LINE Login 也只是身份验证,不能取得官方账号的管理权。所以面板不代建 bot,但会给你正确的入口链接,并把「拿到 Channel ID / Secret 之后」的所有步骤自动化(签 token、设 webhook、测试)。
19
+
20
+ ## 安装
21
+
22
+ 要求:DeepSeek Harness **≥ 0.2.0-rc.1**、Node.js **≥ 22**。人设插件(大肥鱼 `dsh-persona-dafeiyu`)是**可选**的——不装也能用插件内置的自定义人设。
23
+
24
+ ### 方式一:面板安装(推荐)
25
+
26
+ DSH → **设置 → 插件 → 安装 bundle**,输入 `dsh-line-bot` 后安装,刷新页面。设置左侧就会多一个 **LINE Bot**。
27
+
28
+ ### 方式二:npx 一键安装
29
+
30
+ 先完全退出 DSH(桌面版关闭窗口后,确认菜单栏/进程没有残留),再执行:
31
+
32
+ ```bash
33
+ npx dsh-line-bot install
34
+ ```
35
+
36
+ 它会自动判断要装进哪个 profile(默认 `desktop`,可用 `--profile <名称>` 指定),用 DSH 内置的 pnpm 安装套件、把 bundle 加进 profile,最后提醒你重开 DSH。
37
+
38
+ ```bash
39
+ npx dsh-line-bot status # 看目前装在哪個 profile、有没有启用
40
+ npx dsh-line-bot uninstall # 移除(设置与凭证会保留)
41
+ ```
42
+
43
+ ### 方式三:手动
44
+
45
+ ```bash
46
+ dsh plugin --profile desktop add dsh-line-bot
47
+ ```
48
+
49
+ 再把 `dsh-line-bot` 加进 `~/.dsh/profiles/desktop/package.json` 的 `dsh.profile.bundles` 数组(可参考 `npx dsh-line-bot install` 的做法),或改用面板安装。
50
+
51
+ > 还没发布到 npm 时,也可以直接从 GitHub 安装:
52
+ >
53
+ > ```bash
54
+ > npx github:Hanson0901/dsh-line-bot install --spec github:Hanson0901/dsh-line-bot
55
+ > ```
56
+ >
57
+ > 或从源码:`git clone https://github.com/Hanson0901/dsh-line-bot.git`,在 DSH 面板「安装 bundle」选那个文件夹,或 `dsh plugin --profile desktop add /path/to/dsh-line-bot`。
58
+
59
+ ## 第一次上线(5 步)
60
+
61
+ 1. **创建/取得凭证**:到 [LINE Official Account Manager](https://manager.line.biz/) 创建官方账号 → [LINE Developers Console](https://developers.line.biz/console/) 创建(或选用)Messaging API channel → 在「Messaging API」分页把 **Use webhook** 打开 → 复制 **Channel ID** 与 **Channel secret**。
62
+ 2. **填入面板**:「LINE Bot → LINE 凭证」贴入 Channel ID、Channel Secret,按「保存更改」→「验证并读取 bot 信息」。成功会看到 bot 名称与 Basic ID。
63
+ 3. **启动 webhook 服务器**:面板「状态总览」按「启动全部」,或只按「启动」让 webhook server 与 tunnel 都起来(默认会自动启动)。
64
+ 4. **开隧道**:Quick tunnel 直接按「启动隧道」;要固定网址就在「Cloudflare Tunnel」贴 API Token →「验证 API Token」→「创建/更新隧道与 DNS」(API Token 权限:Account → Cloudflare Tunnel → Edit、Zone → DNS → Edit、Zone → Zone → Read)。quick tunnel 每次重启网址都会变(插件会自动把新网址写回 LINE webhook);具名隧道的网址则固定。
65
+ 5. **自动设置 webhook**:隧道就绪后会自动 PUT 到 LINE;若没成功,按「把隧道网址套到 webhook」再「测试 webhook」。之后在 LINE 加 bot 好友、传消息即可。
66
+
67
+ ## 面板各区
68
+
69
+ 面板最上方是**固定(sticky)保存栏**:滚到任何位置都看得到「保存更改/刷新」;保存栏下方是二级快速菜单,按一下就会跳到对应区块(滚动时也会自动高亮当前区块)。
70
+
71
+ | 区块 | 内容 |
72
+ | --- | --- |
73
+ | 状态总览 | webhook server/隧道/LINE/人设/主动传讯的绿灯状态、公开网址、一键上线与启停 |
74
+ | LINE 凭证 | Channel ID、Channel Secret、Access Token、自动签发、loading 动画、bot 信息、webhook 套用/测试、白名单 |
75
+ | Cloudflare Tunnel | quick/API Token 模式、Token、hostname、隧道名称、Account/Zone ID、下载 cloudflared、启停、自动设置 webhook、自行提供的公开网址 |
76
+ | 回复人设 | 选择人设(大肥鱼或插件自定义)、新增/编辑/删除自定义人设、把目前人设复制成自定义、强制套用、允许用户自己换人设、切换大肥鱼 session 模式、测试 Agent 回复 |
77
+ | 主动传讯 | 开关、概率、间隔、每日/每月上限、最短间隔、安静时段、idle、指定对象、提示词、立即试传、统计 |
78
+ | 用户 | 每位 LINE 用户对应的 DSH session、专属工作文件夹、目前人设(可直接改)、消息数、上次互动、清空记忆/重设 session |
79
+ | 高级设置 | 端口、Agent provider/model/effort/cwd/超时、一人一个工作文件夹(含根目录)、欢迎/错误/空回复、日志、开发用 host 重新载入 |
80
+
81
+ ## 配置文件
82
+
83
+ - `<DSH_HOME>/line-bot/config.json`:所有设置,含 LINE secret/Cloudflare token(权限 600,不会回传明文给面板)。
84
+ - `<DSH_HOME>/line-bot/state.json`:LINE 用户 ↔ DSH session 对应、消息数、主动传讯计数、webhook 状态。
85
+ - `<DSH_HOME>/line-bot/logs/YYYY-MM-DD.log`:每日日志(面板也会显示最近 400 行)。
86
+
87
+ `DSH_HOME` 默认是 `~/.dsh`。
88
+
89
+ ## 运作方式
90
+
91
+ 1. LINE 把消息 POST 到 Cloudflare 隧道 → 插件 webhook server(验签后立刻回 200)。
92
+ 2. 依 `userId` 找到(或创建)对应的 DSH session 与工作文件夹,决定这个 session 的人设:选大肥鱼时通过它的 HTTP bridge 指定 session 人设;选插件自定义人设时,先请大肥鱼在该 session 让开(`none`),再由插件把自定义人设注入 system prompt。
93
+ 3. 用 `agents.create`/`agents.resume` 在同一个 DSH 进程内创建/接续 Agent,`followup` 一则用户消息,等到 turn 结束、子代理收敛、session flush。
94
+ 4. 从 session 事件取出最后一段 assistant 文字,用 LINE reply 回复;reply token 过期就自动改 push。
95
+ 5. 主动传讯排程器每分钟检查一次(间隔、安静时段、上限、概率),符合条件就请 Agent 主动说一句,再 push 出去。
96
+
97
+ ## 人设来源
98
+
99
+ - **大肥鱼(可选)**:有安装 `dsh-persona-dafeiyu` 时,面板会列出它的人设(内置+你自定义的),LINE 会逐 session 指定,不会改到 GUI 其他对话。需要它处于 `session` 模式;面板会提示并可一键切换。
100
+ - **插件内置自定义人设**:不需要任何个性插件。面板「回复人设 → 自定义人设」可新增/编辑/删除,数据存在 `<DSH_HOME>/line-bot/personas.json`(0600)。选到自定义人设时,插件会请大肥鱼在该 session 停用(若它有安装),再把自定义人设注入这个 agent 的 system prompt。
101
+ - 两种人设可以在同一个下拉菜单里选;用户自己用 LINE 指令切换时也一样(`人设`、`人设 2`、`人设 名字`)。
102
+
103
+ ## 每位用户各自的记忆与人设
104
+
105
+ - **记忆不共享**:每个 LINE 用户都有自己的 DSH session,以及自己的 Agent 工作文件夹 `<DSH_HOME>/line-bot/users/<LINE userId>/`。Agent 看到的文件、`AGENTS.md`、工作区记忆都是该用户专属的,不会读到别人的。
106
+ 可在「高级设置 → 一人一个文件夹」关闭(关闭后全部共享 `agent.cwd`,等于回到旧行为);也能用 `agent.workspaceRoot` 指定放在哪个根目录。
107
+ - **人设可自选**:「回复人设 → 允许用户自己换人设」开启时,LINE 用户可以传:
108
+ - `人设`(或 `人格`/`persona`)→ 列出可用人设与目前使用中的人设。
109
+ - `人设 2` → 用列表编号切换;`人设 阿茶` → 用名字切换;`人设 预设` → 回到面板默认。
110
+ 切换只影响该用户,下一次回复就会套用;管理员也可以直接在「用户」表的人设下拉帮他改。
111
+ - **清空记忆**:用户表按「清空记忆」会删掉该用户的 session 与专属工作文件夹(含里面的文件),重建后从空白开始;只按「重设 session」则保留文件夹。
112
+
113
+ ## 贴吧学习自动同步
114
+
115
+ 「刷贴吧」插件每天最多演化一次自己的语气(存在 `<DSH_HOME>/tieba-duty/persona.json`),这和 LINE 用的大肥鱼人设(`<DSH_HOME>/storages/dsh-persona-dafeiyu/personas.json`)原本是两套、互不相通。这个插件可以把它们接起来:
116
+
117
+ - 面板「回复人设 → 贴吧学习同步」可设置开关、同步目标人设(只能选大肥鱼的人设;默认关闭,请先选一个)、检查间隔(默认 60 分钟)、是否同步性格数值、写入后是否重载大肥鱼插件。这项功能需要安装大肥鱼插件。
118
+ - 每次检查会比对贴吧人设版本;有新版本才动作。写入时只替换目标人设正文中的 `## 貼吧學來的口氣` 段落(没有就附加),不会覆盖其他手写内容;性格数值(温柔/毒舌/抽象/话痨/发图倾向)可一并更新。
119
+ - 写入前会备份到 `<DSH_HOME>/line-bot/persona-backups/personas-<时间>.json`。
120
+ - 写入后默认会通过 Cordis loader 重启 `dsh-persona-dafeiyu`,让它重新读取 store;重启失败只会记警告,人设文字仍已写入,下次插件重载时生效。
121
+ - 贴吧插件若没在跑、或贴吧人设不存在,检查会跳过(`pending` 显示尚未同步的原因)。
122
+
123
+ ## 贴吧动态(让角色知道自己刚刷过)
124
+
125
+ 面板「进阶设置 → 让 Agent 知道贴吧动态」(默认开)会在每次回复前,把「刷贴吧」插件留下的记录整理成一小段背景事实注入 system prompt:
126
+
127
+ - 最近一次刷贴吧的时间(例如「昨天 22:41,5 小时前」)
128
+ - 今天已刷的场次(中午/晚上)
129
+ - 今天的贴吧日报有没有生成
130
+ - 贴吧人设版本与最后一次回复时间
131
+
132
+ 用户问「你刚刚有刷贴吧吗?」「今天刷过了吗?」时,角色会按实际时间回答,不会编造。这个功能只读 `<DSH_HOME>/tieba-duty/` 的文件,不需要贴吧插件正在运行,也不会写入任何东西;读不到记录时就不注入。
133
+
134
+ ## DeepSeek 余额
135
+
136
+ 面板的「DeepSeek 余额」卡会读取 DSH 当前登录的 DeepSeek 账号钱包余额(只读,不会充值):
137
+
138
+ - 每轮回复都会把当前余额放进角色的背景信息,所以可以直接问它「我还剩多少」。
139
+ - 低于门槛(默认 ¥10,可在面板调整)时,它会用**自己的语气**发一条消息提醒你充值;内容不是固定模板。
140
+ - 提醒有冷却时间(默认 12 小时),并沿用「主动传讯」的静音时段与目标用户设置。
141
+ - 需要 DSH 已登录 DeepSeek 账号(deepseek-account)。未登录或该 profile 没有这个服务时,卡片会说明原因,其他功能不受影响。
142
+ - 门槛用的是钱包金额,不是 token 数。
143
+
144
+ ## 疑难排解
145
+
146
+ - **面板显示「无法连接到 LINE Bot 运行时」**:webServer 可能还没就绪,刷新即可;仍失败就看 DSH 日志里 `[line-bot]` 的消息。
147
+ - **webhook 测试失败**:确认 `Use webhook` 已在 LINE console 打开、隧道网址是 https、且本机 webhook server 正在跑(面板绿灯)。
148
+ - **改了 lib/*.mjs 没生效**:按「高级设置 → 重新载入 host 模块(开发用)」,不用重装插件。
149
+ - **贴吧同步没动作**:先看面板同步状态的 `lastError`;确认贴吧插件的人设文件存在、目标人设 id 正确。按「预览差异」可比对目前段落与即将写入的内容。
150
+ - **cloudflared 找不到**:按「下载 cloudflared」,插件会抓官方 release 放到 `<DSH_HOME>/line-bot/bin/`;也可以自己放一份并在 `tunnel.binaryPath` 指定。
151
+ - **Agent 回复 403(模型只开放给 Codex)**:在「高级设置 → Agent 模型」把 provider/model 改成 `deepseek-account` / `deepseek-flash`(本机默认已设好),保存后再测一次。
152
+ - **人设没套用**:选大肥鱼时,它必须是 `session` 模式;面板会提示,按「切换到大肥鱼 session 模式」即可(这是它官方支持的设置,只影响「可不可以逐 session 指定人设」)。选插件自定义人设则不受此限制。
153
+ - **Cloudflare 回 530 / error code 1016**:具名隧道的 DNS CNAME 目标必须是 `<tunnel-id>.cfargotunnel.com`(不是 `.cfd.tunnel`);另外该子域名若同时被 A 记录占用,边缘会先回那台主机,请确认 DNS 只留一笔 CNAME。
154
+
155
+ ## 隐私
156
+
157
+ - 这个插件不连任何第三方服务器(除了你设置的 Cloudflare API、LINE API)。没有遥测、没有账号。
158
+ - 所有设置与凭证只存在本机 `<DSH_HOME>/line-bot/config.json`(权限 600);面板只显示遮罩后的值。
159
+ - LINE 用户的 session、工作文件夹与人设选择都存在本机,不会上传。
160
+ - 仓库与 npm 套件不含任何人的凭证、LINE userId、Cloudflare 账号或域名。
161
+
162
+ ## 安全提醒
163
+
164
+ - 这等同把 DSH Agent 接到公开的 LINE 账号:拿到 bot 的人可以让 Agent 使用你电脑上的工具。建议在「白名单用户 ID」填入自己的 LINE userId,只允许自己使用。
165
+ - 所有凭证都存在本机 `config.json`(600),面板只会看到遮罩后的值;API 只挂在 `127.0.0.1` 的 DSH webServer 上。
166
+ - Cloudflare API Token 建议只给必要权限,并在不用时撤销。
167
+
168
+ ## 开发
169
+
170
+ - host 逻辑:`lib/*.mjs`(`util`/`config`/`line-api`/`persona`/`agent-runner`/`webhook-server`/`cloudflare`/`tunnel`/`nudge`/`tieba-sync`/`host`)。
171
+ - 面板:`client.js`(`window.__ModuleLoader__` 注册 `settings.section`)。
172
+ - 进入点:`index.js`(挂 `/api/line-bot`、dev reload、卸载清理)。
package/bin/cli.mjs ADDED
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env node
2
+ // dsh-line-bot 安裝小工具。
3
+ //
4
+ // 用法:
5
+ // npx dsh-line-bot install [--profile <名稱>] [--version <版本>] [--dry-run]
6
+ // npx dsh-line-bot uninstall [--profile <名稱>] [--dry-run]
7
+ // npx dsh-line-bot status [--profile <名稱>]
8
+ //
9
+ // 它做的事和 DSH 面板的「安裝 bundle」一樣:
10
+ // 1. 用 DSH 內建的 pnpm 把套件加進 profile;
11
+ // 2. 把套件名稱寫進 profile package.json 的 dsh.profile.bundles;
12
+ // 3. 提示重開 DSH。
13
+ // 不需要 DSH 正在執行;建議先關掉 DSH 再安裝,避免 node_modules 被同時使用。
14
+
15
+ import fs from 'node:fs'
16
+ import os from 'node:os'
17
+ import path from 'node:path'
18
+ import { spawnSync } from 'node:child_process'
19
+ import { fileURLToPath } from 'node:url'
20
+
21
+ const PKG = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
22
+ const NAME = PKG.name || 'dsh-line-bot'
23
+
24
+ function out(line = '') {
25
+ process.stdout.write(`${line}\n`)
26
+ }
27
+ function fail(message) {
28
+ process.stderr.write(`\n✖ ${message}\n`)
29
+ process.exit(1)
30
+ }
31
+
32
+ function parseArgs(argv) {
33
+ const options = { command: '', profile: '', version: '', spec: '', dryRun: false, enable: true }
34
+ const rest = [...argv]
35
+ options.command = (rest.shift() || 'help').toLowerCase()
36
+ for (let index = 0; index < rest.length; index += 1) {
37
+ const arg = rest[index]
38
+ if (arg === '--profile' || arg === '-p') {
39
+ options.profile = rest[index + 1] || ''
40
+ index += 1
41
+ } else if (arg.startsWith('--profile=')) {
42
+ options.profile = arg.slice('--profile='.length)
43
+ } else if (arg === '--version' || arg === '-v') {
44
+ options.version = rest[index + 1] || ''
45
+ index += 1
46
+ } else if (arg.startsWith('--version=')) {
47
+ options.version = arg.slice('--version='.length)
48
+ } else if (arg === '--dry-run' || arg === '-n') {
49
+ options.dryRun = true
50
+ } else if (arg === '--spec') {
51
+ options.spec = rest[index + 1] || ''
52
+ index += 1
53
+ } else if (arg.startsWith('--spec=')) {
54
+ options.spec = arg.slice('--spec='.length)
55
+ } else if (arg === '--no-enable') {
56
+ options.enable = false
57
+ } else if (arg === '--help' || arg === '-h') {
58
+ options.command = 'help'
59
+ } else {
60
+ fail(`不認得的參數:${arg}`)
61
+ }
62
+ }
63
+ return options
64
+ }
65
+
66
+ function dshHome() {
67
+ return process.env.DSH_HOME ? path.resolve(process.env.DSH_HOME) : path.join(os.homedir(), '.dsh')
68
+ }
69
+
70
+ function listProfiles() {
71
+ const dir = path.join(dshHome(), 'profiles')
72
+ if (!fs.existsSync(dir)) return []
73
+ return fs
74
+ .readdirSync(dir, { withFileTypes: true })
75
+ .filter((entry) => entry.isDirectory())
76
+ .filter((entry) => fs.existsSync(path.join(dir, entry.name, 'package.json')))
77
+ .map((entry) => entry.name)
78
+ .sort()
79
+ }
80
+
81
+ function pickProfile(requested) {
82
+ const wanted = requested || process.env.DSH_PROFILE || ''
83
+ const profiles = listProfiles()
84
+ if (wanted) {
85
+ if (!profiles.includes(wanted)) {
86
+ fail(`找不到 profile「${wanted}」。可用的 profile:${profiles.join('、') || '(沒有)'}`)
87
+ }
88
+ return wanted
89
+ }
90
+ for (const candidate of ['desktop', 'web', 'headless', 'tui']) {
91
+ if (profiles.includes(candidate)) return candidate
92
+ }
93
+ if (profiles.length === 1) return profiles[0]
94
+ fail(`偵測不到要安裝到哪個 profile,請加 --profile <名稱>。可用:${profiles.join('、') || '(沒有)'}`)
95
+ }
96
+
97
+ function profileDir(profile) {
98
+ return path.join(dshHome(), 'profiles', profile)
99
+ }
100
+
101
+ function readManifest(profile) {
102
+ const dir = profileDir(profile)
103
+ const file = path.join(dir, 'package.json')
104
+ if (!fs.existsSync(file)) fail(`profile 不存在:${dir}`)
105
+ try {
106
+ return { dir, file, manifest: JSON.parse(fs.readFileSync(file, 'utf8')) }
107
+ } catch (error) {
108
+ fail(`讀不懂 ${file}:${error.message}`)
109
+ }
110
+ }
111
+
112
+ function writeManifestAtomic(file, manifest) {
113
+ const backup = `${file}.bak-${Date.now()}`
114
+ fs.copyFileSync(file, backup)
115
+ const tmp = `${file}.tmp-${process.pid}`
116
+ fs.writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`)
117
+ fs.renameSync(tmp, file)
118
+ return backup
119
+ }
120
+
121
+ function bundlesOf(manifest) {
122
+ const bundles = manifest?.dsh?.profile?.bundles
123
+ return Array.isArray(bundles) ? [...bundles] : []
124
+ }
125
+
126
+ function withBundle(manifest, enabled) {
127
+ const bundles = bundlesOf(manifest)
128
+ if (enabled) {
129
+ if (!bundles.includes(NAME)) bundles.push(NAME)
130
+ } else {
131
+ const index = bundles.indexOf(NAME)
132
+ if (index >= 0) bundles.splice(index, 1)
133
+ }
134
+ return {
135
+ ...manifest,
136
+ dsh: {
137
+ ...(manifest.dsh || {}),
138
+ profile: { ...(manifest.dsh?.profile || {}), bundles },
139
+ },
140
+ }
141
+ }
142
+
143
+ function findPnpm() {
144
+ const localPnpm = process.platform === 'darwin'
145
+ ? '/Applications/DeepSeek Harness.app/Contents/Resources/runtime/primary-runtime/dependencies/pnpm/bin/pnpm.cjs'
146
+ : ''
147
+ if (localPnpm && fs.existsSync(localPnpm)) return { kind: 'pnpm', command: process.execPath, prefix: [localPnpm] }
148
+ const pnpm = spawnSync('pnpm', ['--version'], { stdio: 'ignore' })
149
+ if (pnpm.status === 0) return { kind: 'pnpm', command: 'pnpm', prefix: [] }
150
+ return null
151
+ }
152
+
153
+ function findDsh() {
154
+ const probe = spawnSync('dsh', ['--version'], { stdio: 'ignore' })
155
+ return probe.status === 0 ? 'dsh' : ''
156
+ }
157
+
158
+ function runInstall(profile, versionSpec, { dryRun }) {
159
+ const dir = profileDir(profile)
160
+ const dsh = findDsh()
161
+ let result
162
+ if (dsh) {
163
+ const args = ['plugin', '--profile', profile, 'add', versionSpec]
164
+ out(`→ ${dsh} ${args.join(' ')}`)
165
+ if (!dryRun) result = spawnSync(dsh, args, { cwd: dir, stdio: 'inherit' })
166
+ } else {
167
+ const pnpm = findPnpm()
168
+ if (!pnpm) {
169
+ fail(
170
+ '找不到 dsh 或 pnpm。請先安裝 DeepSeek Harness 桌面版(內含 pnpm),或改用面板「設定 → 外掛 → 安裝」。',
171
+ )
172
+ }
173
+ const args = [...pnpm.prefix, 'add', versionSpec, '--dir', dir]
174
+ out(`→ ${pnpm.command} ${args.join(' ')}`)
175
+ if (!dryRun) result = spawnSync(pnpm.command, args, { cwd: dir, stdio: 'inherit' })
176
+ }
177
+ if (!dryRun && result && result.status !== 0) {
178
+ fail(`安裝套件失敗(pnpm 結束碼 ${result.status})。請確認網路與 ${dir} 的權限後重試。`)
179
+ }
180
+ }
181
+
182
+ function commandInstall(options) {
183
+ const profile = pickProfile(options.profile)
184
+ const { dir, file, manifest } = readManifest(profile)
185
+ const versionSpec = options.spec || (options.version ? `${NAME}@${options.version}` : `${NAME}@${PKG.version}`)
186
+ out('')
187
+ out(`dsh-line-bot ${PKG.version}`)
188
+ out(`profile:${profile}(${dir})`)
189
+ out(`套件:${versionSpec}`)
190
+ if (options.dryRun) out('(dry-run:只顯示,不真的寫入)')
191
+ out('')
192
+
193
+ runInstall(profile, versionSpec, options)
194
+
195
+ if (options.enable) {
196
+ const next = withBundle(manifest, true)
197
+ if (JSON.stringify(next) !== JSON.stringify(manifest)) {
198
+ if (options.dryRun) {
199
+ out(`→ 會把 ${NAME} 加進 ${file} 的 dsh.profile.bundles`)
200
+ } else {
201
+ const backup = writeManifestAtomic(file, next)
202
+ out(`✔ 已啟用 bundle,原檔備份:${path.basename(backup)}`)
203
+ }
204
+ } else {
205
+ out('• bundle 已經在啟用清單裡,略過。')
206
+ }
207
+ }
208
+
209
+ out('')
210
+ out('完成。下一步:')
211
+ out(' 1. 完全結束並重開 DeepSeek Harness(桌面版請確認選單列沒有殘留)。')
212
+ out(' 2. 設定 → LINE Bot,填入 Channel ID / Channel Secret。')
213
+ out(' 3. 按「一鍵上線」,到 LINE 加 bot 好友測試。')
214
+ }
215
+
216
+ function commandUninstall(options) {
217
+ const profile = pickProfile(options.profile)
218
+ const { dir, file, manifest } = readManifest(profile)
219
+ out('')
220
+ out(`移除 dsh-line-bot(profile:${profile})`)
221
+ if (options.dryRun) out('(dry-run:只顯示,不真的寫入)')
222
+
223
+ const next = withBundle(manifest, false)
224
+ if (JSON.stringify(next) !== JSON.stringify(manifest)) {
225
+ if (options.dryRun) out(`→ 會把 ${NAME} 從 ${file} 的 dsh.profile.bundles 移除`)
226
+ else {
227
+ const backup = writeManifestAtomic(file, next)
228
+ out(`✔ 已從 bundle 清單移除,原檔備份:${path.basename(backup)}`)
229
+ }
230
+ }
231
+
232
+ const dsh = findDsh()
233
+ if (dsh) {
234
+ const args = ['plugin', '--profile', profile, 'remove', NAME]
235
+ out(`→ ${dsh} ${args.join(' ')}`)
236
+ if (!options.dryRun) {
237
+ const result = spawnSync(dsh, args, { cwd: dir, stdio: 'inherit' })
238
+ if (result.status !== 0) out('(pnpm 移除失敗,可能本來就沒安裝;bundle 清單已處理)')
239
+ }
240
+ } else {
241
+ out('找不到 dsh 指令,請手動移除依賴,或用面板「外掛」卸載。')
242
+ }
243
+
244
+ out('')
245
+ out('重開 DSH 後生效。設定檔與 LINE 憑證會留在 <DSH_HOME>/line-bot/,不會被刪除。')
246
+ }
247
+
248
+ function commandStatus(options) {
249
+ const profile = pickProfile(options.profile)
250
+ const { dir, manifest } = readManifest(profile)
251
+ const dependency = manifest.dependencies?.[NAME] || manifest.devDependencies?.[NAME] || ''
252
+ const enabled = bundlesOf(manifest).includes(NAME)
253
+ out('')
254
+ out(`dsh-line-bot ${PKG.version}`)
255
+ out(`DSH_HOME:${dshHome()}`)
256
+ out(`profile:${profile}(${dir})`)
257
+ out(`依賴:${dependency || '(未安裝)'}`)
258
+ out(`bundle 啟用:${enabled ? '是' : '否'}`)
259
+ if (fs.existsSync(path.join(dshHome(), 'line-bot', 'config.json'))) {
260
+ out('設定檔:<DSH_HOME>/line-bot/config.json(已存在)')
261
+ }
262
+ }
263
+
264
+ function commandHelp() {
265
+ out('')
266
+ out(`dsh-line-bot ${PKG.version} — 把 LINE Bot 裝進 DeepSeek Harness`)
267
+ out('')
268
+ out('用法:')
269
+ out(' npx dsh-line-bot install 安裝並啟用(可加 --profile <名稱>)')
270
+ out(' npx dsh-line-bot uninstall 移除')
271
+ out(' npx dsh-line-bot status 看目前裝在哪個 profile')
272
+ out('')
273
+ out('選項:')
274
+ out(' --profile, -p <名稱> DSH profile(預設讀 DSH_PROFILE,否則 desktop)')
275
+ out(' --version, -v <版本> 指定要安裝的版本(預設跟這個 CLI 相同)')
276
+ out(' --spec <pnpm 規格> 直接指定套件規格,例如 github:Hanson0901/dsh-line-bot')
277
+ out(' --no-enable 只安裝套件,不寫入 bundle 清單')
278
+ out(' --dry-run, -n 只顯示會做什麼,不寫入')
279
+ out(' --help, -h 顯示這段說明')
280
+ out('')
281
+ out('不用這個 CLI 也可以:DSH 設定 → 外掛 → 安裝 bundle,輸入 dsh-line-bot。')
282
+ }
283
+
284
+ const options = parseArgs(process.argv.slice(2))
285
+ switch (options.command) {
286
+ case 'install':
287
+ case 'add':
288
+ commandInstall(options)
289
+ break
290
+ case 'uninstall':
291
+ case 'remove':
292
+ commandUninstall(options)
293
+ break
294
+ case 'status':
295
+ case 'list':
296
+ commandStatus(options)
297
+ break
298
+ case 'help':
299
+ case '--help':
300
+ commandHelp()
301
+ break
302
+ default:
303
+ commandHelp()
304
+ process.exitCode = 1
305
+ }