Преглед изворни кода

docs: add OpenCode setup guide (#582)

* docs: add OpenCode config guide to usage-doc

* docs: fix OpenCode Scoop command and refactor config snippet

* docs: remove OpenAI-compatible wording from OpenCode guide

* docs: refine OpenCode install and config
Ding пре 1 месец
родитељ
комит
d0cb42bda9

+ 102 - 0
messages/en/usage.json

@@ -473,6 +473,108 @@
     }
   },
 
+  "opencode": {
+    "title": "OpenCode Usage Guide",
+    "description": "OpenCode is a CLI + TUI AI coding agent that runs in your terminal and also provides IDE integrations. You can point OpenCode to cch as a single entry to access Claude, GPT, and Gemini models.",
+
+    "installation": {
+      "title": "Install",
+
+      "macos": {
+        "description": "On macOS, install OpenCode using one of the following methods:",
+        "homebrew": {
+          "title": "Option 2: Homebrew",
+          "description": "Install via Homebrew:"
+        }
+      },
+
+      "linux": {
+        "description": "On Linux, install OpenCode using one of the following methods:",
+        "homebrew": {
+          "title": "Option 2: Homebrew",
+          "description": "Install via Homebrew:"
+        },
+        "paru": {
+          "title": "Option 5: Paru (Arch Linux)",
+          "description": "Install via paru (AUR):"
+        }
+      },
+
+      "script": {
+        "title": "Option 1: Official install script",
+        "description": "Run the following command to install the latest version:"
+      },
+
+      "npm": {
+        "title": "Option 3: npm",
+        "description": "Install globally via npm:"
+      },
+
+      "bun": {
+        "title": "Option 4: Bun",
+        "description": "Install globally via Bun:"
+      },
+
+      "windows": {
+        "description": "On Windows, we recommend a package manager (Chocolatey/Scoop); npm also works:",
+        "choco": {
+          "title": "Option 1: Chocolatey",
+          "description": "Install via Chocolatey:",
+          "command": "choco install opencode"
+        },
+        "scoop": {
+          "title": "Option 2: Scoop",
+          "description": "Install via Scoop:",
+          "command": "scoop bucket add extras\nscoop install extras/opencode"
+        },
+        "note": "Note: Bun on Windows is still in progress. Use Chocolatey/Scoop/npm, or download a binary from GitHub Releases."
+      }
+    },
+
+    "configuration": {
+      "title": "Connect to cch service",
+
+      "configFile": {
+        "title": "Configure opencode.json",
+        "path": "Config file path:",
+        "instruction": "Edit the file and add the following content (one file covers all models):",
+
+        "important": "Important",
+        "importantPoints": [
+          "Create an API key in the cch console and set the CCH_API_KEY environment variable",
+          "All providers use ${resolvedOrigin}/v1 as baseURL (cch v1 API base URL)",
+          "When selecting models, use provider_id/model_id (e.g. cchClaude/claude-sonnet-4-5-20250929)"
+        ]
+      },
+
+      "modelSelection": {
+        "title": "Select a model",
+        "description": "After starting OpenCode, use the following command in the TUI to view/select models:",
+        "command": "/models"
+      }
+    },
+
+    "startup": {
+      "title": "Start opencode",
+      "description": "Run in your project directory:",
+      "initNote": "On first launch, opencode loads the config and creates a session."
+    },
+
+    "commonIssues": {
+      "title": "Common issues",
+
+      "commandNotFound": "1. Command not found",
+      "commandNotFoundWindows": [
+        "If installed via npm, make sure your global npm bin directory is on PATH",
+        "Open a new terminal and try again"
+      ],
+      "commandNotFoundUnix": "Check the install location and add it to PATH (e.g. ~/.local/bin or npm global bin)",
+
+      "connectionFailed": "2. API connection failed",
+      "updateCli": "3. Update opencode"
+    }
+  },
+
   "droid": {
     "title": "Droid CLI Usage Guide",
     "description": "Droid is an interactive terminal AI programming assistant developed by Factory AI that supports integration through the cch proxy service. You must register and log in to your Droid official account before use.",

+ 102 - 0
messages/ja/usage.json

@@ -473,6 +473,108 @@
     }
   },
 
+  "opencode": {
+    "title": "OpenCode 利用ガイド",
+    "description": "OpenCode はターミナルで動作する CLI + TUI の AI コーディングエージェントで、IDE 連携も提供します。OpenCode の接続先を cch に設定することで、Claude / GPT / Gemini の各モデルを利用できます。",
+
+    "installation": {
+      "title": "インストール",
+
+      "macos": {
+        "description": "macOS では以下のいずれかの方法で OpenCode をインストールできます:",
+        "homebrew": {
+          "title": "方法2:Homebrew",
+          "description": "Homebrew でインストール:"
+        }
+      },
+
+      "linux": {
+        "description": "Linux では以下のいずれかの方法で OpenCode をインストールできます:",
+        "homebrew": {
+          "title": "方法2:Homebrew",
+          "description": "Homebrew でインストール:"
+        },
+        "paru": {
+          "title": "方法5:Paru(Arch Linux)",
+          "description": "Arch Linux の場合、paru(AUR)でインストール:"
+        }
+      },
+
+      "script": {
+        "title": "方法1:公式インストールスクリプト",
+        "description": "最新版をインストールするには次のコマンドを実行します:"
+      },
+
+      "npm": {
+        "title": "方法3:npm",
+        "description": "npm でグローバルにインストール:"
+      },
+
+      "bun": {
+        "title": "方法4:Bun",
+        "description": "Bun でグローバルにインストール:"
+      },
+
+      "windows": {
+        "description": "Windows ではパッケージマネージャ(Chocolatey/Scoop)を推奨します。npm でもインストールできます:",
+        "choco": {
+          "title": "方法1:Chocolatey",
+          "description": "Chocolatey でインストール:",
+          "command": "choco install opencode"
+        },
+        "scoop": {
+          "title": "方法2:Scoop",
+          "description": "Scoop でインストール:",
+          "command": "scoop bucket add extras\nscoop install extras/opencode"
+        },
+        "note": "注: Windows での Bun インストールは現在対応中です。Chocolatey/Scoop/npm を使用するか、GitHub Releases からバイナリを取得してください。"
+      }
+    },
+
+    "configuration": {
+      "title": "cch サービスに接続",
+
+      "configFile": {
+        "title": "opencode.json を設定",
+        "path": "設定ファイルのパス:",
+        "instruction": "設定ファイルを編集し、以下を追加します(1 つの設定ファイルで全モデルをカバーします):",
+
+        "important": "重要",
+        "importantPoints": [
+          "cch の管理画面で API Key を作成し、環境変数 CCH_API_KEY を設定してください",
+          "3 つの provider すべてで baseURL は ${resolvedOrigin}/v1(cch の v1 API ベース URL)",
+          "モデル選択は provider_id/model_id 形式(例:cchClaude/claude-sonnet-4-5-20250929)"
+        ]
+      },
+
+      "modelSelection": {
+        "title": "モデルを選択",
+        "description": "OpenCode を起動したら、TUI で次のコマンドを入力してモデルを表示/選択します:",
+        "command": "/models"
+      }
+    },
+
+    "startup": {
+      "title": "opencode を起動",
+      "description": "プロジェクトディレクトリで次を実行します:",
+      "initNote": "初回起動時に、opencode は設定を読み込み、セッションを作成します。"
+    },
+
+    "commonIssues": {
+      "title": "よくある問題",
+
+      "commandNotFound": "1. コマンドが見つからない",
+      "commandNotFoundWindows": [
+        "npm でインストールした場合、npm のグローバル bin パスが PATH に含まれているか確認してください",
+        "新しいターミナルを開いて再試行してください"
+      ],
+      "commandNotFoundUnix": "インストール先を確認し、PATH に追加してください(例:~/.local/bin または npm のグローバル bin)",
+
+      "connectionFailed": "2. API 接続に失敗",
+      "updateCli": "3. opencode を更新"
+    }
+  },
+
   "droid": {
     "title": "Droid CLI 使用ガイド",
     "description": "Droid は Factory AI が開発したインタラクティブターミナル AI プログラミングアシスタントで、cch プロキシサービスを通じた統合をサポートしています。使用する前に、Droid 公式アカウントに登録してログインする必要があります。",

+ 102 - 0
messages/ru/usage.json

@@ -473,6 +473,108 @@
     }
   },
 
