<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Let's Write</title>
    <description>The latest articles on DEV Community by Let's Write (@letswrite).</description>
    <link>https://dev.to/letswrite</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F879108%2F12d6ce35-abca-4678-a503-fc4600997729.png</url>
      <title>DEV Community: Let's Write</title>
      <link>https://dev.to/letswrite</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/letswrite"/>
    <language>en</language>
    <item>
      <title>Agent Skill 開發：用 lets-social 自動將文章轉化為 IG、Threads、FB 社群貼文</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Tue, 01 Sep 2026 14:21:22 +0000</pubDate>
      <link>https://dev.to/letswrite/agent-skill-kai-fa-yong-lets-social-zi-dong-jiang-wen-zhang-zhuan-hua-wei-ig-threads-fb-she-qun-tie-wen-2940</link>
      <guid>https://dev.to/letswrite/agent-skill-kai-fa-yong-lets-social-zi-dong-jiang-wen-zhang-zhuan-hua-wei-ig-threads-fb-she-qun-tie-wen-2940</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;以前流行手寫套件，分享到 GitHub 上讓人取用，而現在，流行的是寫 Skill 了。&lt;/p&gt;

&lt;p&gt;因為來自 Google 的流量一直在下降，所以 Let's Write 這陣子建了 &lt;a href="https://www.instagram.com/i.am.letswrite/" rel="noopener noreferrer"&gt;Instagram&lt;/a&gt;、&lt;a href="https://www.threads.com/@i.am.letswrite" rel="noopener noreferrer"&gt;Threads&lt;/a&gt; 帳號，增加導流的平台。&lt;/p&gt;

&lt;p&gt;只是每個社群都有各自的風格。&lt;/p&gt;

&lt;p&gt;為了讓一篇文章出來後，可以針對不同的平台，撰寫不同的貼文，就讓 AI 建立了本篇的 Skill，期待有效的增加本站流量。&lt;/p&gt;

&lt;p&gt;這是公開的 Skill，大家可以直接從 GitHub 上下載，安裝方式寫在簡易版的 README.md 中，以下筆記文是把安裝、使用的說明貼上來。&lt;/p&gt;

&lt;p&gt;GitHub：&lt;a href="https://github.com/letswritetw/lets-social" rel="noopener noreferrer"&gt;https://github.com/letswritetw/lets-social&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;取用前請給本專案一個星星，你一個小小的動作，對本站都是大大的鼓勵。&lt;/p&gt;




&lt;h2&gt;
  
  
  lets-social
&lt;/h2&gt;

&lt;p&gt;功用：把一篇文章轉寫成符合 Instagram、Facebook、Threads 與 Telegram 等，符合社群平台特性的貼文內容。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lets-social&lt;/code&gt; 是純指令型 Agent Skill。&lt;/p&gt;

&lt;p&gt;當我們提供已完成的文章、Markdown、純文字、本機檔案後，Agent 都能讀取的內容，然後先建立一份內部 Source Brief，再針對每個指定平台獨立撰寫發文文字。&lt;/p&gt;

&lt;p&gt;Instagram 預設會附上 Nano Banana 2 與 ChatGPT Images 2.0 的可直接使用 prompt。&lt;/p&gt;

&lt;p&gt;Threads 只在圖片能補充內容時提供。&lt;/p&gt;

&lt;p&gt;它不負責發佈、排程，也不替來源補上未經證實的資料。&lt;/p&gt;

&lt;p&gt;所有輸出都必須保留原文的事實、不確定性、立場與作者語氣。&lt;/p&gt;




&lt;h2&gt;
  
  
  為什麼要用 lets-social？
&lt;/h2&gt;

&lt;p&gt;每個社群平台都有不同的閱讀方式：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Facebook 適合補充背景。&lt;/li&gt;
&lt;li&gt;Threads 適合從一個明確觀察切入。&lt;/li&gt;
&lt;li&gt;Telegram 則重視資訊密度與掃讀效率。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;lets-social&lt;/code&gt; 會依平台重新判斷 Hook、資訊順序、語氣與 CTA，不會只把同一份文案改成不同長度。&lt;/p&gt;

&lt;p&gt;圖片也採用相同原則：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instagram 預設需要視覺素材。&lt;/li&gt;
&lt;li&gt;Threads 不會為了湊格式加入裝飾圖。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  支援平台
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;平台&lt;/th&gt;
&lt;th&gt;預設輸出&lt;/th&gt;
&lt;th&gt;內容重點&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Instagram&lt;/td&gt;
&lt;td&gt;Caption、CTA、可選 Hashtag 或 Carousel、產圖 prompt&lt;/td&gt;
&lt;td&gt;手機閱讀、實用價值與配圖&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Facebook&lt;/td&gt;
&lt;td&gt;Post、CTA&lt;/td&gt;
&lt;td&gt;背景、寫作動機與作者觀點&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Threads&lt;/td&gt;
&lt;td&gt;單篇貼文或適合的串文；必要時附產圖 prompt&lt;/td&gt;
&lt;td&gt;口語觀察與討論感&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Telegram&lt;/td&gt;
&lt;td&gt;Message、連結位置建議&lt;/td&gt;
&lt;td&gt;清楚、易掃讀與資訊密度&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  運作方式
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;來源文章
    -&amp;gt; 內部 Source Brief
        -&amp;gt; Instagram 草稿
        -&amp;gt; Facebook 草稿
        -&amp;gt; Threads 草稿
        -&amp;gt; Telegram 草稿
        -&amp;gt; Instagram Image Brief
        -&amp;gt; 可選的 Threads Image Brief
    -&amp;gt; 每份 Image Brief 產生兩個模型的完整 prompt
    -&amp;gt; 來源忠實度與平台差異檢查
    -&amp;gt; Markdown 輸出
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;每個平台都從同一份 Source Brief 出發，不會拿某個平台的草稿當成另一個平台的輸入。&lt;/p&gt;

&lt;p&gt;同一平台的兩份產圖 prompt 使用相同 Image Brief，讓主體、構圖與訊息保持一致。&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝
&lt;/h2&gt;

&lt;p&gt;Clone 或下載此 &lt;a href="https://github.com/letswritetw/lets-social" rel="noopener noreferrer"&gt;repository&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;再將完整的 &lt;code&gt;lets-social&lt;/code&gt; 目錄放到 Agent 支援的 Skill 路徑。&lt;/p&gt;

&lt;p&gt;請保留 &lt;code&gt;SKILL.md&lt;/code&gt;、&lt;code&gt;references/&lt;/code&gt; 與 &lt;code&gt;examples/&lt;/code&gt; 的相對位置。&lt;/p&gt;

&lt;p&gt;若希望之後能更新，請使用 Git Clone，並直接 Clone 到下列 Skill 路徑，因為下載 ZIP 的安裝方式不會保留 Git 更新紀錄。&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;

&lt;p&gt;安裝為個人 Skill：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/letswritetw/lets-social.git ~/.claude/skills/lets-social
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows 使用者&lt;/p&gt;

&lt;p&gt;Git Bash 可以直接用上面的指令，但 PowerShell 和 cmd 不會展開 &lt;code&gt;~&lt;/code&gt;，會在當前目錄建一個名字叫 &lt;code&gt;~&lt;/code&gt; 的資料夾，所以請改用下面的指令。&lt;/p&gt;

&lt;p&gt;PowerShell：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/letswritetw/lets-social.git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.claude/skills/lets-social"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cmd：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/letswritetw/lets-social.git &lt;span class="s2"&gt;"%USERPROFILE%/.claude/skills/lets-social"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;或安裝在單一專案，在專案根目錄執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/letswritetw/lets-social.git .claude/skills/lets-social
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code 可以依 description 自動選用，也可以用 &lt;code&gt;/lets-social&lt;/code&gt; 明確呼叫。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/lets-social 把 ./article.md 轉成 Instagram 與 Threads 貼文。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Codex
&lt;/h3&gt;

&lt;p&gt;安裝為個人 Skill：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/letswritetw/lets-social.git ~/.agents/skills/lets-social
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows 使用者&lt;/p&gt;

&lt;p&gt;Git Bash 可以直接用上面的指令。但 PowerShell 和 cmd 不會展開 &lt;code&gt;~&lt;/code&gt;，請改用下面的指令。&lt;/p&gt;

&lt;p&gt;PowerShell：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;https://github.com/letswritetw/lets-social.git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.agents/skills/lets-social"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cmd：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/letswritetw/lets-social.git &lt;span class="s2"&gt;"%USERPROFILE%/.agents/skills/lets-social"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;或安裝在單一 repository，在 repository 根目錄執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/letswritetw/lets-social.git .agents/skills/lets-social
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codex 可以依 description 自動選用，也可以用 &lt;code&gt;$lets-social&lt;/code&gt; 明確呼叫。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$lets&lt;/span&gt;&lt;span class="nt"&gt;-social&lt;/span&gt; 把 ./article.md 轉成 Instagram 與 Threads 貼文。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  其他支援 Agent Skills 的工具
&lt;/h3&gt;

&lt;p&gt;請依工具文件提供的安裝路徑，複製完整的 &lt;code&gt;lets-social&lt;/code&gt; 目錄。&lt;/p&gt;

&lt;p&gt;此專案遵循 &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills 規格&lt;/a&gt;，使用包含 &lt;code&gt;name&lt;/code&gt; 與 &lt;code&gt;description&lt;/code&gt; 的 YAML frontmatter、Markdown 指令本文，以及按需求讀取的 reference 檔案。&lt;/p&gt;

&lt;p&gt;安裝後，可以從工具的 Skill 選單呼叫 &lt;code&gt;lets-social&lt;/code&gt;，或在提示中直接指定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;使用 lets-social，把這篇文章轉成 Facebook 與 Telegram 貼文。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agent Skills 規格定義可攜的封裝格式，不負責統一各工具的安裝路徑，也不保證每個工具都支援相同的產品專屬功能。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lets-social&lt;/code&gt; 只使用共通 frontmatter 欄位與一般 Markdown 相對連結，降低對單一工具的依賴。&lt;/p&gt;




&lt;h2&gt;
  
  
  版本與更新
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Git 更新（建議）
&lt;/h3&gt;

&lt;p&gt;使用 Git Clone 安裝的使用者，可以依安裝位置執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code 個人 Skill&lt;/span&gt;
git &lt;span class="nt"&gt;-C&lt;/span&gt; ~/.claude/skills/lets-social pull &lt;span class="nt"&gt;--ff-only&lt;/span&gt;

&lt;span class="c"&gt;# Codex 個人 Skill&lt;/span&gt;
git &lt;span class="nt"&gt;-C&lt;/span&gt; ~/.agents/skills/lets-social pull &lt;span class="nt"&gt;--ff-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows 使用者&lt;/p&gt;

&lt;p&gt;PowerShell 和 cmd 一樣不會展開 &lt;code&gt;~&lt;/code&gt;。Git Bash 可直接使用上面的指令。&lt;/p&gt;

&lt;p&gt;PowerShell，Claude Code：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.claude/skills/lets-social"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--ff-only&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PowerShell，Codex：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="bp"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.agents/skills/lets-social"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pull&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--ff-only&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cmd，Claude Code：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"%USERPROFILE%/.claude/skills/lets-social"&lt;/span&gt; pull &lt;span class="nt"&gt;--ff-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;cmd，Codex：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"%USERPROFILE%/.agents/skills/lets-social"&lt;/span&gt; pull &lt;span class="nt"&gt;--ff-only&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;專案層級或其他 Agent 的安裝位置，請將上面的路徑替換為實際的 &lt;code&gt;lets-social&lt;/code&gt; 目錄。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--ff-only&lt;/code&gt; 會在本機內容與遠端版本分歧時停止，不會直接覆蓋使用者的修改。&lt;/p&gt;

&lt;p&gt;下載 ZIP 或直接複製資料夾的使用者，請重新下載最新版，並以完整目錄取代舊版。&lt;/p&gt;

&lt;p&gt;若曾自訂 Skill，請先備份或保留差異。&lt;/p&gt;

&lt;h3&gt;
  
  
  使用 Skillshare 追蹤（選用）
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/runkids/skillshare" rel="noopener noreferrer"&gt;Skillshare&lt;/a&gt; 使用者可以用它安裝並更新：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;skillshare &lt;span class="nb"&gt;install &lt;/span&gt;letswritetw/lets-social
skillshare &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;之後檢查與更新：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;skillshare check lets-social
skillshare update lets-social
skillshare &lt;span class="nb"&gt;sync&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;請不要加 &lt;code&gt;--track&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;這個 repository 把 &lt;code&gt;SKILL.md&lt;/code&gt; 放在根目錄，&lt;code&gt;--track&lt;/code&gt; 會改用追蹤模式，把它記成 &lt;code&gt;_lets-social&lt;/code&gt; 並回報 &lt;code&gt;0 skills&lt;/code&gt;，Skill 不會被 sync 到 Agent 的目錄。&lt;/p&gt;

&lt;p&gt;不加 &lt;code&gt;--track&lt;/code&gt; 時 &lt;code&gt;skillshare update&lt;/code&gt; 一樣可以更新。&lt;/p&gt;

&lt;p&gt;Skillshare 是選用的第三方工具，不是執行 &lt;code&gt;lets-social&lt;/code&gt; 的必要相依套件。&lt;/p&gt;




&lt;h2&gt;
  
  
  使用範例
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;使用 lets-social，把 article.md 轉成 Instagram、Facebook、
Threads 與 Telegram 貼文。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;幫我宣傳這篇技術文章，只做 Threads 和 Telegram。Threads 口語一點，
Telegram 像頻道公告，所有平台都不要 Hashtag。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;把這篇文章改寫成 Facebook 宣傳文。保留作者的技術語氣，
CTA 要引導讀者閱讀完整文章。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Instagram 不要產圖 prompt；Threads 這篇要一張配圖。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;如果 Agent 無法讀取網址，Skill 會停止產生貼文，並要求你提供文章本文或 Markdown。&lt;/p&gt;

&lt;p&gt;它不會假裝已經讀過網頁。&lt;/p&gt;




&lt;h2&gt;
  
  
  支援的來源
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;已完成的純文字或 Markdown。&lt;/li&gt;
&lt;li&gt;可讀取的本機文章或文件。&lt;/li&gt;
&lt;li&gt;目前 Agent 具備網頁讀取能力時可使用網址。&lt;/li&gt;
&lt;li&gt;先前由 &lt;code&gt;lets-social&lt;/code&gt; 產生的內容，可用於單一平台調整。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;來源必須包含足以支撐貼文的文章內容。只有標題或搜尋摘要並不足夠。&lt;/p&gt;




&lt;h2&gt;
  
  
  產圖 prompt
&lt;/h2&gt;

&lt;p&gt;Instagram 預設在文案後輸出 &lt;code&gt;Image Direction&lt;/code&gt;、&lt;code&gt;Nano Banana 2 Prompt&lt;/code&gt; 與 &lt;code&gt;ChatGPT Images 2.0 Prompt&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;兩份 prompt 都是完整內容，可以分別貼進 Gemini 與 ChatGPT，不需要再補文章背景。&lt;/p&gt;

&lt;p&gt;Threads 只有在圖片能說明比較、流程、具體物件、空間關係或文章支持的視覺概念時才輸出這三個欄位。純觀點與討論型貼文會保留文字形式。使用者可以明確要求加入或省略圖片 prompt。&lt;/p&gt;

&lt;p&gt;每個平台各自建立 Image Brief。&lt;/p&gt;

&lt;p&gt;同一平台的兩份 prompt 必須使用相同主體、構圖、圖中文字與排除條件。&lt;/p&gt;

&lt;h3&gt;
  
  
  視覺風格依文章挑選
&lt;/h3&gt;

&lt;p&gt;不套用單一固定風格。&lt;/p&gt;

&lt;p&gt;Skill 會依文章實際內容選一個方向，在 Image Brief 說明選擇理由，並讓同一平台的所有圖片維持同一套風格。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;方向&lt;/th&gt;
&lt;th&gt;適合的文章&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;知識圖卡&lt;/td&gt;
&lt;td&gt;檢查清單、評估條件、操作步驟&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;編輯式圖解&lt;/td&gt;
&lt;td&gt;架構、資料流、請求生命週期、前後對照&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;大字海報&lt;/td&gt;
&lt;td&gt;單一主張或一個反直覺的結論&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;終端機或藍圖&lt;/td&gt;
&lt;td&gt;底層工程、工具、協定、除錯&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;插畫場景&lt;/td&gt;
&lt;td&gt;團隊流程、職涯、決策&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;原文若指向更合適的方向，也可以改寫成自訂風格。使用者明確指定風格時以使用者為準。&lt;/p&gt;

&lt;h3&gt;
  
  
  每份 prompt 都會包含的內容
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;至少四個具名顏色，並說明每個顏色負責什麼；使用者要求單色時例外。&lt;/li&gt;
&lt;li&gt;來源支持的重點短句，直接寫在圖上。&lt;/li&gt;
&lt;li&gt;閱讀層級：哪個元素先讀、其次、最後。&lt;/li&gt;
&lt;li&gt;足夠的紋理、標註或結構細節，避免變成空泛的素材圖。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  圖中文字與字體規則
&lt;/h3&gt;

&lt;p&gt;圖中文字預設為必要，不是選配。&lt;/p&gt;

&lt;p&gt;標題一句、重點三到五句、可選的結語一句，全部在 prompt 中逐字列出並指定位置，並要求模型不要輸出其他文字。&lt;/p&gt;

&lt;p&gt;每一句都必須是原文支持的說法，不能新增數字、結果或保證。&lt;/p&gt;

&lt;p&gt;中文、日文、韓文一律指定清楚精確的粗體無襯線字，不使用手寫、毛筆、書法或做舊效果。&lt;/p&gt;

&lt;p&gt;產圖模型處理手寫 CJK 一定會糊，這條規則在所有風格下都成立。裝飾性手寫只用在兩到四個字的英文短語上，並且在 prompt 中明講這個分工。&lt;/p&gt;

&lt;h3&gt;
  
  
  兩個模型的差別
&lt;/h3&gt;

&lt;p&gt;Nano Banana 2 維持單張，對應 Instagram 的 4:5 封面。&lt;/p&gt;

&lt;p&gt;ChatGPT Images 2.0 一次最多可產 10 張，且每張可以承載不同內容，所以它那份 prompt 是一組編號系列：&lt;/p&gt;

&lt;p&gt;第 1 張是封面，之後每個重點各一張，可選最後一張放結語或導讀 CTA。&lt;/p&gt;

&lt;p&gt;預設為封面加三到五個重點，也就是四到六張。&lt;/p&gt;

&lt;p&gt;輸出含 Carousel 大綱時改以大綱為準。整組共用同一個背景、色盤、字體規則與視覺語彙，只有內容改變。&lt;/p&gt;

&lt;p&gt;需要單張時，只取第 1 張。&lt;/p&gt;

&lt;p&gt;Skill 只撰寫 prompt，不會連線到 Gemini 或 ChatGPT。實際產圖功能與費用依使用者的帳號方案而定。&lt;/p&gt;




&lt;h2&gt;
  
  
  自訂品牌語氣
&lt;/h2&gt;

&lt;p&gt;預設語氣清楚、專業、容易親近，並以實用與易懂的方式表達技術內容。&lt;/p&gt;

&lt;p&gt;原文明確呈現的作者語氣擁有較高優先順序。&lt;/p&gt;

&lt;p&gt;我們可以直接用自然語言覆寫設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;語氣更像個人心得，不要 emoji，不要 Hashtag，技術名詞要精確。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;使用者可以調整文風，但不能藉此改變來源事實或加入沒有根據的資訊。&lt;/p&gt;




&lt;h2&gt;
  
  
  只產生部分平台
&lt;/h2&gt;

&lt;p&gt;只要指定需要的平台：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;只產生 Threads 與 Telegram。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;後續也能只調整單一平台：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Threads 再短一點，語氣更口語。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Skill 只會修改 Threads，不會重新產生其他平台內容。&lt;/p&gt;




&lt;h2&gt;
  
  
  專案結構
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lets-social/
├── .gitignore
├── SKILL.md
├── README.md
├── README-full.md
├── CHANGELOG.md
├── LICENSE
├── references/
│   ├── instagram.md
│   ├── facebook.md
│   ├── threads.md
│   ├── telegram.md
│   ├── brand-voice.md
│   ├── image-prompts.md
│   └── output-format.md
└── examples/
    ├── input-example.md
    └── output-example.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;此 &lt;a href="https://github.com/letswritetw/lets-social" rel="noopener noreferrer"&gt;repository&lt;/a&gt; 不需要 runtime、套件管理工具、建置系統或 API server。&lt;/p&gt;

&lt;p&gt;只有實際使用 prompt 產圖時才需要開啟外部產圖服務。&lt;/p&gt;

&lt;h3&gt;
  
  
  新增其他平台
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;references/&lt;/code&gt; 新增一份專用文件，定義平台目標、內容判斷、輸出欄位與檢查項目。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;SKILL.md&lt;/code&gt; 加入平台路由，不要把詳細規則複製回主檔。&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;references/output-format.md&lt;/code&gt; 加入最小且實用的輸出格式。&lt;/li&gt;
&lt;li&gt;補上範例，並確認 Hook、結構、資訊順序與 CTA 都和其他平台不同。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;除非需要可重複且具確定性的工具操作，否則請維持純指令結構。&lt;/p&gt;

</description>
      <category>ai</category>
      <category>skill</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>Claude Code + Sentry MCP + Gitea MCP：每日自動抓 bug、修復、開 PR</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Sun, 23 Aug 2026 07:38:03 +0000</pubDate>
      <link>https://dev.to/letswrite/claude-code-sentry-mcp-gitea-mcpmei-ri-zi-dong-zhua-bug-xiu-fu-kai-pr-43c2</link>
      <guid>https://dev.to/letswrite/claude-code-sentry-mcp-gitea-mcpmei-ri-zi-dong-zhua-bug-xiu-fu-kai-pr-43c2</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;除了開發新專案，工程師日常最耗時的工作通常是排查、修復既有系統的 Bug。&lt;/p&gt;

&lt;p&gt;而當 Anthropic 推出 Model Context Protocol （MCP）後，AI 在軟體開發自動化（DevOps Automation）有了全新落地應用，那有沒有辦法可以讓 AI 自己去找 bug 然後再自己修？&lt;/p&gt;

&lt;p&gt;想到以前曾用過一陣子 Sentry，查了一下有 MCP 可用。&lt;/p&gt;

&lt;p&gt;再查了一下 Gitea，也確定有 MCP 可用。&lt;/p&gt;

&lt;p&gt;那把兩邊尬在一起，不就可以達成本篇的目標：AI 自己找 bug 後自己修？&lt;/p&gt;

&lt;p&gt;本文實作如何透過 Sentry MCP 自動抓取 Production 錯誤堆疊，再結合 Gitea MCP 讓 Claude Code 實現每日自動找 Bug、本機修復測試、自動提交 PR 的完整除錯流程。&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝 Sentry MCP
&lt;/h2&gt;

&lt;p&gt;本機要先安裝 Claude CLI：&lt;a href="https://code.claude.com/docs/en/quickstart" rel="noopener noreferrer"&gt;https://code.claude.com/docs/en/quickstart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;1 先下指令安裝，安裝 Sentry MCP：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http sentry https://mcp.sentry.dev/mcp &lt;span class="nt"&gt;--scope&lt;/span&gt; user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2 登入 Sentry 帳戶進行授權：&lt;/p&gt;

&lt;p&gt;終端機輸入 claude，接著輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/mcp list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;清單裡會看到 sentry 顯示為未授權，對 sentry 那項按 enter，瀏覽器會開啟頁面，都按 Approve 就行：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh6c6vk6nkv65hwafn3yt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh6c6vk6nkv65hwafn3yt.png" alt="Claude Code 終端機執行 MCP list 選擇 Sentry 授權" width="799" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claude Code 終端機執行 MCP list 選擇 Sentry 授權&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5rzn9mumqrvk8mkefed7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5rzn9mumqrvk8mkefed7.png" alt="Sentry 官方網站 OAuth 授權頁面點擊 Approve" width="800" height="555"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry 官方網站 OAuth 授權頁面點擊 Approve&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsmpe8f9e398x2ymg77ue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsmpe8f9e398x2ymg77ue.png" alt="Sentry MCP 授權成功完成提示頁面" width="698" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry MCP 授權成功完成提示頁面&lt;/p&gt;

