Stylebot CLI 運作機制剖析:如何透過 Native Messaging 驅動瀏覽器擴充功能

在網頁客製化與前端樣式調整的工作流中,Stylebot 是一款廣受歡迎的瀏覽器擴充功能,能讓使用者針對特定網站自訂並套用 CSS 樣式。隨著自動化需求與 AI 輔助開發工具(如 Claude Code 等 Coding Agent)的普及,Stylebot 在 v4 架構中推出了官方命令列工具 @stylebot/cli,讓開發者與自動化代理程式能夠直接自終端機檢視網頁結構、動態調整 CSS 樣式並擷取畫面驗證結果,無須手動開啟擴充功能的視覺化編輯器。

然而,Stylebot CLI 的設計並非直接從外部讀寫擴充功能的本機資料庫或設定檔,而是仰賴作業系統底層的跨處理序通訊(IPC)搭配 Chromium 原生的 Native Messaging 通訊協定,在執行期與瀏覽器內的擴充功能進行即時協作。本文將結合 Stylebot CLI 官方文件 與原始碼實作,深入剖析這套三層式遠端控制架構的運作機制。

終端機與 AI Coding Agent 透過本機 IPC 與 Native Messaging 安全橋樑,即時控制 Chromium 瀏覽器內的 Stylebot 擴充功能進行 CSS 樣式注入與畫面擷取的架構示意圖

本文分析基於 ankit/stylebot 儲存庫中的 tools/cli 與 src/apps/background/cli 原始碼,適用環境為 Node.js 20 以上版本、Stylebot 4 以上版本,以及 Chrome、Edge、Brave、Chromium、Vivaldi 與 Arc(macOS)等 Chromium 核心瀏覽器。

為什麼不能直接從外部修改擴充功能檔案?

在探討 Stylebot CLI 的架構之前,許多開發者第一個冒出的問題通常是:為什麼 CLI 不直接去改 Chrome Profile 目錄底下的擴充功能儲存空間就好?

在 Chromium 架構中,擴充功能透過 chrome.storage.local 儲存的設定與樣式表,底層是由瀏覽器管理的 LevelDB 資料庫。當瀏覽器正在執行時,該資料庫會被瀏覽器處理序鎖定(Lock),且擴充功能的背景服務(Background Service Worker)與已開啟分頁中的 Content Script 會在記憶體中維護編譯後的樣式狀態。如果外部 CLI 嘗試繞過瀏覽器直接竄改磁碟檔案,不僅容易觸發檔案鎖定衝突或破壞 LevelDB 資料完整性,已開啟的網頁分頁也無法收到變更通知,必須重啟瀏覽器或重新整理頁面才能生效。

更關鍵的是,AI Coding Agent 在調整網頁樣式時,需要的是一個閉迴路(Closed-loop)的互動環境:它必須先讀取目標分頁的 DOM 大綱(Outline)、查詢現有的 CSS 變數與計算樣式(Computed Styles)、套用新 CSS、檢查選擇器(Selector)命中數量與文字對比度,最後擷取分頁畫面(Screenshot)確認視覺效果。這些動態操作都只有執行中的瀏覽器擴充功能才有權限完成。

核心元件與架構分工

為了讓終端機指令能安全且即時地驅動瀏覽器擴充功能,Stylebot 將整個通訊系統拆解為三個主要元件,形成標準的「客戶端 → 本機中介橋樑 → 瀏覽器擴充功能」三層式架構:

元件名稱 執行位置 通訊介面(上游 / 下游) 核心職責
Stylebot CLI 使用者終端機(短生命週期處理序) Unix Domain Socket / Named Pipe(NDJSON) 解析命令列參數、自動檢查並維護 Host 安裝狀態、將操作意圖封裝為結構化 JSON 請求並輸出結果
Native Messaging Host 本機背景處理序(~/.stylebot/host/host.mjs) IPC Socket / Pipe ↔ 標準輸入輸出(stdin / stdout) 由瀏覽器自動啟動並常駐,負責在 CLI 的本機 Socket 連線與 Chromium Native Messaging 的 stdio 串流之間雙向轉發訊息
Stylebot 擴充功能本體 Chromium 瀏覽器(Background Service Worker 與 Content Script) chrome.runtime.connectNative ↔ WebExtension APIs 驗證通訊協定版本、管理專用預覽視窗、按需注入頁面檢查腳本、讀寫樣式設定並執行畫面擷取