+  "opencode": {
+    "title": "Руководство по OpenCode",
+    "description": "OpenCode — это CLI + TUI агент для программирования в терминале, также есть интеграции для IDE. Вы можете настроить OpenCode на cch как единый вход для доступа к моделям Claude, GPT и Gemini.",
+
+    "installation": {
+      "title": "Установка",
+
+      "macos": {
+        "description": "В macOS установите OpenCode одним из следующих способов:",
+        "homebrew": {
+          "title": "Способ 2: Homebrew",
+          "description": "Установка через Homebrew:"
+        }
+      },
+
+      "linux": {
+        "description": "В Linux установите OpenCode одним из следующих способов:",
+        "homebrew": {
+          "title": "Способ 2: Homebrew",
+          "description": "Установка через Homebrew:"
+        },
+        "paru": {
+          "title": "Способ 5: Paru (Arch Linux)",
+          "description": "Для Arch Linux можно установить через paru (AUR):"
+        }
+      },
+
+      "script": {
+        "title": "Способ 1: Официальный install-скрипт",
+        "description": "Выполните команду для установки последней версии:"
+      },
+
+      "npm": {
+        "title": "Способ 3: npm",
+        "description": "Глобальная установка через npm:"
+      },
+
+      "bun": {
+        "title": "Способ 4: Bun",
+        "description": "Глобальная установка через Bun:"
+      },
+
+      "windows": {
+        "description": "В Windows рекомендуем использовать пакетный менеджер (Chocolatey/Scoop), либо npm:",
+        "choco": {
+          "title": "Способ 1: Chocolatey",
+          "description": "Установка через Chocolatey:",
+          "command": "choco install opencode"
+        },
+        "scoop": {
+          "title": "Способ 2: Scoop",
+          "description": "Установка через Scoop:",
+          "command": "scoop bucket add extras\nscoop install extras/opencode"
+        },
+        "note": "Примечание: поддержка установки через Bun в Windows пока в процессе. Используйте Chocolatey/Scoop/npm или скачайте бинарник из GitHub Releases."
+      }
+    },
+
+    "configuration": {
+      "title": "Подключение к cch",
+
+      "configFile": {
+        "title": "Настройка opencode.json",
+        "path": "Путь к конфигурационному файлу:",
+        "instruction": "Отредактируйте файл и добавьте следующий контент (один файл покрывает все модели):",
+
+        "important": "Важно",
+        "importantPoints": [
+          "Создайте API key в панели cch и задайте переменную окружения CCH_API_KEY",
+          "Все provider используют ${resolvedOrigin}/v1 как baseURL (базовый URL cch v1 API)",
+          "При выборе модели используйте provider_id/model_id (например, cchClaude/claude-sonnet-4-5-20250929)"
+        ]
+      },
+
+      "modelSelection": {
+        "title": "Выбор модели",
+        "description": "После запуска OpenCode в TUI выполните команду для просмотра/выбора моделей:",
+        "command": "/models"
+      }
+    },
+
+    "startup": {
+      "title": "Запуск opencode",
+      "description": "В каталоге проекта выполните:",
+      "initNote": "При первом запуске opencode загрузит конфигурацию и создаст сессию."
+    },
+
+    "commonIssues": {
+      "title": "Типичные проблемы",
+
+      "commandNotFound": "1. Команда не найдена",
+      "commandNotFoundWindows": [
+        "Если установили через npm, убедитесь, что глобальный npm bin добавлен в PATH",
+        "Откройте новый терминал и повторите"
+      ],
+      "commandNotFoundUnix": "Проверьте путь установки и добавьте его в PATH (например, ~/.local/bin или глобальный npm bin)",
+
+      "connectionFailed": "2. Сбой подключения API",
+      "updateCli": "3. Обновление opencode"
+    }
+  },
+
   "droid": {
     "title": "Руководство по использованию Droid CLI",
     "description": "Droid - это интерактивный помощник программирования на основе ИИ, разработанный Factory AI, поддерживающий интеграцию через прокси-сервис cch. Перед использованием вы должны зарегистрировать и войти в свой официальный аккаунт Droid.",

+ 102 - 0
messages/zh-CN/usage.json

@@ -469,6 +469,108 @@
     }
   },
 