&lt;p&gt;接著 Claude 要重啟，可以問問 Claude 有了 Sentry MCP 後它可以做到哪些事，August 詢問後回答如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;查 issue&lt;/strong&gt;：&lt;code&gt;search_issues&lt;/code&gt; 搜錯誤，按專案/時間/狀態過濾&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;查事件&lt;/strong&gt;：&lt;code&gt;search_events&lt;/code&gt; 挖詳細 event 資料，stack trace、tag、breadcrumb 都能看&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI 診斷&lt;/strong&gt;：&lt;code&gt;analyze_issue_with_seer&lt;/code&gt; 丟給 Seer 分析根因，給修復建議&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;改狀態&lt;/strong&gt;：&lt;code&gt;update_issue&lt;/code&gt; 標 resolved/ignored、指派負責人&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;找組織/專案&lt;/strong&gt;：&lt;code&gt;find_organizations&lt;/code&gt;、&lt;code&gt;find_projects&lt;/code&gt; 列出可存取範圍&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;通用查詢&lt;/strong&gt;：&lt;code&gt;get_sentry_resource&lt;/code&gt;、&lt;code&gt;execute_sentry_tool&lt;/code&gt; 存取更細資源&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  讓 Claude 知道要抓取哪個專案
&lt;/h2&gt;

&lt;p&gt;Sentry 後台的側邊導覽列，點擊「設定 &amp;gt; 專案」，然後點擊要讓 Claude 分析的專案後，會看到專案資訊：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8gwbsg9v5gaee4ytnv10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8gwbsg9v5gaee4ytnv10.png" alt="Sentry 後台專案設定中查看 Project Slug 與專案編號" width="719" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sentry 後台專案設定中查看 Project Slug 與專案編號&lt;/p&gt;

&lt;p&gt;提供 Slug 或是專案編號給 Claude 都行，就會抓到了，以下是 August 提供專案編號的示範：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fikigixt4xom2q7moi8p8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fikigixt4xom2q7moi8p8.png" alt="在 Claude Code 對話中提供 Sentry 專案編號進行讀取" width="653" height="866"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 Claude Code 對話中提供 Sentry 專案編號進行讀取&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝 Gitea MCP
&lt;/h2&gt;

&lt;p&gt;下列有三種方式，無論選擇哪種安裝方式，都需要先取得一組具備讀寫權限的 Gitea Token。&lt;/p&gt;

&lt;p&gt;詳細的 Token 建立步驟與權限設定，請參考站內筆記文：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.letswrite.tw/gitea-ai-code-review-2/" rel="noopener noreferrer"&gt;CodiumAI PR-Agent，在 Gitea 上用 AI 來 Code Review&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  方式一：直接用 go run
&lt;/h3&gt;

&lt;p&gt;需要本機有 Go 才能使用：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio &lt;span class="nt"&gt;--scope&lt;/span&gt; user gitea &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;GITEA_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;你的token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;GITEA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-gitea.internal &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; go run gitea.com/gitea/gitea-mcp@latest &lt;span class="nt"&gt;-t&lt;/span&gt; stdio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  方式二：下載官方執行檔
&lt;/h3&gt;

&lt;p&gt;官方 &lt;a href="https://gitea.com/gitea/gitea-mcp/releases" rel="noopener noreferrer"&gt;releases&lt;/a&gt; 有提供 gitea-mcp_Windows_x86_64.zip 給 Windows，以及 gitea-mcp_Darwin_arm64.tar.gz 給 Apple Silicon Mac。&lt;/p&gt;

&lt;p&gt;Windows：&lt;/p&gt;

&lt;p&gt;從 &lt;a href="https://gitea.com/gitea/gitea-mcp/releases" rel="noopener noreferrer"&gt;https://gitea.com/gitea/gitea-mcp/releases&lt;/a&gt; 下載 gitea-mcp_Windows_x86_64.zip&lt;br&gt;
解壓縮,把 gitea-mcp.exe 放到一個固定資料夾(例如 C:\Tools\gitea-mcp)。&lt;/p&gt;

&lt;p&gt;把該資料夾加進系統環境變數 PATH。&lt;/p&gt;

&lt;p&gt;Mac：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-LO&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;https://gitea.com/gitea/gitea-mcp/releases/download/v1.2.0/gitea-mcp_Darwin_arm64.tar.gz]&lt;span class="o"&gt;(&lt;/span&gt;https://gitea.com/gitea/gitea-mcp/releases/download/v1.2.0/gitea-mcp_Darwin_arm64.tar.gz&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; gitea-mcp_Darwin_arm64.tar.gz
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;gitea-mcp /usr/local/bin/
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x /usr/local/bin/gitea-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;安裝好 Gitea MCP 後，接上 Claude Code：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio &lt;span class="nt"&gt;--scope&lt;/span&gt; user gitea &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;GITEA_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;你的token &lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;GITEA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-gitea.internal &lt;span class="nt"&gt;--&lt;/span&gt; gitea-mcp &lt;span class="nt"&gt;-t&lt;/span&gt; stdio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  方式三：用 Docker
&lt;/h3&gt;