1. Stylebot CLI

位於使用者終端機環境的命令列工具(套件名稱為 @stylebot/cli)。當使用者或自動化指令稿輸入操作命令時,CLI 擔任請求發起端(Client)的角色。它是一個短生命週期的 Node.js 處理序:每次執行命令時,它會將指令名稱(command)、參數(args)以及當前 CLI 的通訊協定版本(protocol 與 version)打包成單行 JSON(Newline-Delimited JSON,簡稱 NDJSON),透過本機 IPC 送往 Native Messaging Host,收到回應並輸出至終端機後即結束執行。

2. Native Messaging Host

這是一個獨立安裝在本機家目錄(~/.stylebot/)底下的輕量級轉發程式,主機識別名稱註冊為 dev.stylebot.cli。因為 Chromium 的安全沙盒限制擴充功能無法直接開啟網路連接埠或監聽本機 Socket,且外部終端機每次執行命令都是全新的處理序,無法直接掛載到已經啟動的瀏覽器 stdio 管線上;因此必須由這個常駐的 Native Messaging Host 扮演橋樑:

  • 面對瀏覽器擴充功能:透過標準輸入(stdin)與標準輸出(stdout),遵循 Chromium Native Messaging 的二進位長度前綴(4-byte Length-Prefixed JSON)格式維持長連線。
  • 面對終端機 CLI:在作業系統底層建立 IPC 監聽端點。在 macOS 與 Linux 環境下,它會建立權限為 0600(僅限當前使用者讀寫)的 Unix Domain Socket(預設路徑為 ~/.stylebot/cli.sock);在 Windows 環境下,因不支援相同語意的檔案路徑 Socket,則改用 Named Pipe(預設路徑為 \\.\pipe\stylebot-cli-<username>)進行監聽。

3. Stylebot 擴充功能本體

安裝於 Chrome、Edge 等 Chromium 核心瀏覽器中的擴充功能。擴充功能掌握瀏覽器分頁管理(chrome.tabs、chrome.windows)、DOM 結構存取、CSS 注入、樣式儲存(chrome.storage)以及畫面擷取(chrome.tabs.captureVisibleTab)等完整權限,是唯一真正對網頁與樣式資料庫執行更動的實體。

運作流程與生命週期

整個 Stylebot CLI 的運作生命週期可以劃分為三個階段:從初次部署註冊、權限授權與長連線建立,到每一次終端機指令的雙向傳遞。

階段一:安裝、Node 路徑釘選與主機註冊

在初次使用時,開發者會透過 npm 全域安裝 CLI 並執行初始化指令:

npm install -g @stylebot/cli
stylebot install