+  "opencode": {
+    "title": "OpenCode 使用指南",
+    "description": "OpenCode 是一款在终端中运行的 CLI + TUI AI 编程代理工具,也提供 IDE 插件集成。你可以将 OpenCode 指向 cch 作为统一入口来接入 Claude、GPT 与 Gemini 等模型。",
+
+    "installation": {
+      "title": "安装",
+
+      "macos": {
+        "description": "在 macOS 上可以选择以下任一种方式安装 OpenCode:",
+        "homebrew": {
+          "title": "方式二:Homebrew",
+          "description": "也可以使用 Homebrew 安装:"
+        }
+      },
+
+      "linux": {
+        "description": "在 Linux 上可以选择以下任一种方式安装 OpenCode:",
+        "homebrew": {
+          "title": "方式二:Homebrew",
+          "description": "也可以使用 Homebrew 安装:"
+        },
+        "paru": {
+          "title": "方式五:Paru(Arch Linux)",
+          "description": "如果你使用 Arch Linux,也可以通过 paru(AUR)安装:"
+        }
+      },
+
+      "script": {
+        "title": "方式一:官方安装脚本",
+        "description": "执行以下命令安装最新版:"
+      },
+
+      "npm": {
+        "title": "方式三:npm",
+        "description": "也可以通过 npm 全局安装:"
+      },
+
+      "bun": {
+        "title": "方式四:Bun",
+        "description": "如果你使用 Bun,也可以全局安装:"
+      },
+
+      "windows": {
+        "description": "Windows 推荐使用包管理器(Chocolatey/Scoop),也可以使用 npm:",
+        "choco": {
+          "title": "方式一:Chocolatey",
+          "description": "使用 Chocolatey 安装:",
+          "command": "choco install opencode"
+        },
+        "scoop": {
+          "title": "方式二:Scoop",
+          "description": "使用 Scoop 安装:",
+          "command": "scoop bucket add extras\nscoop install extras/opencode"
+        },
+        "note": "提示:官方说明 Windows 上通过 Bun 安装仍在推进。建议使用 Chocolatey/Scoop/npm,或从 GitHub Releases 下载二进制。"
+      }
+    },
+
+    "configuration": {
+      "title": "连接 cch 服务",
+
+      "configFile": {
+        "title": "配置 opencode.json",
+        "path": "配置文件路径:",
+        "instruction": "编辑配置文件,写入以下内容(只需一份配置文件即可覆盖全部模型):",
+
+        "important": "重要说明",
+        "importantPoints": [
+          "请先在 cch 后台创建 API Key,并设置环境变量 CCH_API_KEY",
+          "三个 provider 的 baseURL 都使用 ${resolvedOrigin}/v1(cch v1 API 地址)",
+          "模型选择时使用 provider_id/model_id 格式(例如 cchClaude/claude-sonnet-4-5-20250929)"
+        ]
+      },
+
+      "modelSelection": {
+        "title": "选择模型",
+        "description": "启动 OpenCode 后,在 TUI 中输入以下命令查看/选择模型:",
+        "command": "/models"
+      }
+    },
+
+    "startup": {
+      "title": "启动 opencode",
+      "description": "在项目目录下运行:",
+      "initNote": "首次启动时,opencode 会加载配置并创建会话。"
+    },
+
+    "commonIssues": {
+      "title": "常见问题",
+
+      "commandNotFound": "1. 命令未找到",
+      "commandNotFoundWindows": [
+        "如果使用 npm 安装,请确保 npm 全局路径已添加到系统 PATH",
+        "重新打开终端窗口后再试"
+      ],
+      "commandNotFoundUnix": "检查安装路径并添加到 PATH(例如 ~/.local/bin 或 npm 全局目录)",
+
+      "connectionFailed": "2. API 连接失败",
+      "updateCli": "3. 更新 opencode"
+    }
+  },
+
   "droid": {
     "title": "Droid CLI 使用指南",
     "description": "Droid 是 Factory AI 开发的交互式终端 AI 编程助手,支持通过 cch 代理服务使用。使用前必须先注册并登录 Droid 官方账号。",

+ 102 - 0
messages/zh-TW/usage.json

@@ -469,6 +469,108 @@
     }
   },
 