&lt;p&gt;官方也提供 &lt;a href="http://docker.gitea.com/gitea-mcp-server" rel="noopener noreferrer"&gt;docker.gitea.com/gitea-mcp-server&lt;/a&gt; 這個 image，不需要額外編譯或裝 Go。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio &lt;span class="nt"&gt;--scope&lt;/span&gt; user gitea &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;GITEA_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;你的token &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--env&lt;/span&gt; &lt;span class="nv"&gt;GITEA_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-gitea.internal &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; GITEA_ACCESS_TOKEN &lt;span class="nt"&gt;-e&lt;/span&gt; GITEA_HOST &lt;span class="o"&gt;[&lt;/span&gt;docker.gitea.com/gitea-mcp-server]&lt;span class="o"&gt;(&lt;/span&gt;http://docker.gitea.com/gitea-mcp-server&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不管選擇哪種方式，安裝好後在 Claude Code 輸入 /mcp，確認 Gitea 連線成功。&lt;/p&gt;




&lt;h2&gt;
  
  
  Claude Code 設定排程
&lt;/h2&gt;

&lt;p&gt;Claude Code 選好本機專案後，貼上以下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;建立一個本機的排程，在本專案執行以下「Sentry issue 自動修復」功能：

Sentry 組織 slug: your-org-slug
Sentry 專案 slug: your-project
Gitea repo: Gitea 專案網址

步驟 1 - 找 issue:
呼叫 search_issues 找未解決的 error 類型 issue，
一次只查一種類型，不要用 OR / AND 組合查詢：
&lt;span class="p"&gt;
-&lt;/span&gt; "unresolved error issues" (limit 3)
  依事件數排序，取前 3 個。

步驟 2 - 抓細節:
對每個候選 issue 呼叫 get_issue_details，
取得完整 stack trace 與 trace ID。

步驟 3 - 分析根因:
對每個 issue 呼叫 analyze_issue_with_seer。
若 Seer 沒有給出可行的修正建議，
改用步驟 2 拿到的 stack trace 自行分析程式碼、
定位根因所在的檔案與函式。

步驟 4 - 本機修復:
只處理「根因明確、修改範圍小」的 issue（跳過需要
架構調整或影響多個模組的問題，改走步驟 6 的 fallback）。
在本機修改對應程式碼，接著執行專案既有的測試指令，
確認測試全數通過才進入下一步；測試沒過就放棄這個
issue、改走步驟 6。

步驟 5 - 開 PR（Gitea，透過 gitea-mcp）:
a. 呼叫 pull_request_read（method: list, state: open）
檢查 owner/repo 是否已有相同 issue 的開啟中 PR，
有的話跳過，避免重複開單。
b. git checkout -b fix/issue-&lt;span class="nt"&gt;&amp;lt;issue-id&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;date&amp;gt;&lt;/span&gt; main
commit 訊息包含 Sentry issue ID。
git push 到 Gitea。
c. 呼叫 pull_request_write（method: create）：- owner/repo - head: fix/issue-&lt;span class="nt"&gt;&amp;lt;issue-id&amp;gt;&lt;/span&gt;-&lt;span class="nt"&gt;&amp;lt;date&amp;gt;&lt;/span&gt; - base: main - title: "fix: &lt;span class="nt"&gt;&amp;lt;根因摘要&amp;gt;&lt;/span&gt;" - body:
"Sentry issue: &lt;span class="nt"&gt;&amp;lt;issue&lt;/span&gt; &lt;span class="na"&gt;連結&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
Trace ID: &lt;span class="nt"&gt;&amp;lt;trace&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sb"&gt;

        根因: &amp;lt;Seer 或自行分析的結論&amp;gt;
        修改內容: &amp;lt;變更摘要&amp;gt;
        測試結果: 已跑過既有測試並通過"

&lt;/span&gt;d. 每次執行最多開 2 個 PR，超過就停止並在最後總結說明。

步驟 6 - 無法修復時（fallback）:
若 Seer 與自行分析都無法定位可行修正，或修改範圍過大、
或測試沒通過，改呼叫 issue_write（method: create）
在 Gitea 開一個 issue，內容包含 Sentry issue 連結、
trace ID、已知線索，標記給人工處理，不要硬修。

執行完畢後，用一段文字總結：處理了幾個 issue、開了幾個 PR、
有幾個轉成 fallback issue，並列出每個 PR / issue 的連結。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;需要替換的是前面幾行的這 3 個：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sentry 組織 slug: your-org-slug
Sentry 專案 slug: your-project
Gitea repo: Gitea 專案網址
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;前 2 個值可以在 Sentry 後台取得。&lt;/p&gt;

&lt;p&gt;第 3 個值就是 Gitea 上的專案取得。&lt;/p&gt;

&lt;p&gt;送出後，Claude Code 會詢問執行的時間，提供後就會建立完成。&lt;/p&gt;

&lt;p&gt;建議是建立完成後，執行一次，看還會遇到什麼問題。&lt;/p&gt;

&lt;p&gt;建立好的排程，會出現在「Routines」裡：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgij2ckqn11s8rz2n0kfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgij2ckqn11s8rz2n0kfw.png" alt="Claude Code 自動排程 Routine 建立成功介面" width="800" height="620"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Claude Code 自動排程 Routine 建立成功介面&lt;/p&gt;




&lt;h2&gt;
  
  
  進階整合：搭配 CodiumAI PR-Agent 實現 Gitea 自動 Code Review
&lt;/h2&gt;

&lt;p&gt;最後再搭配這篇，就會在有 PR 時自動再做一次 Code Review：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.letswrite.tw/gitea-ai-code-review-2/" rel="noopener noreferrer"&gt;CodiumAI PR-Agent，在 Gitea 上用 AI 來 Code Review&lt;/a&gt;&lt;/p&gt;

</description>
      <category>gitea</category>
      <category>sentry</category>
      <category>claude</category>
      <category>mcp</category>
    </item>
    <item>
      <title>公司內部自架 Claude Code Marketplace，讓團隊共用 skill</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Tue, 18 Aug 2026 13:18:47 +0000</pubDate>
      <link>https://dev.to/letswrite/gong-si-nei-bu-zi-jia-claude-code-marketplacerang-tuan-dui-gong-yong-skill-2fg3</link>
      <guid>https://dev.to/letswrite/gong-si-nei-bu-zi-jia-claude-code-marketplacerang-tuan-dui-gong-yong-skill-2fg3</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;有些 skill 只適合在公司內部使用，而這個 skill 又想讓其他同事能一起使用的話，就需要一個可以讓大家方便安裝、更新的平台。&lt;/p&gt;

&lt;p&gt;本篇主要筆記，用自架的 Gitea 建立一個內部專用的 Claude Code plugin marketplace，讓團隊裡每個人寫的 skill 不再各自散落在自己電腦裡，而是能集中在一個地方統一發布、安裝、更新版本。&lt;/p&gt;

&lt;p&gt;其他同事只要裝一次就能共用、之後改版也能一起同步拿到最新版。&lt;/p&gt;

&lt;p&gt;這篇整理實作過程中每一個能正確跑通的步驟，讓大家避免在這過程中踩到的坑。&lt;/p&gt;

&lt;p&gt;可用 GitHub、GitLab、Gitea，主要是專案可以有版控就行。&lt;/p&gt;

&lt;p&gt;本篇示範的是在自架的 Gitea 上實作。&lt;/p&gt;




&lt;h2&gt;
  
  
  建立 marketplace 專案結構
&lt;/h2&gt;

&lt;p&gt;Gitea 上新建一個 repo（例如 &lt;code&gt;claude-plugins&lt;/code&gt;），本機目錄結構：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;claude-plugins/
├── .claude-plugin/
│   └── marketplace.json
└── plugins/
    └── your-plugin-name/
        ├── .claude-plugin/
        │   └── plugin.json
        └── skills/
            └── your-skill/
                └── SKILL.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  讓 Claude Code 直接產生 SKILL.md、plugin.json、marketplace.json
&lt;/h2&gt;

&lt;p&gt;看到上面的架構，有一堆的檔案要建立時，不用感到驚慌失措或是害怕。&lt;/p&gt;

&lt;p&gt;因為我們只需要把上面的架構貼給 Claude Code，讓它讀取之前已經寫好的 skill 內容，依照結構把 &lt;code&gt;SKILL.md&lt;/code&gt;、&lt;code&gt;plugin.json&lt;/code&gt;、&lt;code&gt;marketplace.json&lt;/code&gt; 都建出來即可。&lt;/p&gt;

&lt;p&gt;底下附上完整的 &lt;code&gt;marketplace.json&lt;/code&gt; 範例，供對照參考：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"claude-plugins"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"August"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai-tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./plugins/ai-tools"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ai-tools2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./plugins/ai-tools2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;必要欄位：&lt;code&gt;name&lt;/code&gt;（kebab-case，公開可見）、&lt;code&gt;owner&lt;/code&gt;、&lt;code&gt;plugins&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;每個 plugin entry 至少要有 &lt;code&gt;name&lt;/code&gt; 和 &lt;code&gt;source&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;若 &lt;code&gt;source&lt;/code&gt; 是同一個 repo 內的相對路徑，要以 &lt;code&gt;./&lt;/code&gt; 開頭，不可用 &lt;code&gt;../&lt;/code&gt; 跳出去。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;一個 plugin 可以裝多個 skill&lt;/strong&gt;：&lt;code&gt;skills/&lt;/code&gt; 資料夾下每個子資料夾都是獨立一個 skill，不需要每個 skill 各自開一個 plugin。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Host 到 Gitea
&lt;/h2&gt;

&lt;p&gt;不限 GitHub，GitLab、Bitbucket、自架伺服器都支援。把整個 &lt;code&gt;claude-plugins&lt;/code&gt; repo push 到 Gitea 即可，不需要額外設定。&lt;/p&gt;

&lt;h2&gt;
  
  
  驗證
&lt;/h2&gt;

&lt;p&gt;claude-plugins 專案上打開終端機，輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude plugin validate &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;會檢查 JSON schema、重複的 plugin 名稱、路徑穿越等問題。&lt;/p&gt;

&lt;p&gt;檔案寫的有問題，就會看到哪邊寫錯、哪邊建議要寫，再照著回應讓 Claude 繼續做修改即可。&lt;/p&gt;

&lt;h2&gt;
  
  
  本機測試安裝
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add ./claude-plugins
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;ai-tools@claude-plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;安裝語法格式固定是 &lt;code&gt;plugin名稱@marketplace名稱&lt;/code&gt;，分別對應 &lt;code&gt;plugin.json&lt;/code&gt; 的 &lt;code&gt;name&lt;/code&gt; 和 &lt;code&gt;marketplace.json&lt;/code&gt; 的 &lt;code&gt;name&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;有幾個 plugin 就要執行幾次安裝指令。&lt;/p&gt;

&lt;p&gt;要注意的是，這步可以跳過嗎？技術上可以，因為下一步的遠端安裝跟這步沒有依賴關係，直接對 Gitea 網址下指令一樣裝得起來。但跳過等於少了一層「出包前先在本機攔下來」的保護。&lt;/p&gt;

&lt;h2&gt;
  
  
  正式指向 Gitea
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add https://gitea.你們公司.internal/team/claude-plugins.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  讓團隊自動加入（選用，非必要步驟）
&lt;/h2&gt;

&lt;h3&gt;
  
  
  最直接的方式：手動下指令。
&lt;/h3&gt;

&lt;p&gt;同事不需要碰任何設定檔，兩行指令就能裝好：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace add https://gitea.你們公司.internal/team/claude-plugins.git
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;ai-tools@claude-plugins
/plugin &lt;span class="nb"&gt;install &lt;/span&gt;ai-tools2@claude-plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;裝完就能直接用，缺點是每次換電腦、換專案，或新同事加入，都要重新打一次這幾行。&lt;/p&gt;

&lt;p&gt;如果不想讓同事記指令、手動輸入，才需要往下看設定檔自動化這條路。&lt;/p&gt;

&lt;h3&gt;
  
  
  進階：讓同事 trust 專案資料夾就自動裝好。
&lt;/h3&gt;

&lt;p&gt;以下的內容，要放在每一個開發中的專案裡的 &lt;code&gt;.claude/settings.json&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extraKnownMarketplaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"claude-plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://gitea.你們公司.internal/team/claude-plugins.git"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"enabledPlugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ai-tools@claude-plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ai-tools2@claude-plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;extraKnownMarketplaces&lt;/code&gt; 只負責自動註冊 marketplace 來源。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;enabledPlugins&lt;/code&gt; 才是真正讓 plugin 自動裝上並啟用的關鍵，兩者要同時寫，同事 trust 專案資料夾後才會完全不用手動 &lt;code&gt;/plugin install&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果目前還沒有同事需要同步使用，這一步可以先跳過，不影響前面步驟的成果，等有需要再回頭補上即可。&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gitea 上有新版本時，同事怎麼更新
&lt;/h2&gt;

&lt;p&gt;如果有發佈新版本，記得 &lt;code&gt;plugin.json&lt;/code&gt; 裡的 &lt;code&gt;version&lt;/code&gt; 要往上加一版，沒改版號的話，其他同事那邊不會被判斷成有更新。&lt;/p&gt;

&lt;p&gt;同事更新分兩步，順序不能反：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/plugin marketplace update claude-plugins
/plugin update ai-tools@claude-plugins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;第一行只是刷新 marketplace 目錄，讓 Claude Code 知道有新版本存在。&lt;/p&gt;

&lt;p&gt;第二行才是真正把新內容拉下來、覆蓋本機快取。&lt;/p&gt;

&lt;p&gt;也可以用互動面板 &lt;code&gt;/plugin&lt;/code&gt; → 選到該 plugin → &lt;strong&gt;Update now&lt;/strong&gt; 達到一樣效果。&lt;/p&gt;




&lt;h2&gt;
  
  
  參考資料
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/plugin-marketplaces" rel="noopener noreferrer"&gt;Create and distribute a plugin marketplace — Claude Code Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/discover-plugins" rel="noopener noreferrer"&gt;Discover and install prebuilt plugins — Claude Code Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/plugins-reference" rel="noopener noreferrer"&gt;Plugins reference — Claude Code Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;這篇文章記錄的是目前版本觀察到的行為，Claude Code 的 plugin 系統仍在快速迭代中，實際行為可能隨版本更新而改變，建議操作前對照當下的官方文件再確認一次。&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>claude</category>
      <category>marketplace</category>
      <category>skill</category>
      <category>ai</category>
    </item>
    <item>
      <title>Agency Agents 教學：讓 Claude Code 與 Codex 自動切換 AI 專家角色</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Sat, 15 Aug 2026 01:53:09 +0000</pubDate>
      <link>https://dev.to/letswrite/agency-agents-jiao-xue-rang-claude-code-yu-codex-zi-dong-qie-huan-ai-zhuan-jia-jiao-se-40mc</link>
      <guid>https://dev.to/letswrite/agency-agents-jiao-xue-rang-claude-code-yu-codex-zi-dong-qie-huan-ai-zhuan-jia-jiao-se-40mc</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;現在工作上幾乎都用 Claude Code、Codex 來進行開發，只是常常會遇到以下狀況：&lt;/p&gt;

&lt;p&gt;要讓 AI 做 UI，會希望它像 UI 設計師一樣。&lt;/p&gt;

&lt;p&gt;要 Code Review，希望它變成 Code Reviewer。&lt;/p&gt;

&lt;p&gt;下一個專案要處理 Docker，又希望它不要一直卡在前端的事情上。&lt;/p&gt;

&lt;p&gt;雖然我們可以每次都在 Prompt 裡打一大串：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;你現在是一位資深前端工程師，
熟悉 Vue、React、Accessibility、Core Web Vitals……
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再雖然我們可以把自己習慣的 Prompt 存起來複製貼上，但基本上人類都是懶惰的，能少打一個字就少打一個字，可以的話最好是 AI 能自己懂現在要扮演什麼樣的專業角色，這就是為什麼貓咪們看到人類都會覺得：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftavn7e9rla733yqcodsd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftavn7e9rla733yqcodsd.jpg" alt="愚蠢的人類" width="800" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最近看到一個 GitHub 專案，發現可以解決上面很愛演的困擾：&lt;a href="https://github.com/msitarzewski/agency-agents" rel="noopener noreferrer"&gt;Agency Agents&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;簡單來說，這專案就是：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;幫 AI 準備好一大票不同領域的專家角色，需要誰時，就叫誰出來&lt;del&gt;當奴才&lt;/del&gt;工作。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;官方目前已經有 230+ 個不同領域的 Agent，而且除了 Claude Code，也支援 Codex、Cursor、GitHub Copilot、Gemini CLI …… 等工具。&lt;/p&gt;

&lt;p&gt;本篇除了筆記 Agency Agents 的安裝方式，也會多做一步：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;建立一個 Router Skill，讓 Claude Code、Codex 自己判斷這次應該找哪個 Agent。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;這樣平常只要正常描述需求：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;幫我重新設計這個 Dashboard，完成後用 Vue 實作。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI 就可以自己判斷要用 UI Designer，或是 Frontend Developer~~，或是鏟屎的~~。&lt;/p&gt;




&lt;h2&gt;
  
  
  Agency Agents 是什麼？
&lt;/h2&gt;

&lt;p&gt;Agency Agents 不是新的 AI 模型，也不是另一套 Claude Code 或 Codex。&lt;/p&gt;

&lt;p&gt;它比較像是一套「&lt;strong&gt;AI 專家角色設定檔&lt;/strong&gt;」。&lt;/p&gt;

&lt;p&gt;每一個 Agent 裡都已經寫好角色、工作方式、核心任務、交付內容、檢查項目等設定。&lt;/p&gt;

&lt;p&gt;官方對它的定位也是「specialized AI agent personalities」，每個角色除了專業領域之外，還會包含自己的工作流程與交付方式。&lt;/p&gt;

&lt;p&gt;例如 Frontend Developer，就不是只有：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a Frontend Developer.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;然後就句點。&lt;/p&gt;

&lt;p&gt;它裡面還會定義前端開發時要注意的東西，例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Vue / React / Angular
Responsive Web Design
Accessibility
Core Web Vitals
Component architecture
Performance
Testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;所以原本我們跟 Codex 說：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;幫我做這個 Vue 頁面
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;裝完 Agency Agents 後，就可以直接指定要哪個角色，該角色會自動補足我們遺忘的專業度：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use the Frontend Developer agent to implement this Vue page.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  安裝 Agency Agents App
&lt;/h2&gt;

&lt;p&gt;官方做了一套 Agency Agents App，它是 Agency Agents 的管理工具，可以瀏覽有哪些角色，再把 Agent 安裝進我們使用的 Coding Agent。&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;p&gt;Windows 建議直接用 Agency Agents App。&lt;/p&gt;

&lt;p&gt;到 Agency Agents App 的 &lt;a href="https://github.com/msitarzewski/agency-agents-app/releases/latest" rel="noopener noreferrer"&gt;GitHub Release&lt;/a&gt; 下載 Windows Installer 後安裝即可。&lt;/p&gt;

&lt;p&gt;目前官方提供 x64 與 ARM64 的 Windows installer；官方也有特別註明 Windows 版本目前尚未 Code Sign，所以如果跳出 Windows SmartScreen，可以從「其他資訊」繼續執行。&lt;/p&gt;

&lt;p&gt;裝完開啟 Agency Agents。&lt;/p&gt;

&lt;p&gt;接著就可以選想使用的 Agent，以及要安裝到哪一套工具。&lt;/p&gt;

&lt;h3&gt;
  
  
  Mac
&lt;/h3&gt;

&lt;p&gt;Mac 可以直接使用 Homebrew：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--cask&lt;/span&gt; msitarzewski/agency-agents/agency-agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這也是 Agency Agents 主專案目前提供的快速安裝方式。&lt;/p&gt;

&lt;p&gt;裝完後開啟 Agency Agents App。&lt;/p&gt;

&lt;p&gt;不論是 Windows 還是 Mac，Agency Agents App 的介面都長得相同：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbmmy4rsxo88au71r3mv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbbmmy4rsxo88au71r3mv.png" alt="Agency Agents App 介面" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  前端工程師可以先裝哪些 Agent？
&lt;/h2&gt;

&lt;p&gt;Agency Agents 的角色很多，不建議看到什麼就全部裝，最後搞得像寶可夢一樣不全部收集完就睡不著。&lt;/p&gt;

&lt;p&gt;以平常前端開發來說，可以先從這幾個開始：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;適合工作&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Frontend Developer&lt;/td&gt;
&lt;td&gt;Vue、React、JavaScript、CSS、RWD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI Designer&lt;/td&gt;
&lt;td&gt;UI、Dashboard、Design System、版面設計&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UX Researcher&lt;/td&gt;
&lt;td&gt;UX、流程、資訊架構、易用性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Software Architect&lt;/td&gt;
&lt;td&gt;系統架構、大型重構&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Reviewer&lt;/td&gt;
&lt;td&gt;PR、Code Review、Bug、Regression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Senior Developer&lt;/td&gt;
&lt;td&gt;比較複雜的開發工作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DevOps Automator&lt;/td&gt;
&lt;td&gt;Docker、CI/CD、Jenkins、部署&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minimal Change Engineer&lt;/td&gt;
&lt;td&gt;修 Bug，而且不要順手就重構整個宇宙&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;安裝角色時，就可以直接選擇要裝在哪些 AI 上：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk85dob5lb18l74u6rt5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk85dob5lb18l74u6rt5o.png" alt="選擇裝在全域的哪個 AI 上" width="800" height="329"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  讓 Claude Code、Codex 自動選 Agent
&lt;/h2&gt;

&lt;p&gt;Agency Agents 官方提供的是「角色」。&lt;/p&gt;

&lt;p&gt;接下來這段則是自己另外加上的功能：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agency Agents Router。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;概念很簡單，我們再建立一個 Skill，專門負責判斷以下情境：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;分析使用者需求 → 需要專家嗎？&lt;br&gt;
需要專家 → 找最適合的 Agency Agent。&lt;br&gt;
不需要專家 → 自己直接做。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;就不會看到「Vue」三個字，就一定叫 Frontend Developer。&lt;/p&gt;

&lt;p&gt;所以 Router 要判斷的是：&lt;strong&gt;這個工作有沒有必要使用專家。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;例如：&lt;/p&gt;

&lt;p&gt;「把 margin-top 20px 改成 24px」→ 不用 Agent。&lt;/p&gt;

&lt;p&gt;「重新設計整個後台，考慮 RWD 與無障礙」→ 用 Agent。&lt;/p&gt;




&lt;h2&gt;
  
  
  Claude Code、Codex 建立 agency-agents Skill
&lt;/h2&gt;

&lt;p&gt;Claude Code、Codex 的 Skill 放置位置不同。&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;

&lt;p&gt;Mac：&lt;/p&gt;

&lt;p&gt;終端機輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open ~/.claude/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows：&lt;/p&gt;

&lt;p&gt;開啟任一個資料夾，檔案路徑輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nv"&gt;%USERPROFILE%&lt;/span&gt;\.claude\skills\
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著新增以下資料夾跟檔案：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;agency-agents&lt;/span&gt;
&lt;span class="s"&gt;└── SKILL.md&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Codex
&lt;/h3&gt;

&lt;p&gt;Mac：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open ~/.agents/skills
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;%USERPROFILE%&lt;span class="se"&gt;\.&lt;/span&gt;agents&lt;span class="se"&gt;\s&lt;/span&gt;kills&lt;span class="se"&gt;\&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;同樣建立：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agency-agents
└── &lt;span class="o"&gt;[&lt;/span&gt;SKILL.md]&lt;span class="o"&gt;(&lt;/span&gt;http://skill.md/&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著 SKILL.md 的內容，Claude Code、Codex 有一點不同，可以各自複製貼上。&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/letswritetw/agency-agents-skill/blob/main/claude/SKILL.md" rel="noopener noreferrer"&gt;https://github.com/letswritetw/agency-agents-skill/blob/main/claude/SKILL.md&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Codex
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/letswritetw/agency-agents-skill/blob/main/codex/SKILL.md" rel="noopener noreferrer"&gt;https://github.com/letswritetw/agency-agents-skill/blob/main/codex/SKILL.md&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  讓 Claude Code 自動使用 Router
&lt;/h2&gt;

&lt;p&gt;Skill 建好後其實就可以用了，Claude Code 會依照 description 判斷什麼時候載入。&lt;/p&gt;

&lt;p&gt;不過如果要讓 AI 更積極一點想到 Agency Agents Router，需要再到全域 &lt;code&gt;CLAUDE.md&lt;/code&gt; 補一段規則。&lt;/p&gt;

&lt;p&gt;打開 CLAUDE.md 檔的方式如下。&lt;/p&gt;

&lt;p&gt;Mac：&lt;/p&gt;

&lt;p&gt;終端機輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open ~/.claude/CLAUDE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows：&lt;/p&gt;

&lt;p&gt;開啟任一個資料夾，檔案路徑輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nv"&gt;%USERPROFILE%&lt;/span&gt;\.claude\CLAUDE.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在打開的 CLAUDE.md 中加入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Agency Agents&lt;/span&gt;

For development tasks that would materially benefit from specialist expertise, use the &lt;span class="sb"&gt;`agency-agents`&lt;/span&gt; skill to select and delegate to the most appropriate installed Agency Agent.

Prefer using the &lt;span class="sb"&gt;`agency-agents`&lt;/span&gt; skill automatically when the current task clearly matches its purpose. The user should not need to explicitly request the skill.

Use the minimum number of agents necessary.

Prefer direct execution for simple or mechanical tasks.

Do not delegate unless specialist expertise is likely to materially improve correctness, design quality, architecture, maintainability, reliability, performance, accessibility, or review quality.

Do not expand the requested scope merely because additional agents are available.

The user's original requirements, constraints, and project conventions always take priority over agent routing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;之後平常就不用輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Use agency-agents.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;直接講需求即可。&lt;/p&gt;




&lt;h2&gt;
  
  
  Codex 的 AGENTS.md
&lt;/h2&gt;

&lt;p&gt;Codex 也可以在自己的 &lt;code&gt;AGENTS.md&lt;/code&gt; 補上 Router 使用規則。&lt;/p&gt;

&lt;p&gt;Mac：&lt;/p&gt;

&lt;p&gt;終端機輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;open ~/.codex/AGENTS.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows：&lt;/p&gt;

&lt;p&gt;開啟任一個資料夾，檔案路徑輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nv"&gt;%USERPROFILE%&lt;/span&gt;\.codex\AGENTS.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;在打開的 AGENTS.md 中加入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- AGENCY_AGENTS_START --&amp;gt;&lt;/span&gt;

&lt;span class="gu"&gt;## Agency Agents&lt;/span&gt;

For software development tasks that would materially benefit from specialist expertise, use the &lt;span class="sb"&gt;`agency-agents`&lt;/span&gt; skill to select and delegate to the most appropriate installed custom agent.

When the current task clearly matches the &lt;span class="sb"&gt;`agency-agents`&lt;/span&gt; skill description, prefer implicit invocation instead of requiring the user to explicitly request &lt;span class="sb"&gt;`$agency-agents`&lt;/span&gt;.

Use the minimum number of agents necessary.

Prefer direct execution for simple or mechanical tasks.

Do not delegate unless specialist expertise is likely to materially improve correctness, design quality, architecture, maintainability, reliability, performance, accessibility, or review quality.

For tasks spanning multiple disciplines, let the &lt;span class="sb"&gt;`agency-agents`&lt;/span&gt; skill determine whether sequential delegation is useful.

Do not expand the requested scope merely because additional agents are available.

The user's original requirements, constraints, project conventions, and requested scope remain the source of truth.

&lt;span class="c"&gt;&amp;lt;!-- AGENCY_AGENTS_END --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  總結
&lt;/h2&gt;

&lt;p&gt;Agency Agents 裡面有一堆角色可以使用，目前 August 還在實驗中，看是不是在開發上會產生更精緻的結果。&lt;/p&gt;

&lt;p&gt;記得幾年前 ChatGPT 剛出來時，網路上有很多人在分享怎麼寫 Prompt，很多開頭的第一句都是寫「請你扮演 XXX」。&lt;/p&gt;

&lt;p&gt;這件事情現在已經不用了，因為已經可以讓 AI 自己選擇適合的角色來扮演。&lt;/p&gt;

&lt;p&gt;這個 Agency Agents 專案目前有 143k 的星星數，看來懶惰的人類是很多的（誤），只好再放一次貓咪怎麼看我們人類的圖：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftavn7e9rla733yqcodsd.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftavn7e9rla733yqcodsd.jpg" alt="愚蠢的人類" width="800" height="717"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>chatgpt</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Buzz 自架教學：使用 Docker 架設 Relay 與設定 AI Agent 自動回覆</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Fri, 31 Jul 2026 14:38:33 +0000</pubDate>
      <link>https://dev.to/letswrite/buzz-zi-jia-jiao-xue-shi-yong-docker-jia-she-relay-yu-she-ding-ai-agent-zi-dong-hui-fu-2b09</link>
      <guid>https://dev.to/letswrite/buzz-zi-jia-jiao-xue-shi-yong-docker-jia-she-relay-yu-she-ding-ai-agent-zi-dong-hui-fu-2b09</guid>
      <description>&lt;h2&gt;
  
  
  寫在文章之前，關於本文配圖
&lt;/h2&gt;

&lt;p&gt;這篇文章的配圖，選擇了熊本城，是因為這周發生了熊本大地震，而 August 在今年的五月才去那裡旅遊過。&lt;/p&gt;

&lt;p&gt;記得在逛熊本城的時候，裡面有個影片，是在講十年前的熊本大地震發生後，這幾年一路是怎麼走過來，一路又修復成目前的樣子。&lt;/p&gt;

&lt;p&gt;雖然，這次又發生了地震，但相信熊本可以再站起來的。&lt;/p&gt;




&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;最近一直在找一個可以把 AI Agent 一起加入到群組聊天的通訊軟體。&lt;/p&gt;

&lt;p&gt;大部份都要錢，比如 Slack、Google Chat、Teams。&lt;/p&gt;

&lt;p&gt;剛好不知怎麼回事，最近也常常看到講 AI 的電子報提到一個 Buzz，似乎可以解決這需求。&lt;/p&gt;

&lt;p&gt;Buzz，是一套可自架的團隊協作工具。除了頻道聊天，也能加入 Codex、Claude Code 等 AI Agent，讓團隊成員直接在頻道中透過 tag Agent 來呼叫 AI。&lt;/p&gt;

&lt;p&gt;雖說官方提供了 Self Hosting 的安裝說明，但實際上還是踩了幾個坑才順利的讓 Agent 回覆訊息。&lt;/p&gt;

&lt;p&gt;本篇就是筆記實作安裝 Buzz 時的筆記，也把怎麼跳過這些坑的方式紀錄下來，讓看到這篇的你可以從從容容、遊刃有餘的閃過。&lt;/p&gt;

&lt;p&gt;完成本文後，可以做到：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在 Mac 上透過 Docker Compose 自架 Buzz Relay。&lt;/li&gt;
&lt;li&gt;使用 Buzz Desktop 連接自己的 Relay。&lt;/li&gt;
&lt;li&gt;建立 Buzz Community 與頻道。&lt;/li&gt;
&lt;li&gt;啟動內建的三個 Agent。&lt;/li&gt;
&lt;li&gt;使用 Codex 作為 Fizz 的 AI 執行環境。（也可以用 Claude）&lt;/li&gt;
&lt;li&gt;在頻道中透過 &lt;code&gt;@Fizz&lt;/code&gt; 讓 Agent 回覆訊息。&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  安裝 Buzz Relay
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 安裝必要工具
&lt;/h3&gt;

&lt;p&gt;需要先安裝好以下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Docker Desktop&lt;/li&gt;
&lt;li&gt;Docker Compose 2.24.4 以上&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;確認 Docker Desktop 已經啟動後，再繼續安裝。&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 下載 Buzz 原始碼
&lt;/h3&gt;

&lt;p&gt;Clone 官方 GitHub 上的專案：&lt;a href="https://github.com/block/buzz" rel="noopener noreferrer"&gt;https://github.com/block/buzz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;接著進到「deploy/compose」資料夾中，本篇會用 Docker 的方式安裝。&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 建立 &lt;code&gt;.env&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;deploy/compose 的資料夾裡有一個「.env.example」，這份檔另存為「.env」。&lt;/p&gt;

&lt;p&gt;接著，把 .env 裡「buzz.example.com」、「CHANGE_ME…」的部份全都修改，變成我們要的內容。&lt;/p&gt;

&lt;p&gt;另外要注意，目前官方的版本有個 bug，主要是Buzz Relay 容器會顯示 &lt;code&gt;unhealthy&lt;/code&gt;，造成後續安裝不起來，目前解決的方式就是：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 原本&lt;/span&gt;
&lt;span class="nv"&gt;BUZZ_GIT_CONFORMANCE_PROBE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# 改成&lt;/span&gt;
&lt;span class="nv"&gt;BUZZ_GIT_CONFORMANCE_PROBE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;需要 KEY 的部份，可以使用以下指令產生隨機密鑰：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;產生一般密碼：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不同用途的密鑰應分開產生，不要全部使用同一組。&lt;/p&gt;

&lt;p&gt;如果覺得一行行修改很麻煩，這一步 ChatGPT 有給了一組指令，直接專案內開啟終端機執行以下也行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .env &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
BUZZ_IMAGE=ghcr.io/block/buzz:main

BUZZ_DOMAIN=127.0.0.1
RELAY_URL=ws://127.0.0.1:3300

BUZZ_MEDIA_BASE_URL=http://127.0.0.1:3300/media
BUZZ_MEDIA_SERVER_DOMAIN=127.0.0.1

BUZZ_CORS_ORIGINS=tauri://localhost,http://tauri.localhost,https://tauri.localhost,http://127.0.0.1:3300

BUZZ_REQUIRE_AUTH_TOKEN=false
BUZZ_REQUIRE_RELAY_MEMBERSHIP=false
BUZZ_ALLOW_NIP_OA_AUTH=false

BUZZ_AUTO_MIGRATE=true
BUZZ_GIT_CONFORMANCE_PROBE=false

RUST_LOG=buzz_relay=info,buzz_db=info,buzz_auth=info,buzz_pubsub=info,tower_http=info

BUZZ_RELAY_PRIVATE_KEY=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
BUZZ_GIT_HOOK_HMAC_SECRET=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 32&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;

POSTGRES_DB=buzz
POSTGRES_USER=buzz
POSTGRES_PASSWORD=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;

REDIS_PASSWORD=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;

BUZZ_S3_ACCESS_KEY=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 10&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
BUZZ_S3_SECRET_KEY=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;openssl rand &lt;span class="nt"&gt;-hex&lt;/span&gt; 16&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
BUZZ_S3_BUCKET=buzz-media
BUZZ_S3_ADDRESSING_STYLE=path

BUZZ_HTTP_PORT=3300
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;因為 3000 是個很多應用程式都會預設使用的 PORT，所以上面的範例改用 3300。&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 啟動 Buzz Relay
&lt;/h3&gt;

&lt;p&gt;專案中開啟終端機，依序執行以下：&lt;/p&gt;

&lt;p&gt;加入執行權限：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x run.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;啟動：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./run.sh start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;查看狀態：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./run.sh status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;正常情況下會看到：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;relay       healthy
postgres    healthy
redis       healthy
minio       healthy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  5. 測試 Relay
&lt;/h3&gt;

&lt;p&gt;打開瀏覽器，網址貼上：&lt;a href="http://127.0.0.1:3300%E3%80%82" rel="noopener noreferrer"&gt;http://127.0.0.1:3300。&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;成功的話就會看到結果：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs22q0rbzx9qcdlym7i08.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs22q0rbzx9qcdlym7i08.png" alt="Buzz Relay Docker 部署成功畫面與 JSON 網頁回傳結果" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;順便推薦一下本站開發的擴充功能 &lt;a href="https://chromewebstore.google.com/detail/lets-json/gcbgdeakiaapdhjeoinmodehiemedimf" rel="noopener noreferrer"&gt;Let’s JSON&lt;/a&gt;，網址如果回傳的是 JSON，就會看到像上面截圖那樣美美的樣子。&lt;/p&gt;

&lt;p&gt;Buzz Relay 安裝成功，剩下的事就是讓各個要使用的團隊成員電腦安裝 Buzz，然後設定抓我們自架的 Buzz Relay 了。&lt;/p&gt;




&lt;h2&gt;
  
  
  下載、安裝 Buzz
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 下載 Buzz Desktop
&lt;/h3&gt;

&lt;p&gt;前往 Buzz Releases：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/block/buzz/releases" rel="noopener noreferrer"&gt;https://github.com/block/buzz/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Apple Silicon Mac，例如 M1、M2、M3、M4，下載名稱包含以下文字的版本：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aarch64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intel Mac 則下載：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Windows 下載：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Buzz_0.5.2_x64-setup_alpha-unsigned.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;但因為 Windows 版本的 Buzz 目前尚未完成數位簽章，開啟時系統會跳出安全性警告；此時可點擊「更多資訊」並選擇「仍要執行」（或於檔案屬性中勾選「取消封鎖」）即可完成安裝。&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 登入 Buzz
&lt;/h3&gt;

&lt;p&gt;安裝完 Buzz，打開後會遇到幾個項目，因為 Relay 我們是自架的，因此以下選項要這樣選擇。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Join or create a community&lt;/strong&gt;：選擇「I already have a community」。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxitsu5lrk2k9j0g7gxkm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fxitsu5lrk2k9j0g7gxkm.png" alt="Buzz 桌面端建立 Community 選擇畫面：I already have a community" width="800" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reconnect to your community&lt;/strong&gt;：選擇「I’m a member or admin」。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitsidygh7sap9lue7ez7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fitsidygh7sap9lue7ez7.png" alt="Buzz 桌面端身份選擇：I am a member or admin" width="800" height="614"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;接著會需要輸入我們 Buzz Relay 的網址，之前的 .env 裡寫的是 &lt;a href="http://127.0.0.1:3300%EF%BC%8C%E9%80%99%E9%82%8A%E5%B0%B1%E7%85%A7%E5%A1%AB%EF%BC%8C%E5%A6%82%E6%9E%9C%E4%B8%8A%E9%9D%A2%E4%BD%A0%E6%9C%89%E8%87%AA%E8%A1%8C%E4%BF%AE%E6%94%B9%E7%9A%84%E8%A9%B1%EF%BC%8C%E9%80%99%E9%82%8A%E5%B0%B1%E5%A1%AB%E5%AF%AB%E4%BD%A0%E4%BF%AE%E6%94%B9%E7%9A%84%E3%80%82" rel="noopener noreferrer"&gt;http://127.0.0.1:3300，這邊就照填，如果上面你有自行修改的話，這邊就填寫你修改的。&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqgdlmlqs6dj2dmcx1cg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmqgdlmlqs6dj2dmcx1cg.png" alt="輸入安裝 Buzz Relay 時填的網址" width="800" height="541"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以上，就可以進到我們自架的 Buzz 了。&lt;/p&gt;




&lt;h2&gt;
  
  
  使用 AI Agents 功能
&lt;/h2&gt;

&lt;p&gt;這段就是 August 一直踩坑的部份，只要下列幾個坑都閃過，之後在對話框裡 @ AI Agents 就會得到回應。&lt;/p&gt;

&lt;h3&gt;
  
  
  確認有使用 AI
&lt;/h3&gt;

&lt;p&gt;本機確定有安裝好 Claude CLI，或 Codex CLI。&lt;/p&gt;

&lt;p&gt;如果沒有訂閱這兩家方案，也可以找網路上提供有免費額度的 LLM 來使用。&lt;/p&gt;

&lt;p&gt;August 因為 Claude、ChatGPT 都有付費訂閱，所以示範的是這兩家的。&lt;/p&gt;

&lt;p&gt;在 Buzz 的設定頁裡，要確認有使用 AI，如圖：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2f1eldko9dytoiurqtj7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2f1eldko9dytoiurqtj7.png" alt="Buzz 系統設定：確認 AI Agent 狀態為 Ready" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;顯示為「Ready」代表有連結成功。&lt;/p&gt;

&lt;p&gt;Buzz Agent 指的是用各家的 API。&lt;/p&gt;

&lt;h3&gt;
  
  
  確認 Community 網址相同
&lt;/h3&gt;

&lt;p&gt;點擊左下角自己的帳號，接著「Local Dev &amp;gt; Community settings」：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnzmjh3ovd12xq9usyisf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fnzmjh3ovd12xq9usyisf.png" alt="Buzz 設定選單：進入 Community settings 社群設定" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;裡面會看到一個設定是「Relay URL」：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjidicbwuykfwk5q44mcr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjidicbwuykfwk5q44mcr.png" alt="確認 Relay URL 跟本機網址相同" width="800" height="1118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;要確認 URL 裡，&lt;code&gt;ws://&lt;/code&gt; 後的網址，跟我們的網址是相同的，比方上面在設定 Community 的網址是 &lt;a href="http://127.0.0.1:3300%EF%BC%8C%E9%82%A3%E9%80%99%E9%82%8A" rel="noopener noreferrer"&gt;http://127.0.0.1:3300，那這邊&lt;/a&gt; &lt;code&gt;ws://&lt;/code&gt; 後就要是 127.0.0.1:3300。&lt;/p&gt;

&lt;p&gt;這邊如果不相同，那 Agents 收到的來源就會不同，就會一直抓不到我們 @ 它們的訊息。&lt;/p&gt;

&lt;h3&gt;
  
  
  開放 Agents 接受回應
&lt;/h3&gt;

&lt;p&gt;在 Agent 的設定畫面裡，點擊「Advanced」，裡面有一項「Who can talk to this agent」，要選成「Anyone」：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvazedip9tj3y2f6zbxn4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvazedip9tj3y2f6zbxn4.png" alt="Buzz AI Agent 進階設定：將 Who can talk to this agent 權限設為 Anyone" width="800" height="1052"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;然後，上面的頁籤切到「Customize for this agent」，確認有選好要使用的模型：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqelel4q3qpapmtbqkjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbqelel4q3qpapmtbqkjq.png" alt="Buzz Agent 模型配置：選擇對應的 LLM Model" width="800" height="536"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;最後，要確認 Agents 是運行中的：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgp987uakwrltnrv3l6q2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgp987uakwrltnrv3l6q2.png" alt="Buzz 頻道控制列：確認 AI Agent 處於 Running 運行狀態" width="800" height="582"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  實際測試
&lt;/h2&gt;

&lt;p&gt;可以做一個簡單的測試，看 AI Agents 是不是會回應。&lt;/p&gt;

&lt;p&gt;我們在預設建好的 Channel 裡，@ Agent 就行，就像我們在 LINE 上 @ 朋友們一樣。&lt;/p&gt;

&lt;p&gt;成功的話，就會看到被 @ 的 Agent 正在思考中的訊息，也會對訊息回應 Emoji，思考完成，Agent 就會回覆訊息，如下圖：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvnjzftuudl4d6u8eni6o.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvnjzftuudl4d6u8eni6o.jpeg" alt="Buzz 頻道實際測試：@Fizz 呼叫 AI Agent 並獲得自動回覆訊息" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  小結
&lt;/h2&gt;

&lt;p&gt;雖然可以把 AI Agents 加進聊天中，但 Windows 版的安裝比較麻煩。&lt;/p&gt;

&lt;p&gt;而且，Codex、Claude Code 上都裝了很多習慣的 Skills，表面上 AI 是回說可以直接用，但輸入 &lt;code&gt;/&lt;/code&gt; 或 &lt;code&gt;$&lt;/code&gt; 時沒有出現選項，也不知道到底有用 Skills 還是沒有。&lt;/p&gt;

&lt;p&gt;只能說，Buzz 還是一個正在開發中的工具，看 GitHub 上的 Release 紀錄更新得很頻繁，再給它一點時間，它會更好的（？）。&lt;/p&gt;

</description>
      <category>buzz</category>
      <category>docker</category>
      <category>claude</category>
      <category>agents</category>
    </item>
    <item>
      <title>Outline Wiki 自架教學（三）：Codex 串接 MCP</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:44:02 +0000</pubDate>
      <link>https://dev.to/letswrite/outline-wiki-zi-jia-jiao-xue-san-codex-chuan-jie-mcp-5c4b</link>
      <guid>https://dev.to/letswrite/outline-wiki-zi-jia-jiao-xue-san-codex-chuan-jie-mcp-5c4b</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;上一篇是 &lt;a href="https://www.letswrite.tw/outline-claude-mcp/" rel="noopener noreferrer"&gt;Outline + Claude&lt;/a&gt;，不一定每個人都有訂閱 Claude 方案，所以也提供 Codex 的方式。&lt;/p&gt;

&lt;p&gt;跟著本篇一步步走，完成設定後，Codex 可以透過 MCP 呼叫 Outline 工具，執行很多好棒棒的事，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;搜尋或列出 Outline 文件&lt;/li&gt;
&lt;li&gt;讀取、編輯文件集內的文件&lt;/li&gt;
&lt;li&gt;建立文件集&lt;/li&gt;
&lt;li&gt;移動文件&lt;/li&gt;
&lt;li&gt;執行 MCP Server 已提供的其他 Outline 操作&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Codex App、Codex CLI 與 IDE 擴充套件會共用 Codex 的 MCP 設定，因此設定完成後，不需要在每個客戶端重複設定。&lt;/p&gt;




&lt;h2&gt;
  
  
  前置準備
&lt;/h2&gt;

&lt;p&gt;開始前，請先確認已具備以下環境：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;安裝 &lt;a href="https://chatgpt.com/download/" rel="noopener noreferrer"&gt;Codex&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;安裝 &lt;a href="https://nodejs.org/zh-tw/download" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;可正常連線的 &lt;a href="https://www.letswrite.tw/outline-wiki-install/" rel="noopener noreferrer"&gt;Outline&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  建立 Outline API Token
&lt;/h2&gt;

&lt;h3&gt;
  
  
  進入喜好設定
&lt;/h3&gt;

&lt;p&gt;登入 Outline 後，點擊左下角帳號旁的「⋯」，再選擇「喜好設定」。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmrw46lqu1goihbjhfu6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmrw46lqu1goihbjhfu6.png" alt="從左下角選單進入喜好設定"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  開啟 API &amp;amp; Access
&lt;/h3&gt;

&lt;p&gt;在左側設定選單中選擇「API &amp;amp; Access」，接著點擊右上角的「新 API 金鑰」。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbdrrx9bhqgd4ytn13fus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbdrrx9bhqgd4ytn13fus.png" alt="進入 API &amp;amp; Access 並建立新 API 金鑰"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  設定金鑰名稱、範圍與到期日
&lt;/h3&gt;

&lt;p&gt;建議輸入容易辨識用途的名稱，例如：「AI MCP」、「Codex MCP」等等。&lt;/p&gt;

&lt;p&gt;原始流程中的設定為：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;範圍：&lt;/strong&gt; 留空&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;到期日：&lt;/strong&gt; 沒有期限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F678ptivgzmstlca5rksq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F678ptivgzmstlca5rksq.png" alt="設定 API 金鑰範圍與到期日"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;範圍留空通常代表不限制特定 API 權限。這種設定操作最簡單，但權限也較大。&lt;/p&gt;

&lt;h3&gt;
  
  
  複製 API Token
&lt;/h3&gt;

&lt;p&gt;建立完成後，點擊「複製」，並先將 Token 暫存在安全的位置，因為 Token 只會出現一次。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvkirz2pqrugh1lu8g94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvkirz2pqrugh1lu8g94.png" alt="複製 Outline API Token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;注意：API Token 等同於帳號憑證，不要貼到 Git、公開文件、部落格文章、聊天群組或未加密的筆記中。&lt;/p&gt;




&lt;h2&gt;
  
  
  設定 Codex MCP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  開啟 Codex 設定檔
&lt;/h3&gt;

&lt;p&gt;Codex 使用 &lt;code&gt;config.toml&lt;/code&gt; 儲存 MCP 與其他本機設定。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;作業系統&lt;/th&gt;
&lt;th&gt;使用者層級設定檔路徑&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%USERPROFILE%\.codex\config.toml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.codex/config.toml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;使用者層級的設定會套用到 Codex App、Codex CLI 與 IDE 擴充套件。&lt;/p&gt;

&lt;h4&gt;
  
  
  Windows
&lt;/h4&gt;

&lt;p&gt;開啟 PowerShell，執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Directory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.codex"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;New-Item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ItemType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.codex\config.toml"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-Null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;notepad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;USERPROFILE&lt;/span&gt;&lt;span class="s2"&gt;\.codex\config.toml"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這三個指令會依序：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;建立 &lt;code&gt;.codex&lt;/code&gt; 資料夾。&lt;/li&gt;
&lt;li&gt;建立 &lt;code&gt;config.toml&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;使用記事本開啟設定檔。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;也可以開啟任一個資料夾，在檔案路徑列貼上：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;%USERPROFILE%&lt;span class="se"&gt;\.&lt;/span&gt;codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著開啟 &lt;code&gt;config.toml&lt;/code&gt;。&lt;/p&gt;

&lt;h4&gt;
  
  
  macOS：使用 Finder 開啟
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;開啟 Finder。&lt;/li&gt;
&lt;li&gt;點擊上方選單的「前往」。&lt;/li&gt;
&lt;li&gt;選擇「前往檔案夾⋯」。&lt;/li&gt;
&lt;li&gt;貼上下列路徑：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/.codex/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;找到並開啟：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="err"&gt;config.toml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  macOS：使用終端機開啟
&lt;/h4&gt;

&lt;p&gt;也可以在終端機執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.codex"&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.codex/config.toml"&lt;/span&gt;
open &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.codex/config.toml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這三個指令會依序建立設定資料夾、建立設定檔，並使用 macOS 文字編輯器開啟。&lt;/p&gt;

&lt;p&gt;若檔案已存在，建議先複製一份備份，再進行修改。&lt;/p&gt;




&lt;h2&gt;
  
  
  加入 Outline MCP Server
&lt;/h2&gt;

&lt;p&gt;請將下列設定加入 &lt;code&gt;config.toml&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mcp_servers.outline]&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npx"&lt;/span&gt;
&lt;span class="py"&gt;args&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"https://192.168.x.x:3023/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"--header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"Authorization:${AUTH_HEADER}"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;[mcp_servers.outline.env]&lt;/span&gt;
&lt;span class="py"&gt;AUTH_HEADER&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer YOUR_OUTLINE_API_TOKEN"&lt;/span&gt;
&lt;span class="py"&gt;NODE_TLS_REJECT_UNAUTHORIZED&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;請替換以下內容：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;設定值&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;https://192.168.x.x:3023/mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outline MCP Server 的連線網址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YOUR_OUTLINE_API_TOKEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前一步建立的 Outline API Token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  使用有效 HTTPS 憑證時的簡化設定
&lt;/h2&gt;

&lt;p&gt;如果 Outline MCP Server 已使用有效且受信任的 HTTPS 憑證，Codex 可以直接連線，不需要 Node.js、&lt;code&gt;npx&lt;/code&gt; 或 &lt;code&gt;mcp-remote&lt;/code&gt;。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mcp_servers.outline]&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://YOUR_MCP_HOST/mcp"&lt;/span&gt;
&lt;span class="py"&gt;http_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;Authorization&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer YOUR_OUTLINE_API_TOKEN"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[mcp_servers.outline]&lt;/span&gt;
&lt;span class="py"&gt;url&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://outline-mcp.example.com/mcp"&lt;/span&gt;
&lt;span class="py"&gt;http_headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;Authorization&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Bearer YOUR_OUTLINE_API_TOKEN"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這是較簡單的正式環境設定方式。&lt;/p&gt;

