部落格
從手機遠端執行 Claude Code
Tactic Remote 如何將你的 Mac 變成始終在線的 AI 程式開發伺服器,並將 iPhone 變成一流的控制面——包含真實的架構細節,而非行銷話術。
繼續查看該欄目下的產品與工程更新。
瀏覽此分類Claude Code 是一個基於終端機的 AI 程式開發助手。它在你的 Mac 上執行,讀取程式碼庫、撰寫程式碼、執行測試並提交變更。問題在於:它工作時你必須坐在 Mac 前面。
Tactic Remote 就是為了打破這個限制而存在的。Mac 仍然是執行引擎,iPhone 則成為控制面。本文將詳細介紹這一切是如何運作的——架構、協議、權衡取捨以及設定方法。沒有含糊其辭。
TL;DR: Mac 上的 Node.js WebSocket 伺服器透過 tmux 管理 Claude Code。iPhone 透過區域網路或 Cloudflare Tunnel 連線,發送命令並接收串流終端輸出。Claude 完成任務或需要輸入時,推播通知會提醒你。
架構
四個元件,三條網路路徑:
Node.js 伺服器僅有一個執行時相依套件:npm 套件 ws。其他一切——HTTP 伺服器、認證、速率限制、tmux 管理、檔案瀏覽——全部基於 Node.js 內建模組建構。這是刻意為之的。更少的相依套件意味著更少的故障點、更低的供應鏈風險和更小的攻擊面。
伺服器如何控制 Claude Code
這一部分出乎大多數人的意料:伺服器和 Claude Code 之間沒有 API。伺服器控制 Claude Code 的方式和你完全一樣——在終端機中輸入。
Claude Code 在一個 tmux 工作階段中執行。伺服器透過 tmux send-keys 發送按鍵,透過 tmux capture-pane 讀取輸出。這是終端自動化,不是 SDK 整合。
建立工作階段
當你從 iPhone 建立新工作階段時:
send-keys 的 -l 參數至關重要——它告訴 tmux 按字面方式解釋內容,防止特殊字元被當作 tmux 鍵名處理。
50,000 行的回捲緩衝區是 tmux 預設值的 25 倍。Claude Code 工作階段會產生大量輸出——編譯日誌、檔案差異、測試套件。如果不加大緩衝區,幾分鐘的高強度工作後你就會遺失上下文。
向 Claude 發送訊息
當你在 iPhone 上輸入提示語並點擊發送時:
開頭的 C-u 會清空輸入行。如果沒有它,當 Claude 在提示符中留有之前中斷輸入的殘留文字時,你的訊息就會追加到亂碼後面。
停止 Claude
停止是一個多步驟序列,因為 Claude Code 有兩種需要不同處理方式的狀態:
如果 Claude 正在執行中,Ctrl+C 會中斷它。如果 Claude 已經在提示符處,/exit 是 Claude Code 的退出命令。兩者都發送可以涵蓋兩種狀態。200ms 的延遲給 Claude 足夠的時間在接收下一個訊號前處理目前訊號。
終端輸出串流
這是整個系統中最難的工程問題。Claude Code 在編譯或測試執行期間以每秒數百行的速度輸出文字。這些輸出需要即時到達你的 iPhone,同時不能耗盡電池或壓垮網路。
輪詢迴圈
伺服器使用 1 秒的輪詢間隔擷取終端內容:
這會抓取最近 2,000 行的回捲內容,以保留 ANSI 跳脫碼的純文字形式。
為什麼用輪詢而不是事件驅動?因為 tmux 不提供變更通知。我們評估過對 tmux 內部 socket 使用檔案系統監控——在不同 macOS 版本間表現脆弱。1 秒的輪詢間隔是一個好的平衡:足夠快,讓輸出感覺是即時的;又足夠慢,在工作階段閒置時最小化 CPU 使用。
穩定性偵測
輸出監視器不只是串流發送原始擷取內容。它執行了一個穩定性偵測演算法:
- 每秒擷取終端內容
- 與前一次擷取進行比較
- 如果不同:將更新發送到 iPhone,重設穩定性計數器
- 如果連續 5 次擷取相同(5 秒穩定):檢查 Claude 是否在等待輸入
- 如果 Claude 在提示符處:發送帶有
final: true的最終更新,然後停止輪詢
這處理了 Claude 的漸進式渲染——在長輸出期間,終端變化很快。Claude 完成後,畫面趨於穩定。5 秒的穩定是 Claude 已完成的可靠訊號。
監視器有 120 次檢查(2 分鐘)的硬性上限。如果 2 分鐘後輸出仍在變化,監視器停止執行,用戶端可以請求手動重新整理。
如何偵測 Claude 的狀態
伺服器透過螢幕擷取 Claude Code 的 TUI 來判斷它在做什麼。這必然是啟發式的:
我們檢查終端輸出的最後幾行是否包含這些模式。如果看到 Goodbye! 或 Session ended,說明 Claude 已退出。如果看到 shell 提示符(行尾的 $)但沒有 Claude 特有的指示器,說明工作階段已退回到 shell。
這是否脆弱?有一定程度。如果 Claude Code 改變了 TUI 版面配置,我們的啟發式規則就會失效。但替代方案——在 Claude Code 中建構自訂 IPC 通道——需要維護一個 fork 並追蹤上游變更。螢幕擷取現有 TUI 是務實的選擇:它無需修改即可與每個版本的 Claude Code 搭配使用。
ANSI 顏色保留
一個關鍵的設計決策:伺服器保留輸出中的 ANSI 跳脫碼。它會剝離有問題的終端序列(游標移動、OSC 標題序列、私有模式變更),但刻意保留顏色碼:
iOS 應用程式隨後將這些代碼解析為原生 AttributedString 樣式。這讓你在手機螢幕上看到 Claude Code 的完整彩色輸出——語法高亮、diff 著色、狀態指示器——都被忠實地渲染出來。解析器支援標準 16 色、256 色模式和 24 位元真彩色。
WebSocket 協議
伺服器在同一個 WebSocket 連線上同時使用兩種協議——舊版扁平協議和新版請求/回應/事件協議。
新協議(建議使用)
三種框架類型:
id 欄位實現了正確的非同步請求多工。用戶端可以同時發起多個請求,並正確配對回應。舊版協議沒有請求 ID——回應是按位置對應的,如果你快速發送兩個請求,就無法分辨哪個回應屬於哪個請求。
完整方法目錄
| 方法 | 用途 |
|---|---|
list_sessions | 取得所有 tmux 工作階段的名稱、路徑和連線狀態 |
create_session | 在指定路徑建立新的 tmux 工作階段 |
attach_session | 切換到現有工作階段,取得其目前輸出 |
delete_session | 立即終止 tmux 工作階段 |
kill_session | 優雅地停止 Claude,然後終止工作階段 |
start_claude | 在目前工作階段中啟動 Claude Code |
stop_claude | 發送 Ctrl+C + /exit 序列 |
send_to_claude | 輸入訊息並按 Enter |
send_key | 發送單一按鍵(Enter、Escape、Tab、y、n、Ctrl+C) |
get_output | 取得目前終端內容和 Claude 的狀態 |
get_full_output | 取得最多 5,000 行的回捲內容 |
change_path | 變更工作目錄 |
list_directory | 瀏覽指定路徑的檔案 |
create_folder | 建立新目錄 |
send_key 方法有一個明確的白名單,包含 18 個按鍵。你不能發送任意按鍵序列——只能發送與 Claude Code 互動時有意義的按鍵。y 和 n 鍵專門用於在手機上回應 Claude 的權限提示。
網路:LAN vs. Cloudflare Tunnel
兩種連線模式解決兩類不同的問題。
區域網路(LAN)
你的 iPhone 透過 WiFi 直接連線到 Mac:
Mac 伴侶應用程式發布了一個 Bonjour 服務(_ws._tcp.),使你的 iPhone 可以自動發現區域網路內的 Mac。無需設定——開啟應用程式,點擊橫幅,即可連線。
延遲:往返低於 10ms。終端串流傳輸感覺是瞬間的。
限制:只能在同一網路中使用。走出家門就會斷開連線。
Cloudflare Tunnel
對於遠端存取,Mac 伴侶應用程式可以啟動 Cloudflare Tunnel:
隧道是從 Mac 發起的純出站連線。不需要在防火牆上開放連接埠。不需要路由器設定。Cloudflare 負責 TLS 終止、DDoS 防護和 WebSocket 代理。
快速隧道會產生一個隨機的 *.trycloudflare.com 子網域和一個隨機的 32 字元 API 金鑰:
無需 Cloudflare 帳號。免費。大約 10 秒即可啟動。
延遲:通常 30–80ms,取決於你與最近 Cloudflare 邊緣節點的距離。
權衡:每次重新啟動隧道 URL 都會變。如需固定 URL,你需要一個 Cloudflare 帳號和命名隧道(仍然免費)。
QR 碼快速連線
手動設定連線意味著在手機鍵盤上輸入 IP 位址、連接埠和 API 金鑰。這既容易出錯又很煩人。
Mac 伴侶應用程式產生一個包含連線詳情的 JSON 格式 QR 碼:
用 iPhone 應用程式透過 Vision 框架的條碼偵測器掃描即可。一次掃描就能完成整個連線設定——模式、位址、認證。大約 3 秒搞定。
通知管線
推播通知是 Tactic Remote「設好就忘」工作流的殺手級功能。啟動一個長任務,起身離開,Claude 完成或需要你輸入時收到通知。
運作原理
Claude Code 支援 hooks——在生命週期事件上執行的 shell 腳本。Mac 伴侶應用程式會自動設定這些 hooks,使其向本地伺服器發送 POST 請求:
當 Claude 完成任務時,此 hook 會觸發。伺服器接收 POST 請求,向所有已連線的用戶端廣播通知事件,iOS 應用程式透過 UNNotificationCenter 觸發本地通知。
事件去重
Hook 事件可能會快速連續觸發(例如,Claude 停止並重新啟動多個子任務)。iOS 應用程式使用 3 秒時間視窗進行事件去重,以 event|project|session|timestamp 為鍵。如果相同的邏輯事件在 3 秒內重複到達,重複的事件會被靜默丟棄。
前景 vs. 背景
當應用程式在前景時,通知事件會在終端緩衝區追加一個視覺指示器:
當應用程式在背景時,觸發系統通知。點擊通知可直接跳回你的工作階段。
安全模型
遠端執行 AI 產生的程式碼需要嚴肅的安全模型。以下是我們的方案。
認證
伺服器透過兩種機制支援 API 金鑰認證:
Authorization: Bearer <key>標頭(標準方式)Sec-WebSocket-Protocol: <key>標頭(為無法在升級請求中設定自訂標頭的 iOS WebSocket 程式庫提供的備用方案——這是常見的行動端限制)
認證在連線時檢查一次。如果失敗,WebSocket 會立即以代碼 1008(策略違規)關閉。
命令注入防護
伺服器執行的每個外部命令都透過 execSafe() 函式,該函式呼叫 child_process.spawn() 並設定 shell: false。參數以陣列形式傳遞,從不串接到字串中。Shell 注入在結構上是不可能的。
輸入驗證提供縱深防禦:
| 輸入 | 驗證規則 |
|---|---|
| 工作階段名稱 | /^[a-zA-Z0-9_-]{1,64}$/ |
| Claude 命令 | /^claude(\s+--?[a-zA-Z0-9-]+)*$/ |
| 按鍵名稱 | 明確的 18 鍵白名單 |
| 檔案路徑 | 解析為絕對路徑,使用 startsWith() 檢查 |
路徑穿越防護
檔案瀏覽被限制在使用者主目錄樹內。無論其他設定如何,敏感目錄始終被阻止存取:
檢查使用逐段比對(而非子字串比對),因此名為 my-.ssh-backup 的目錄不會觸發誤判。
速率限制
認證失敗的嘗試被限制為每個 IP 每分鐘 30 次,使用記憶體中的滑動視窗實作。超過限制後,伺服器回傳 429 Too Many Requests 並附帶 retryAfter 提示。
無雲端相依
你的程式碼永遠不會離開你的 Mac。Tactic Remote 是控制平面,而非資料平面。WebSocket 傳輸的是終端文字——命令輸入和輸出字串。原始碼檔案、git 歷史和建構產物都留在你的機器上。
使用 Cloudflare Tunnel 時,隧道對流量進行端到端加密。Cloudflare 看到的是加密的 WebSocket 框架,而不是你的程式碼。
iOS 應用程式:不是終端模擬器
開發早期,我們做了一個深思熟慮的決定:iPhone 應用程式不是一個笨重的終端模擬器。把完整的 80x24 終端塞進 4 吋的手機螢幕是糟糕的體驗。
相反,應用程式提供的是:
- 對話式輸入:在文字欄位中向 Claude 發送訊息,而不是終端提示符
- 終端輸出檢視:支援 ANSI 顏色的渲染文字,自動捲動,雙指縮放字體大小(預設 8pt)
- 快捷操作按鈕:Enter、Escape、Tab、Y、N、Ctrl+C——與 Claude Code 互動時你真正需要的按鍵
- 工作階段選擇器:瀏覽和切換 tmux 工作階段
- 路徑瀏覽器:導覽 Mac 的檔案系統來設定工作目錄
- 斜線命令:自動補全
/help、/clear、/compact、/cost、/model、/status
重新連線策略
行動應用程式所處的網路環境非常惡劣。你的手機會在 WiFi 和行動網路間切換,進入電梯,進入休眠。應用程式的重新連線策略處理了所有這些情況:
- 偵測到連線斷開:WebSocket 關閉事件觸發
- 指數退避:在
min(attempts * 3, 15)秒後重試,最多 5 次嘗試 - 背景感知:如果應用程式進入背景,跳過重新連線。設定旗標,在回到前景時重新連線
- 工作階段恢復:重新連線後,列出工作階段 → 找到之前活躍的工作階段 → 重新連接 → 以 0.2s、0.7s 和 1.5s 的延遲輪詢輸出
重新連線後的錯峰輸出輪詢(0.2s、0.7s、1.5s)處理了 tmux 在重新連接後需要一點時間才能產生擷取內容的情況。單次立即輪詢往往會回傳空內容。
終端緩衝區
TerminalBuffer 是一個環形緩衝區,上限為 500,000 字元和 10,000 行。溢出時,內容從前端截斷,對齊到最近的換行符邊界,這樣你永遠不會看到不完整的第一行。
這個上限存在的原因是 AttributedString 渲染在大規模時開銷很大。500K 字元大約相當於 30 分鐘的高強度 Claude Code 輸出——足以涵蓋任何合理的工作階段,同時保持 UI 的回應性。
Mac 伴侶應用程式
Mac 伴侶應用程式是一個選單列應用程式,負責管理 Node.js 伺服器、Cloudflare 隧道和系統設定。
伺服器打包
整個 Node.js 伺服器打包在 Mac 應用程式內部。首次啟動時,它將伺服器檔案複製到 ~/Library/Application Support/ClaudeCodeRemote/Server/。後續啟動時,它逐位元組比較檔案,只重新複製已變更的檔案。只有在 package.json 發生變化時才重新執行 npm install。
這意味著使用者永遠不需要開啟終端機來安裝或更新伺服器。
相依套件偵測
應用程式透過先掃描已知路徑(/opt/homebrew/bin/、/usr/local/bin/),然後退回到 which 來檢查所需工具(Node.js、tmux、Homebrew)。如果缺少相依套件,設定介面會引導使用者完成安裝。
防止休眠
當伺服器執行時,應用程式呼叫 IOPMAssertionCreateWithName 來阻止 macOS 進入休眠。這是必要的——休眠的 Mac 無法提供 WebSocket 連線。
Hook 自動設定
伴侶應用程式將 shell 腳本寫入 ~/.claude/hooks/,這些腳本會將生命週期事件透過 POST 發送到本地伺服器。這在伺服器啟動時自動完成——不需要任何手動設定。
我們沒有做的事
有些東西是我們刻意選擇不做的:
應用程式內不做終端多工。 我們在伺服端使用 tmux,但 iPhone 應用程式每次只顯示一個工作階段。在手機螢幕上做分割面板完全不可讀。
不做檔案編輯。 應用程式可以瀏覽檔案但不能編輯。Claude Code 負責檔案編輯——你是審閱者,不是編輯者。加入行動端程式碼編輯器是功能蔓延,會降低核心體驗。
沒有離線模式。 如果你的 Mac 不可達,應用程式會顯示最後已知的狀態,但無法做任何有用的事。我們不會裝模作樣。
不做遠端桌面備選方案。 螢幕共享和 VNC 確實存在,但對於這個使用情境它們差了 10 倍。Tactic Remote 發送的是結構化文字,而非像素。這意味著更低的頻寬、更低的延遲,以及為任務量身打造的 UI,而非縮小版的桌面。
快速開始
想跳過細節?以下是執行的最低設定。
Mac 端
- 安裝 Mac 伴侶應用程式,下載位址 tacticremote.com/download
- 啟動應用程式——它會自動安裝相依套件並啟動伺服器
- 記下 QR 碼,它顯示在應用程式中
iPhone 端
- 安裝 Tactic Remote,從 App Store 取得
- 掃描 QR 碼,來自 Mac 應用程式
- 建立工作階段,指向你的專案目錄
- 啟動 Claude 並發送你的第一條提示
對於遠端存取(家庭網路之外),在 Mac 應用程式的設定中啟用 Cloudflare Tunnel 開關。應用程式會自動產生公開 URL。
設定通知
在 Mac 應用程式中,hooks 在伺服器啟動時自動設定。在 iPhone 上,當系統提示時授予通知權限。就這些。
常見問題
關閉 iPhone 應用程式後 Claude 會繼續執行嗎?
會的。Claude 在你 Mac 上的 tmux 中執行。iPhone 應用程式只是那個工作階段的一個視窗。關閉應用程式,把手機放進口袋——Claude 繼續工作。再次開啟應用程式,你就會看到你不在時發生的一切。
網路斷了怎麼辦?
iPhone 應用程式會自動重新連線,使用指數退避(在約 45 秒內最多嘗試 5 次)。重新連線後,它會重新連接到之前的工作階段並取得累積的輸出。不會遺失任何工作——tmux 在 Mac 端緩衝了一切。
耗電量如何?
iPhone 應用程式不會維持持久的背景連線。進入背景時,它會正常斷開連線。不使用時對電池的影響基本為零。
可以用於其他 AI 程式開發工具嗎?
Tactic Remote 是圍繞 Claude Code 的終端介面建構的,但底層架構(tmux 工作階段管理、終端串流傳輸)適用於任何基於終端的工具。只要能在終端中執行,伺服器就能管理它。狀態偵測的啟發式規則是 Claude Code 特有的,但基本的 send-keys/capture-pane 迴圈是通用的。
我的程式碼安全嗎?
你的程式碼永遠不會離開你的 Mac。Tactic Remote 透過加密的 WebSocket 連線傳輸終端文字。伺服器沒有雲端元件、沒有遙測、沒有分析。使用 Cloudflare Tunnel 時,流量是端到端加密的——Cloudflare 代理的是加密框架,而非你的原始碼。
如果你想深入了解特定子系統,請參閱為什麼我們選擇 tmux 作為工作階段骨架、建構即時終端串流和安全模型文件。