瀏覽器當 Claude Code 的遙控器








從一個念頭、寫成文章、跑完整條 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 的按鍵名(Enter、Escape、Up、C-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 更是絕對不能開到公網,一開等於是把一台「能在我電腦上跑指令、還能直接發文到我社群帳號」的機器送出去。
想照抄的話,真正該抄的是這三個決定
- 不要重寫一個 AI,去驅動電腦上那支已經裝好、已經登入的 Claude Code。 省掉一整套計費、認證,也不用一路追著補功能,還天生跟本機環境完全一致。
- 要做決定的那一關,得能被機器認出來。 一行
::GATE::標記,就把「非得有人在終端機前面」變成「網頁上一顆按鈕」;而且要準備好它沒印的那條路。 - 遠端存取交給私有網路,不要自己做登入。 前提是你清楚知道邊界在哪,也守得住。
Studio 沒有做一個網頁版的 Claude Code——它只是把我那台 Mac 的鍵盤跟螢幕,接到我手上這支手機。
最近開始做免費的一對一諮詢,幫你把 AI 接進自己的工作流——有需要的話可以約:https://www.dawsonwang.com/