&lt;p&gt;注意：這個範例會把 Token 直接寫在 &lt;code&gt;config.toml&lt;/code&gt; 中。請妥善保護設定檔，不要提交到 Git。&lt;/p&gt;




&lt;h2&gt;
  
  
  Windows 與 macOS 的 npx 路徑問題
&lt;/h2&gt;

&lt;p&gt;Codex App 原則上可以直接執行 &lt;code&gt;npx&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;若 MCP 啟動失敗，可能是桌面程式找不到 &lt;code&gt;npx&lt;/code&gt; 的執行路徑。&lt;/p&gt;

&lt;h3&gt;
  
  
  Windows
&lt;/h3&gt;

&lt;p&gt;在 PowerShell 執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;where.exe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;npx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可能會回傳：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;C&lt;/span&gt;:\Program &lt;span class="kd"&gt;Files&lt;/span&gt;\nodejs\npx.cmd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;可以將：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;改成 TOML 的單引號字串：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'C:\Program Files\nodejs\npx.cmd'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;TOML 的單引號字串不需要將 Windows 反斜線寫成 &lt;code&gt;\\&lt;/code&gt;。&lt;/p&gt;

&lt;h3&gt;
  
  
  macOS
&lt;/h3&gt;

&lt;p&gt;在終端機執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which npx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apple Silicon Mac 使用 Homebrew 時，可能回傳：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/homebrew/bin/npx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intel Mac 使用 Homebrew 時，可能回傳：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/local/bin/npx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著將：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"npx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;改成實際查到的完整路徑，例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/opt/homebrew/bin/npx"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;注意：不要直接照抄範例路徑，應先使用 &lt;code&gt;where.exe npx&lt;/code&gt; 或 &lt;code&gt;which npx&lt;/code&gt;，再填入自己電腦實際回傳的結果。&lt;/p&gt;




&lt;h2&gt;
  
  
  重新啟動 Codex
&lt;/h2&gt;

&lt;p&gt;修改 &lt;code&gt;config.toml&lt;/code&gt; 後，需要重新啟動正在使用的 Codex。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Codex App／ChatGPT 桌面版：&lt;/strong&gt; 完整結束後重新開啟。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex CLI：&lt;/strong&gt; 離開目前工作階段，再重新執行 &lt;code&gt;codex&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IDE 擴充套件：&lt;/strong&gt; 重新啟動擴充套件或重新載入編輯器視窗。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;只關閉視窗不一定代表程式已完全結束：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows：&lt;/strong&gt; 從系統匣結束程式，或到工作管理員確認是否仍在背景執行。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS：&lt;/strong&gt; 按下 &lt;code&gt;Command + Q&lt;/code&gt;，必要時到「活動監視器」確認程式是否仍在執行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;重新啟動時，Codex 會：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;讀取 &lt;code&gt;~/.codex/config.toml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;透過 &lt;code&gt;npx&lt;/code&gt; 啟動 &lt;code&gt;mcp-remote&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;使用 Authorization Header 連接 Outline MCP Server&lt;/li&gt;
&lt;li&gt;載入 MCP Server 提供的工具&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;首次執行 &lt;code&gt;npx -y mcp-remote&lt;/code&gt; 時，可能需要下載套件，因此等待時間會比後續啟動稍長。&lt;/p&gt;




&lt;h2&gt;
  
  
  測試 Outline MCP 是否連線成功
&lt;/h2&gt;

&lt;h3&gt;
  
  
  使用 Codex CLI 檢查
&lt;/h3&gt;

&lt;p&gt;先執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex mcp list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;正常情況下，清單中會出現：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;outline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;codex mcp &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;查看目前版本支援的 MCP 管理指令。&lt;/p&gt;

&lt;p&gt;進入 Codex CLI 後，輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;即可查看目前工作階段載入的 MCP Server 與工具。&lt;/p&gt;

&lt;h3&gt;
  
  
  使用對話測試
&lt;/h3&gt;

&lt;p&gt;在 Codex 中輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;可以接到 Outline MCP 嗎？請列出目前可使用的 Outline 工具。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著再測試實際讀取：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;請列出 Outline 中目前可以看到的文件與文件集。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以指定操作：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;請搜尋 Outline 中包含「前端」關鍵字的文件。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;連線成功時，Codex 會顯示已載入 Outline 工具，並能回傳工作區中的文件或 Collection 資訊。&lt;/p&gt;

</description>
      <category>outline</category>
      <category>ai</category>
      <category>codex</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Outline Wiki 自架教學（二）：ClaudeCodex 串接 MCP</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:07:20 +0000</pubDate>
      <link>https://dev.to/letswrite/outline-wiki-zi-jia-jiao-xue-er-claude-codex-chuan-jie-mcp-42k4</link>
      <guid>https://dev.to/letswrite/outline-wiki-zi-jia-jiao-xue-er-claude-codex-chuan-jie-mcp-42k4</guid>
      <description>&lt;h1&gt;
  
  
  Outline Wiki 自架教學（二）：Claude 串接 MCP
&lt;/h1&gt;

&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;上一篇我們自架了 &lt;a href="https://www.letswrite.tw/outline-wiki-install/" rel="noopener noreferrer"&gt;Outline Wiki&lt;/a&gt;，接著我們就要發揮它的威力了，就是可以使用 MCP，讓 Claude、Codex 來幫我們新增、編輯、彙整……我們的文件。&lt;/p&gt;

&lt;p&gt;本篇主要是寫 Outline + Claude，下一篇會是 Outline + Codex。&lt;/p&gt;

&lt;p&gt;照著本篇實作，完成設定後，Claude Desktop 可以透過 MCP 呼叫 Outline 工具，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;搜尋或列出 Outline 文件&lt;/li&gt;
&lt;li&gt;讀取、編輯文件集內的文件&lt;/li&gt;
&lt;li&gt;建立文件集&lt;/li&gt;
&lt;li&gt;移動文件&lt;/li&gt;
&lt;li&gt;執行 MCP Server 已提供的其他 Outline 操作&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  前置準備
&lt;/h2&gt;

&lt;p&gt;開始前，請先確認已具備以下環境：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;安裝 &lt;a href="https://claude.com/download" rel="noopener noreferrer"&gt;Claude Desktop&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;安裝 &lt;a href="https://nodejs.org/zh-tw/download" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;可正常連線的 &lt;a href="https://www.letswrite.tw/outline-wiki-install/" rel="noopener noreferrer"&gt;Outline&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  建立 Outline API Token
&lt;/h2&gt;

&lt;h3&gt;
  
  
  進入喜好設定
&lt;/h3&gt;

&lt;p&gt;登入 Outline 後，點擊左下角帳號旁的「⋯」，再選擇「喜好設定」。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmrw46lqu1goihbjhfu6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkmrw46lqu1goihbjhfu6.png" alt="從左下角選單進入喜好設定"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;從左下角選單進入喜好設定&lt;/p&gt;

&lt;h3&gt;
  
  
  開啟 API &amp;amp; Access
&lt;/h3&gt;

&lt;p&gt;在左側設定選單中選擇「API &amp;amp; Access」，接著點擊右上角的「新 API 金鑰」。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbdrrx9bhqgd4ytn13fus.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbdrrx9bhqgd4ytn13fus.png" alt="進入 API &amp;amp; Access 並建立新 API 金鑰"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;進入 API &amp;amp; Access 並建立新 API 金鑰&lt;/p&gt;

&lt;h3&gt;
  
  
  設定金鑰名稱、範圍與到期日
&lt;/h3&gt;

&lt;p&gt;建議輸入容易辨識用途的名稱，例如：「AI MCP」、「Claude MCP」等等。&lt;/p&gt;

&lt;p&gt;原始流程中的設定為：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;範圍：&lt;/strong&gt; 留空&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;到期日：&lt;/strong&gt; 沒有期限&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F678ptivgzmstlca5rksq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F678ptivgzmstlca5rksq.png" alt="設定 API 金鑰範圍與到期日"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;設定 API 金鑰範圍與到期日&lt;/p&gt;

&lt;p&gt;範圍留空通常代表不限制特定 API 權限。這種設定操作最簡單，但權限也較大。&lt;/p&gt;

&lt;h3&gt;
  
  
  複製 API Token
&lt;/h3&gt;

&lt;p&gt;建立完成後，點擊「複製」，並先將 Token 暫存在安全的位置，因為 Token 只會出現一次。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvkirz2pqrugh1lu8g94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbvkirz2pqrugh1lu8g94.png" alt="複製 Outline API Token"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;複製 Outline API Token&lt;/p&gt;

&lt;p&gt;注意：API Token 等同於帳號憑證，不要貼到 Git、公開文件、部落格文章、聊天群組或未加密的筆記中。&lt;/p&gt;




&lt;h2&gt;
  
  
  設定 Claude Desktop MCP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  開啟 Claude Desktop 設定檔
&lt;/h3&gt;

&lt;p&gt;Claude Desktop 在 Windows 與 macOS 使用相同的設定檔名稱，但存放路徑不同。&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;作業系統&lt;/th&gt;
&lt;th&gt;設定檔路徑&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Windows
&lt;/h4&gt;

&lt;p&gt;開啟任一個資料夾，檔案路徑列貼上：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nv"&gt;%APPDATA%&lt;/span&gt;\Claude\claude_desktop_config.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以按下 &lt;code&gt;Win + R&lt;/code&gt;，貼上相同路徑後按 Enter。&lt;/p&gt;

&lt;h4&gt;
  
  
  macOS：使用 Finder 開啟
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;開啟 Finder。&lt;/li&gt;
&lt;li&gt;點擊上方選單的「前往」。&lt;/li&gt;
&lt;li&gt;選擇「前往檔案夾⋯」。&lt;/li&gt;
&lt;li&gt;貼上下列路徑：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/Library/Application Support/Claude/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;找到並開啟：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;claude_desktop_config.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  macOS：使用終端機開啟
&lt;/h4&gt;

&lt;p&gt;也可以在終端機執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Library/Application Support/Claude"&lt;/span&gt;
&lt;span class="nb"&gt;touch&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Library/Application Support/Claude/claude_desktop_config.json"&lt;/span&gt;
open &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/Library/Application Support/Claude/claude_desktop_config.json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這三個指令會依序建立設定資料夾、建立設定檔，並使用 macOS 文字編輯器開啟。&lt;/p&gt;

&lt;p&gt;若檔案已存在，建議先複製一份備份，再進行修改。&lt;/p&gt;

&lt;h3&gt;
  
  
  加入 Outline MCP Server
&lt;/h3&gt;

&lt;p&gt;請將下列設定加入 JSON 最外層物件中的 &lt;code&gt;mcpServers&lt;/code&gt;：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://192.168.x.x:3023/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Authorization:${AUTH_HEADER}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AUTH_HEADER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_OUTLINE_API_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"NODE_TLS_REJECT_UNAUTHORIZED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;NODE_TLS_REJECT_UNAUTHORIZED=0&lt;/code&gt; 會停用 Node.js 的 TLS 憑證驗證。它只適合在可信任的內部網路中暫時測試，不建議用於公開網路或正式環境。較安全的做法是替 MCP Server 設定可被系統信任的有效憑證。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Windows 與 macOS 原則上可共用這份設定。如果 macOS 無法直接找到 &lt;code&gt;npx&lt;/code&gt;，可以先在終端機執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which npx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;假設回傳：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/opt/homebrew/bin/npx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;便可將 &lt;code&gt;command&lt;/code&gt; 改成完整路徑：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin/npx"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;常見位置如下：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;安裝方式&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;npx&lt;/code&gt; 可能的位置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Apple Silicon Mac 使用 Homebrew&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/opt/homebrew/bin/npx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intel Mac 使用 Homebrew&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/usr/local/bin/npx&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js 官方 &lt;code&gt;.pkg&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;以 &lt;code&gt;which npx&lt;/code&gt; 的回傳結果為準&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;注意：不要直接照抄表格中的路徑。應先執行 &lt;code&gt;which npx&lt;/code&gt;，再使用自己電腦實際回傳的結果。&lt;/p&gt;

&lt;p&gt;請替換以下內容：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;設定值&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;https://YOUR_MCP_HOST/mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Outline MCP Server 的連線網址&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;YOUR_OUTLINE_API_TOKEN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;前一步建立的 Outline API Token&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;例如 MCP Server 位於內部網路，可改成：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="s2"&gt;"https://192.168.x.x:3023/mcp"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  合併既有設定時的注意事項
&lt;/h3&gt;

&lt;p&gt;若 &lt;code&gt;claude_desktop_config.json&lt;/code&gt; 已經有其他設定，不要直接覆蓋整份檔案，只需要把 &lt;code&gt;mcpServers&lt;/code&gt; 合併進最外層物件。&lt;/p&gt;

&lt;h4&gt;
  
  
  Windows 設定範例
&lt;/h4&gt;

&lt;p&gt;例如原本已有其他欄位：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"coworkUserFilesPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Users&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Sean&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;Claude"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preferences"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://YOUR_MCP_HOST/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Authorization:${AUTH_HEADER}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AUTH_HEADER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_OUTLINE_API_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"NODE_TLS_REJECT_UNAUTHORIZED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  macOS 設定範例
&lt;/h4&gt;

&lt;p&gt;macOS 也使用相同的 JSON 結構。若 &lt;code&gt;npx&lt;/code&gt; 可直接執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"preferences"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"outline"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"https://YOUR_MCP_HOST/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--header"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Authorization:${AUTH_HEADER}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"AUTH_HEADER"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer YOUR_OUTLINE_API_TOKEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"NODE_TLS_REJECT_UNAUTHORIZED"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;若 Claude Desktop 找不到 &lt;code&gt;npx&lt;/code&gt;，將 &lt;code&gt;command&lt;/code&gt; 改成 &lt;code&gt;which npx&lt;/code&gt; 查到的完整路徑，例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin/npx"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSON 常見錯誤包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;欄位之間少了逗號&lt;/li&gt;
&lt;li&gt;最後一個欄位多了逗號&lt;/li&gt;
&lt;li&gt;大括號或中括號數量不一致&lt;/li&gt;
&lt;li&gt;Windows 路徑中的反斜線沒有寫成 &lt;code&gt;\\&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;macOS 路徑含有空白時，終端機指令沒有使用引號或跳脫字元&lt;/li&gt;
&lt;li&gt;把 &lt;code&gt;mcpServers&lt;/code&gt; 貼到最外層物件之外&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  重新啟動 Claude Desktop
&lt;/h2&gt;

&lt;p&gt;修改設定檔後，必須完整關閉 Claude Desktop，再重新開啟。&lt;/p&gt;

&lt;p&gt;只關閉視窗不一定代表程式已完全結束：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Windows：&lt;/strong&gt; 從系統匣結束 Claude，或到工作管理員確認 Claude 是否仍在背景執行。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;macOS：&lt;/strong&gt; 按下 &lt;code&gt;Command + Q&lt;/code&gt;，或從上方選單選擇「Claude」→「結束 Claude」。必要時可到「活動監視器」確認程式是否仍在執行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;重新啟動時，Claude Desktop 會：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;讀取 &lt;code&gt;claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;透過 &lt;code&gt;npx&lt;/code&gt; 啟動 &lt;code&gt;mcp-remote&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;使用 Authorization Header 連接 Outline MCP Server&lt;/li&gt;
&lt;li&gt;載入 MCP Server 提供的工具&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;首次執行 &lt;code&gt;npx -y mcp-remote&lt;/code&gt; 時，可能需要下載套件，因此等待時間會比後續啟動稍長。&lt;/p&gt;




&lt;h2&gt;
  
  
  測試 Outline MCP 是否連線成功
&lt;/h2&gt;

&lt;p&gt;重新開啟 Claude Desktop 後，可以輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;可以接到 Outline MCP 嗎？請列出目前可使用的 Outline 工具。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著再測試實際讀取：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;請列出 Outline 中目前可以看到的文件與文件集。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;也可以指定操作：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;請搜尋 Outline 中包含「前端」關鍵字的文件。
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;連線成功時，Claude 會顯示已載入 Outline 工具，並能回傳工作區中的文件或 Collection 資訊。&lt;/p&gt;

