DAY 208

瀏覽器當 Claude Code 的遙控器

SLIDES · 8
Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 1Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 2Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 3Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 4Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 5Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 6Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 7Day 208 瀏覽器當 Claude Code 的遙控器 — 投影片 8
1 / 8

從一個念頭、寫成文章、跑完整條 pipeline、看三個平台的草稿、到最後按下發佈——全部在一個網頁上點完,這陣子我發文幾乎沒再打開過終端機。網址是 http://dawsons-macbook-pro:4317/,我叫它 Studio。

先講清楚,那個網址你點不進去。它沒開到公網,只在我自己的 Tailscale 私有網路裡通,跑在我書桌上那台 MBP,不是什麼公開服務。

這篇拆解三件事:它憑什麼在瀏覽器裡用我自己的 Claude Code 訂閱額度(而不是另外再付一筆 API 的錢)、為什麼整套操作可以不用再回去開終端機、還有為什麼我連躺在沙發上用手機都能發文。

一張圖看完它在幹嘛

  手機 / 筆電的瀏覽器
        │   打字、按按鈕、上傳截圖
        ▼
  Studio server(Node,port 4317)      ← 跑在 MBP 上,掛在 GUI 登入 session
        │   load-buffer → paste-buffer → Enter
        ▼
  tmux session「studio」(私有 socket)
        │
        ▼
  真正的 claude CLI ─── 讀 macOS 鑰匙圈裡的登入憑證
        │                → 額度算在我自己的訂閱帳號上
        │   畫面
        ▼
  server 每秒 capture-pane 抓一次 pane 文字
        │   SSE 推送
        ▼
  瀏覽器面板(即時看到 Claude 現在做到哪)

整張圖的重點在中間那一段:瀏覽器沒有自己跑 AI

這個網頁自己不會跑 AI

Studio 沒有接 Anthropic 的 API,也沒有實作任何「網頁版的 Claude Code」。它做的事說穿了很土法煉鋼:在我的 Mac 上開一個 tmux session(一個可以在背景常駐、之後隨時接回去的終端機工作區),裡面跑真正的 claude 這支要在終端機裡打指令的程式,然後把那個 session 的鍵盤跟螢幕接到瀏覽器上。

去程:我在網頁打的字,server 用 tmux load-buffer 塞進 tmux 的貼上緩衝區,paste-buffer 貼進 Claude Code 的輸入框,最後補一個 Enter。

回程:server 每秒用 capture-pane 抓一次那個 pane 的文字,用 SSE(伺服器主動把更新推給瀏覽器的長連線)推到網頁上。

所以網頁上那顆「Send to Claude」,本質上跟我坐在 Mac 前面自己敲鍵盤是同一件事。額度算在我自己的訂閱帳號上,因為跑的就是我自己登入的那支 CLI——沒有 API 金鑰、沒有中間層代打、沒有把存取權分給第二個人。一台自己的機器、一個自己的登入、一個人在用,瀏覽器在這裡的角色是遙控器,不是替身。

差別很實際:如果我改成用 Anthropic API 自己寫一個網頁版,那是另一套計費,訂閱額度一毛都用不到。

【為什麼送一行字這麼囉嗦?】

送字這一段是整套裡最囉嗦的地方,但沒有它,網頁按了等於沒按。

Claude Code 不會馬上把貼進來的內容收進輸入框,它會先等一下、把整段湊齊。這時候如果 Enter 按得太早,那個 Enter 會被吃掉,我的話就靜靜躺在輸入框裡沒送出去——網頁看起來一切正常,Claude 那邊什麼都沒發生。

所以這段程式是這樣走的:貼完之後每隔一下就去看一次畫面,等到那段文字真的出現在輸入框(或被摺成 [Pasted text …] 的小方塊),才按 Enter;按完再確認一次,還在的話就再按,最多重試 12 次。空的輸入框按 Enter 是沒作用的,所以多按幾次也不會怎樣。

另外整段文字是從 stdin 灌進 tmux 緩衝區,不是塞在指令參數裡。這樣換行、引號、$、反引號、開頭的 / 全部原封不動送過去——不會被 shell 解讀,開頭那個斜線也不會誤觸 Claude Code 的斜線指令選單。

【為什麼卡最久的不是介面,是 macOS 鑰匙圈?】

這是整個專案我覺得最值得講的一段,而且症狀很會騙人。

Claude Code 在 macOS 會優先讀登入鑰匙圈(keychain)裡的登入憑證,而鑰匙圈只對「GUI 登入 session(Aqua)裡跑的 process」解鎖。(它還有一份 ~/.claude/.credentials.json 的備援,但當時那份不是最新的,所以照樣過不了。)

第一版我從另一台機器 ssh 進去,把 server 掛在背景跑。結果:server 生出來的 claude 一律登入失敗(401),但我用螢幕共享開一個終端機、跑一模一樣的指令,完全正常。

第一層的解法是把 server 做成 GUI domain 的 LaunchAgent,plist 裡設 LimitLoadToSessionType = Aqua,用 launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.dawson.100days.studio.plist 掛上去。這樣它就活在 GUI session 裡,鑰匙圈讀得到。

第二層更陰。tmux 指令預設會連到「這台機器上已經存在的那個 tmux server」——而那個常常是我某次 ssh 進來時開的。它生出來的 process 活在 ssh 那個 session 底下,鑰匙圈一樣是鎖的。於是 server 本身明明在 GUI session 裡,驅動出來的 claude 還是 401。

