返回列表

部落格

30 秒完成連線:`npx tacticremote` 分發路徑的建構之路

`npx tacticremote` 背後的工程故事,從 Bun 編譯二進位與輕量 npm 啟動器,到掃碼自動儲存和更快的首次引導。

發布於 標籤: engineering / cli / distribution / onboarding
部落格

繼續查看該欄目下的產品與工程更新。

瀏覽此分類

我們的目標說起來很簡單,實作起來卻出乎意料地難:新使用者只要執行一條指令、掃一下 QR Code,就能在 30 秒內把手機上的即時終端接起來。

這篇文章就是在講我們如何做到這件事。

問題在哪裡

在 npx tacticremote 出現之前,啟動伺服器是一個多步驟流程,而且會因平台而異:

  • macOS: 下載 DMG、掛載、把 App 移到 Applications、啟動、再和 Gatekeeper 周旋。
  • Linux / CI: clone 倉庫、跑 npm install,然後自己想辦法把伺服器在那個環境裡跑起來並維持執行。

對於在遠端 Linux VM 或 Docker 容器裡工作的開發者來說,這種流程非常痛苦。clone 倉庫這條路需要 git、Node.js,以及能正常存取 npm registry 的網路環境。根本沒有一條指令可以直接丟給別人說「就跑這個」。

我們想要的是:npx tacticremote → 掃碼 → 完成。

為什麼不直接 npm install

伺服器的執行期依賴不算多,但 npm install 依然會帶來明顯摩擦。現在的 runtime package 包含 ws、bonjour-service 和 qrcode-terminal。

npm 解析依賴本身就要時間,而伺服器在這之前什麼也不會開始做。在企業內網或容器化 CI 這類受限環境中,npm install 還可能因為 proxy 設定、registry mirror 或權限問題直接失敗。

更重要的是,CLI 路徑本身應該足夠輕。如果發佈到 npm 的套件還是帶著完整的 runtime tree,第一印象依然會是「先下載一堆 JavaScript,然後才開始伺服器」,這離我們想要的引導標準還差得很遠。

所以發佈到 npm 的那一層必須盡可能薄。

Bun 編譯方案

我們用 Bun 把伺服器編譯成單一、自包含的二進位檔。結果是一個大約 20 MB 的可執行檔,gzip 前就能直接執行,裡面包含:

  • Bun 執行環境
  • 所有 runtime 依賴
  • 完整的伺服器原始碼

沒有 node_modules,沒有額外安裝 Bun 的步驟,也沒有啟動時的依賴解析。你拿到二進位檔就能直接跑。

GitHub Actions(.github/workflows/build-cli.yml)會在每次發佈時建置四個目標平台:

  • bun-darwin-arm64 - Apple Silicon Mac
  • bun-darwin-x64 - Intel Mac
  • bun-linux-x64 - x86_64 Linux
  • bun-linux-arm64 - ARM Linux(Raspberry Pi、AWS Graviton 等)

每個目標產物都會以 gzip 壓縮檔的形式上傳到 GitHub Release。

薄封裝層

發佈到 npmjs.com 的套件不是伺服器本體,而是一個 124 行的啟動器腳本 bin/tacticremote.js。它實際做的事情如下:

  1. 用 process.platform 和 process.arch 判斷目前的作業系統與 CPU 架構
  2. 解析出對應的平台二進位名稱
  3. 在 ~/.tacticremote/bin 中檢查目前版本的快取二進位
  4. 如果沒有快取,就從 GitHub Releases 下載 gzip 壓縮的二進位,解壓後寫入磁碟並設定可執行權限
  5. 啟動該二進位,繼承標準輸入輸出,並轉送 SIGINT、SIGTERM 之類的訊號
const binaryName = `tacticremote-${platform}-${arch}`
const url = `https://github.com/TacticSpaceTech/TacticRemote/releases/download/cli-v${version}/${binaryName}.gz`
const cacheDir = path.join(os.homedir(), '.tacticremote', 'bin')
// 下載、gunzip、寫入本地快取、chmod +x、spawn

第一次執行時會下載對應的 release 產物並快取到本機。之後再次執行,就會重用目前 npm 套件版本對應的本機二進位。