</description>
      <category>outline</category>
      <category>ai</category>
      <category>claude</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Outline Wiki 自架教學（一）：Docker 安裝、Gitea OIDC 與 AI 知識庫準備</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Fri, 26 Jun 2026 13:49:09 +0000</pubDate>
      <link>https://dev.to/letswrite/outline-wiki-zi-jia-jiao-xue-docker-an-zhuang-gitea-oidc-yu-ai-zhi-shi-ku-zhun-bei-4c8d</link>
      <guid>https://dev.to/letswrite/outline-wiki-zi-jia-jiao-xue-docker-an-zhuang-gitea-oidc-yu-ai-zhi-shi-ku-zhun-bei-4c8d</guid>
      <description>&lt;h1&gt;
  
  
  Outline Wiki 自架教學（一）：Docker 安裝、Gitea OIDC 與 AI 知識庫準備
&lt;/h1&gt;

&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;公司開發團隊裡，想架一套內部的知識庫，又要能支援 AI，常見的痛點有幾個：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;文件資料留在自己的機器上，不對外，以免洩露商業機密。&lt;/li&gt;
&lt;li&gt;想要加上 AI？得付 $$，比如 Notion，或是也可以自架的 Docmost。&lt;/li&gt;
&lt;li&gt;Outline Wiki 裝起來很難，August 失敗過一次，最後是靠 Claude Opus 4.8 的輔助才成功。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本篇筆記文，會用一份 Docker Compose 檔，把 Outline，以及需要的 PostgreSQL、Redis、Caddy 全包進去，並針對上面這些坑一次閃掉。&lt;/p&gt;

&lt;p&gt;另外也先說明本文裡的基本設定：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;本機已裝好 Docker。&lt;/li&gt;
&lt;li&gt;對內 PORT 是 &lt;code&gt;3002&lt;/code&gt;，對外是 &lt;code&gt;3001&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;關於 Outline + AI 的補充說明：&lt;/p&gt;

&lt;p&gt;自架版 Outline 沒有內建的「AI Answers」功能，網路上常看到的 OPENAI_API_KEY 對自架版是無效的。想要 Outline 加上 AI，正解是用 MCP 外接，讓 Claude / Codex 透過 Outline 的 API 來操作（作法將寫在下一篇）。&lt;/p&gt;




&lt;h2&gt;
  
  
  步驟 1：安裝 Gitea
&lt;/h2&gt;

&lt;p&gt;第一個管理員帳號要用 Gitea 登入，所以得先有一台 Gitea。&lt;/p&gt;

&lt;p&gt;Gitea 本身也能用 Docker 自架，這部分 August 之前寫過，直接看這篇：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.letswrite.tw/gitea-ai-code-review/#%e4%b8%80%e3%80%81%e5%ae%89%e8%a3%9d-gitea" rel="noopener noreferrer"&gt;安裝 Gitea&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;只要你的 Gitea 已經能正常登入，就可以往下走。&lt;/p&gt;

&lt;p&gt;Outline 並不限定 Gitea，任何相容 OIDC 的供應商（Keycloak、Authentik、Google⋯）都行，差別只在 &lt;code&gt;OIDC_*&lt;/code&gt; 那幾個網址。&lt;/p&gt;




&lt;h2&gt;
  
  
  步驟 2：從 Gitea 取得 Client ID 與 Secret
&lt;/h2&gt;

&lt;p&gt;Outline 走 OIDC 登入，需要在 Gitea 建立一個「OAuth2 應用程式」，拿到 &lt;strong&gt;Client ID&lt;/strong&gt; 和 &lt;strong&gt;Client Secret&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;進入 Gitea 後，到 &lt;strong&gt;設定（Settings）→ 應用程式（Applications）&lt;/strong&gt;，找到「管理 OAuth2 應用程式」區塊。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frr9do1xie7tabxt25c9a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frr9do1xie7tabxt25c9a.png" alt="在 Gitea 的「設定 → 應用程式」頁面，找到「管理 OAuth2 應用程式」的建立表單" width="800" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 Gitea 的「設定 → 應用程式」頁面，找到「管理 OAuth2 應用程式」的建立表單&lt;/p&gt;

&lt;p&gt;需要填兩個欄位：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;應用程式名稱：填一個自己記得註是做什麼的名稱，如：&lt;code&gt;Outline Wiki Login&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;重新導向 URI：要填的 PORT 是有對外加上憑證，比如本篇用的是 &lt;code&gt;3001&lt;/code&gt;。
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;你的IP或網域&amp;gt;:3001/auth/oidc.callback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;例如主機 IP 是 &lt;code&gt;192.168.11.111&lt;/code&gt;，就填 &lt;code&gt;https://192.168.11.111:3001/auth/oidc.callback&lt;/code&gt;。這個 IP 與埠號要跟等一下 Compose 裡的 &lt;code&gt;URL=&lt;/code&gt; 完全一致。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphzxibgb2hucl5t5opbf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphzxibgb2hucl5t5opbf.png" alt="Gitea OAuth2 應用程式表單，填入應用程式名稱與重新導向 URI（重點是 Redirect URI 結尾為 /auth/oidc.callback）" width="730" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gitea OAuth2 應用程式表單，填入應用程式名稱與重新導向 URI（重點是 Redirect URI 結尾為 /auth/oidc.callback）&lt;/p&gt;

&lt;p&gt;按下「建立應用程式」後，Gitea 會顯示 &lt;strong&gt;Client ID&lt;/strong&gt; 與 &lt;strong&gt;Client Secret&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp1q68lwxl45vclsj812g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp1q68lwxl45vclsj812g.png" alt="Gitea 建立完成後顯示的 Client ID 與 Client Secret 畫面。" width="731" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Gitea 建立完成後顯示的 Client ID 與 Client Secret 畫面。&lt;/p&gt;

&lt;p&gt;要注意，&lt;code&gt;Client Secret&lt;/code&gt; 只會完整顯示這一次，先複製存好。等一下要填進 Compose 的 &lt;code&gt;OIDC_CLIENT_ID&lt;/code&gt; 與 &lt;code&gt;OIDC_CLIENT_SECRET&lt;/code&gt;。&lt;/p&gt;




&lt;h2&gt;
  
  
  步驟 3-1：安裝 Outline Wiki：Windows 版
&lt;/h2&gt;

&lt;p&gt;Windows 用 Docker Desktop，這裡用具名 volume（由 Docker 管理）存附件，啟動最單純。&lt;/p&gt;

&lt;p&gt;先新增一個空資料夾，接著我們要放進下面的 2 個檔案。&lt;/p&gt;

&lt;h3&gt;
  
  
  Caddyfile
&lt;/h3&gt;

&lt;p&gt;資料夾內新增一個檔案，檔名：&lt;code&gt;Caddfile&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Caddyfile&lt;/code&gt; 負責「對外用 HTTPS、把流量轉給 Outline」。&lt;/p&gt;

&lt;p&gt;檔案內容複製貼上以下，再把 &lt;code&gt;192.168.11.111&lt;/code&gt; 換成我們 Windows 主機的區網 IP：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  default_sni 192.168.11.111
}

192.168.11.111:3001 {
  tls internal
  reverse_proxy outline:3002
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tls internal&lt;/code&gt;：Caddy 自己簽一張憑證。因為我們用的是區網 IP，本來就申請不到正式憑證，自簽夠用。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3001&lt;/code&gt; 是對外埠、&lt;code&gt;outline:3002&lt;/code&gt; 是 Outline 容器內部埠，兩者分工，注意不能寫成一樣。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;資料夾內再新增一個檔案，檔名：&lt;code&gt;docker-compose.yml&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;檔案內容複製貼上以下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c1"&gt;# Outline Wiki — Docker Compose 教學範例&lt;/span&gt;
&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c1"&gt;# 架構：&lt;/span&gt;
&lt;span class="c1"&gt;#   outline   → 主應用 (Node.js)&lt;/span&gt;
&lt;span class="c1"&gt;#   caddy     → 反向代理 + 自動 HTTPS&lt;/span&gt;
&lt;span class="c1"&gt;#   postgres  → 資料庫&lt;/span&gt;
&lt;span class="c1"&gt;#   (外部)    → Redis，負責快取 / WebSocket / 背景工作佇列&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# ⚠️ 發佈前務必把所有 &amp;lt;...&amp;gt; 佔位符換成你自己的值。&lt;/span&gt;
&lt;span class="c1"&gt;#    範例裡的密鑰、密碼、IP 全是假的，不要直接拿去用。&lt;/span&gt;
&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Outline 主應用&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;outline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outlinewiki/outline:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 安全密鑰 (必填) ---------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 兩把都要用 32-byte 隨機 hex 字串。產生方式:&lt;/span&gt;
      &lt;span class="c1"&gt;#   openssl rand -hex 32&lt;/span&gt;
      &lt;span class="c1"&gt;# ⚠️ 換掉後不要再改，改了會讓既有 session / 加密資料失效。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY=&amp;lt;openssl rand -hex 32 產生&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UTILS_SECRET=&amp;lt;openssl rand -hex 32 產生&amp;gt;&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 資料庫連線 (必填) -------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 格式：postgres://帳號:密碼@主機:埠/資料庫名&lt;/span&gt;
      &lt;span class="c1"&gt;# 主機名 postgres = 下面那個 service 名稱，Docker 內部 DNS 解析。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://outline_user:&amp;lt;DB_密碼&amp;gt;@postgres:5432/outline&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGSSLMODE=disable&lt;/span&gt;   &lt;span class="c1"&gt;# 同一台 Docker 內部連線，不需 SSL&lt;/span&gt;

      &lt;span class="c1"&gt;# --- Redis 連線 (必填) -------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 格式：redis://:密碼@主機:埠/DB編號&lt;/span&gt;
      &lt;span class="c1"&gt;# 這裡指向下面內建的 redis service，主機名 redis = service 名。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379/1&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 對外網址 (必填) ---------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 使用者瀏覽器看到的完整網址，要跟 Caddy / 反代對外位置一致。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;URL=https://&amp;lt;你的網域或IP&amp;gt;:8443&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PORT=3019&lt;/span&gt;            &lt;span class="c1"&gt;# 容器內部監聽埠 (給 Caddy 反代用,不直接對外)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FORCE_HTTPS=false&lt;/span&gt;    &lt;span class="c1"&gt;# HTTPS 由 Caddy 處理，這裡關掉避免重導迴圈&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 檔案儲存 (必填) ---------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# local = 存本機磁碟 (搭配下面 volumes)。也可改 s3 接物件儲存。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE=local&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data&lt;/span&gt;
      &lt;span class="c1"&gt;# 上傳/匯入大小上限 (bytes)。預設約 1MB,匯入大 zip (如從&lt;/span&gt;
      &lt;span class="c1"&gt;# Docmost/Notion 匯出) 會跳「file too large」。調大解決。&lt;/span&gt;
      &lt;span class="c1"&gt;# 算法: MB × 1048576。下面 = 250MB。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_UPLOAD_MAX_SIZE=262144000&lt;/span&gt;            &lt;span class="c1"&gt;# 附件上限&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_IMPORT_MAX_SIZE=262144000&lt;/span&gt;            &lt;span class="c1"&gt;# 單篇匯入&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_WORKSPACE_IMPORT_MAX_SIZE=262144000&lt;/span&gt;  &lt;span class="c1"&gt;# zip 整包匯入 (關鍵)&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 登入: Gitea OIDC (選填) -------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 任何相容 OIDC 的供應商都行 (Gitea / Keycloak / Authentik / Google...)。&lt;/span&gt;
      &lt;span class="c1"&gt;# client id/secret 從你的 OIDC 供應商後台拿。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_CLIENT_ID=&amp;lt;OIDC_CLIENT_ID&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_CLIENT_SECRET=&amp;lt;OIDC_CLIENT_SECRET&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_AUTH_URI=http://&amp;lt;OIDC主機&amp;gt;/login/oauth/authorize&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_TOKEN_URI=http://&amp;lt;OIDC主機&amp;gt;/login/oauth/access_token&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_USERINFO_URI=http://&amp;lt;OIDC主機&amp;gt;/login/oauth/userinfo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_USERNAME_CLAIM=preferred_username&lt;/span&gt;   &lt;span class="c1"&gt;# 用哪個欄位當帳號&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_DISPLAY_NAME=Gitea&lt;/span&gt;                   &lt;span class="c1"&gt;# 登入按鈕顯示文字&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_SCOPES=openid profile email&lt;/span&gt;

      &lt;span class="c1"&gt;# --- SMTP 寄信 (選填) --------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 用途：magic-link 登入信 + 通知信。&lt;/span&gt;
      &lt;span class="c1"&gt;# Gmail 範例：密碼要用「應用程式密碼」，不是帳號密碼。&lt;/span&gt;
      &lt;span class="c1"&gt;# (Google 帳號 → 安全性 → 兩步驟驗證 → 應用程式密碼)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_HOST=smtp.gmail.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_PORT=587&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_SECURE=false&lt;/span&gt;    &lt;span class="c1"&gt;# 587 用 STARTTLS，所以這裡 false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_USERNAME=&amp;lt;你的gmail@gmail.com&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_PASSWORD=&amp;lt;Gmail 應用程式密碼&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_FROM_EMAIL=&amp;lt;你的gmail@gmail.com&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_REPLY_EMAIL=&amp;lt;你的gmail@gmail.com&amp;gt;&lt;/span&gt;

    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 用具名 volume，由 Docker 管理。注意：這裡存的是「附件/上傳檔」&lt;/span&gt;
      &lt;span class="c1"&gt;# (圖片、PDF...)，不是文章本體。文章內容存在 PostgreSQL，不是 .md 檔。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;outline_data:/var/lib/outline/data&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;   &lt;span class="c1"&gt;# 等 DB 健康檢查通過才啟動 outline&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;   &lt;span class="c1"&gt;# 等 Redis 就緒&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Redis — 快取 / WebSocket / 背景工作佇列 (內建獨立 service)&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline_redis&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_data:/data&lt;/span&gt;   &lt;span class="c1"&gt;# 持久化 (選用,Outline 主要當快取，掉了也能重建)&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# 回 PONG 才算就緒&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Caddy — 反向代理，自動處理 HTTPS 憑證&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;caddy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caddy:2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline_caddy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8443:8443"&lt;/span&gt;   &lt;span class="c1"&gt;# 對外開的埠 (主機：容器)。實際反代規則寫在 Caddyfile。&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./Caddyfile:/etc/caddy/Caddyfile:ro&lt;/span&gt;   &lt;span class="c1"&gt;# :ro = 唯讀掛載&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_data:/data&lt;/span&gt;       &lt;span class="c1"&gt;# 存 TLS 憑證，別刪，刪了要重新申請&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_config:/config&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;outline&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# PostgreSQL&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pgvector/pgvector:pg16&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline_postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 這三個要跟上面 DATABASE_URL 裡的帳號/密碼/DB名一致。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=outline_user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=&amp;lt;DB_密碼&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=outline&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_data:/var/lib/postgresql/data&lt;/span&gt;   &lt;span class="c1"&gt;# 資料庫實體資料，千萬別誤刪&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 只綁 127.0.0.1 = 僅本機可連，不對外曝露 DB。安全做法。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 確認 DB 真的可接受連線，outline 才會啟動 (見上面 depends_on)。&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;outline_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;outline"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# 具名 volume — 由 Docker 管理,容器刪掉資料還在。&lt;/span&gt;
&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;outline_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pg_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著要把所有 &lt;code&gt;&amp;lt;...&amp;gt;&lt;/code&gt; 的部份換成自己的值，要改這幾項：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SECRET_KEY&lt;/code&gt;、&lt;code&gt;UTILS_SECRET&lt;/code&gt;：開啟終端機，執行兩次 &lt;code&gt;openssl rand -hex 32&lt;/code&gt; 產生 32-byte 隨機字串。沒有 openssl 的話，那就用 Claude、ChatGPT 等 AI，對話中讓它們生成 2 組使用。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DATABASE_URL&lt;/code&gt; 與 &lt;code&gt;POSTGRES_PASSWORD&lt;/code&gt;：兩邊的密碼要一致。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;URL&lt;/code&gt;：填 &lt;code&gt;https://&amp;lt;你的IP&amp;gt;:3001&lt;/code&gt;，IP 要跟 Caddyfile 一樣。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OIDC_CLIENT_ID&lt;/code&gt;、&lt;code&gt;OIDC_CLIENT_SECRET&lt;/code&gt;：填上一段從 Gitea 拿到的值。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OIDC_AUTH_URI&lt;/code&gt; / &lt;code&gt;OIDC_TOKEN_URI&lt;/code&gt; / &lt;code&gt;OIDC_USERINFO_URI&lt;/code&gt;：把 &lt;code&gt;&amp;lt;OIDC主機&amp;gt;&lt;/code&gt; 換成 Gitea 網址。&lt;/li&gt;
&lt;li&gt;（選填）SMTP：要寄邀請信 / magic-link 才需要填。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  啟動
&lt;/h3&gt;

&lt;p&gt;在 &lt;code&gt;docker-compose.yml&lt;/code&gt; 所在資料夾開終端機：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;第一次啟動 Outline 會自動跑資料庫遷移（migration），會需要多等個十幾秒。&lt;/p&gt;

&lt;p&gt;看到 outline 正常起來後，瀏覽器打開 &lt;code&gt;https://&amp;lt;你的IP&amp;gt;:3001&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;瀏覽器會跳「您的連線不是私人連線 / 不安全」警告。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;這是因為 &lt;code&gt;tls internal&lt;/code&gt; 用的是自簽憑證，屬於正常現象。點「進階 → 繼續前往」就能進到 Outline。&lt;/p&gt;

&lt;p&gt;進站後點 &lt;strong&gt;Continue with Gitea&lt;/strong&gt; 用 Gitea 帳號登入，第一個登入的人就是管理員。&lt;/p&gt;

&lt;p&gt;之後的成員，在 Outline 後台用 Email 邀請即可（這也是為什麼上面要設定 SMTP）。&lt;/p&gt;




&lt;h2&gt;
  
  
  步驟 3-2：安裝 Outline Wiki：Ubuntu 版
&lt;/h2&gt;

&lt;p&gt;Ubuntu 版跟 Windows 版幾乎一樣，只有兩個差別：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;附件改用 bind mount（&lt;code&gt;./data&lt;/code&gt;）&lt;/strong&gt;：附件直接落在專案資料夾，搬家 / 備份複製整個資料夾就帶走，不必像具名 volume 還要 &lt;code&gt;tar&lt;/code&gt; 撈。&lt;/li&gt;
&lt;li&gt;OIDC 的 Gitea 可以在&lt;strong&gt;另一台&lt;/strong&gt;機器，只要這台 Ubuntu 連得到即可。&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  檔案
&lt;/h3&gt;

&lt;p&gt;一樣是兩個檔案：&lt;code&gt;docker-compose.yml&lt;/code&gt; 和 &lt;code&gt;Caddyfile&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Caddyfile&lt;/code&gt; 內容（把 IP 換成你的 Ubuntu 主機 IP）：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  default_sni 192.168.11.111
}