執行 stylebot install(或首次執行任意 stylebot 指令觸發 prepareHost())時,CLI 會在底層完成三件關鍵準備工作:

  1. 部署獨立的 Host 程式碼與版本標記:將 host.mjs 與 paths.mjs 複製到 ~/.stylebot/host/ 目錄下,並將當前 CLI 版本號寫入 ~/.stylebot/host/version。如此一來,即便開發者日後切換工作目錄或移除原始碼,瀏覽器啟動的 Host 依然能穩定運作。

  2. 產生釘選 Node.js 絕對路徑的啟動腳本(Launcher):在 macOS 與 Linux 上,GUI 瀏覽器是由作業系統的視窗管理員直接啟動,其環境變數 PATH 通常不包含使用者在 .zshrc 或透過 nvm、fnm、Homebrew 設定的 Node.js 路徑。為了確保瀏覽器一定能喚起 Host,CLI 會讀取當前的 process.execPath,在 ~/.stylebot/native-host(Windows 為 native-host.cmd)寫入明確指定 Node 執行檔絕對路徑的啟動腳本:

    #!/bin/sh
    exec "/opt/homebrew/bin/node" "/Users/username/.stylebot/host/host.mjs"
  3. 向系統中已安裝的 Chromium 瀏覽器註冊 Manifest:CLI 會掃描各瀏覽器的使用者資料目錄(檢查是否存在首次啟動後產生的 Local State 檔案),並註冊 Native Messaging 清單檔案 dev.stylebot.cli.json:

    {
      "name": "dev.stylebot.cli",
      "description": "Stylebot CLI",
      "path": "/Users/username/.stylebot/native-host",
      "type": "stdio",
      "allowed_origins": [
        "chrome-extension://oiaejidbmkiecgbjeifoejpgmdaleoha/",
        "chrome-extension://mjolbpfednnbebfapicajpifliopnnai/"
      ]
    }

    這份清單中的 allowed_origins 明確鎖定了 Stylebot 在 Chrome Web Store 與 Microsoft Edge Add-ons 的官方擴充功能識別碼(Extension ID),確保作業系統與瀏覽器只允許經過授權的 Stylebot 擴充功能呼叫此 Host。

不同作業系統與瀏覽器尋找這份 Manifest 的位置各有差異(例如 Brave 在 macOS 與 Windows 會直接共用 Chrome 的註冊目錄,Vivaldi 在 Windows 也會讀取 Chrome 的登錄檔機碼)。Stylebot CLI 針對各平臺的註冊路徑整理如下:

瀏覽器 macOS(目錄下之 NativeMessagingHosts/) Linux(目錄下之 NativeMessagingHosts/) Windows(登錄檔 HKCU\Software\...\NativeMessagingHosts\dev.stylebot.cli)
Chrome ~/Library/Application Support/Google/Chrome ~/.config/google-chrome HKCU\Software\Google\Chrome
Edge ~/Library/Application Support/Microsoft Edge ~/.config/microsoft-edge HKCU\Software\Microsoft\Edge
Brave 共用 Google/Chrome 目錄 ~/.config/BraveSoftware/Brave-Browser 共用 Google\Chrome 機碼
Chromium ~/Library/Application Support/Chromium ~/.config/chromium HKCU\Software\Chromium
Vivaldi ~/Library/Application Support/Vivaldi ~/.config/vivaldi 共用 Google\Chrome 機碼
Arc ~/Library/Application Support/Arc/User Data 不支援 不支援

此外,CLI 內建了自動修復機制(refreshHost()):每次在終端機執行 stylebot 命令前,CLI 都會檢查 ~/.stylebot/host/version 是否與目前 CLI 版本一致,並檢查 native-host 啟動腳本中釘選的 Node.js 執行檔是否仍然存在。如果使用者升級了 Node.js 導致舊路徑失效,CLI 會自動重新釘選新的 Node.js 路徑,並提示使用者重啟瀏覽器以套用新的啟動腳本。

階段二:連線建立、權限啟用與 Service Worker 熱重載

基於最小權限原則,瀏覽器擴充功能預設無法任意啟動本機程式。使用者必須先在 Stylebot 的設定頁面(基本 / Options → Basics)中,手動開啟「允許這台電腦上的應用程式控制 Stylebot」(英文介面為 Allow command line access)選項。若尚未在終端機執行過安裝或尚未完成連線,開關下方會明確提示「尚未連線。請在終端機中執行: stylebot install」:

Stylebot 基本設定頁面中的「允許這台電腦上的應用程式控制 Stylebot」開關與「尚未連線。請在終端機中執行: stylebot install」狀態提示

