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

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

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

![終端機與 AI Coding Agent 透過本機 IPC 與 Native Messaging 安全橋樑，即時控制 Chromium 瀏覽器內的 Stylebot 擴充功能進行 CSS 樣式注入與畫面擷取的架構示意圖](https://stwillblogassets.blob.core.windows.net/files/images/2026/10/stylebot-cli-native-messaging/stylebot-cli-native-messaging-banner.webp)

> 本文分析基於 [ankit/stylebot](https://github.com/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 並執行初始化指令：

```bash
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 執行檔絕對路徑的啟動腳本：
    
    ```bash
    #!/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`：
    
    ```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」狀態提示](https://stwillblogassets.blob.core.windows.net/files/images/2026/10/stylebot-cli-native-messaging/stylebot-options-allow-cli-access.webp)

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

![開啟命令列控制選項時，瀏覽器彈出「Stylebot 要求其他權限：與合作原生應用程式建立連線」的授權對話方塊](https://stwillblogassets.blob.core.windows.net/files/images/2026/10/stylebot-cli-native-messaging/stylebot-native-messaging-permission-prompt.webp)

在完成授權的瞬間，這裡有一個非常值得注意的 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` 送交瀏覽器：
    
    ```javascript
    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 可以在終端機中完成從「探索頁面 → 建立設定檔 → 套用樣式 → 截圖驗證」的完整循環：

```bash
# 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 路徑，避免與日常使用的瀏覽器衝突：

```bash
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 打造出一套兼具安全性、即時視覺回饋與高度自動化潛力的前端樣式控制架構。

### 相關連結

-   [Stylebot CLI 官方文件（docs/cli.md）](https://github.com/ankit/stylebot/blob/main/docs/cli.md)
-   [Stylebot GitHub 儲存庫（ankit/stylebot）](https://github.com/ankit/stylebot)
-   [Chrome Extensions Native Messaging 官方開發文件](https://developer.chrome.com/docs/extensions/develop/concepts/native-messaging)