192.168.11.111:3001 {
  tls internal
  reverse_proxy outline:3002
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;docker-compose.yml 的內容複製貼上以下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c1"&gt;# Outline Wiki — Ubuntu 單主機部署版 (教學範例)&lt;/span&gt;
&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;
&lt;span class="c1"&gt;# 跟 Windows 版的差異：&lt;/span&gt;
&lt;span class="c1"&gt;#   1. 內建 Redis service — 不依賴外部 Redis 主機，一台 Ubuntu 全包。&lt;/span&gt;
&lt;span class="c1"&gt;#   2. 全部服務同一台主機，對外只露 Caddy 的 8443。&lt;/span&gt;
&lt;span class="c1"&gt;#   3. OIDC 仍可指向「另一台」Gitea/OIDC 主機，只要這台網路連得到。&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# 架構：&lt;/span&gt;
&lt;span class="c1"&gt;#   outline   → 主應用 (Node.js)&lt;/span&gt;
&lt;span class="c1"&gt;#   redis     → 快取 / WebSocket / 背景佇列&lt;/span&gt;
&lt;span class="c1"&gt;#   caddy     → 反向代理 + 自動 HTTPS&lt;/span&gt;
&lt;span class="c1"&gt;#   postgres  → 資料庫&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# ⚠️ 發佈前把所有 &amp;lt;...&amp;gt; 佔位符換成你自己的值。範例的密鑰/密碼/IP 全是假的。&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# 部署步驟：&lt;/span&gt;
&lt;span class="c1"&gt;#   1. 整個資料夾複製到 Ubuntu (含 Caddyfile)。&lt;/span&gt;
&lt;span class="c1"&gt;#   2. 全域搜尋取代 &amp;lt;你的Ubuntu主機IP&amp;gt; → 實際 IP。&lt;/span&gt;
&lt;span class="c1"&gt;#   3. docker compose up -d&lt;/span&gt;
&lt;span class="c1"&gt;# =============================================================================&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Outline 主應用&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;outline&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outlinewiki/outline:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;NODE_ENV=production&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 安全密鑰 (必填) ---------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 兩把都用 32-byte 隨機 hex。產生: openssl rand -hex 32&lt;/span&gt;
      &lt;span class="c1"&gt;# ⚠️ 換掉後別再改，改了會讓既有 session / 加密資料失效。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SECRET_KEY=&amp;lt;openssl rand -hex 32 產生&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UTILS_SECRET=&amp;lt;openssl rand -hex 32 產生&amp;gt;&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 資料庫連線 (必填) -------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 格式：postgres://帳號:密碼@主機:埠/資料庫名&lt;/span&gt;
      &lt;span class="c1"&gt;# 主機名 postgres = 下面的 service 名，Docker 內部 DNS 解析。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgres://outline_user:&amp;lt;DB_密碼&amp;gt;@postgres:5432/outline&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PGSSLMODE=disable&lt;/span&gt;   &lt;span class="c1"&gt;# 同台 Docker 內部連線,不需 SSL&lt;/span&gt;

      &lt;span class="c1"&gt;# --- Redis 連線 (必填) -------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# ★ Ubuntu 版重點：指向「內建」redis service，主機名 redis = service 名。&lt;/span&gt;
      &lt;span class="c1"&gt;#   不像基本版要填外部 Redis IP/密碼。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;REDIS_URL=redis://redis:6379/1&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 對外網址 (必填) ---------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 使用者瀏覽器看到的完整網址，要跟 Caddy 對外位置一致。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;URL=https://&amp;lt;你的Ubuntu主機IP&amp;gt;:8443&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PORT=3019&lt;/span&gt;            &lt;span class="c1"&gt;# 容器內部監聽埠 (給 Caddy 反向代理，不直接對外)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FORCE_HTTPS=false&lt;/span&gt;    &lt;span class="c1"&gt;# HTTPS 由 Caddy 處理，這裡關掉避免重導迴圈&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 檔案儲存 (必填) ---------------------------------------------------&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE=local&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data&lt;/span&gt;
      &lt;span class="c1"&gt;# 上傳/匯入大小上限 (bytes)。預設約 1MB，匯入大 zip 會跳「file too large」。&lt;/span&gt;
      &lt;span class="c1"&gt;# 算法: MB × 1048576。下面 = 250MB。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_UPLOAD_MAX_SIZE=262144000&lt;/span&gt;            &lt;span class="c1"&gt;# 附件上限&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_IMPORT_MAX_SIZE=262144000&lt;/span&gt;            &lt;span class="c1"&gt;# 單篇匯入&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;FILE_STORAGE_WORKSPACE_IMPORT_MAX_SIZE=262144000&lt;/span&gt;  &lt;span class="c1"&gt;# zip 整包匯入 (關鍵)&lt;/span&gt;

      &lt;span class="c1"&gt;# --- 登入: Gitea OIDC (選填) -------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 任何相容 OIDC 供應商皆可 (Gitea / Keycloak / Authentik / Google...)。&lt;/span&gt;
      &lt;span class="c1"&gt;# ★ Ubuntu 版注意：OIDC 主機可在「另一台」機器，確認這台 Ubuntu 連得到。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_CLIENT_ID=&amp;lt;OIDC_CLIENT_ID&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_CLIENT_SECRET=&amp;lt;OIDC_CLIENT_SECRET&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_AUTH_URI=http://&amp;lt;OIDC主機&amp;gt;/login/oauth/authorize&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_TOKEN_URI=http://&amp;lt;OIDC主機&amp;gt;/login/oauth/access_token&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_USERINFO_URI=http://&amp;lt;OIDC主機&amp;gt;/login/oauth/userinfo&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_USERNAME_CLAIM=preferred_username&lt;/span&gt;   &lt;span class="c1"&gt;# 用哪個欄位當帳號&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_DISPLAY_NAME=Gitea&lt;/span&gt;                   &lt;span class="c1"&gt;# 登入按鈕顯示文字&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;OIDC_SCOPES=openid profile email&lt;/span&gt;

      &lt;span class="c1"&gt;# --- SMTP 寄信 (選填) --------------------------------------------------&lt;/span&gt;
      &lt;span class="c1"&gt;# 用途：magic-link 登入信 + 通知信。&lt;/span&gt;
      &lt;span class="c1"&gt;# Gmail 範例：密碼要用「應用程式密碼」,不是帳號密碼。&lt;/span&gt;
      &lt;span class="c1"&gt;# (Google 帳號 → 安全性 → 兩步驟驗證 → 應用程式密碼)&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_HOST=smtp.gmail.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_PORT=587&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_SECURE=false&lt;/span&gt;    &lt;span class="c1"&gt;# 587 用 STARTTLS，所以 false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_USERNAME=&amp;lt;你的gmail@gmail.com&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_PASSWORD=&amp;lt;Gmail 應用程式密碼&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_FROM_EMAIL=&amp;lt;你的gmail@gmail.com&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;SMTP_REPLY_EMAIL=&amp;lt;你的gmail@gmail.com&amp;gt;&lt;/span&gt;

    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 存的是「附件/上傳檔」(圖片、PDF...)，不是文章本體 (文章在 PostgreSQL)。&lt;/span&gt;
      &lt;span class="c1"&gt;# ★ 用 bind mount: 附件直接落在專案資料夾 ./data,搬家複製即帶走。&lt;/span&gt;
      &lt;span class="c1"&gt;#   對比 named volume (outline_data:) 要 tar 才能撈出，bind 好搬很多。&lt;/span&gt;
      &lt;span class="c1"&gt;#   注意：DB 不要 bind，DB 檔不能直接 copy，一律用 pg_dump 備份。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./data:/var/lib/outline/data&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;   &lt;span class="c1"&gt;# 等 DB 健康檢查通過才啟動&lt;/span&gt;
      &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_healthy&lt;/span&gt;   &lt;span class="c1"&gt;# 等 Redis 就緒&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Redis — 內建獨立 service (★ Ubuntu 版才有,基本版用外部 Redis)&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;redis&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;redis:7-alpine&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline_redis&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;redis_data:/data&lt;/span&gt;   &lt;span class="c1"&gt;# 持久化 (選用，Outline 主要當快取，掉了也能重建)&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;redis-cli"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ping"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;   &lt;span class="c1"&gt;# 回 PONG 才算就緒&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# Caddy — 反向代理，自動處理 HTTPS 憑證&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;caddy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caddy:2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline_caddy&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8443:8443"&lt;/span&gt;   &lt;span class="c1"&gt;# 對外唯一開的埠 (主機:容器)。反代規則寫在 Caddyfile。&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./Caddyfile:/etc/caddy/Caddyfile:ro&lt;/span&gt;   &lt;span class="c1"&gt;# :ro = 唯讀掛載&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_data:/data&lt;/span&gt;       &lt;span class="c1"&gt;# 存 TLS 憑證，別刪，刪了要重新申請&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;caddy_config:/config&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;outline&lt;/span&gt;

  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="c1"&gt;# PostgreSQL — pgvector 映像 (支援向量搜尋)&lt;/span&gt;
  &lt;span class="c1"&gt;# ---------------------------------------------------------------------------&lt;/span&gt;
  &lt;span class="na"&gt;postgres&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pgvector/pgvector:pg16&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;outline_postgres&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 這三個要跟上面 DATABASE_URL 的帳號/密碼/DB名一致。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=outline_user&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=&amp;lt;DB_密碼&amp;gt;&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=outline&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_data:/var/lib/postgresql/data&lt;/span&gt;   &lt;span class="c1"&gt;# 資料庫實體資料，千萬別誤刪&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# 只綁 127.0.0.1 = 僅本機可連，不對外曝露 DB。安全做法。&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:5432:5432"&lt;/span&gt;
    &lt;span class="na"&gt;healthcheck&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CMD-SHELL"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg_isready&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-U&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;outline_user&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-d&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;outline"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
      &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10s&lt;/span&gt;
      &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
      &lt;span class="na"&gt;retries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# 具名 volume — 由 Docker 管理，容器刪掉資料還在。&lt;/span&gt;
&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# outline_data 改用 bind mount (./data)，不在這裡宣告。&lt;/span&gt;
  &lt;span class="na"&gt;pg_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;redis_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;caddy_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;要修改的部份，如：密鑰、DB、OIDC、SMTP，都與 Windows 版填法完全相同。&lt;/p&gt;

&lt;h3&gt;
  
  
  啟動
&lt;/h3&gt;

&lt;p&gt;在 &lt;code&gt;docker-compose.yml&lt;/code&gt; 所在資料夾開終端機：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;確認與登入流程跟 Windows 版一樣：瀏覽器打開 &lt;code&gt;https://&amp;lt;你的IP&amp;gt;:3001&lt;/code&gt;，遇到憑證警告點「繼續前往」，再用 Gitea 登入。&lt;/p&gt;




&lt;h2&gt;
  
  
  結語
&lt;/h2&gt;

&lt;p&gt;照著上面的步驟做，我們就會有一套 &lt;strong&gt;完全自架 + 免費 + 可以用 MCP 接上 AI&lt;/strong&gt; 的 Outline Wiki。&lt;/p&gt;

&lt;p&gt;怎麼用 MCP 接上 Claude、Codex，下一篇會說明。&lt;/p&gt;

</description>
      <category>outline</category>
      <category>ai</category>
      <category>gitea</category>
      <category>docker</category>
    </item>
    <item>
      <title>OpenClaw：安裝教學，在 macOS 用虛擬機 (Ubuntu) 安全部署龍蝦 AI</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Tue, 03 Mar 2026 12:39:26 +0000</pubDate>
      <link>https://dev.to/letswrite/openclawan-zhuang-jiao-xue-zai-macos-yong-xu-ni-ji-ubuntu-an-quan-bu-shu-long-xia-ai-4b00</link>
      <guid>https://dev.to/letswrite/openclawan-zhuang-jiao-xue-zai-macos-yong-xu-ni-ji-ubuntu-an-quan-bu-shu-long-xia-ai-4b00</guid>
      <description>&lt;h1&gt;
  
  
  OpenClaw：安裝教學，在 macOS 用虛擬機 (Ubuntu) 安全部署龍蝦 AI
&lt;/h1&gt;

&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;自從 OpenClaw 橫空出世後，三不五時就會看到相關的文章和影音介紹，看起來是個跨時代的產物。不過伴隨而來的，也有不少資安方面的疑慮。&lt;/p&gt;

&lt;p&gt;相信很多人都想試試龍蝦的威力，但說實話，安裝起來沒那麼簡單。&lt;/p&gt;

&lt;p&gt;本篇主要筆記怎麼安裝 OpenClaw。為了安全考量，選擇先在本機架設虛擬機，再於虛擬機上安裝 OpenClaw。這樣萬一出事了，大不了直接把虛擬機砍掉就好。&lt;/p&gt;

&lt;p&gt;本篇注意事項：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;本篇安裝 OpenClaw 時，官方的版本是 2026.3.2。官方更新速度很快，也許會遇到不同的安裝設定，如果遇到有疑問或不知道是什麼的狀況，建議詢問 AI。&lt;/li&gt;
&lt;li&gt;本篇是使用 macOS，虛擬機是使用 Ubuntu-24.04.4。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;以下將 OpenClaw 簡稱龍蝦。&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝虛擬機
&lt;/h2&gt;

&lt;p&gt;下載並安裝 VirtualBuddy：&lt;a href="https://github.com/insidegui/VirtualBuddy/releases" rel="noopener noreferrer"&gt;https://github.com/insidegui/VirtualBuddy/releases&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;安裝完 VirtualBuddy 後，打開來，選擇 Linux &amp;gt; 左下角的 Custom Link。&lt;/p&gt;

&lt;p&gt;輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://cdimage.ubuntu.com/releases/24.04.4/release/ubuntu-24.04.4-live-server-arm64.iso
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;或者手動下載 iso：&lt;a href="https://cdimage.ubuntu.com/releases/24.04.4/release/" rel="noopener noreferrer"&gt;https://cdimage.ubuntu.com/releases/24.04.4/release/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;點擊「64-bit ARM (ARMv8/AArch64) server install image」。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3cmd0bc4r3jy7awh4ku.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj3cmd0bc4r3jy7awh4ku.png" alt="VirtualBuddy 安裝 Ubuntu 24.04 ARM 架構伺服器版本映像檔選擇畫面" width="725" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在 VirtualBuddy 的介面，選擇 Linux &amp;gt; 左下角的 Custom File，選擇剛剛下載的檔案。&lt;/p&gt;

&lt;p&gt;安裝的設定可以如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Virtual CPUs：3 或 4&lt;/li&gt;
&lt;li&gt;Memory：4&lt;/li&gt;
&lt;li&gt;Display Width / Height：都拉到最小值&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;安裝時，遇到「Install OpenSSH server」，記得這選項要打勾。&lt;/p&gt;

&lt;p&gt;安裝完並重開機後，建議後續改用 Mac 的終端機 &lt;code&gt;ssh&lt;/code&gt; 的方式來操作。&lt;/p&gt;

&lt;p&gt;比方登入後，會看到這段訊息：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzk4rkco4ctjb0bjfy4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzk4rkco4ctjb0bjfy4g.png" alt="Ubuntu VM 啟動後顯示的 IPv4 位址資訊，用於 SSH 遠端連線" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;我們只需要在終端機上輸入：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="o"&gt;[&lt;/span&gt;你的帳號]@192.168.64.13
範例：
ssh letswrite@192.168.64.13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著輸入帳號，就可以遠端登入了。&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝 Node.js
&lt;/h2&gt;

&lt;p&gt;本篇透過 npm 來安裝 OpenClaw，因此需要先安裝 Node.js。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;下載並執行 NVM 安裝腳本&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-o-&lt;/span&gt; https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;更新環境設定&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc

&lt;span class="c"&gt;# 如果是用 Zsh&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;安裝 Node.js（npm 會一併安裝）&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nvm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--lts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  取得 Telegram Bot Token
&lt;/h2&gt;

&lt;p&gt;龍蝦可以在多個平台使用，在看了幾個官方的 &lt;a href="https://github.com/openclaw/openclaw/releases" rel="noopener noreferrer"&gt;Releases&lt;/a&gt;，會發現每次更版都有針對 Telegram 的部分，看起來對 Telegram 的支援度較高，因此本篇使用 Telegram。&lt;/p&gt;

&lt;p&gt;註冊了 Telegram 會員後，登入，我們要先建立一個機器人用的帳號。&lt;/p&gt;

&lt;p&gt;Telegram 上要新增一個機器人很容易，一樣跟機器人對話就可以建立。&lt;/p&gt;

&lt;p&gt;點擊這個網址加入 BotFather 為好友：&lt;a href="https://telegram.me/BotFather" rel="noopener noreferrer"&gt;https://telegram.me/BotFather&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;接著對話框中傳送這個給 BotFather：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/newbot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;再按照 BotFather 需要我們回應的訊息，一步步回應後，就可以得到 Telegram Bot 的 Token，如下圖：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16cfv6oanzfv6f0kg0sh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F16cfv6oanzfv6f0kg0sh.png" alt="Telegram BotFather 申請機器人後取得的 HTTP API Token 範例" width="618" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;這個 Token 要存下來，之後安裝龍蝦時，就可以在安裝過程中直接輸入。&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝 OpenClaw：正常版
&lt;/h2&gt;

&lt;p&gt;以下是平常有扶老婆婆過馬路，有積功德，可以一路順順走下去的 Happy Path 版本。&lt;/p&gt;

&lt;p&gt;輸入指令：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; openclaw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;安裝完後，接著輸入以下指令，開始進行設定：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw onboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Onboarding mode&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;直接使用 &lt;code&gt;QuickStart&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Model/auth provider&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;根據網路上看到的災情 +&lt;br&gt;
August 本人的 Google Antigravity 被封殺過的人體實驗證明 +&lt;br&gt;
各家 API 的花費如果要到 Pro 等級的很燒 $$ 經驗……&lt;/p&gt;

&lt;p&gt;那個，對，August 曾經選擇 Gemini Pro 3 的 API，安裝個 gog 跟一些 skills 就燒掉了 3 美金，所以除非口袋很深，不然不建議用 API 的方式。&lt;/p&gt;

&lt;p&gt;建議二種：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ChatGPT 是 Plus 以上方案的，就選 &lt;code&gt;OpenAI&lt;/code&gt; 的 &lt;code&gt;OpenAI Codex (ChatGPT OAuth)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果有訂閱 GitHub Copilot，就選 &lt;code&gt;Copilot&lt;/code&gt; 的 &lt;code&gt;GitHub Copilot (GitHub device login)&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果是選 OpenAI Codex，選了以後，就會看到一個網址，貼到瀏覽器上後，執行登入，接著會看到「無法連上這個網站」的畫面，莫驚慌莫害怕，我們只要把登入結果的 URL 回貼到終端機上就可以了。&lt;/p&gt;

&lt;p&gt;如果是選 GitHub Copilot，選了以後，會看到一個網址，用 GitHub 帳號授權登入後，填寫介面提供的代碼，就完成了。&lt;/p&gt;

&lt;p&gt;其他使用模型的方式 August 沒試過，大家可以抱著探索自己口袋深度的心態勇敢按下去。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Select channel&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;選擇 &lt;code&gt;Telegram (Bot API)&lt;/code&gt;，再選 &lt;code&gt;Enter Telegram bot token&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;然後把我們在前一段從 Telegram 上取得的 Token 貼上去，就可以了。&lt;/p&gt;

&lt;p&gt;接著會問要不要安裝 skill，在這邊可裝可不裝，看個人需求，這個階段跳過也無所謂，後續再跟 Agent 說要裝 XX skill 就行。&lt;/p&gt;

&lt;p&gt;下一步會看到問要不要再輸入「GOOGLE_PLACES_API_KEY」、「GEMINI_API_KEY」、「OPENAI_API_KEY」、「ELEVENLABS_API_KEY」，可以都選「No」，因為凡是 Key、Token……都是要發動魔法小卡的。&lt;/p&gt;

&lt;p&gt;「NOTION_API_KEY」，如果想讓 Agent 控制 Notion，可以填，在 Notion 的 &lt;a href="https://www.notion.so/profile/integrations/public" rel="noopener noreferrer"&gt;integrations&lt;/a&gt; 頁面上可以取得 Key。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable hooks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;這一步一開始沒看懂，問了 Gemini 後才懂意思，如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;boot-md：當系統（Gateway）啟動時，會自動讀取並執行工作區中的 BOOT.md 檔案。這適合用來初始化環境變數或設定當次啟動的特殊邏輯。&lt;/li&gt;
&lt;li&gt;bootstrap-extra-files：允許系統在啟動時載入額外的設定檔案（如 USER.md 或 SOUL.md）。這對於維持 AI 對你（使用者）的身份認知非常重要。&lt;/li&gt;
&lt;li&gt;command-logger：（推薦勾選）將你下達的所有指令記錄到審計日誌（Audit log）中。重視 ISO 27001 與安全稽核的作業習慣。&lt;/li&gt;
&lt;li&gt;session-memory：（核心功能）當執行 /new 或 /reset 指令開新對話時，會自動將之前的上下文（Context）存入記憶。這能確保 AI 即使重啟對話，也能維持開發進度的連續性。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;勾不勾選就看個人需求，後續也都可以再更改。&lt;/p&gt;

&lt;p&gt;最後，就安裝成功了，之後只要在 Telegram 建的機器人上隨便傳一句訊息，就會收到要配對的指令：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9puccdpi9dlkleetkmq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh9puccdpi9dlkleetkmq.png" alt="OpenClaw 終端機配對指令與 Telegram 成功授權畫面" width="437" height="227"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;把最後一行的指令在終端機上執行，會看到收到以下的回傳訊息：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Approved telegram sender xxxxxxxxxx.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;代表配對完成，可以在 Telegram Bot 上使用 OpenClaw 了。&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝 OpenClaw：遇到神奇錯誤版
&lt;/h2&gt;

&lt;p&gt;過年前 August 安裝龍蝦，都是上一段的正常版本。&lt;/p&gt;

&lt;p&gt;但遇到 Google Antigravity 用到被停權，前幾天帳號終於解封後，再安裝時就出現了錯誤。&lt;/p&gt;

&lt;p&gt;如果進行到最後一步，看到以下錯誤，就代表恭喜你，要走這段的版本：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: systemctl is-enabled unavailable: Command failed: systemctl --user is-enabled openclaw-gateway.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;解決方式如下，就照著一步步輸入指令就可以，以下都是來自於 Claude 提供的解方。&lt;/p&gt;

&lt;h3&gt;
  
  
  第一步：前置準備（安裝前先做）
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;loginctl enable-linger &lt;span class="nv"&gt;$USER&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export XDG_RUNTIME_DIR=/run/user/$(id -u)'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'export DBUS_SESSION_BUS_ADDRESS=unix:path=${XDG_RUNTIME_DIR}/bus'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.bashrc
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  第二步：正常安裝 OpenClaw
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw onboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;遇到 systemctl 相關錯誤直接忽略，繼續完成設定。&lt;/p&gt;

&lt;h3&gt;
  
  
  第三步：手動建立 Gateway Service
&lt;/h3&gt;

&lt;p&gt;安裝完成後執（一次性的複製貼上即可）：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.config/systemd/user

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.config/systemd/user/openclaw-gateway.service &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[Unit]
Description=OpenClaw Gateway
After=network.target

[Service]
Type=simple
Environment="PATH=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which openclaw&lt;span class="si"&gt;))&lt;/span&gt;&lt;span class="sh"&gt;:/usr/local/bin:/usr/bin:/bin"
ExecStart=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;which openclaw&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt; gateway run
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; daemon-reload
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nb"&gt;enable &lt;/span&gt;openclaw-gateway
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; start openclaw-gateway
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  第四步：確認正常運作
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; status openclaw-gateway
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; is-enabled openclaw-gateway
openclaw status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;is-enabled&lt;/code&gt; 應顯示 &lt;code&gt;enabled&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;openclaw status&lt;/code&gt; 裡 gateway 應顯示 &lt;code&gt;reachable&lt;/code&gt;。&lt;/p&gt;

&lt;h3&gt;
  
  
  重新安裝 gateway
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw gateway &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;接著重啟：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw gateway restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  常用管理指令
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; stop openclaw-gateway      &lt;span class="c"&gt;# 停止&lt;/span&gt;
systemctl &lt;span class="nt"&gt;--user&lt;/span&gt; restart openclaw-gateway   &lt;span class="c"&gt;# 重啟&lt;/span&gt;
journalctl &lt;span class="nt"&gt;--user&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; openclaw-gateway &lt;span class="nt"&gt;-f&lt;/span&gt;    &lt;span class="c"&gt;# 即時 log&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;按照上面的步驟執行後，OpenClaw 就安裝成功了。&lt;/p&gt;




&lt;h2&gt;
  
  
  後續想要修改設定
&lt;/h2&gt;

&lt;p&gt;比方在安裝時，選了使用 GitHub Copilot，之後如果想換用 Openai Codex，或是一些設定值想要修改，要怎麼做呢？&lt;/p&gt;

&lt;p&gt;很簡單，執行以下命令：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;修改完成後，要再重啟 gateway：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openclaw gateway restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>ubuntu</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>站內搜尋加上 AI：使用 Google Vertex AI Search（RAG）打造智慧問答型搜尋</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Tue, 20 Jan 2026 14:40:02 +0000</pubDate>
      <link>https://dev.to/letswrite/zhan-nei-sou-xun-jia-shang-aishi-yong-google-vertex-ai-search-rag-da-zao-zhi-hui-wen-da-xing-sou-xun-302c</link>
      <guid>https://dev.to/letswrite/zhan-nei-sou-xun-jia-shang-aishi-yong-google-vertex-ai-search-rag-da-zao-zhi-hui-wen-da-xing-sou-xun-302c</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;之前就一直想試試看 &lt;strong&gt;RAG（Retrieval-Augmented Generation）&lt;/strong&gt; 要怎麼應用在實際場景，但卡在自建 RAG 架構的門檻不低（像是要處理向量資料庫、轉檔、索引……bla bla bla），又想說成本會不會很高？直到這幾天發現了 Google 有 Vertex AI Search 這功能，讓 Gemini 判斷了一下可能成本，發現，意外的蠻便宜的耶，就決定來應用一下。&lt;/p&gt;

&lt;p&gt;簡單說明 RAG（以下為 Gemini 提供）：&lt;/p&gt;

&lt;p&gt;RAG 就像是為 AI 掛載了一份「即時參考資料」。它並非單一資料庫，而是包含三個核心動作，而 Vertex AI Search 已經把這些複雜的底層邏輯都封裝好了：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;檢索（Retrieval）：當使用者問問題時，系統去你的網站（資料庫）找出最相關的幾段文字。&lt;/li&gt;
&lt;li&gt;增強（Augmented）：把這些找出來的文字，連同原本的問題，一起塞進給 AI 的指令（Prompt）裡。&lt;/li&gt;
&lt;li&gt;生成（Generation）：AI 閱讀了你給它的「臨時參考資料」，最後產出正確的回答。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因為本站重點都在實作，因此本篇的使用情境設定如下：&lt;/p&gt;

&lt;p&gt;當網站內容累積到一定程度後，傳統的關鍵字搜尋往往無法精準回應使用者的需求。透過 &lt;strong&gt;Vertex AI Search&lt;/strong&gt;，可以實作 RAG 架構，讓站內搜尋不只是「找關鍵字」，而是能「閱讀」我們的網站內容並整理出答案。&lt;/p&gt;

&lt;p&gt;而在打不過 AI 就加入它的時代，我們可以達成：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;自然語言提問&lt;/strong&gt;：使用者問「怎麼實作 code review」，AI 能從多篇文章中彙整答案。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;低開發成本&lt;/strong&gt;：不需自行維護向量資料庫 (Vector DB) 或處理複雜的資料清洗。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;高精準度&lt;/strong&gt;：結合 Google 的語意搜尋與生成能力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本篇實作成果，已經放上了首頁，以及每篇筆記文的文末，會看到一個「Vertex AI 搜尋」的輸入框，大家可以試一試喔，覺得好用或不好用，都歡迎留言。&lt;/p&gt;




&lt;h2&gt;
  
  
  建立 Google Vertex AI Search (RAG) 實作流程：資料匯入
&lt;/h2&gt;

&lt;p&gt;首先，必須要有一個 Google Cloud 的帳號，有了帳號後新增專案，而這個專案必須是「付費帳戶」，就是要付 $$ 的。&lt;/p&gt;

&lt;p&gt;有了一個付費帳戶的專案，接著開始以下幾個步驟。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;進入 Vertex AI 後台&lt;/strong&gt;：從 Google Cloud 控制台進入 Vertex AI 介面。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdke7khnimaa7hvairn2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdke7khnimaa7hvairn2.png" alt="Google Cloud Vertex AI 控制台首頁介面" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;啟用 API&lt;/strong&gt;：點擊「啟用所有建議的 API」以確保功能完整。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因為啟用不用 $$，之後有使用到才要，為了防止後續步驟陣亡的莫名其妙，這邊就給它全部打開。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2burwsih9ycj5rskvvws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2burwsih9ycj5rskvvws.png" alt="啟用 Vertex AI 相關建議 API 畫面" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;選擇應用程式類型&lt;/strong&gt;：點擊「運用 AI 模式打造站內搜尋服務」。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtqw9icoh6i1i1ase17m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtqw9icoh6i1i1ase17m.png" alt="選擇 Vertex AI Search 搜尋應用程式類型" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;填寫應用程式資訊&lt;/strong&gt;：輸入名稱並選取區域（建議選 global）。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fow7mdz6eqnl0khv2zt7k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fow7mdz6eqnl0khv2zt7k.png" alt="填寫應用程式基本資訊與地理位置" width="800" height="867"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;建立資料儲存庫 (Data Store)&lt;/strong&gt;：點擊建立按鈕。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3ahhpjfdtb79xgityn9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl3ahhpjfdtb79xgityn9.png" alt="點擊建立資料儲存庫按鈕" width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;選取資料來源&lt;/strong&gt;：選擇「網站內容」。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因為本篇是應用在站內搜尋，因此是選「網站內容」，如果大家使用時目的不同，可以詢問 AI 用哪一個省成本。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jogwsumyjo9urccphtf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5jogwsumyjo9urccphtf.png" alt="選擇資料來源為網站內容" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;指定索引路徑&lt;/strong&gt;：輸入網址模式，如 &lt;code&gt;www.letswrite.tw/*&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;「自動檢索網址並持續更新」：建議打勾，之後網站有更新，Vertex 就會自動更新。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcpvntw0dq0ysev3d943.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzcpvntw0dq0ysev3d943.png" alt="設定要建立索引的網址路徑模式" width="800" height="803"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;啟用強化功能&lt;/strong&gt;：設定儲存庫名稱並建議勾選「文件處理設定」。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;下面截圖中的選項要不要勾看網站的內容，一樣可以跟 AI 討論是否都勾。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcg6okzngst28cta13rg6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcg6okzngst28cta13rg6.png" alt="設定資料儲存庫名稱與啟用文件處理設定" width="800" height="857"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;選取計費模式&lt;/strong&gt;：初期選擇「一般計費模式」。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mb2elx1ulakuq2uisyt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9mb2elx1ulakuq2uisyt.png" alt="選擇搜尋應用程式的計費模式" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;連結 Data Store&lt;/strong&gt;：勾選剛建好的儲存庫並點擊繼續。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeud6t3nn9d3jhlvipv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyeud6t3nn9d3jhlvipv4.png" alt="將資料儲存庫連結至搜尋應用程式" width="800" height="202"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;監控進度&lt;/strong&gt;：在資料頁面查看「正在建立初始索引」。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs95hnibr08e4z6g8q0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frs95hnibr08e4z6g8q0m.png" alt="在後台查看初始索引建立進度" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;增加 Sitemap&lt;/strong&gt;：手動新增 Sitemap 網址以確保文章完整收錄。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvrri6dde7igrl3fr9hg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvrri6dde7igrl3fr9hg.png" alt="手動新增 Sitemap 網址以加速索引" width="800" height="438"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;索引完成&lt;/strong&gt;：當狀態顯示「初始索引建立完成」時，可以看到目前文件的總體積與數量（本站約 340 MB，共 696 份文件）。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpe6ootqp1r18pp79p5o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbpe6ootqp1r18pp79p5o.png" alt="資料索引完成狀態，顯示 696 份文件與 339.84 MiB" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  將 Vertex AI Search 放上網站
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;調整回覆風格&lt;/strong&gt;：在「設定」中將搜尋類型改為「搜尋答案」，並在操作說明加入「簡明扼要、不超過 100 字、繁體中文」等指令。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41dllcps2hjwfpago6o9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F41dllcps2hjwfpago6o9.png" alt="調整搜尋小工具 UI 設定為搜尋答案，並輸入自訂操作說明" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;整合權限設定&lt;/strong&gt;：選取「公開存取權」並填入允許的網域。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobky2jqpweh082ry0955.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobky2jqpweh082ry0955.png" alt="設定公開存取網域並複製搜尋小工具程式碼" width="800" height="655"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  實作：iframe 與加到 WordPress
&lt;/h2&gt;

&lt;p&gt;本站是用 WordPress 架站的，照著上圖直接照放程式碼，發現樣式會被影響，因此把 Vertex Search 的頁面做一個 HTML 檔案，然後在 WordPress 上嵌入。&lt;/p&gt;

&lt;h3&gt;
  
  
  iframe.html
&lt;/h3&gt;

&lt;p&gt;我們將 Widget 放在新增的 HTML 檔案中，並處理 Shadow DOM 的 Enter 事件以動態撐開高度。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!doctype html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"zh-TW"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Vertex AI Search - Let's Write&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;body&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;sans-serif&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;#searchWidgetTrigger&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;box-sizing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;border-box&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="m"&gt;#ccc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;-webkit-appearance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* 移除 iOS 預設樣式 */&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nt"&gt;gen-search-widget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;block&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;body&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"searchWidgetTrigger"&lt;/span&gt; &lt;span class="na"&gt;inputmode=&lt;/span&gt;&lt;span class="s"&gt;"search"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;gen-search-widget&lt;/span&gt;
      &lt;span class="na"&gt;configId=&lt;/span&gt;&lt;span class="s"&gt;"9a35bc9d-f398-4b2b-bfeb-28091c39e3f6"&lt;/span&gt;
      &lt;span class="na"&gt;triggerId=&lt;/span&gt;&lt;span class="s"&gt;"searchWidgetTrigger"&lt;/span&gt;
      &lt;span class="na"&gt;anchorsTarget=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;
      &lt;span class="na"&gt;alwaysOpened&lt;/span&gt;
      &lt;span class="na"&gt;placeholder=&lt;/span&gt;&lt;span class="s"&gt;"Vertex AI 搜尋"&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;gt;&amp;lt;/gen-search-widget&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cloud.google.com/ai/gen-app-builder/client?hl=zh_TW"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;widget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gen-search-widget&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;searchWidgetTrigger&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

      &lt;span class="c1"&gt;// 處理 Enter 按下時的邏輯&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onEnterPressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 防止重複觸發&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;triggered&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// 通知父頁面調整 iframe 高度&lt;/span&gt;
        &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;postMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;setHeight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.letswrite.tw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 2 秒後重置觸發狀態，允許下次搜尋&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="c1"&gt;// 深度遍歷 shadow DOM，為所有 input 和 button 添加事件監聽&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setupDeepListener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 防止無限遞迴&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 為所有 input 元素添加事件監聽&lt;/span&gt;
        &lt;span class="nx"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// 避免重複添加監聽器&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listenerAdded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listenerAdded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="c1"&gt;// 桌機：Enter 鍵 (keyup)&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="c1"&gt;// 手機：Enter 鍵 (keypress)&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="c1"&gt;// 手機：輸入變化時觸發 (失焦或完成輸入)&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;change&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// 為所有 button 元素添加點擊監聽 (手機端主要觸發方式)&lt;/span&gt;
        &lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listenerAdded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listenerAdded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="nx"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="c1"&gt;// 遞迴檢查嵌套的 shadow DOM&lt;/span&gt;
        &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setupDeepListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;el&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="c1"&gt;// 設置 shadow DOM 監聽器&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;setupShadowListener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shadow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;widget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

          &lt;span class="c1"&gt;// 初始化深度監聽&lt;/span&gt;
          &lt;span class="nf"&gt;setupDeepListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="c1"&gt;// 使用 MutationObserver 監聽動態添加的元素&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;MutationObserver&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;mutations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nx"&gt;mutation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addedNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodeType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                  &lt;span class="c1"&gt;// 如果新增節點有 shadow root，遞迴設置監聽&lt;/span&gt;
                  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;setupDeepListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shadowRoot&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
                  &lt;span class="c1"&gt;// 如果新增 input 或 button，重新掃描整個 shadow DOM&lt;/span&gt;
                  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INPUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUTTON&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nf"&gt;setupDeepListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                  &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shadow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;childList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;subtree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;});&lt;/span&gt;

          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

      &lt;span class="c1"&gt;// 等待 widget 定義並設置監聽&lt;/span&gt;
      &lt;span class="nx"&gt;customElements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;whenDefined&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gen-search-widget&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// 每 500ms 嘗試設置監聽，直到成功&lt;/span&gt;
          &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;setupShadowListener&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

          &lt;span class="c1"&gt;// 15 秒後停止嘗試&lt;/span&gt;
          &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;clearInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// 外層 input 的 Enter 鍵監聽 (keyup)&lt;/span&gt;
      &lt;span class="nx"&gt;searchInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// 外層 input 的 Enter 鍵監聽 (keypress)&lt;/span&gt;
      &lt;span class="nx"&gt;searchInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nf"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;});&lt;/span&gt;

      &lt;span class="c1"&gt;// 監聽整個文檔的 Enter 鍵 (keyup,捕獲階段)&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keyup&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INPUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 監聽整個文檔的 Enter 鍵 (keypress 捕獲階段)&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INPUT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Enter&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keyCode&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// 監聽所有按鈕點擊 (捕獲階段 主要針對手機端)&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;click&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tagName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;BUTTON&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;closest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;onEnterPressed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  WordPress 裡嵌入 iframe 頁面
&lt;/h3&gt;

&lt;p&gt;透過 WP Code 等插件，可以選擇在標題上方，或各文章頁底部自動插入 iframe。&lt;/p&gt;

&lt;p&gt;以下是範例。&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.bwp-section-header-separator&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vertexIframe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
            &amp;lt;div class="vertex-ai-container" style="width: 100%; margin: 20px 0;"&amp;gt;
                &amp;lt;iframe id="vertexIframe" 
                        src="/iframe.html" 
                        style="width: 100%; border: none; overflow: hidden; transition: height 0.3s ease; height: 85px;" 
                        scrolling="no"&amp;gt;
                &amp;lt;/iframe&amp;gt;
            &amp;lt;/div&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertAdjacentHTML&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beforebegin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;htmlString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 建議加入來源網域檢查（因 iframe 同源可省略，但若跨網域則必須）&lt;/span&gt;
        &lt;span class="c1"&gt;// if (event.origin !== "https://YOUR_IFRAME_DOMAIN") return;&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setHeight&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vertexIframe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  成本分析預估
&lt;/h2&gt;

&lt;p&gt;基於目前的網站規模（約 700 份文件），August 預計在官網測試一個月多來評估，主要是成本考量。&lt;/p&gt;

&lt;p&gt;以下是「Gemini 3 思考型」做的成本預估：&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;項目&lt;/th&gt;
&lt;th&gt;估計單價 (USD)&lt;/th&gt;
&lt;th&gt;說明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;資料儲存 (Storage)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;約 $1.00 / GB / 月&lt;/td&gt;
&lt;td&gt;340 MB 體積產生的月費微乎其微。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;企業版查詢 (Enterprise)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$4.00 / 1,000 次查詢&lt;/td&gt;
&lt;td&gt;包含 RAG 生成式回答與語意搜尋。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemini 2.5 Flash Token&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;依流量計費&lt;/td&gt;
&lt;td&gt;由於 Flash 價格極低，主要成本集中在查詢次數費。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;如果每月有 1,000 次有效查詢，預估成本約為 &lt;strong&gt;$4 ~ $10 美金&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;一個月 5 美金以下，就還可以接受，超過 5 美金……就要看本站能不能帶來一些收入了，不然一直造成支出也不是辦法。&lt;/p&gt;

</description>
      <category>google</category>
      <category>vertexai</category>
      <category>rag</category>
      <category>gemini</category>
    </item>
    <item>
      <title>CodiumAI PR-Agent，在 Gitea 上用 AI 來 Code Review</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Sat, 10 Jan 2026 10:18:06 +0000</pubDate>
      <link>https://dev.to/letswrite/codiumai-pr-agentzai-gitea-shang-yong-ai-lai-code-review-m6l</link>
      <guid>https://dev.to/letswrite/codiumai-pr-agentzai-gitea-shang-yong-ai-lai-code-review-m6l</guid>
      <description>&lt;h1&gt;
  
  
  CodiumAI PR-Agent，在 Gitea 上用 AI 來 Code Review
&lt;/h1&gt;

&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;之前有寫過一篇：〈&lt;a href="https://www.letswrite.tw/gitea-ai-code-review/" rel="noopener noreferrer"&gt;使用 Gitea Actions 與 OpenAI 實現自動化 PR Code Review&lt;/a&gt;〉。&lt;/p&gt;

&lt;p&gt;底下有人留言說，Gitea 還有其他 Code Review 的方式，研究了以後發現，哎哎哎，這不就是 CodiumAI 的 PR-Agent 嗎？之前寫過在 GitHub、GitLab 上使用的，沒想到 Gitea 也有：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.letswrite.tw/github-ai-code-review/" rel="noopener noreferrer"&gt;CodiumAI PR-Agent，在 GitHub 上用 AI 來 Code Review&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.letswrite.tw/gitlab-ai-code-review/" rel="noopener noreferrer"&gt;CodiumAI PR-Agent，在 GitLab 上用 AI 來 Code Review&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;再加上本篇，本站對於 CodiumAI PR-Agent Code Review 這塊就湊滿 Git 三神獸了，看能不能解開什麼封印之門（咦？）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CodiumAI PR-Agent&lt;/strong&gt; 是一個開源的 AI 工具，能夠自動分析 Pull Request 的變更內容。&lt;/p&gt;

&lt;p&gt;本篇將用 Docker 來部署並整合到用 Docker 自架的 &lt;strong&gt;Gitea&lt;/strong&gt;，實現以下自動化流程：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;自動審查&lt;/strong&gt;：當開發者建立或更新 PR 時，自動觸發 AI 進行分析。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;自動描述&lt;/strong&gt;：AI 自動生成 PR 的摘要修改點，減少工程師撰寫文件的時間。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;品質建議&lt;/strong&gt;：提供程式碼改進建議、偵測潛在 Bug。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;繁體中文友善&lt;/strong&gt;：設定 AI 以繁體中文回應，好吸收好閱讀。&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Docker 安裝 Gitea
&lt;/h2&gt;

&lt;p&gt;架設 Gitea 伺服器，可以參考前一篇 Gitea 的文章，就邊就不重覆寫：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.letswrite.tw/gitea-ai-code-review/#%e4%b8%80%e3%80%81%e5%ae%89%e8%a3%9d-gitea" rel="noopener noreferrer"&gt;Docker 安裝 Gitea&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;本文假設我們已經有一個運行中的 Gitea，並且擁有管理員權限。&lt;/p&gt;




&lt;h2&gt;
  
  
  取得需要的 Token 與 Webhook Secret
&lt;/h2&gt;

&lt;p&gt;在部署 CodiumAI PR-Agent 前，我們需要先準備好 Gitea 的 Token 與 OpenAI API Key。&lt;/p&gt;

&lt;h3&gt;
  
  
  取得 Gitea Personal Access Token
&lt;/h3&gt;

&lt;p&gt;Token 是為了讓 PR-Agent 可以讀取專案，並進行留言。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;登入 Gitea 伺服器。&lt;/li&gt;
&lt;li&gt;點擊右上角頭像，進入「設定」。&lt;/li&gt;
&lt;li&gt;選擇左側選單的「應用程式」。&lt;/li&gt;
&lt;li&gt;在「管理存取權杖」區塊中，點擊「生成新權杖」。&lt;/li&gt;
&lt;li&gt;填寫權杖名稱（例如：&lt;code&gt;pr-agent&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;權限設定（重要）&lt;/strong&gt;：請務必勾選以下權限：&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;issue&lt;/code&gt;：讀取和寫入&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;repository&lt;/code&gt;：讀取和寫入&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;點擊「產生 Token」並 &lt;strong&gt;立即複製&lt;/strong&gt;（離開頁面後將無法再次查看）。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu08m9k7cvm4nwq55vwf7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu08m9k7cvm4nwq55vwf7.jpeg" alt="Gitea 使用者設定頁面中生成應用程式存取權杖 (Token) 的權限勾選畫面"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  生成 Webhook Secret
&lt;/h3&gt;

&lt;p&gt;為了確保安全性，我們需要設定一個 Secret 來驗證 Webhook 請求。&lt;/p&gt;

&lt;p&gt;產生 Secret 的方式有二種。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在終端機使用 &lt;code&gt;openssl&lt;/code&gt; 產生一組高強度的隨機字串：
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 24
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;直接跟 AI 要一組，比方在 ChatGPT 或 Gemini 上，輸入：提供我一組 16 碼金鑰。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;產生的 Secret 要記下來，之後會填到設定檔中。&lt;/p&gt;

&lt;h3&gt;
  
  
  準備 OpenAI API Key
&lt;/h3&gt;

&lt;p&gt;前往 &lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;OpenAI Platform&lt;/a&gt; 建立一組 API Key。&lt;/p&gt;

&lt;p&gt;建立 Key，必須要先存一筆金額才能使用。&lt;/p&gt;

&lt;p&gt;這功能是要 $$ 的，記得選用 CP 高的模型，才不會幾次 Code Review 下來，荷包大失血。&lt;/p&gt;




&lt;h2&gt;
  
  
  Docker Compose 與環境變數設定
&lt;/h2&gt;

&lt;h3&gt;
  
  
  .env
&lt;/h3&gt;

&lt;p&gt;建立一個專案用資料夾，取名「pr-agent-gitea」。&lt;/p&gt;

&lt;p&gt;資料夾裡新增 &lt;code&gt;.env&lt;/code&gt; 的檔案，把敏感的資料，如我們前二步取得的 Token，都寫進去，方便管理與保密。&lt;/p&gt;

&lt;p&gt;複製貼上以下到 .env：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# ==================== Gitea 連線設定 ====================
# 若 Gitea 在另一台主機，請填寫該主機的固定 IP 或網域
&lt;/span&gt;&lt;span class="n"&gt;GITEA_URL&lt;/span&gt;=&lt;span class="n"&gt;http&lt;/span&gt;://&lt;span class="m"&gt;192&lt;/span&gt;.&lt;span class="m"&gt;168&lt;/span&gt;.&lt;span class="n"&gt;xx&lt;/span&gt;.&lt;span class="n"&gt;xx&lt;/span&gt;:&lt;span class="m"&gt;3000&lt;/span&gt;
&lt;span class="n"&gt;GITEA_TOKEN&lt;/span&gt;=在此貼上在 &lt;span class="n"&gt;Gitea&lt;/span&gt; 取得的&lt;span class="n"&gt;Token&lt;/span&gt;
&lt;span class="n"&gt;GITEA_WEBHOOK_SECRET&lt;/span&gt;=在此貼上 &lt;span class="n"&gt;Secret&lt;/span&gt;

&lt;span class="c"&gt;# ==================== LLM 模型設定 ====================
&lt;/span&gt;&lt;span class="n"&gt;OPENAI_KEY&lt;/span&gt;=在此貼上 &lt;span class="n"&gt;Openai&lt;/span&gt; 的 &lt;span class="n"&gt;Key&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  docker-compose.yml
&lt;/h3&gt;

&lt;p&gt;專案裡新增一個 docker-compose.yml 檔，複製貼上以下內容：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pr-agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;codiumai/pr-agent:0.31-gitea_app&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pr-agent&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unless-stopped&lt;/span&gt;

    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env&lt;/span&gt;

    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# ==================== Gitea 連線設定 ====================&lt;/span&gt;
      &lt;span class="na"&gt;CONFIG__GIT_PROVIDER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gitea"&lt;/span&gt;
      &lt;span class="na"&gt;GITEA__URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GITEA_URL}&lt;/span&gt;
      &lt;span class="na"&gt;GITEA__PERSONAL_ACCESS_TOKEN&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GITEA_TOKEN}&lt;/span&gt;
      &lt;span class="na"&gt;GITEA__WEBHOOK_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GITEA_WEBHOOK_SECRET}&lt;/span&gt;
      &lt;span class="na"&gt;GITEA__SKIP_SSL_VERIFICATION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;

      &lt;span class="c1"&gt;# ==================== OpenAI 模型設定 ====================&lt;/span&gt;
      &lt;span class="na"&gt;CONFIG__MODEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-4.1-mini"&lt;/span&gt;
      &lt;span class="na"&gt;OPENAI__KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${OPENAI_KEY}&lt;/span&gt;

      &lt;span class="c1"&gt;# ==================== PR 觸發設定 ====================&lt;/span&gt;
      &lt;span class="c1"&gt;# 設定自動執行的指令：描述 (/describe)、審查 (/review)、優化 (/improve)&lt;/span&gt;
      &lt;span class="na"&gt;GITEA__PR_COMMANDS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;["/describe",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/review",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/improve"]'&lt;/span&gt;
      &lt;span class="c1"&gt;# 設定觸發動作：開啟 PR 或 同步更新代碼時&lt;/span&gt;
      &lt;span class="na"&gt;GITEA__HANDLE_PR_ACTIONS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;["opened",&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"synchronize"]'&lt;/span&gt;

      &lt;span class="c1"&gt;# ==================== 語言與輸出設定 ====================&lt;/span&gt;
      &lt;span class="na"&gt;CONFIG__RESPONSE_LANGUAGE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zh-TW"&lt;/span&gt;
      &lt;span class="na"&gt;CONFIG__PUBLISH_OUTPUT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
      &lt;span class="na"&gt;CONFIG__PUBLISH_OUTPUT_PROGRESS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;

      &lt;span class="c1"&gt;# ==================== 留言行為設定 ====================&lt;/span&gt;
      &lt;span class="c1"&gt;# 強制使用新留言模式 (false)，不覆蓋舊留言，保留審查歷史&lt;/span&gt;
      &lt;span class="na"&gt;PR_DESCRIPTION__PERSISTENT_COMMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="na"&gt;PR_REVIEW__PERSISTENT_COMMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="c1"&gt;# 關閉行內程式碼註解 (依喜好開啟)&lt;/span&gt;
      &lt;span class="na"&gt;PR_REVIEW__INLINE_CODE_COMMENTS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;

      &lt;span class="c1"&gt;# ==================== 功能開關設定 ====================&lt;/span&gt;
      &lt;span class="c1"&gt;# 簡化輸出，關閉額外的分類標籤&lt;/span&gt;
      &lt;span class="na"&gt;PR_DESCRIPTION__ENABLE_SEMANTIC_FILES_TYPES&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="na"&gt;PR_REVIEW__ENABLE_REVIEW_LABELS_EFFORT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="na"&gt;PR_REVIEW__ENABLE_REVIEW_LABELS_SECURITY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
      &lt;span class="na"&gt;GITEA__PUBLISH_LABELS&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;

      &lt;span class="c1"&gt;# ==================== 檔案忽略清單 (Cost Saving) ====================&lt;/span&gt;
      &lt;span class="c1"&gt;# 忽略鎖定檔、靜態資源等不需要分析的檔案，大幅節省 Token 費用&lt;/span&gt;
      &lt;span class="na"&gt;CONFIG__IGNORE__GLOB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;['dist/**',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'build/**',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'out/**',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'.next/**',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'coverage/**',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'node_modules/**',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'package-lock.json',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'pnpm-lock.yaml',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'yarn.lock',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.md',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.txt',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.log',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.min.js',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.min.css',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.map',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.svg',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.png',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.jpg',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.jpeg',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.gif',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.ico',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.woff',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.woff2',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.ttf',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.eot',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'.env*',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'.gitignore',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'.eslintrc*',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'.prettierrc*',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'tsconfig.json',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'jest.config.*',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'vite.config.*',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'webpack.config.*',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.spec.ts',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.spec.js',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.test.ts',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'*.test.js']"&lt;/span&gt;

    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3001:3000"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;內容說明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;模型選擇 (&lt;code&gt;CONFIG__MODEL&lt;/code&gt;)&lt;/strong&gt;：使用 &lt;code&gt;gpt-4.1-mini&lt;/code&gt;，這是 CP 值高的選擇。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;語言設定 (&lt;code&gt;CONFIG__RESPONSE_LANGUAGE&lt;/code&gt;)&lt;/strong&gt;：指定 &lt;code&gt;zh-TW&lt;/code&gt;，讓 AI 輸出時用正體中文。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;忽略清單 (&lt;code&gt;CONFIG__IGNORE__GLOB&lt;/code&gt;)&lt;/strong&gt;：如果想省 $$，這點很重要！要排除 &lt;code&gt;node_modules&lt;/code&gt;、圖片檔、lock 檔等，避免 AI 浪費 Token 去讀取這些非程式碼檔案。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ports&lt;/strong&gt;：這邊指定 3001，主要是 Gitea 預設是 3000，如果 PR-Agent 一樣用 3000 會啟動失敗，因此改用 3001。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;docker-compose.yml 檔完成後，專案內開啟終端機，執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Container 上看到有 pr-agent，代表啟動成功。&lt;/p&gt;




&lt;h2&gt;
  
  
  實際執行 Code Review
&lt;/h2&gt;

&lt;h3&gt;
  
  
  設定 Gitea Repository Webhook
&lt;/h3&gt;

&lt;p&gt;最後一步，我們要告訴 Gitea 當有 PR 發生時，要把資料送給誰。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;進入要進行 Code Review 的專案，點擊「設定」 &amp;gt; 「Webhook」 &amp;gt; 「新增 Webhook」 &amp;gt; 「Gitea」。&lt;/li&gt;
&lt;li&gt;Webhook 填寫以下資訊：&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;目標 URL&lt;/strong&gt;：&lt;code&gt;http://&amp;lt;我們的_PR_AGENT_IP&amp;gt;:3001/api/v1/gitea_webhooks&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP 方法&lt;/strong&gt;：POST&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST Content Type&lt;/strong&gt;：application/json&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;密鑰&lt;/strong&gt;：填入 &lt;code&gt;.env&lt;/code&gt; 中的 &lt;code&gt;GITEA_WEBHOOK_SECRET&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;觸發條件&lt;/strong&gt;：選取「自訂事件」，建議勾選：&lt;/li&gt;
&lt;li&gt;問題留言（Issue Comment）&lt;/li&gt;
&lt;li&gt;合併請求（Pull Request）&lt;/li&gt;
&lt;li&gt;合併請求留言（Pull Request Comment）&lt;/li&gt;
&lt;li&gt;合併請求同步（Pull Request Synchronize）&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;點擊「新增 Webhook」。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxz36mzm4frox0yqryv4.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhxz36mzm4frox0yqryv4.jpeg" alt="在 Gitea 儲存庫設定中新增 Webhook 的詳細參數填寫範例"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  驗證成果
&lt;/h3&gt;

&lt;p&gt;我們可以建一個小專案，故意寫錯出幾個 bug，然後發一個 Pull Request。&lt;/p&gt;

&lt;p&gt;等待約 10~30 秒，我們會看到：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;AI 自動留言 (Describe)&lt;/strong&gt;：AI 會摘要這個 PR 做了什麼改動。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI 自動審查 (Review)&lt;/strong&gt;：AI 會列出「主要變更」、「潛在問題」以及「改進建議」。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmg5yvcdwbn1okwd9nn5h.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmg5yvcdwbn1okwd9nn5h.jpeg" alt="CodiumAI PR-Agent 在 Gitea Pull Request 頁面自動產生的繁體中文審查報告與摘要"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  進階：手動觸發指令
&lt;/h3&gt;

&lt;p&gt;除了 PR 時自動觸發，我也可以在 PR 的留言區輸入指令與 AI 互動：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/describe&lt;/code&gt;：重新生成 PR 描述。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/review&lt;/code&gt;：重新執行程式碼審查。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/ask "這段改動有 SQL Injection 風險嗎？"&lt;/code&gt;：針對特定問題詢問 AI。&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  總結
&lt;/h2&gt;

&lt;p&gt;透過 PR-Agent 與 Gitea 的整合，我們可以用極低的成本搭建 Code Review 功能。&lt;/p&gt;

&lt;p&gt;雖然 AI 無法 100% 取代人工，但它能幫忙過濾低級錯誤、自動撰寫文件，讓攻城獅能將精力集中在怎麼攻城（欸這什麼結論）。&lt;/p&gt;

</description>
      <category>gitea</category>
      <category>ai</category>
      <category>codereview</category>
      <category>codiumai</category>
    </item>
    <item>
      <title>在本機安裝 Mattermost：打造自主控制的團隊通訊平台</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Wed, 10 Dec 2025 12:19:20 +0000</pubDate>
      <link>https://dev.to/letswrite/zai-ben-ji-an-zhuang-mattermostda-zao-zi-zhu-kong-zhi-de-tuan-dui-tong-xun-ping-tai-4hk2</link>
      <guid>https://dev.to/letswrite/zai-ben-ji-an-zhuang-mattermostda-zao-zi-zhu-kong-zhi-de-tuan-dui-tong-xun-ping-tai-4hk2</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;工作上，各家公司用的通訊軟體，常聽到的是以下三種：LINE、Teams、Slack。&lt;/p&gt;

&lt;p&gt;如果稍微比較一下，用起來的經驗如下（以下為 August 個人體感，不代表本台立場 XD）：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LINE、Telegram：適合閒聊&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;這兩個工具雖然方便，尤其台灣人幾乎都有 LINE，但主要設計給日常聊天使用。&lt;/p&gt;

&lt;p&gt;面對工程團隊需要更結構化的溝通方式時，比如：頻道分類、討論串、檔案管理、發送程式碼……等功能就不太行。&lt;/p&gt;

&lt;p&gt;還容易讓生活 + 工作的訊息都摻在一起作灑尿牛丸，重要資訊很容易被淹沒。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Slack、Teams：貴&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Slack 夠強大，也是專門針對開發團隊用，但免費版只能保留最近 90 天的訊息記錄，而且發送訊息的數量每個月有上限，想要完整保存對話歷史就必須付費。&lt;/p&gt;

&lt;p&gt;對小團隊或個人專案來說，魔法小卡沒辦法說發動就發動。&lt;/p&gt;

&lt;p&gt;Microsoft Teams 需要企業或教育機構的授權才能充分使用，個人或小團隊要獨立使用並不方便，比如想要用 Webhook，家庭方案還無法使用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discord：資料及傳檔限制&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;雖然 Discord 免費且功能豐富，但所有資料都存放在 Discord 的伺服器上，對於重視資料隱私或需要完全掌控資料的人來說，這是一個重要的考量點。&lt;/p&gt;

&lt;p&gt;傳檔案時，如果是免費方案有 10 MB 的限制，想傳大一點的檔案就會被擋下來。&lt;/p&gt;

&lt;p&gt;因為使用上述幾個通訊軟體的體感，在詢問了 Gemini 3 Pro 有沒有更適合的工具後，才知道了今天要筆記的這個 Mattermost。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mattermost 優勢&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;開源，可以完全安裝在自己的電腦或伺服器上。&lt;/li&gt;
&lt;li&gt;類似 Slack 的使用方式，支援頻道分類、討論串、檔案分享等功能，還能透過外掛擴充更多能力。&lt;/li&gt;
&lt;li&gt;可以整合 Webhook、機器人帳號。&lt;/li&gt;
&lt;li&gt;所有資料都在本地，不用擔心第三方服務終止或資料外洩的問題。&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  本機安裝 Mattermost
&lt;/h2&gt;

&lt;h3&gt;
  
  
  事前準備
&lt;/h3&gt;

&lt;p&gt;在開始之前，請確認電腦已經安裝：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Desktop（包含 Docker Compose）&lt;/li&gt;
&lt;li&gt;一個行動硬碟（選用，用來存放圖片和檔案）&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Docker Compose 設定檔
&lt;/h3&gt;

&lt;p&gt;建立一個工作目錄，在裡面新增 &lt;code&gt;docker-compose.yml&lt;/code&gt; 檔案。&lt;/p&gt;

&lt;p&gt;可以直接複製貼上以下：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/db:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=mattermost&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=mmuser&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=mmuser_password&lt;/span&gt;

  &lt;span class="na"&gt;mattermost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mattermost/mattermost-team-edition:latest&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
    &lt;span class="na"&gt;security_opt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;no-new-privileges:true&lt;/span&gt;
    &lt;span class="na"&gt;pids_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/config:/mattermost/config:rw&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/logs:/mattermost/logs:rw&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/plugins:/mattermost/plugins:rw&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/client/plugins:/mattermost/client/plugins:rw&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/Volumes/backup/mattermost_data:/mattermost/data:rw&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SQLSETTINGS_DRIVERNAME=postgres&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:mmuser_password@db:5432/mattermost?sslmode=disable&amp;amp;connect_timeout=10&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SERVICESETTINGS_SITEURL=http://localhost:8065&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_PLUGINSETTINGS_ENABLEUPLOADS=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_PLUGINSETTINGS_ENABLEMARKETPLACE=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SERVICESETTINGS_ALLOWCORSFROM=*&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_WEBSERVER_WEBSOCKETSECUREPORT=8065&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SERVICESETTINGS_ENABLEINSECUREOUTGOINGCONNECTIONS=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_EMAILSETTINGS_SENDPUSHNOTIFICATIONS=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_EMAILSETTINGS_PUSHNOTIFICATIONSERVER=https://push-test.mattermost.com&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_EMAILSETTINGS_PUSHNOTIFICATIONCONTENTS=full&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_LOGSETTINGS_FILEMAXAGEDAYS=30&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_LOGSETTINGS_FILECOMPRESS=true&lt;/span&gt;

    &lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
      &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10m"&lt;/span&gt;
        &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;

    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8065:8065"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;以下逐段說明設定內容：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;資料庫服務&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:16-alpine&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/db:/var/lib/postgresql/data&lt;/span&gt;
  &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB=mattermost&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER=mmuser&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD=mmuser_password&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這段設定建立了 PostgreSQL 16 資料庫容器。使用輕量的 Alpine Linux 版本可以減少映像檔大小。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;restart: "no"&lt;/code&gt; 表示電腦重開機後不會自動啟動，需要手動執行啟動指令，主要是避免重新開機時，行動硬碟沒有插在主機上。&lt;/p&gt;

&lt;p&gt;資料庫檔案存放在 &lt;code&gt;./volumes/db&lt;/code&gt; 目錄，這樣即使容器被刪除，資料也不會遺失。&lt;/p&gt;

&lt;p&gt;環境變數設定了資料庫名稱、使用者名稱和密碼，這些資訊稍後會被 Mattermost 用來連線。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mattermost 服務&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mattermost&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mattermost/mattermost-team-edition:latest&lt;/span&gt;
  &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no"&lt;/span&gt;
  &lt;span class="na"&gt;security_opt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;no-new-privileges:true&lt;/span&gt;
  &lt;span class="na"&gt;pids_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mattermost 服務依賴資料庫，Docker 會確保資料庫先啟動。&lt;/p&gt;

&lt;p&gt;使用 Team Edition 免費版本，已經足夠小型團隊使用。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;security_opt&lt;/code&gt; 和 &lt;code&gt;pids_limit&lt;/code&gt; 是安全性設定，限制容器的權限提升和程序數量。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;儲存策略&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/config:/mattermost/config:rw&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/logs:/mattermost/logs:rw&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/plugins:/mattermost/plugins:rw&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./volumes/client/plugins:/mattermost/client/plugins:rw&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/Volumes/backup/mattermost_data:/mattermost/data:rw&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這裡採用混合儲存策略，解決 Mattermost 不刪檔特性帶來的空間問題：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;存放在本機的檔案&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;設定檔（config）、日誌（logs）、外掛（plugins）都存在本機的 &lt;code&gt;./volumes/&lt;/code&gt; 目錄。這些檔案體積小且需要頻繁讀取，放在 Volume 可以確保系統反應速度，避免操作介面卡頓。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;存放在行動硬碟的靜態資源&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;圖片和檔案（data）存在行動硬碟的 &lt;code&gt;/Volumes/backup/mattermost_data&lt;/code&gt; 路徑。這是整個設定的關鍵考量點：&lt;strong&gt;Mattermost 預設不會刪除任何上傳的檔案&lt;/strong&gt;，即使訊息被刪除，檔案依然保留在伺服器上。&lt;/p&gt;

&lt;p&gt;隨著時間累積，照片、文件、影片……會越來越多，如果全部存在本機，很快就會面臨空間不足的問題。&lt;/p&gt;

&lt;p&gt;將這些靜態資源檔案改存到行動硬碟，既能完整保留所有歷史檔案，又不會壓縮主機空間。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;請記得修改硬碟路徑&lt;/strong&gt;：將 &lt;code&gt;/Volumes/backup&lt;/code&gt; 改成行動硬碟在 Mac 上的實際掛載位置。&lt;/p&gt;

&lt;p&gt;在 Finder 中打開「前往」→「電腦」就能看到硬碟名稱。如果你的行動硬碟名稱是「My Passport」，路徑就是 &lt;code&gt;/Volumes/My Passport/mattermost_data&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;最後一行掛載系統時間檔案，確保容器內的時間與主機同步。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;資料庫連線設定&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SQLSETTINGS_DRIVERNAME=postgres&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SQLSETTINGS_DATASOURCE=postgres://mmuser:mmuser_password@db:5432/mattermost?sslmode=disable&amp;amp;connect_timeout=10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;指定使用 PostgreSQL 資料庫，連線字串包含使用者名稱、密碼、主機名稱（db 就是前面定義的資料庫服務名稱）、Port 和資料庫名稱。在區域網路環境下，為了簡化設定，這裡停用了 SSL 加密。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;網站網址設定&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SERVICESETTINGS_SITEURL=http://localhost:8065&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這是 Mattermost 對外的網址。如果你只在本機使用，就維持 &lt;code&gt;http://localhost:8065&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;如果要讓區域網路內其他裝置存取，可以設定為你電腦的區域網路 IP，例如 &lt;code&gt;http://192.168.1.100:8065&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;如果想用 Cloudflare Tunnel 進行內網穿透，可以設定為對外的網址，例如 &lt;code&gt;https://mattermost.yourdomain.com&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;外掛市集功能&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_PLUGINSETTINGS_ENABLEUPLOADS=true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_PLUGINSETTINGS_ENABLEMARKETPLACE=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;開啟外掛上傳和市集功能，這樣才能安裝 Boards、Todo 等擴充功能。&lt;/p&gt;

&lt;p&gt;預設情況下這些功能可能被關閉，需要明確啟用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;WebSocket 連線設定&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SERVICESETTINGS_ALLOWCORSFROM=*&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_WEBSERVER_WEBSOCKETSECUREPORT=8065&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_SERVICESETTINGS_ENABLEINSECUREOUTGOINGCONNECTIONS=true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;WebSocket 是實現即時訊息的關鍵技術，而且後續使用 Boards 看版功能時也需要。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ALLOWCORSFROM=*&lt;/code&gt; 允許所有來源的跨域請求，方便在不同裝置上存取。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ENABLEINSECUREOUTGOINGCONNECTIONS&lt;/code&gt; 允許 Mattermost 連接到沒有 HTTPS 的外部服務，這在測試環境中很實用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;推播通知設定&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_EMAILSETTINGS_SENDPUSHNOTIFICATIONS=true&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_EMAILSETTINGS_PUSHNOTIFICATIONSERVER=https://push-test.mattermost.com&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_EMAILSETTINGS_PUSHNOTIFICATIONCONTENTS=full&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;開啟推播通知功能，使用 Mattermost 官方提供的測試推播伺服器（TPNS）。這是免費服務，讓你的手機 App 能收到即時通知。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;PUSHNOTIFICATIONCONTENTS=full&lt;/code&gt; 表示通知會顯示發訊者姓名和訊息內容；如果改成 &lt;code&gt;generic&lt;/code&gt; 則只顯示「您有一則新訊息」。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;日誌管理&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_LOGSETTINGS_FILEMAXAGEDAYS=30&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MM_LOGSETTINGS_FILECOMPRESS=true&lt;/span&gt;

&lt;span class="na"&gt;logging&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json-file"&lt;/span&gt;
  &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;max-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10m"&lt;/span&gt;
    &lt;span class="na"&gt;max-file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mattermost 內部的日誌檔案會保留 30 天，超過後自動壓縮舊檔案。&lt;/p&gt;

&lt;p&gt;Docker 容器輸出的日誌則限制每個檔案最大 10MB，最多保留 5 個檔案，避免日誌無限膨脹佔用硬碟空間。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;對外 Port&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;8065:8065"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;將容器內的 8065 Port 對應到主機的 8065 Port。&lt;/p&gt;

&lt;p&gt;啟動後可以透過 &lt;code&gt;http://localhost:8065&lt;/code&gt; 存取 Mattermost。&lt;/p&gt;

&lt;h3&gt;
  
  
  啟動服務
&lt;/h3&gt;

&lt;p&gt;在 &lt;code&gt;docker-compose.yml&lt;/code&gt; 所在目錄執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-d&lt;/code&gt; 參數表示在背景執行。第一次啟動會需要下載映像檔，可能需要幾分鐘時間。&lt;/p&gt;

&lt;p&gt;啟動完成後，打開瀏覽器前往 &lt;code&gt;http://localhost:8065&lt;/code&gt;，就會看到 Mattermost 的初始設定頁面。按照指示建立管理員帳號、設定團隊名稱，就能開始使用了。&lt;/p&gt;

&lt;h3&gt;
  
  
  停止服務
&lt;/h3&gt;

&lt;p&gt;當不需要使用時，可以執行：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這會停止並移除容器，但所有資料都保留在 volumes 目錄中，下次啟動時會自動載入。&lt;/p&gt;




&lt;h2&gt;
  
  
  切換介面語言為繁體中文
&lt;/h2&gt;

&lt;p&gt;預設安裝後，Mattermost 的介面是英文。如果想改成繁體中文，只需要簡單幾個步驟：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;點擊畫面右上角的齒輪圖示（Settings）&lt;/li&gt;
&lt;li&gt;在設定選單中找到「Display」（顯示設定）&lt;/li&gt;
&lt;li&gt;往下捲動到底部，會看到「Language」（語言）選項&lt;/li&gt;
&lt;li&gt;點擊「Edit」，從下拉選單中選擇「繁體中文（台灣）」&lt;/li&gt;
&lt;li&gt;點擊「Save」儲存&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;介面會立即切換成繁體中文，所有選單、按鈕、提示訊息都會變成中文顯示。&lt;/p&gt;




&lt;h2&gt;
  
  
  安裝擴充功能
&lt;/h2&gt;

&lt;p&gt;Mattermost 跟 LINE 很不同的地方，在於可以安裝另外的擴充功能。&lt;/p&gt;

&lt;p&gt;以下介紹兩個實用的擴充功能。&lt;/p&gt;

&lt;h3&gt;
  
  
  Focalboard
&lt;/h3&gt;

&lt;p&gt;Focalboard 是內建的看板工具，類似 Jira 或 Notion，可以用來管理專案任務、追蹤進度。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;安裝步驟：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;進到 Focalboard 的 &lt;a href="https://github.com/mattermost-community/focalboard/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt; 頁面。&lt;/li&gt;
&lt;li&gt;下載最新版本的檔案，因為 Docker 是 Linux，要選擇 mattermost-plugin-focalboard-vxxxx-linux-xxx.tar.gz 的檔案。&lt;/li&gt;
&lt;li&gt;Mattermost 點擊左上角的 Mattermost Logo，選擇「System Console」（系統控制台）。&lt;/li&gt;
&lt;li&gt;在左側選單找到「Plugins」（擴充程式）→「Plugin Management」（擴充程式管理）。&lt;/li&gt;
&lt;li&gt;在上傳擴充程式中，點擊「選擇檔案」，接著選擇剛剛下載的檔案後，點擊「上傳」。&lt;/li&gt;
&lt;li&gt;安裝完成後，左側選單會出現「Mattermost Boards」的選項，點擊進去，再啟用擴充程式功能。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;使用 Boards：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;回到主畫面，點擊左上角的 Mattermost Logo 會出現「Boards」圖示。點擊後可以建立新的看板。&lt;/p&gt;

&lt;p&gt;選擇範本（如待辦清單、專案追蹤、內容行事曆等），或從空白看板開始。&lt;/p&gt;

&lt;p&gt;看板支援卡片拖曳、自訂欄位、篩選排序等功能。&lt;/p&gt;

&lt;h3&gt;
  
  
  Todo
&lt;/h3&gt;

&lt;p&gt;Todo 外掛可以讓你在對話中直接建立待辦事項，並在側邊欄追蹤所有未完成的任務。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;安裝步驟：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;安裝方式跟上面的 Boards 相同。&lt;/p&gt;

&lt;p&gt;Todo 的 &lt;a href="https://github.com/mattermost-community/mattermost-plugin-todo/releases" rel="noopener noreferrer"&gt;GitHub Releases&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;使用 Todo：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在任何頻道或私訊中，右側會出現更多 Todo 的圖示（三個點 + 三條線）。&lt;/p&gt;

&lt;p&gt;點擊後選擇「Add Todo」。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;實用技巧：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;你可以在任何頻道輸入 &lt;code&gt;/todo add [任務內容]&lt;/code&gt; 快速建立待辦事項，不需要依附在特定訊息上。例如：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/todo add 下週一前完成專案提案
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  4 使用整合的機器人帳號
&lt;/h2&gt;

&lt;p&gt;機器人帳號可以讓 Mattermost 與其他系統整合，執行像是自動化通知等的訊息發送。&lt;/p&gt;

&lt;h3&gt;
  
  
  建立機器人帳號
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;步驟：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;進入「System Console」（系統控製台）→「Integrations」（整合）→「Bot Accounts」（機器人帳號）。&lt;/li&gt;
&lt;li&gt;點擊「Add Bot Account」（新增機器人帳號）。&lt;/li&gt;
&lt;li&gt;填寫機器人資訊：

&lt;ul&gt;
&lt;li&gt;Username：例如 &lt;code&gt;notification-bot&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bot Icon：上傳機器人的頭像圖片（選用）&lt;/li&gt;
&lt;li&gt;Display Name：顯示名稱，例如「通知機器人」&lt;/li&gt;
&lt;li&gt;Description：說明這個機器人的用途&lt;/li&gt;
&lt;li&gt;Role：選擇「Member」即可&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;點擊「Create Bot Account」&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;建立完成後，頁面會顯示一個 Token（存取權杖）。&lt;strong&gt;請務必複製並妥善保存這個 Token&lt;/strong&gt;，它只會顯示一次。如果遺失，需要重新產生新的 Token。&lt;/p&gt;

&lt;h3&gt;
  
  
  用 n8n 發送訊息
&lt;/h3&gt;

&lt;p&gt;n8n 是一個開源的工作流程自動化工具，可以串接各種服務。&lt;/p&gt;

&lt;p&gt;以下示範如何讓 n8n 透過機器人帳號發送訊息到 Mattermost。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;前置作業：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;首先需要在 Mattermost 建立一個接收訊息的頻道，例如「系統通知」。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;在 n8n 中設定：&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在 n8n 工作流程中新增「Mattermost」節點&lt;/li&gt;
&lt;li&gt;建立 Credential（憑證）：

&lt;ul&gt;
&lt;li&gt;Base URL：填入你的 Mattermost 網址，例如 &lt;code&gt;http://localhost:8065&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;Access Token：貼上前面複製的機器人 Token。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;設定要執行的動作：

&lt;ul&gt;
&lt;li&gt;Operation：選擇「Post Message」。&lt;/li&gt;
&lt;li&gt;Channel ID：下拉式選單選取。&lt;/li&gt;
&lt;li&gt;Message：輸入要發送的訊息內容。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;簡單的測試範例：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;建立一個「Schedule Trigger」節點，設定每天早上 9 點執行，連接到 Mattermost 節點發送「早安！今天也要加油」的訊息。這樣就完成了一個簡單的每日問候機器人。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;進階應用：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;你可以組合更多節點，例如：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;監控網站是否正常運作，異常時發送警告訊息&lt;/li&gt;
&lt;li&gt;從 Google Sheets 讀取資料，定期發送報表摘要&lt;/li&gt;
&lt;li&gt;串接 Webhook，當特定事件發生時自動通知團隊&lt;/li&gt;
&lt;li&gt;整合 RSS 閱讀器，自動分享新文章到頻道&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;機器人帳號讓 Mattermost 不只是通訊軟體，還能執行自動化流程的訊息中心。&lt;/p&gt;




&lt;h2&gt;
  
  
  結語
&lt;/h2&gt;

&lt;p&gt;透過 Docker Compose，我們可以在本機快速部署 Mattermost，擁有完整的團隊協作功能，同時保有資料的完全控制權。合理的儲存策略配置（本機設定、行動硬碟存檔）讓系統既快速又節省空間。&lt;/p&gt;

&lt;p&gt;搭配 Boards 和 Todo 等外掛，以及機器人帳號的自動化整合，Mattermost 可以滿足從團隊溝通到專案管理的各種需求。&lt;/p&gt;

&lt;p&gt;而且，以上這些功能都是免費且開源的，不用擔心訂閱費用或資料隱私問題。&lt;/p&gt;

&lt;p&gt;最後，August 其實也還用不到一個月，但覺得好用，又能保有隱私，所以先寫了這篇筆記，紀錄一下安裝、使用的過程。&lt;/p&gt;

&lt;p&gt;如果有更好的通訊軟體，歡迎留言提供。&lt;/p&gt;

</description>
      <category>mattermost</category>
      <category>docker</category>
      <category>selfhost</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