一旦切換開啟該選項,擴充功能便會向瀏覽器動態請求選用的 nativeMessaging 權限,此時瀏覽器會彈出「『Stylebot』要求其他權限:與合作原生應用程式建立連線」的系統授權對話方塊,必須由使用者親自點擊「允許」才會生效:

開啟命令列控制選項時,瀏覽器彈出「Stylebot 要求其他權限:與合作原生應用程式建立連線」的授權對話方塊

在完成授權的瞬間,這裡有一個非常值得注意的 Chromium 底層實作細節:當執行中的 Background Service Worker 動態取得 nativeMessaging 權限時,Chromium 並不會把 chrome.runtime.connectNative API 直接注入到已經在執行中的 Worker 環境裡,必須等擴充功能重新啟動後才會掛載該 API。

因此,Stylebot 在 src/apps/background/cli/index.ts 中實作了一段無縫重載邏輯:

  1. 當偵測到 cliAccess 已開啟且具備權限,但 typeof chrome.runtime.connectNative !== 'function' 時,擴充功能會先將目前已開啟編輯器的分頁狀態暫存至 chrome.storage.local(cli-reloaded-for-grant),接著呼叫 chrome.runtime.reload() 重新啟動擴充功能。
  2. 由於擴充功能重啟會中斷原先已在各分頁運作的 Content Script,重啟完成後,Stylebot 會自動在所有已開啟的分頁重新注入 Content Script、清除舊面板殘留的 DOM 節點,並自動重新打開 Options 設定頁面,讓使用者感覺不到中斷。
  3. 重啟後的 Service Worker 即可順利呼叫 chrome.runtime.connectNative('dev.stylebot.cli')。瀏覽器隨即根據 Manifest 啟動本機的 ~/.stylebot/native-host,並將 Host 處理序的 stdin 與 stdout 綁定至擴充功能的通訊埠(chrome.runtime.Port)。
  4. Native Messaging Host 啟動後,會在 ~/.stylebot/cli.sock.started 寫入啟動時間戳記(供 CLI 在連線失敗時診斷「從未啟用設定」與「瀏覽器目前已關閉」的差異),並開始監聽本機 Socket 或 Named Pipe,直到瀏覽器關閉或使用者停用該選項導致 stdin 結束(EOF)為止。

階段三:指令傳遞、封包轉換與執行

當開發者或 AI Agent 在終端機執行具體操作(例如讀取分頁列表、選取元素、修改 CSS 樣式或截圖)時,完整的資料流向與協定轉換過程如下:

+---------------------+                                         +-----------------------+
|                     |  1. NDJSON 請求 (\n 結尾)               |                       |
|    Stylebot CLI     | --------------------------------------> | Native Messaging Host |
|   (@stylebot/cli)   |  Unix Socket (~/.stylebot/cli.sock)     |  (dev.stylebot.cli)   |
|                     |  或 Windows Named Pipe                  |                       |
|                     | <-------------------------------------- |                       |
+---------------------+  6. NDJSON 回應並關閉連線               +-----------------------+
                                                                      |           ^
                                        2. stdout 轉發                |           | 5. stdin 接收
                                        [4-byte UInt32LE 長度][JSON]  |           | [4-byte UInt32LE 長度][JSON]
                                                                      v           |