這個模式不是什麼新發明。esbuild 是先驅,turbo 也在用,prisma 也有類似做法。核心概念是:npm 是分發管道,不是編譯環境。你要用它來分發下載器,而不是分發依賴樹。

package.json 的分工

我們並行維護兩份套件清單:

  • package.json - 開發設定,包含所有 devDependencies(Jest、ESLint、Prettier)、測試腳本、lint 腳本,以及本機開發時使用的完整依賴。
  • package.npm.json - 精簡後的發佈清單,只保留 bin 指向 tacticremote.js,並移除 runtime 依賴樹。

這種拆分讓開發清單與發佈清單各自清楚,但也帶來一個明確的維護成本:兩份清單中重疊的 metadata 必須保持一致。

目前這仍是流程中的已知脆弱點。倉庫裡還沒有一條發佈期校驗能保證這個切換永遠正確,所以 CI 自動化仍然是下一步最直接的改進。

30 秒引導流程

分發問題解決之後,我們就開始檢查連線流程的每一步,逐一移除摩擦。

第 0-5 秒。 使用者在終端執行 npx tacticremote。第一次執行時,啟動器會發現沒有快取二進位並下載它。之後每次執行,快取檢查都只要幾毫秒。

第 5-10 秒。 伺服器二進位啟動。它會偵測本機區域網路 IP、產生隨機 API key,並直接在終端用 Unicode 方塊字元印出 QR Code。沒有瀏覽器,也不需要打開任何 Web UI。

第 10-15 秒。 使用者在手機上打開 Tactic Remote。首頁會顯示「連接你的電腦」卡片,裡面有 npx tacticremote 指令和「掃描 QR Code」按鈕。點按按鈕就會開啟相機。

第 15-25 秒。 使用者掃描終端上的 QR Code。QR Code 內含伺服器 IP、連接埠與 API key 的 JSON payload。App 解碼這個 payload,並自動儲存伺服器設定,無需「新增伺服器」畫面、無需手動輸入 IP,也不用複製貼上 API key。

第 25-30 秒。 WebSocket handshake 完成。終端輸出開始串流到手機。連線指示燈亮綠。

QR 自動儲存這一步,移除了最多的摩擦。以前的引導流程需要:點「新增伺服器」、輸入 IP、輸入連接埠、輸入 API key、按儲存、再按連線。五次點擊、三次手動輸入,每一步都可能出錯。掃碼把這一切濃縮成一個手勢。

如果重做,我們會怎麼做

有幾件事我們知道不夠理想,而且還沒修掉:

還沒有簽章驗證。 啟動器會透過 HTTPS 下載二進位,但目前還不會拿公開簽章或校驗和來驗證檔案完整性。這仍是目前方案中最重要的完整性缺口。

清單切換還需要自動化。 我們把開發清單與發佈清單分開是合理的,但目前還缺少更嚴格的發佈期保護,來確保它們一直同步。

沒有備援鏡像。 如果 GitHub Releases 暫時不可用,啟動器就沒有第二個下載來源。增加備援鏡像會讓整體分發路徑更穩定。

更大的圖景

這條分發路徑圍繞一個原則設計:使用者不該被基礎設施細節卡住。他們不需要知道什麼是 Bun,不需要知道自己的 Linux 機器是什麼架構,也不需要手動抄 API key。指令負責起點,啟動器負責二進位,QR Code 負責連線資訊。

伺服器本身的架構、單一依賴設計與安全模型,已經在姊妹文章 《一個依賴:僅用 ws 套件建構生產級伺服器》 中說明。這篇文章講的是如何把那個伺服器更快送到使用者手上。

如果你還沒試過這個 30 秒流程,去 /download 試試看。


伺服器架構詳見《一個依賴:僅用 ws 套件建構生產級伺服器》。安裝說明詳見快速入門指南。

立即體驗 Tactic Remote

用手機遠端操作程式開發 Agent

連線到 Mac、Windows 或 Linux 電腦上的 Claude Code、Codex 等 Agent,在 iPhone 或 iPad 上查看進度並傳送下一個請求。