+  "opencode": {
+    "title": "OpenCode 使用指南",
+    "description": "OpenCode 是一款在終端中運行的 CLI + TUI AI 編程代理工具,也提供 IDE 插件集成。你可以將 OpenCode 指向 cch 作為統一入口接入 Claude、GPT 與 Gemini 等模型。",
+
+    "installation": {
+      "title": "安裝",
+
+      "macos": {
+        "description": "在 macOS 上可以選擇以下任一方式安裝 OpenCode:",
+        "homebrew": {
+          "title": "方式二:Homebrew",
+          "description": "也可以使用 Homebrew 安裝:"
+        }
+      },
+
+      "linux": {
+        "description": "在 Linux 上可以選擇以下任一方式安裝 OpenCode:",
+        "homebrew": {
+          "title": "方式二:Homebrew",
+          "description": "也可以使用 Homebrew 安裝:"
+        },
+        "paru": {
+          "title": "方式五:Paru(Arch Linux)",
+          "description": "如果你使用 Arch Linux,也可以通過 paru(AUR)安裝:"
+        }
+      },
+
+      "script": {
+        "title": "方式一:官方安裝腳本",
+        "description": "執行以下命令安裝最新版:"
+      },
+
+      "npm": {
+        "title": "方式三:npm",
+        "description": "也可以通過 npm 全局安裝:"
+      },
+
+      "bun": {
+        "title": "方式四:Bun",
+        "description": "如果你使用 Bun,也可以全局安裝:"
+      },
+
+      "windows": {
+        "description": "Windows 推薦使用包管理器(Chocolatey/Scoop),也可以使用 npm:",
+        "choco": {
+          "title": "方式一:Chocolatey",
+          "description": "使用 Chocolatey 安裝:",
+          "command": "choco install opencode"
+        },
+        "scoop": {
+          "title": "方式二:Scoop",
+          "description": "使用 Scoop 安裝:",
+          "command": "scoop bucket add extras\nscoop install extras/opencode"
+        },
+        "note": "提示:官方說明 Windows 上通過 Bun 安裝仍在推進。建議使用 Chocolatey/Scoop/npm,或從 GitHub Releases 下載二進制。"
+      }
+    },
+
+    "configuration": {
+      "title": "連接 cch 服務",
+
+      "configFile": {
+        "title": "配置 opencode.json",
+        "path": "配置文件路徑:",
+        "instruction": "編輯配置文件,寫入以下內容(只需一份配置文件即可覆蓋全部模型):",
+
+        "important": "重要說明",
+        "importantPoints": [
+          "請先在 cch 後台創建 API Key,並設置環境變量 CCH_API_KEY",
+          "三個 provider 的 baseURL 都使用 ${resolvedOrigin}/v1(cch v1 API 地址)",
+          "模型選擇時使用 provider_id/model_id 格式(例如 cchClaude/claude-sonnet-4-5-20250929)"
+        ]
+      },
+
+      "modelSelection": {
+        "title": "選擇模型",
+        "description": "啟動 OpenCode 後,在 TUI 中輸入以下命令查看/選擇模型:",
+        "command": "/models"
+      }
+    },
+
+    "startup": {
+      "title": "啟動 opencode",
+      "description": "在項目目錄下運行:",
+      "initNote": "首次啟動時,opencode 會加載配置並創建會話。"
+    },
+
+    "commonIssues": {
+      "title": "常見問題",
+
+      "commandNotFound": "1. 命令未找到",
+      "commandNotFoundWindows": [
+        "如果使用 npm 安裝,請確保 npm 全局路徑已添加到系統 PATH",
+        "重新打開終端窗口後再試"
+      ],
+      "commandNotFoundUnix": "檢查安裝路徑並添加到 PATH(例如 ~/.local/bin 或 npm 全局目錄)",
+
+      "connectionFailed": "2. API 連接失敗",
+      "updateCli": "3. 更新 opencode"
+    }
+  },
+
   "droid": {
     "title": "Droid CLI 使用指南",
     "description": "Droid 是 Factory AI 開發的交互式終端 AI 編程助手,支持通過 cch 代理服務使用。使用前必須先註冊並登錄 Droid 官方賬號。",

+ 5 - 2
src/app/[locale]/usage-doc/_components/quick-links.tsx

@@ -1,5 +1,6 @@
 "use client";
 
+import { useTranslations } from "next-intl";
 import { Link } from "@/i18n/routing";
 
 interface QuickLinksProps {
@@ -12,6 +13,8 @@ interface QuickLinksProps {
  * 支持桌面端和移动端复用
  */
 export function QuickLinks({ isLoggedIn, onBackToTop }: QuickLinksProps) {
+  const t = useTranslations("usage");
+
   const handleBackToTop = () => {
     window.scrollTo({ top: 0, behavior: "smooth" });
     onBackToTop?.();
@@ -24,14 +27,14 @@ export function QuickLinks({ isLoggedIn, onBackToTop }: QuickLinksProps) {
           href="/dashboard"
           className="block text-sm text-muted-foreground hover:text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded px-2 py-1"
         >
-          返回仪表盘
+          {t("navigation.backToDashboard")}
         </Link>
       )}
       <button
         onClick={handleBackToTop}
         className="block w-full text-left text-sm text-muted-foreground hover:text-primary transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-2 rounded px-2 py-1 cursor-pointer"
       >
-        回到顶部
+        {t("navigation.backToTop")}
       </button>
     </div>
   );

+ 5 - 2
src/app/[locale]/usage-doc/_components/toc-nav.tsx

@@ -1,5 +1,6 @@
 "use client";
 
+import { useTranslations } from "next-intl";
 import { Skeleton } from "@/components/ui/skeleton";
 import { cn } from "@/lib/utils";
 
@@ -24,8 +25,10 @@ interface TocNavProps {
  * 支持桌面端和移动端复用
  */
 export function TocNav({ tocItems, activeId, tocReady, onItemClick }: TocNavProps) {
+  const t = useTranslations("usage");
+
   return (
-    <nav aria-label="文档目录" className="space-y-1">
+    <nav aria-label={t("navigation.tableOfContents")} className="space-y-1">
       {!tocReady && (
         <div className="space-y-2">
           {Array.from({ length: 5 }).map((_, index) => (
@@ -34,7 +37,7 @@ export function TocNav({ tocItems, activeId, tocReady, onItemClick }: TocNavProp
         </div>
       )}
       {tocReady && tocItems.length === 0 && (
-        <p className="text-xs text-muted-foreground">本页暂无可用章节</p>
+        <p className="text-xs text-muted-foreground">{t("navigation.tableOfContentsEmpty")}</p>
       )}
       {tocReady &&
         tocItems.length > 0 &&

+ 275 - 21
src/app/[locale]/usage-doc/page.tsx

@@ -48,6 +48,12 @@ interface CLIConfig {
   id: string;
   cliName: string;
   packageName?: string;
+  /**
+   * 是否需要 Node.js 环境
+   * - true:在安装步骤前展示 Node.js 环境准备
+   * - false:不展示 Node.js 环境准备(例如二进制安装、或 Node.js 非必需)
+   */
+  requiresNodeJs?: boolean;
   officialInstallUrl?: { macos: string; windows: string };
   requiresOfficialLogin?: boolean;
   vsCodeExtension?: {
@@ -70,6 +76,7 @@ function getCLIConfigs(t: (key: string) => string): Record<string, CLIConfig> {
       id: "claude-code",
       cliName: "claude",
       packageName: "@anthropic-ai/claude-code",
+      requiresNodeJs: true,
       vsCodeExtension: {
         name: "Claude Code for VS Code",
         configFile: "config.json",
@@ -85,6 +92,7 @@ function getCLIConfigs(t: (key: string) => string): Record<string, CLIConfig> {
       id: "codex",
       cliName: "codex",
       packageName: "@openai/codex",
+      requiresNodeJs: true,
       vsCodeExtension: {
         name: "Codex – OpenAI's coding agent",
         configFile: "config.toml 和 auth.json",
@@ -100,11 +108,20 @@ function getCLIConfigs(t: (key: string) => string): Record<string, CLIConfig> {
       id: "gemini",
       cliName: "gemini",
       packageName: "@google/gemini-cli",
+      requiresNodeJs: true,
+    },
+    opencode: {
+      title: t("opencode.title"),
+      id: "opencode",
+      cliName: "opencode",
+      packageName: "opencode-ai",
+      requiresNodeJs: false,
     },
     droid: {
       title: t("droid.title"),
       id: "droid",
       cliName: "droid",
+      requiresNodeJs: false,
       officialInstallUrl: {
         macos: "https://app.factory.ai/cli",
         windows: "https://app.factory.ai/cli/windows",
@@ -118,7 +135,7 @@ interface UsageDocContentProps {
   origin: string;
 }
 
-function UsageDocContent({ origin }: UsageDocContentProps) {
+export function UsageDocContent({ origin }: UsageDocContentProps) {
   const t = useTranslations("usage");
   const resolvedOrigin = origin || t("ui.currentSiteAddress");
   const CLI_CONFIGS = getCLIConfigs(t);
@@ -940,6 +957,203 @@ gemini`}
     );
   };
 
+  /**
+   * 渲染 OpenCode 安装
+   */
+  const renderOpenCodeInstallation = (os: OS) => {
+    if (os === "windows") {
+      return (
+        <div className="space-y-4">
+          <p>{t("opencode.installation.windows.description")}</p>
+
+          <div className="space-y-3">
+            <h5 className="font-semibold text-foreground">
+              {t("opencode.installation.windows.choco.title")}
+            </h5>
+            <p>{t("opencode.installation.windows.choco.description")}</p>
+            <CodeBlock
+              language="powershell"
+              code={t("opencode.installation.windows.choco.command")}
+            />
+          </div>
+
+          <div className="space-y-3">
+            <h5 className="font-semibold text-foreground">
+              {t("opencode.installation.windows.scoop.title")}
+            </h5>
+            <p>{t("opencode.installation.windows.scoop.description")}</p>
+            <CodeBlock
+              language="powershell"
+              code={t("opencode.installation.windows.scoop.command")}
+            />
+          </div>
+
+          <div className="space-y-3">
+            <h5 className="font-semibold text-foreground">
+              {t("opencode.installation.npm.title")}
+            </h5>
+            <p>{t("opencode.installation.npm.description")}</p>
+            <CodeBlock language="powershell" code={`npm install -g opencode-ai`} />
+          </div>
+
+          <p className="text-sm text-muted-foreground">{t("opencode.installation.windows.note")}</p>
+        </div>
+      );
+    }
+
+    const lang = "bash";
+
+    return (
+      <div className="space-y-4">
+        <p>
+          {t(
+            os === "macos"
+              ? "opencode.installation.macos.description"
+              : "opencode.installation.linux.description"
+          )}
+        </p>
+
+        <div className="space-y-3">
+          <h5 className="font-semibold text-foreground">
+            {t("opencode.installation.script.title")}
+          </h5>
+          <p>{t("opencode.installation.script.description")}</p>
+          <CodeBlock language={lang} code={`curl -fsSL https://opencode.ai/install | bash`} />
+        </div>
+
+        <div className="space-y-3">
+          <h5 className="font-semibold text-foreground">
+            {t(
+              os === "macos"
+                ? "opencode.installation.macos.homebrew.title"
+                : "opencode.installation.linux.homebrew.title"
+            )}
+          </h5>
+          <p>
+            {t(
+              os === "macos"
+                ? "opencode.installation.macos.homebrew.description"
+                : "opencode.installation.linux.homebrew.description"
+            )}
+          </p>
+          <CodeBlock language="bash" code={`brew install anomalyco/tap/opencode`} />
+        </div>
+
+        <div className="space-y-3">
+          <h5 className="font-semibold text-foreground">{t("opencode.installation.npm.title")}</h5>
+          <p>{t("opencode.installation.npm.description")}</p>
+          <CodeBlock language="bash" code={`npm install -g opencode-ai`} />
+        </div>
+
+        <div className="space-y-3">
+          <h5 className="font-semibold text-foreground">{t("opencode.installation.bun.title")}</h5>
+          <p>{t("opencode.installation.bun.description")}</p>
+          <CodeBlock language="bash" code={`bun add -g opencode-ai`} />
+        </div>
+
+        {os === "linux" && (
+          <div className="space-y-3">
+            <h5 className="font-semibold text-foreground">
+              {t("opencode.installation.linux.paru.title")}
+            </h5>
+            <p>{t("opencode.installation.linux.paru.description")}</p>
+            <CodeBlock language="bash" code={`paru -S opencode-bin`} />
+          </div>
+        )}
+      </div>
+    );
+  };
+
+  /**
+   * 渲染 OpenCode 配置
+   */
+  const renderOpenCodeConfiguration = (os: OS) => {
+    const configPath =
+      os === "windows"
+        ? "%USERPROFILE%\\.config\\opencode\\opencode.json"
+        : "~/.config/opencode/opencode.json";
+
+    const opencodeConfigJson = JSON.stringify(
+      {
+        $schema: "https://opencode.ai/config.json",
+        theme: "opencode",
+        autoupdate: false,
+        provider: {
+          cchClaude: {
+            npm: "@ai-sdk/anthropic",
+            name: "Claude via cch",
+            options: {
+              baseURL: `${resolvedOrigin}/v1`,
+              apiKey: "{env:CCH_API_KEY}",
+            },
+            models: {
+              "claude-haiku-4-5-20251001": { name: "Claude Haiku 4.5" },
+              "claude-sonnet-4-5-20250929": { name: "Claude Sonnet 4.5" },
+              "claude-opus-4-5-20251101": { name: "Claude Opus 4.5" },
+            },
+          },
+          cchGPT: {
+            npm: "@ai-sdk/openai",
+            name: "GPT via cch",
+            options: {
+              baseURL: `${resolvedOrigin}/v1`,
+              apiKey: "{env:CCH_API_KEY}",
+            },
+            models: {
+              "gpt-5.2": { name: "GPT-5.2" },
+            },
+          },
+          cchGemini: {
+            npm: "@ai-sdk/google",
+            name: "Gemini via cch",
+            options: {
+              baseURL: `${resolvedOrigin}/v1`,
+              apiKey: "{env:CCH_API_KEY}",
+            },
+            models: {
+              "gemini-3-pro-preview": { name: "Gemini 3 Pro Preview" },
+              "gemini-3-flash-preview": { name: "Gemini 3 Flash Preview" },
+            },
+          },
+        },
+      },
+      null,
+      2
+    );
+
+    return (
+      <div className="space-y-4">
+        <h4 className={headingClasses.h4}>{t("opencode.configuration.configFile.title")}</h4>
+
+        <div className="space-y-3">
+          <p>{t("opencode.configuration.configFile.path")}</p>
+          <CodeBlock language={os === "windows" ? "powershell" : "bash"} code={configPath} />
+          <p>{t("opencode.configuration.configFile.instruction")}</p>
+          <CodeBlock language="json" code={opencodeConfigJson} />
+
+          <blockquote className="space-y-2 rounded-lg border-l-2 border-primary/50 bg-muted/40 px-4 py-3">
+            <p className="font-semibold text-foreground">
+              {t("opencode.configuration.configFile.important")}
+            </p>
+            <ul className="list-disc space-y-2 pl-4">
+              {(t.raw("opencode.configuration.configFile.importantPoints") as string[]).map(
+                (point: string, i: number) => (
+                  <li key={i}>{point.replace("${resolvedOrigin}", resolvedOrigin)}</li>
+                )
+              )}
+            </ul>
+          </blockquote>
+        </div>
+
+        <h4 className={headingClasses.h4}>{t("opencode.configuration.modelSelection.title")}</h4>
+        <div className="space-y-3">
+          <p>{t("opencode.configuration.modelSelection.description")}</p>
+          <CodeBlock language="text" code={t("opencode.configuration.modelSelection.command")} />
+        </div>
+      </div>
+    );
+  };
+
   /**
    * 渲染 Droid 安装
    */
@@ -1120,19 +1334,25 @@ gemini`}
         ? "claudeCode.startup.title"
         : cli.id === "codex"
           ? "codex.startup.title"
-          : "droid.startup.title";
+          : cli.id === "opencode"
+            ? "opencode.startup.title"
+            : "droid.startup.title";
     const descKey =
       cli.id === "claude-code"
         ? "claudeCode.startup.description"
         : cli.id === "codex"
           ? "codex.startup.description"
-          : "droid.startup.description";
+          : cli.id === "opencode"
+            ? "opencode.startup.description"
+            : "droid.startup.description";
     const initKey =
       cli.id === "claude-code"
         ? "claudeCode.startup.initNote"
         : cli.id === "codex"
           ? "codex.startup.initNote"
-          : "droid.startup.initNote";
+          : cli.id === "opencode"
+            ? "opencode.startup.initNote"
+            : "droid.startup.initNote";
 
     return (
       <div className="space-y-3">
@@ -1153,6 +1373,9 @@ ${cli.cliName}`}
    */
   const renderCommonIssues = (cli: CLIConfig, os: OS) => {
     const lang = os === "windows" ? "powershell" : "bash";
+    const envKeyName = ["codex", "opencode"].includes(cli.id)
+      ? "CCH_API_KEY"
+      : "ANTHROPIC_AUTH_TOKEN";
     const titleKey =
       cli.id === "claude-code"
         ? "claudeCode.commonIssues.title"
@@ -1160,7 +1383,9 @@ ${cli.cliName}`}
           ? "codex.commonIssues.title"
           : cli.id === "gemini"
             ? "gemini.commonIssues.title"
-            : "droid.commonIssues.title";
+            : cli.id === "opencode"
+              ? "opencode.commonIssues.title"
+              : "droid.commonIssues.title";
     const cmdNotFoundKey =
       cli.id === "claude-code"
         ? "claudeCode.commonIssues.commandNotFound"
@@ -1168,7 +1393,9 @@ ${cli.cliName}`}
           ? "codex.commonIssues.commandNotFound"
           : cli.id === "gemini"
             ? "gemini.commonIssues.commandNotFound"
-            : "droid.commonIssues.commandNotFound";
+            : cli.id === "opencode"
+              ? "opencode.commonIssues.commandNotFound"
+              : "droid.commonIssues.commandNotFound";
     const cmdNotFoundWinKey =
       cli.id === "claude-code"
         ? "claudeCode.commonIssues.commandNotFoundWindows"
@@ -1176,7 +1403,9 @@ ${cli.cliName}`}
           ? "codex.commonIssues.commandNotFoundWindows"
           : cli.id === "gemini"
             ? "gemini.commonIssues.commandNotFoundWindows"
-            : "droid.commonIssues.commandNotFoundWindows";
+            : cli.id === "opencode"
+              ? "opencode.commonIssues.commandNotFoundWindows"
+              : "droid.commonIssues.commandNotFoundWindows";
     const cmdNotFoundUnixKey =
       cli.id === "claude-code"
         ? "claudeCode.commonIssues.commandNotFoundUnix"
@@ -1184,13 +1413,17 @@ ${cli.cliName}`}
           ? "codex.commonIssues.commandNotFoundUnix"
           : cli.id === "gemini"
             ? "gemini.commonIssues.commandNotFoundUnix"
-            : "droid.commonIssues.commandNotFoundUnix";
+            : cli.id === "opencode"
+              ? "opencode.commonIssues.commandNotFoundUnix"
+              : "droid.commonIssues.commandNotFoundUnix";
     const connFailedKey =
       cli.id === "claude-code"
         ? "claudeCode.commonIssues.connectionFailed"
         : cli.id === "gemini"
           ? "gemini.commonIssues.connectionFailed"
-          : "codex.commonIssues.connectionFailed";
+          : cli.id === "opencode"
+            ? "opencode.commonIssues.connectionFailed"
+            : "codex.commonIssues.connectionFailed";
     const updateKey =
       cli.id === "claude-code"
         ? "claudeCode.commonIssues.updateCli"
@@ -1198,7 +1431,9 @@ ${cli.cliName}`}
           ? "codex.commonIssues.updateCli"
           : cli.id === "gemini"
             ? "gemini.commonIssues.updateCli"
-            : "droid.commonIssues.updateCli";
+            : cli.id === "opencode"
+              ? "opencode.commonIssues.updateCli"
+              : "droid.commonIssues.updateCli";
 
     return (
       <div className="space-y-4">
@@ -1241,7 +1476,7 @@ source ~/.${os === "macos" ? "zshrc" : "bashrc"}`}
               <CodeBlock
                 language="powershell"
                 code={`# 检查环境变量
-echo $env:${cli.id === "codex" ? "CCH_API_KEY" : "ANTHROPIC_AUTH_TOKEN"}
+echo $env:${envKeyName}
 
 # 测试网络连接
 Test-NetConnection -ComputerName ${resolvedOrigin.replace("https://", "").replace("http://", "")} -Port 443`}
@@ -1250,7 +1485,7 @@ Test-NetConnection -ComputerName ${resolvedOrigin.replace("https://", "").replac
               <CodeBlock
                 language="bash"
                 code={`# 检查环境变量
-echo $${cli.id === "codex" ? "CCH_API_KEY" : "ANTHROPIC_AUTH_TOKEN"}
+echo $${envKeyName}
 
 # 测试网络连接
 curl -I ${resolvedOrigin}`}
@@ -1294,7 +1529,7 @@ curl -I ${resolvedOrigin}`}
         </h3>
 
         {/* 环境准备 */}
-        {cli.packageName && (
+        {cli.requiresNodeJs && (
           <div className="space-y-3">
             <h4 className={headingClasses.h4}>{t("claudeCode.environmentSetup.title")}</h4>
             <p>{t("claudeCode.environmentSetup.description")}</p>
@@ -1312,12 +1547,15 @@ curl -I ${resolvedOrigin}`}
                 ? t("codex.installation.title")
                 : cli.id === "gemini"
                   ? t("gemini.installation.title")
-                  : t("droid.installation.title")}{" "}
+                  : cli.id === "opencode"
+                    ? t("opencode.installation.title")
+                    : t("droid.installation.title")}{" "}
             {cli.cliName}
           </h4>
           {cli.id === "claude-code" && renderClaudeCodeInstallation(os)}
           {cli.id === "codex" && renderCodexInstallation(os)}
           {cli.id === "gemini" && renderGeminiInstallation(os)}
+          {cli.id === "opencode" && renderOpenCodeInstallation(os)}
           {cli.id === "droid" && renderDroidInstallation(os)}
         </div>
 
@@ -1330,11 +1568,14 @@ curl -I ${resolvedOrigin}`}
                 ? t("codex.configuration.title")
                 : cli.id === "gemini"
                   ? t("gemini.configuration.title")
-                  : t("droid.configuration.title")}
+                  : cli.id === "opencode"
+                    ? t("opencode.configuration.title")
+                    : t("droid.configuration.title")}
           </h4>
           {cli.id === "claude-code" && renderClaudeCodeConfiguration(os)}
           {cli.id === "codex" && renderCodexConfiguration(os)}
           {cli.id === "gemini" && renderGeminiConfiguration(os)}
+          {cli.id === "opencode" && renderOpenCodeConfiguration(os)}
           {cli.id === "droid" && renderDroidConfiguration(os)}
         </div>
 
@@ -1358,7 +1599,7 @@ curl -I ${resolvedOrigin}`}
       {/* Claude Code 使用指南 */}
       <section className="space-y-6">
         <h2 id={CLI_CONFIGS.claudeCode.id} className={headingClasses.h2}>
-          📚 {CLI_CONFIGS.claudeCode.title}
+          {CLI_CONFIGS.claudeCode.title}
         </h2>
         <p>{t("claudeCode.description")}</p>
         {(["macos", "windows", "linux"] as OS[]).map((os) =>
@@ -1371,7 +1612,7 @@ curl -I ${resolvedOrigin}`}
       {/* Codex CLI 使用指南 */}
       <section className="space-y-6">
         <h2 id={CLI_CONFIGS.codex.id} className={headingClasses.h2}>
-          📚 {CLI_CONFIGS.codex.title}
+          {CLI_CONFIGS.codex.title}
         </h2>
         <p>{t("codex.description")}</p>
         {(["macos", "windows", "linux"] as OS[]).map((os) =>
@@ -1384,7 +1625,7 @@ curl -I ${resolvedOrigin}`}
       {/* Gemini CLI 使用指南 */}
       <section className="space-y-6">
         <h2 id={CLI_CONFIGS.gemini.id} className={headingClasses.h2}>
-          📚 {CLI_CONFIGS.gemini.title}
+          {CLI_CONFIGS.gemini.title}
         </h2>
         <p>{t("gemini.description")}</p>
         {(["macos", "windows", "linux"] as OS[]).map((os) =>
@@ -1394,10 +1635,23 @@ curl -I ${resolvedOrigin}`}
 
       <hr className="border-border/60" />
 
+      {/* OpenCode 使用指南 */}
+      <section className="space-y-6">
+        <h2 id={CLI_CONFIGS.opencode.id} className={headingClasses.h2}>
+          {CLI_CONFIGS.opencode.title}
+        </h2>
+        <p>{t("opencode.description")}</p>
+        {(["macos", "windows", "linux"] as OS[]).map((os) =>
+          renderPlatformGuide(CLI_CONFIGS.opencode, os)
+        )}
+      </section>
+
+      <hr className="border-border/60" />
+
       {/* Droid CLI 使用指南 */}
       <section className="space-y-6">
         <h2 id={CLI_CONFIGS.droid.id} className={headingClasses.h2}>
-          📚 {CLI_CONFIGS.droid.title}
+          {CLI_CONFIGS.droid.title}
         </h2>
         <p>{t("droid.description")}</p>
         {(["macos", "windows", "linux"] as OS[]).map((os) =>
@@ -1410,7 +1664,7 @@ curl -I ${resolvedOrigin}`}
       {/* 常用命令 */}
       <section className="space-y-4">
         <h2 id="common-commands" className={headingClasses.h2}>
-          📚 {t("commonCommands.title")}
+          {t("commonCommands.title")}
         </h2>
         <p>{t("commonCommands.description")}</p>
         <ul className="list-disc space-y-2 pl-6">
@@ -1441,7 +1695,7 @@ curl -I ${resolvedOrigin}`}
       {/* 通用故障排查 */}
       <section className="space-y-4">
         <h2 id="troubleshooting" className={headingClasses.h2}>
-          🔍 {t("troubleshooting.title")}
+          {t("troubleshooting.title")}
         </h2>
 
         <div className="space-y-3">

+ 125 - 0
tests/unit/usage-doc/opencode-usage-doc.test.tsx

@@ -0,0 +1,125 @@
+/**
+ * @vitest-environment happy-dom
+ */
+
+import fs from "node:fs";
+import path from "node:path";
+import type { ReactNode } from "react";
+import { act } from "react";
+import { createRoot } from "react-dom/client";
+import { NextIntlClientProvider } from "next-intl";
+import { describe, expect, test, vi } from "vitest";
+import { UsageDocContent } from "@/app/[locale]/usage-doc/page";
+import { locales } from "@/i18n/config";
+
+// 测试环境不加载 next-intl/navigation -> next/navigation 的真实实现(避免 Next.js 运行时依赖)
+vi.mock("@/i18n/routing", () => ({
+  Link: ({ children }: { children: ReactNode }) => children,
+}));
+
+function loadUsageMessages(locale: string) {
+  return JSON.parse(
+    fs.readFileSync(path.join(process.cwd(), "messages", locale, "usage.json"), "utf8")
+  );
+}
+
+function renderWithIntl(locale: string, node: ReactNode) {
+  const container = document.createElement("div");
+  document.body.appendChild(container);
+  const root = createRoot(container);
+  const usageMessages = loadUsageMessages(locale);
+
+  act(() => {
+    root.render(
+      <NextIntlClientProvider locale={locale} messages={{ usage: usageMessages }} timeZone="UTC">
+        {node}
+      </NextIntlClientProvider>
+    );
+  });
+
+  return {
+    unmount: () => {
+      act(() => root.unmount());
+      container.remove();
+    },
+  };
+}
+
+describe("UsageDoc - OpenCode 配置教程", () => {
+  test("OpenCode 段落应位于 Gemini CLI 之后、Droid 之前", () => {
+    const { unmount } = renderWithIntl("en", <UsageDocContent origin="http://localhost:23000" />);
+
+    const h2Ids = Array.from(document.querySelectorAll("h2")).map((el) => el.id);
+
+    expect(h2Ids).toContain("gemini");
+    expect(h2Ids).toContain("opencode");
+    expect(h2Ids).toContain("droid");
+    expect(h2Ids.indexOf("gemini")).toBeLessThan(h2Ids.indexOf("opencode"));
+    expect(h2Ids.indexOf("opencode")).toBeLessThan(h2Ids.indexOf("droid"));
+
+    unmount();
+  });
+
+  test("应提供单份 opencode.json 示例,且包含 cch 端点与所有要求模型", () => {
+    const { unmount } = renderWithIntl("en", <UsageDocContent origin="http://localhost:23000" />);
+
+    const text = document.body.textContent || "";
+
+    expect(text).toContain('"$schema": "https://opencode.ai/config.json"');
+    expect(text).toContain('"baseURL": "http://localhost:23000/v1"');
+
+    expect(text).toContain('"npm": "@ai-sdk/anthropic"');
+    expect(text).toContain('"npm": "@ai-sdk/openai"');
+    expect(text).toContain('"npm": "@ai-sdk/google"');
+    expect(text).not.toContain("@ai-sdk/openai-compatible");
+
+    expect(text).toContain("claude-haiku-4-5-20251001");
+    expect(text).toContain("claude-sonnet-4-5-20250929");
+    expect(text).toContain("claude-opus-4-5-20251101");
+
+    expect(text).toContain("gpt-5.2");
+
+    expect(text).toContain("gemini-3-pro-preview");
+    expect(text).toContain("gemini-3-flash-preview");
+
+    unmount();
+  });
+
+  test("应包含官方安装方式示例(curl/npm/bun/brew/paru,以及 Windows 包管理器)", () => {
+    const { unmount } = renderWithIntl("en", <UsageDocContent origin="http://localhost:23000" />);
+
+    const text = document.body.textContent || "";
+
+    expect(text).toContain("curl -fsSL https://opencode.ai/install | bash");
+    expect(text).toContain("npm install -g opencode-ai");
+    expect(text).toContain("bun add -g opencode-ai");
+    expect(text).toContain("brew install anomalyco/tap/opencode");
+    expect(text).toContain("paru -S opencode-bin");
+
+    expect(text).toContain("choco install opencode");
+    expect(text).toContain("scoop bucket add extras");
+    expect(text).toContain("scoop install extras/opencode");
+
+    unmount();
+  });
+
+  test("5 语言 messages/ 需包含 OpenCode 段落的关键翻译键", () => {
+    for (const locale of locales) {
+      const usageMessages = loadUsageMessages(locale);
+
+      expect(usageMessages).toHaveProperty("opencode.title");
+      expect(usageMessages).toHaveProperty("opencode.description");
+      expect(usageMessages).toHaveProperty("opencode.installation.title");
+      expect(usageMessages).toHaveProperty("opencode.installation.script.title");
+      expect(usageMessages).toHaveProperty("opencode.installation.npm.title");
+      expect(usageMessages).toHaveProperty("opencode.installation.bun.title");
+      expect(usageMessages).toHaveProperty("opencode.installation.macos.homebrew.title");
+      expect(usageMessages).toHaveProperty("opencode.installation.linux.homebrew.title");
+      expect(usageMessages).toHaveProperty("opencode.installation.linux.paru.title");
+      expect(usageMessages).toHaveProperty("opencode.installation.windows.note");
+      expect(usageMessages).toHaveProperty("opencode.configuration.title");
+      expect(usageMessages).toHaveProperty("opencode.startup.title");
+      expect(usageMessages).toHaveProperty("opencode.commonIssues.title");
+    }
+  });
+});

+ 108 - 0
tests/unit/usage-doc/usage-doc-page.test.tsx

@@ -0,0 +1,108 @@
+/**
+ * @vitest-environment happy-dom
+ */
+
+import fs from "node:fs";
+import path from "node:path";
+import type { ReactNode } from "react";
+import { act } from "react";
+import { createRoot } from "react-dom/client";
+import { NextIntlClientProvider } from "next-intl";
+import { describe, expect, test, vi } from "vitest";
+import UsageDocPage from "@/app/[locale]/usage-doc/page";
+
+vi.mock("@/i18n/routing", () => ({
+  Link: ({
+    href,
+    children,
+    ...rest
+  }: {
+    href: string;
+    children: ReactNode;
+    className?: string;
+  }) => (
+    <a href={href} {...rest}>
+      {children}
+    </a>
+  ),
+}));
+
+function loadUsageMessages(locale: string) {
+  return JSON.parse(
+    fs.readFileSync(path.join(process.cwd(), "messages", locale, "usage.json"), "utf8")
+  );
+}
+
+async function renderWithIntl(locale: string, node: ReactNode) {
+  const container = document.createElement("div");
+  document.body.appendChild(container);
+  const root = createRoot(container);
+  const usageMessages = loadUsageMessages(locale);
+
+  await act(async () => {
+    root.render(
+      <NextIntlClientProvider locale={locale} messages={{ usage: usageMessages }} timeZone="UTC">
+        {node}
+      </NextIntlClientProvider>
+    );
+  });
+
+  return {
+    unmount: async () => {
+      await act(async () => root.unmount());
+      container.remove();
+    },
+  };
+}
+
+describe("UsageDocPage - 目录/快速链接交互", () => {
+  test("应渲染 skip links,且登录态显示返回仪表盘链接", async () => {
+    Object.defineProperty(window, "scrollTo", {
+      value: vi.fn(),
+      writable: true,
+    });
+
+    document.cookie = "auth-token=test-token";
+
+    const { unmount } = await renderWithIntl("en", <UsageDocPage />);
+
+    expect(document.querySelector('a[href="#main-content"]')).not.toBeNull();
+    expect(document.querySelector('a[href="#toc-navigation"]')).not.toBeNull();
+
+    const dashboardLink = document.querySelector('a[href="/dashboard"]');
+    expect(dashboardLink).not.toBeNull();
+
+    await unmount();
+  });
+
+  test("目录项点击后应触发平滑滚动", async () => {
+    const scrollToMock = vi.fn();
+    Object.defineProperty(window, "scrollTo", {
+      value: scrollToMock,
+      writable: true,
+    });
+
+    const { unmount } = await renderWithIntl("en", <UsageDocPage />);
+
+    const tocNav = document.querySelector("#toc-navigation nav");
+    expect(tocNav).not.toBeNull();
+
+    let tocButtons = tocNav?.querySelectorAll("button") ?? [];
+    for (let i = 0; i < 10 && tocButtons.length === 0; i++) {
+      await act(async () => {
+        await new Promise((r) => setTimeout(r, 0));
+      });
+      tocButtons = tocNav?.querySelectorAll("button") ?? [];
+    }
+
+    expect(tocButtons.length).toBeGreaterThan(0);
+
+    await act(async () => {
+      (tocButtons[0] as HTMLButtonElement).click();
+    });
+
+    expect(scrollToMock).toHaveBeenCalled();
+
+    await unmount();
+  });
+});