+---------------------+  3. 調用 API 或按需注入 cli-inspector   +-----------------------+
|    目標網頁分頁     | <-------------------------------------- |   Stylebot 擴充功能   |
| (DOM / CSS / 截圖)  | --------------------------------------> |  (Background Worker)  |
+---------------------+  4. 回傳檢查報告或 PNG 畫面資料         +-----------------------+
  1. CLI 發送請求:Stylebot CLI 連線至本機的 Unix Domain Socket 或 Named Pipe,將包含 command、args、protocol: 1 與 version 的 JSON 字串加上換行符號 \n 發送給 Native Messaging Host。

  2. Host 封裝並經由 stdout 轉發至瀏覽器:Native Messaging Host 從 Socket 讀入一行 JSON 後,會為該連線分配一個遞增的請求識別碼 id(存入 pending Map 中追蹤),再依照 Chromium Native Messaging 規範,在 JSON UTF-8 位元組前方加上 4 個位元組的 32-bit 無號整數(Little-Endian UInt32LE)長度標頭,寫入自身的 process.stdout 送交瀏覽器:

    const send = message => {
      const body = Buffer.from(JSON.stringify(message));
      const header = Buffer.alloc(4);
      header.writeUInt32LE(body.length);
      process.stdout.write(Buffer.concat([header, body]));
    };
  3. 擴充功能驗證協定並操作目標分頁:擴充功能的 Background Service Worker 透過 port.onMessage 收到解析後的 JSON 物件後,會先比對請求中的 protocol 編號是否與擴充功能內建的 CLI_PROTOCOL 一致(若 CLI 與擴充功能因各自從 npm 或商店更新而產生版本落差,會立即回報應更新哪一端)。驗證通過後,Service Worker 會根據指令類型執行對應操作:

    • 按需載入檢查器(Lazy-loaded Inspector):當執行 outline、css-variables、page-rules、computed-styles 或 match-count 等頁面讀取指令時,擴充功能只有在 CLI 首次詢問該分頁時,才會將 cli-inspector 腳本載入該分頁,確保平時未受 CLI 操作的一般分頁完全不會背負額外的腳本開銷。
    • 智慧樣式寫入與驗證(css set):寫入 CSS 時,擴充功能不僅會拒絕語法錯誤的 CSS 並指出行號,還會自動補齊字型所需的 Google Fonts @import、將部分由前端框架動態產生的雜湊 class 名稱替換為穩定選擇器,並在樣式套用至開啟中的分頁後,立即檢測各選擇器的命中元素數量與文字可讀性(對比度問題)。
  4. 擴充功能經由 Host stdin 原路回傳結果:操作完成後,Service Worker 呼叫 port.postMessage(response) 將帶有相同 id 的結果送回。瀏覽器將此訊息以同樣的「4-byte UInt32LE 長度標頭 + JSON」格式寫入 Native Messaging Host 的 process.stdin。Host 解析出完整訊息後,根據 message.id 從 pending Map 找出對應的 CLI Socket 連線,寫入單行 JSON 並關閉 Socket,最終由 CLI 格式化輸出至終端機(或透過 --json 輸出原始 JSON)。

為 AI Coding Agent 打造的實戰設計細節

除了底層的 IPC 與 Native Messaging 橋接之外,Stylebot CLI 在指令設計上也處處針對 Claude Code 等 AI Coding Agent 的多步驟自動化工作流進行了最佳化:

1. 獨立且不搶焦點的 CLI 專屬視窗

當 Agent 執行 stylebot open news.ycombinator.com 時,Stylebot 並不會去切換使用者正在閱讀的分頁,而是透過 chrome.windows.create({ url, focused: false }) 在背景開啟一個與使用者當前視窗尺寸相同、但不會搶走畫面焦點(Unfocused)的 CLI 專屬視窗,並將視窗 ID 記錄在 chrome.storage.session 中。後續的開啟網頁都會重複利用這個背景視窗,工作結束後只需執行 stylebot done 即可將其關閉,完全不干擾使用者的日常瀏覽。

2. 強制具名目標以防止競態條件

在只讀取頁面資訊的指令(如 outline、css-variables)中,CLI 允許預設針對當前作用中分頁(Active Tab)操作;但凡是會修改樣式、擷取畫面或移動游標的指令(css set、profile、screenshot、hover、inspect),Stylebot 都強制要求必須明確指定目標網域或分頁 ID(Tab ID)。這是因為 AI Agent 往往會分多個步驟執行命令,若期間使用者切換了瀏覽器分頁,缺乏明確目標的指令就可能誤改其他網站的樣式。

3. 完整的閉迴路指令操作範例