這層是給 Studio 一個私有的 tmux socket(-L studio),保證那個 tmux server 是 Studio 自己開的、繼承 Studio 的 session。

兩個問題症狀一模一樣,都是 401,原因卻差了一層。要不是先確認「螢幕共享的終端機可以、ssh 掛的不行」,我大概會一直以為是登入壞掉。

網頁怎麼幫我按下那些「要不要」

這是「為什麼不用再回終端機」的答案。原本非得回終端機的,其實只剩下兩件事。先講最常遇到的那件:流程中途會停下來等我做決定——寫稿時標題要不要換、發佈前這篇要發哪幾個平台。(另一件是有些畫面得真的按鍵才回得了,下一段講。)

做法是 server 在送出 Run / Draft 這類會跑到決策點的指令時,在後面附上一段標準指示:你等一下要停下來等我做決定的時候,先印一行 ::GATE:: {"id":..., "question":..., "options":[...]},再問。

server 每次抓畫面先看有沒有在轉圈圈;一停下來,就去找最後一個 ::GATE:: 標記,解析出裡面的 JSON,在瀏覽器彈一個真的有按鈕的對話框。關鍵是這段指示是 server 在執行當下附加上去的,skill 檔案一個字都沒改。

而它不保證會照做——跑很久的 run 有時候就是忘了印。所以有一條 fallback:

  Claude 跑到要問我的地方
        │
        ▼
  server 看畫面上沒有轉圈圈的 spinner = 它停下來在等人
        │
        ├─ 有印 ::GATE:: {...}  → server 解析 → 瀏覽器彈出真的按鈕
        │
        └─ 沒印(它忘了)        → 開一個自由輸入框讓我打字回

照做就是按鈕,沒照做就是輸入框,不會卡死。

發佈那一關(p7-publish)的對話框會把三個平台的草稿跟要發的圖攤在同一頁。在那之前,stage 1 跑完什麼都還沒 commit、沒部署、沒發文,內容全部留在本機;我按下去才進 stage 2,才真的 commit、部署 /day/N 頁面、然後發文。

只會貼字還不夠,得能真的按鍵

有些東西不是「貼一段文字然後 Enter」能回的:Claude Code 自己的原生選單要用上下鍵選、Enter 確認;跑歪了想中途打斷要按 Esc。

所以另外開了一條路——瀏覽器把鍵盤事件翻成 tmux 的按鍵名(EnterEscapeUpC-c 這種),server 照著送。有一份白名單擋著(外加放行 C- / M- 這類組合鍵),不在名單上、又不是組合鍵、又不是單一字元的東西直接拒絕;一批最多 64 個鍵,免得鍵盤一卡住就一直送、變成無限迴圈。

有了這條,網頁才算真的是遙控器,而不是一個只能單向丟訊息的聊天框。這也是我不用再回去開終端機的最後一塊拼圖。

為什麼手機也能發文

因為那個 dawsons-macbook-pro 是 Tailscale 的 MagicDNS 名稱,我的 iPhone 也在同一個私有網路裡。Safari 直接打 http://dawsons-macbook-pro:4317/ 就連到 MBP——沒有 port forwarding、沒有反向代理、沒有把任何東西開到公網。

手機上實際會點的就這幾顆:

  • Capture:想到一個題目,丟一段話進去,它另外開一個一次性的 claude,把它存成 content/pending/ 底下的一筆待寫題目(跑在不同的 tmux socket,不會跟主 session 打架)
  • Draft:把素材丟進去,daily-writer 寫出當天文章的草稿 source.md,標題那關彈按鈕給我選
  • Review:讀 source.md、直接改三個平台的稿、點縮圖看要發的投影片
  • Run:跑整條 pipeline,停在發佈那關,看過草稿跟圖再決定

還能貼圖:手機裡的截圖直接上傳(單張上限 10 MB,png / jpeg / webp / gif),存進那天的資料夾,路徑跟著訊息一起送給 Claude。

老實講一個代價:Studio 本身沒有做任何登入驗證,而且它是綁在所有網路介面上的——沒開到公網,但同一個區網裡的裝置其實也連得到。真正該做的是把它綁在 Tailscale 那個位址上,或用防火牆擋掉其他介面,不然這個邊界比想像中鬆。這個 port 更是絕對不能開到公網,一開等於是把一台「能在我電腦上跑指令、還能直接發文到我社群帳號」的機器送出去。

想照抄的話,真正該抄的是這三個決定

  1. 不要重寫一個 AI,去驅動電腦上那支已經裝好、已經登入的 Claude Code。 省掉一整套計費、認證,也不用一路追著補功能,還天生跟本機環境完全一致。
  2. 要做決定的那一關,得能被機器認出來。 一行 ::GATE:: 標記,就把「非得有人在終端機前面」變成「網頁上一顆按鈕」;而且要準備好它沒印的那條路。
  3. 遠端存取交給私有網路,不要自己做登入。 前提是你清楚知道邊界在哪,也守得住。

Studio 沒有做一個網頁版的 Claude Code——它只是把我那台 Mac 的鍵盤跟螢幕,接到我手上這支手機。

最近開始做免費的一對一諮詢,幫你把 AI 接進自己的工作流——有需要的話可以約:https://www.dawsonwang.com/

延伸閱讀
看完整 208 篇 →