透過這套機制,開發者或 AI Agent 可以在終端機中完成從「探索頁面 → 建立設定檔 → 套用樣式 → 截圖驗證」的完整循環:

# 1. 在不搶焦點的背景專屬視窗開啟目標網站,回傳 Tab ID(例如 54432929)
stylebot open news.ycombinator.com

# 2. 檢視頁面可見元素的 DOM 結構大綱與現有 CSS 變數
stylebot outline 54432929
stylebot css-variables 54432929

# 3. 查詢特定選擇器的現有規則、計算樣式與匹配元素數量
stylebot page-rules '.titleline > a' --tab 54432929
stylebot computed-styles body color background-color font-size --tab 54432929
stylebot match-count '.titleline > a' '.subline' --tab 54432929

# 4. 建立獨立的樣式設定檔(Profile)並寫入自訂 CSS(自動檢查選擇器命中數與對比度)
stylebot profile create "Claude: Dark" news.ycombinator.com --use
stylebot css set news.ycombinator.com --profile "Claude: Dark" < dark.css

# 5. 擷取套用樣式後的分頁畫面存為 PNG,完成後關閉 CLI 專屬視窗
stylebot screenshot 54432929 -o hn-dark.png
stylebot done

若需要同時啟動第二個測試用瀏覽器(例如在不同的 Git Worktree 進行自動化測試),還可以透過環境變數指定獨立的 Profile 與 Socket 路徑,避免與日常使用的瀏覽器衝突:

STYLEBOT_PROFILE_DIR=.chrome-dev-profile-test STYLEBOT_SOCKET=~/.stylebot/test.sock yarn start:chrome

架構優勢與安全考量

綜合上述剖析,Stylebot CLI 採用「本機 IPC + Native Messaging」而非直接讀寫擴充功能檔案的設計,具備以下三點核心優勢:

  1. 即時性與反應能力:樣式修改、DOM 結構檢視與無障礙對比度檢查,全部是在執行期的瀏覽器記憶體與實際渲染管線中動態完成。使用者與自動化 Agent 可以立即看到視覺變化、取得選擇器命中報告與最新截圖,無需重啟瀏覽器或重新載入分頁。
  2. 資料完整性與向後相容:擴充功能保留了完整的樣式管理邏輯、Profile 切換、Google Fonts 自動引入與快取同步機制,避免外部程式直接竄改底層 LevelDB 儲存空間時可能造成的資料庫損毀或狀態不同步;同時透過隨附於每次請求的 protocol 版本號握手,確保 CLI 與擴充功能不會因各自獨立更新而發生指令格式不相容。
  3. 嚴密的安全隔離:完整遵循 Chromium 的 Native Messaging 安全模型與作業系統權限控管:
    • 本機 Unix Domain Socket 強制設定為 0600(僅限該系統使用者本人可連線)。
    • Native Messaging Host 清單透過 allowed_origins 綁定官方擴充功能 ID,防止其他惡意擴充功能濫用。
    • 必須由使用者在瀏覽器內部的 Stylebot 設定介面明確開啟「允許這台電腦上的應用程式控制 Stylebot」(Allow command line access)並授予「與合作原生應用程式建立連線」(nativeMessaging)權限後,擴充功能才會啟動 Host 開始監聽;一旦關閉選項即刻斷開連線並釋放權限,有效降低非預期的安全性風險。

結論

Stylebot CLI 並非傳統意義上的本機設定檔修改器,而是一個以作業系統底層 IPC(Unix Domain Socket / Named Pipe)與 Chromium Native Messaging 技術為基礎的瀏覽器擴充功能遠端控制器。透過將終端機的 NDJSON 指令經由 Native Messaging Host 即時轉送至瀏覽器環境內部的 Stylebot 擴充功能,不僅完美繞開了瀏覽器沙盒與儲存空間鎖定的限制,更為人類開發者與 AI Coding Agent 打造出一套兼具安全性、即時視覺回饋與高度自動化潛力的前端樣式控制架構。

相關連結

留言評論