<?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>告別逾期罰單！用 n8n 免費打造全台路邊停車費自動查詢 Telegram 機器人通知</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Tue, 15 Sep 2026 12:41:53 +0000</pubDate>
      <link>https://dev.to/letswrite/gao-bie-yu-qi-fa-dan-yong-n8n-mian-fei-da-zao-quan-tai-lu-bian-ting-che-fei-zi-dong-cha-xun-telegram-ji-qi-ren-tong-zhi-io3</link>
      <guid>https://dev.to/letswrite/gao-bie-yu-qi-fa-dan-yong-n8n-mian-fei-da-zao-quan-tai-lu-bian-ting-che-fei-zi-dong-cha-xun-telegram-ji-qi-ren-tong-zhi-io3</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;最近裝的一個 App，因為變成必須要付費訂閱，才能有自動繳停車費的功能。為了避免停車費過期而多收錢，所以讓 ChatGPT 寫一個簡單的 n8n Workflow，匯入後修改一下設定資料，就可以每天自動通知還有沒有未繳的停車費。&lt;/p&gt;

&lt;p&gt;August 希望做到以下幾件事：&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;一次查詢多個縣市。&lt;/li&gt;
&lt;li&gt;每天固定時間自動執行。&lt;/li&gt;
&lt;li&gt;有未繳停車費時，主動透過 Telegram 通知。&lt;/li&gt;
&lt;li&gt;某個縣市 API 發生錯誤時，也要告訴我，不能直接把它當成「沒有欠費」。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;而且，以上全部的需求，都可以放在 &lt;strong&gt;同一個 n8n Workflow&lt;/strong&gt; 裡，不需要汽車、機車各做一份。&lt;/p&gt;




&lt;h2&gt;
  
  
  Workflow JSON 下載、匯入
&lt;/h2&gt;

&lt;p&gt;直接複製以下 n8n Workflow，接著進入 n8n，直接貼上：&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;"全台路邊停車費自動查詢｜多車輛＋21縣市＋Telegram v4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nodes"&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;"parameters"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9d4d1385-782c-4c2d-9c50-5b1a80cf6d83"&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;"手動測試"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.manualTrigger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;-1160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;160&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;"parameters"&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;"rule"&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;"interval"&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;"field"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cronExpression"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"expression"&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 9 * * *"&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;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec576177-01d7-468f-8777-196e1656a71d"&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;"每天 09:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.scheduleTrigger"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;-1160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-40&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;"parameters"&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;"assignments"&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;"assignments"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4728964e-002f-4430-838c-6e44d2deda12"&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;"telegramChatId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"請改成你的 Telegram Chat ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"990ecf7e-feaa-4a91-9118-f378adb5d4d9"&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;"notifyWhenClear"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c69ea629-43b8-466a-bbb0-a5610d2d83e7"&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;"vehiclesJson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;我的汽車&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;carId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;ABC-1234&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;carType&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;C&lt;/span&gt;&lt;span class="se"&gt;\"\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;label&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;我的機車&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;carId&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;XYZ-5678&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;carType&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;M&lt;/span&gt;&lt;span class="se"&gt;\"\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fc5463ba-96f0-49a6-a280-3863bdd345bc"&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;"citiesJson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;臺北市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://trafficapi.pma.gov.taipei/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;新北市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://trafficapi.traffic.ntpc.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;桃園市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://bill-epark.tycg.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;臺中市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;http://tcparkingapi.taichung.gov.tw:8081/NationalParkingPayBillInquiry.Api/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;note&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;來源未涵蓋：尚未確認此 :8081 API 是否支援 HTTPS，因此暫保留原 HTTP endpoint。&lt;/span&gt;&lt;span class="se"&gt;\"\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;臺南市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://parkingbill.tainan.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;高雄市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://kpp.tbkc.gov.tw/parking/V1/parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;基隆市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://park.klcg.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;新竹市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://his.futek.com.tw:5443/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;新竹縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://hcpark.hchg.gov.tw/NationalParkingPayBillInquiry/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;苗栗縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://miaoliparking.jotangi.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;彰化縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://chpark.chcg.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;南投縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://parking.nantou.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;雲林縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://parking.yunlin.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;嘉義縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://8voc0wuf1g.execute-api.ap-southeast-1.amazonaws.com/default/chiayi/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;嘉義市&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://parking.chiayi.gov.tw/cypark/api/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;屏東縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://8voc0wuf1g.execute-api.ap-southeast-1.amazonaws.com/default/pingtung/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;宜蘭縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://billparking.e-land.gov.tw/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;花蓮縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://hl.parchere.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;臺東縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://tt.guoyun.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;澎湖縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://parking.penghu.gov.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  },&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;金門縣&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;urlTemplate&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://km.guoyun.com.tw/TrafficPayBill/Parking/PayBill/CarID/{CarID}/CarType/{CarType}&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;enabled&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;"options"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cfc69392-3221-4479-b078-437991a0b749"&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;"設定（只改這裡）"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.set"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;-920&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;"parameters"&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runOnceForAllItems"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jsCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"const cfg = $input.first().json;&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;function parseJsonField(value, fieldName) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  if (Array.isArray(value)) return value;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  try {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    return JSON.parse(value);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  } catch (e) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    throw new Error(`${fieldName} 不是合法 JSON：${e.message}`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const vehicles = parseJsonField(cfg.vehiclesJson, 'vehiclesJson');&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const cities = parseJsonField(cfg.citiesJson, 'citiesJson');&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;if (!Array.isArray(vehicles) || vehicles.length === 0) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  throw new Error('vehiclesJson 至少要有一台車。');&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const enabledCities = Array.isArray(cities)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  ? cities.filter(c =&amp;gt; c?.enabled !== false &amp;amp;&amp;amp; c?.name &amp;amp;&amp;amp; c?.urlTemplate)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  : [];&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;if (enabledCities.length === 0) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  throw new Error('citiesJson 至少要啟用一個縣市。');&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const allowedTypes = new Set(['C', 'M', 'O']);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const output = [];&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;for (const vehicle of vehicles) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  const carId = String(vehicle?.carId ?? '').trim();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  const carType = String(vehicle?.carType ?? '').trim().toUpperCase();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  const label = String(vehicle?.label ?? carId).trim();&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;  if (!carId) throw new Error(`車輛缺少 carId：${JSON.stringify(vehicle)}`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  if (!allowedTypes.has(carType)) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    throw new Error(`${carId} 的 carType 必須是 C、M 或 O，目前為：${carType}`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;  for (const city of enabledCities) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    const queryUrl = String(city.urlTemplate)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      .replace('{CarID}', encodeURIComponent(carId))&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      .replace('{CarType}', encodeURIComponent(carType));&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;    output.push({&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      json: {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        label,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        carId,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        carType,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        cityName: city.name,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        queryUrl,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        telegramChatId: cfg.telegramChatId ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        notifyWhenClear: Boolean(cfg.notifyWhenClear)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    });&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;return output;"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fe4cf409-253c-453c-bb87-34849dfcc386"&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;"建立 車輛 × 縣市 查詢清單"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;-680&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;"parameters"&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;"batchSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"da0e1f61-1d54-4afb-82af-752252f5c50f"&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;"Loop Over Items（每次 1 筆）"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.splitInBatches"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;-420&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;"parameters"&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;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET"&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;"={{ $json.queryUrl }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"response"&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;"response"&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;"fullResponse"&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;"neverError"&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;"responseFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"json"&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;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10000&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0707ce96-44ea-4630-a5fd-184d25e128bc"&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;"查詢停車費 API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.httpRequest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;4.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;-160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"onError"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"continueRegularOutput"&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;"parameters"&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runOnceForEachItem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jsCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"const row = $json ?? {};&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const base = {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  cityName: row.cityName ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  label: row.label ?? row.carId ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  carId: row.carId ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  carType: row.carType ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  queryUrl: row.queryUrl ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  telegramChatId: row.telegramChatId ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  notifyWhenClear: Boolean(row.notifyWhenClear)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;};&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;// Network/TLS/DNS errors continued by the HTTP Request node arrive as `error`.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;if (row.error) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  return {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    json: {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      ...base,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      status: 'ERROR',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      apiMessage: row.error.message || String(row.error),&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      httpStatus: null,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      hasUnpaid: false,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      totalCount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      totalAmount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      details: [],&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      isQueryFailed: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  };&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const httpStatus = Number(row.statusCode ?? 0);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;let res = row.body ?? {};&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;if (typeof res === 'string') {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  try {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    res = JSON.parse(res);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  } catch {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    return {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      json: {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        ...base,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        status: 'ERROR',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        apiMessage: `API 回傳非 JSON 內容${httpStatus ? `（HTTP ${httpStatus}）` : ''}`,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        httpStatus: httpStatus || null,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        hasUnpaid: false,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        totalCount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        totalAmount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        details: [],&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        isQueryFailed: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    };&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;if (httpStatus &amp;amp;&amp;amp; (httpStatus &amp;lt; 200 || httpStatus &amp;gt;= 300)) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  return {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    json: {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      ...base,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      status: 'ERROR',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      apiMessage:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        res?.Message ||&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        res?.message ||&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        `HTTP ${httpStatus}`,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      httpStatus,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      hasUnpaid: false,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      totalCount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      totalAmount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      details: [],&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      isQueryFailed: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  };&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const status = String(res?.Status ?? '').toUpperCase();&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const apiMessage = String(res?.Message ?? '');&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;let result = res?.Result ?? null;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;if (typeof result === 'string' &amp;amp;&amp;amp; result.trim()) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  try {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    result = JSON.parse(result);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  } catch {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    return {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      json: {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        ...base,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        status: 'ERROR',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        apiMessage: 'Result 欄位不是合法 JSON',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        httpStatus: httpStatus || null,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        hasUnpaid: false,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        totalCount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        totalAmount: 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        details: [],&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        isQueryFailed: true&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    };&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const bills = Array.isArray(result?.Bills) ? result.Bills : [];&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const reminders = Array.isArray(result?.Reminders) ? result.Reminders : [];&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const details = [];&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;for (const bill of bills) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  details.push({&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    kind: '停車單',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    amount: Number(bill?.PayAmount ?? bill?.Amount ?? 0),&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    parkingDate: bill?.ParkingDate ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    dueDate: bill?.PayLimitDate ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    billNo: bill?.BillNo ?? ''&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  });&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;for (const reminder of reminders) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  details.push({&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    kind: '催繳單',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    amount: Number(&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      reminder?.PayAmount ??&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      (Number(reminder?.Amount ?? 0) + Number(reminder?.ExtraCharge ?? 0))&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    ),&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    parkingDate: reminder?.ParkingDate ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    dueDate: reminder?.ReminderLimitDate ?? reminder?.PayLimitDate ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    billNo: reminder?.ReminderNo ?? reminder?.BillNo ?? ''&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  });&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const totalCount = Number(result?.TotalCount ?? details.length ?? 0);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const computedAmount = details.reduce((sum, d) =&amp;gt; sum + Number(d.amount || 0), 0);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const totalAmount = Number(result?.TotalAmount ?? computedAmount);&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const success = status === 'SUCCESS';&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;return {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  json: {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    ...base,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    status,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    apiMessage,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    httpStatus: httpStatus || null,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    hasUnpaid: success &amp;amp;&amp;amp; !!result &amp;amp;&amp;amp; (totalCount &amp;gt; 0 || details.length &amp;gt; 0),&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    totalCount,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    totalAmount,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    details,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    isQueryFailed: !success&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0a7b65f9-6d37-49d4-9fd4-f4f749f73c46"&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;"整理單筆結果"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&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;"parameters"&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;"amount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"seconds"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aa5bc5ca-c35c-4f8a-97b4-1b29fef023a8"&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;"等待 1 秒（API 節流）"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.wait"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"webhookId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"61c9dc0a-3b6d-49d5-b022-e6a9054e3dd3"&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;"parameters"&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"runOnceForAllItems"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"jsCode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"const rows = $input.all().map(i =&amp;gt; i.json);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const first = rows[0] ?? {};&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const unpaidRows = rows.filter(r =&amp;gt; r.hasUnpaid);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const failedRows = rows.filter(r =&amp;gt; r.isQueryFailed);&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const totalCount = unpaidRows.reduce((s, r) =&amp;gt; s + Number(r.totalCount || 0), 0);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;const totalAmount = unpaidRows.reduce((s, r) =&amp;gt; s + Number(r.totalAmount || 0), 0);&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const lines = [];&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;if (unpaidRows.length) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  lines.push('🚗 路邊停車費提醒', '');&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;  for (const row of unpaidRows) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    const vehicleIcon = row.carType === 'M' ? '🛵' : '🚙';&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    lines.push(`${vehicleIcon}【${row.label}｜${row.carId}】`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    lines.push(`${row.cityName}：${row.totalCount} 筆，合計 $${row.totalAmount}`);&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;    for (const d of row.details.slice(0, 10)) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      const parts = [&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        d.kind,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        `$${d.amount}`,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        d.parkingDate ? `停車日 ${d.parkingDate}` : null,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;        d.dueDate ? `期限 ${d.dueDate}` : null&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      ].filter(Boolean);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      lines.push(`- ${parts.join('｜')}`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    }&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;    if (row.details.length &amp;gt; 10) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;      lines.push(`- 另有 ${row.details.length - 10} 筆未列出`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    lines.push('');&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;  lines.push(`總計：${totalCount} 筆，$${totalAmount}`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;} else {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  lines.push('✅ 目前沒有查到待繳路邊停車費');&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;if (failedRows.length) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  lines.push('', `⚠️ 有 ${failedRows.length} 個查詢失敗，這些縣市「不能視為沒有欠費」：`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  for (const row of failedRows.slice(0, 20)) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    lines.push(`- ${row.label}｜${row.cityName}｜${row.apiMessage || row.status || '未知錯誤'}`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  if (failedRows.length &amp;gt; 20) {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    lines.push(`- 另有 ${failedRows.length - 20} 個失敗未列出`);&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;const shouldNotify =&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  unpaidRows.length &amp;gt; 0 ||&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  failedRows.length &amp;gt; 0 ||&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  Boolean(first.notifyWhenClear);&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;return [{&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  json: {&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    telegramChatId: first.telegramChatId ?? '',&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    shouldNotify,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    hasUnpaid: unpaidRows.length &amp;gt; 0,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    totalCount,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    totalAmount,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    failedQueries: failedRows.length,&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;    message: lines.join('&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n')&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;  }&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"4b6dabed-b0b9-4e24-bd69-249d9cac4bbf"&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;"彙總全部結果"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.code"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;-160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-120&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;"parameters"&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;"conditions"&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;"options"&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;"caseSensitive"&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;"leftValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"typeValidation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&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;"conditions"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"60315e8a-26c6-47fe-b311-0864f215d707"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"leftValue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"={{ $json.shouldNotify }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"rightValue"&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;"operator"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"operation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"singleValue"&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;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"combinator"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"and"&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;"options"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c44271f5-b4ec-4a02-bd5a-37e9b53cc1ab"&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;"需要通知？"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.if"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-120&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;"parameters"&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;"chatId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"={{ $json.telegramChatId }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"={{ $json.message }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"additionalFields"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"427558ca-fc5c-4d35-aa31-3e1d2286e556"&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;"Telegram 通知"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.telegram"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;320&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-160&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;"parameters"&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;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"combine"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"combineBy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"combineByPosition"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"options"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e11fc337-e4bd-406f-9f9b-9cebc88e6e96"&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;"合併查詢參數與 HTTP 回應"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"n8n-nodes-base.merge"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"typeVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"position"&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="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;120&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;"connections"&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;"手動測試"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&gt;"每天 09:00"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Loop Over Items（每次 1 筆）"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&gt;"Loop Over Items（每次 1 筆）"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"查詢停車費 API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"合併查詢參數與 HTTP 回應"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&gt;"查詢停車費 API"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"合併查詢參數與 HTTP 回應"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;span class="nl"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"等待 1 秒（API 節流）"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&gt;"等待 1 秒（API 節流）"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Loop Over Items（每次 1 筆）"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="nl"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Telegram 通知"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"合併查詢參數與 HTTP 回應"&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;"main"&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;span class="nl"&gt;"node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pinData"&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;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"settings"&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;"executionOrder"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"v1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Asia/Taipei"&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;"versionId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1aa3b0b1-0acf-40f7-b71c-03a93d7beb1a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"meta"&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;"templateCredsSetupCompleted"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;"tags"&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;如果是想用匯入的方式，就把上面的程式碼複製後存成 JSON，進入 n8n：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Workflows
→ Import from File
→ 選擇剛才下載的 JSON
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;不管是用複製貼上的，還是匯入，之後只需要修改 &lt;strong&gt;「設定（只改這裡）」&lt;/strong&gt; 這個 Node 就可以，說明如下。&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 plaintext"&gt;&lt;code&gt;vehiclesJson
&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 json"&gt;&lt;code&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;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"carId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ABC-1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"carType"&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="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;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"carId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XYZ-5678"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"carType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M"&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;&lt;code&gt;carType&lt;/code&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;C&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;汽車&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;M&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;機車&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;O&lt;/code&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;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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"carId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ABC-1234"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"carType"&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="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;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"carId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"XYZ-5678"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"carType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M"&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;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"carId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AAA-8888"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"carType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"M"&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;h3&gt;
  
  
  Telegram 設定
&lt;/h3&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;telegramChatId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;填入自己的 Telegram Chat ID。&lt;/p&gt;

&lt;p&gt;如果不知道自己的 Telegram Chat ID 是多少，只需要加入這個帳號，進行對話後就會看得到了：&lt;a href="https://t.me/userinfobot" rel="noopener noreferrer"&gt;User Info&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;Telegram 通知
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;設定自己的 Telegram Bot Credential。&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;notifyWhenClear = false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這樣平常沒有欠費就完全不打擾。&lt;/p&gt;

&lt;p&gt;如果沒有用 Telegram，或是想接收別的通知，那只需要把上面 Workflow 的 JSON，貼給 ChatGPT，請它改成你習慣的通知方式即可。&lt;/p&gt;




&lt;h2&gt;
  
  
  n8n Workflow 說明
&lt;/h2&gt;

&lt;p&gt;這段是詳細說明上面那段 JSON 的各個部份，是在做什麼的。&lt;/p&gt;

&lt;p&gt;整個 Workflow 最主要可以分成幾個部分。&lt;/p&gt;

&lt;h3&gt;
  
  
  每天自動執行
&lt;/h3&gt;

&lt;p&gt;Workflow 預設：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;每天 09:00
&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;手動測試
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;剛設定完成時，可以先按手動執行確認是否正常。&lt;/p&gt;




&lt;h3&gt;
  
  
  設定車輛與縣市
&lt;/h3&gt;

&lt;p&gt;「設定（只改這裡）」Node 集中放：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;telegramChatId
notifyWhenClear
vehiclesJson
citiesJson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;所以日後新增汽車、機車，基本上只需要改這一個地方。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;citiesJson&lt;/code&gt; 則放各縣市停車費查詢端點。&lt;/p&gt;

&lt;p&gt;目前這份 Workflow 已經預先放入 21 個縣市的設定；如果某個 API 日後有更換網址，也只需要修改這裡，不必重新改整個流程。&lt;/p&gt;

&lt;h3&gt;
  
  
  建立查詢清單
&lt;/h3&gt;

&lt;p&gt;接下來的 Code Node 會把：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;車輛
×
縣市
&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;我的汽車 → 臺北市
我的汽車 → 新北市
我的汽車 → 桃園市

我的機車 → 臺北市
我的機車 → 新北市
我的機車 → 桃園市
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;所以新增車輛時，不需要增加新的 HTTP Request Node。&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 plaintext"&gt;&lt;code&gt;Loop Over Items
&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;查詢 API
↓
整理結果
↓
等待 1 秒
↓
下一筆
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;August 刻意加上 &lt;strong&gt;等待 1 秒&lt;/strong&gt;，避免短時間內大量呼叫各縣市 API。&lt;/p&gt;

&lt;h3&gt;
  
  
  整理待繳停車費
&lt;/h3&gt;

&lt;p&gt;每一個縣市回傳結果之後，Workflow 會統一整理成：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;縣市
車牌
車種
待繳筆數
待繳金額
停車日期
繳費期限
停車單 / 催繳單
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;所以即使不同縣市的資料來源不同，後面的通知流程仍然可以使用相同格式。&lt;/p&gt;

&lt;h3&gt;
  
  
  最後統一通知
&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 plaintext"&gt;&lt;code&gt;🚗 路邊停車費提醒

🚙【我的汽車｜ABC-1234】
臺北市：2 筆，合計 $80
- 停車單｜$40｜停車日 2026-09-10｜期限 2026-09-25
- 停車單｜$40｜停車日 2026-09-11｜期限 2026-09-26

🛵【我的機車｜XYZ-5678】
新北市：1 筆，合計 $20
- 停車單｜$20｜停車日 2026-09-12｜期限 2026-09-27

總計：3 筆，$100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這樣不用收到幾十則 Telegram 訊息，而是每天最多收到一份整理好的結果。&lt;/p&gt;

&lt;p&gt;另外，如果某個 API 查詢失敗，也會另外標示：&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⚠️ 有 2 個查詢失敗，
這些縣市不能視為沒有欠費：

- 我的汽車｜某縣市｜查詢失敗
- 我的機車｜某縣市｜Timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;這點很重要，因為「API 查不到」跟「沒有停車費」是兩件完全不同的事情。&lt;/p&gt;

&lt;p&gt;最後把 Workflow 啟用後，基本上就不用再管它了。&lt;/p&gt;

&lt;p&gt;之後只要有未繳的路邊停車費，就會自動收到 Telegram 通知。&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>telegram</category>
      <category>api</category>
      <category>auto</category>
    </item>
    <item>
      <title>用 Chrome 擴充 + 自架 n8n，讓 AI 幫你整理 Google Chat 對話與自動產生 Jira 任務</title>
      <dc:creator>Let's Write</dc:creator>
      <pubDate>Fri, 11 Sep 2026 10:09:28 +0000</pubDate>
      <link>https://dev.to/letswrite/yong-chrome-kuo-chong-zi-jia-n8nrang-ai-bang-ni-zheng-li-google-chat-dui-hua-yu-zi-dong-chan-sheng-jira-ren-wu-1c4m</link>
      <guid>https://dev.to/letswrite/yong-chrome-kuo-chong-zi-jia-n8nrang-ai-bang-ni-zheng-li-google-chat-dui-hua-yu-zi-dong-chan-sheng-jira-ren-wu-1c4m</guid>
      <description>&lt;h2&gt;
  
  
  本篇要解決的問題
&lt;/h2&gt;

&lt;p&gt;如果想把工作、私人生活切開來，在公司的通訊軟體就不能還是在用 LINE，像之前有寫過 &lt;a href="https://www.letswrite.tw/mattermost/" rel="noopener noreferrer"&gt;Mattermost&lt;/a&gt; 這個通訊軟體。&lt;/p&gt;

&lt;p&gt;最近處理工作的事情，團隊選擇的是 Google Chat，用了一陣子後才發現，因為是免費版，沒有 Google Chat App、Chat API，也不能裝 Chat Bot，等於想在 Chat 使用 AI 是被封印的事。&lt;/p&gt;

&lt;p&gt;可是總會有要讓 AI 幫忙整理討論或是撈舊訊息的時候，跟 ChatGPT 討論後，發現只剩下直接從瀏覽器下手這方法。&lt;/p&gt;

&lt;p&gt;於是就有了這篇的 Let's Chat AI 擴充功能，直接讀畫面上的聊天內容，再送去 AI，最後把 AI 處理後的結果顯示出來。&lt;/p&gt;

&lt;p&gt;本篇使用的擴充功能：&lt;a href="https://chromewebstore.google.com/detail/lets-chat-ai/cjhllfpcohiiclodggnfemcchidlgaaj" rel="noopener noreferrer"&gt;Let's Chat AI&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Chat AI 是什麼？
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Let's Chat AI&lt;/strong&gt; 是一個 Chrome 擴充。&lt;/p&gt;

&lt;p&gt;我們在 Google Chat 裡開著某個聊天室，點一下按鈕，它就把「螢幕上已經載入的訊息」送去整理，幾秒後回給你：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;討論摘要，如重點、決策、工作項目、待確認項目。&lt;/li&gt;
&lt;li&gt;工作項目清單，如誰負責什麼。&lt;/li&gt;
&lt;li&gt;Jira 議題草稿，可一鍵複製成純文字貼進 Jira。&lt;/li&gt;
&lt;li&gt;自己打一句指示，如「把上週跟晶片掃描器有關的討論整理出來」「列出 Amy 提到的待辦」。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;簡單來說，就是給用免費版 Google Chat，沒有 Chat API 可用的小團隊。&lt;/p&gt;

&lt;h3&gt;
  
  
  怎麼運作
&lt;/h3&gt;



&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
  A["Google Chat&amp;lt;br/&amp;gt;你點一下「AI」"] --&amp;gt;|送出已載入的訊息| B["你自己的 n8n&amp;lt;br/&amp;gt;＋ 你的 AI 金鑰"]
  B --&amp;gt;|帶金鑰呼叫| C["AI&amp;lt;br/&amp;gt;Gemini / OpenAI"]
  C --&amp;gt;|回結構化結果| B
  B --&amp;gt;|整理好的內容| D["擴充面板&amp;lt;br/&amp;gt;看結果 / 複製"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;中間站是「我們自己的 n8n」，n8n 是一個開源的自動化工具，提供自架版本。&lt;/p&gt;

&lt;p&gt;AI 的金鑰只放在 n8n 裡，擴充本身不儲存任何金鑰。&lt;/p&gt;

&lt;p&gt;因此換 AI 供應商只要改 n8n。&lt;/p&gt;

&lt;h3&gt;
  
  
  關於隱私
&lt;/h3&gt;

&lt;p&gt;只有在主動按功能鍵、看到「即將送出 N 則訊息」確認畫面、按下送出之後，訊息才會離開瀏覽器。&lt;/p&gt;

&lt;p&gt;不會背景監聽、不會自動上傳、不會保存聊天紀錄。解析結果只存在記憶體，關掉面板或切換聊天室就丟掉。&lt;/p&gt;

&lt;p&gt;擴充的本機設定只有三樣：webhook 網址、擷取則數上限、debug 開關。&lt;/p&gt;

&lt;p&gt;只在 &lt;code&gt;chat.google.com&lt;/code&gt; 上運作，權限只要 &lt;code&gt;storage&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;連 n8n 的網域權限是在設定時才執行請求的。&lt;/p&gt;




&lt;h3&gt;
  
  
  能做什麼
&lt;/h3&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;產生 Jira Draft&lt;/td&gt;
&lt;td&gt;Title / Description / Acceptance Criteria / Technical Notes / Pending Questions，一鍵複製純文字&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自由指示&lt;/td&gt;
&lt;td&gt;面板上直接打字，讓 AI 依指示撈 / 比對 / 整理已載入的訊息&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自動往回載入&lt;/td&gt;
&lt;td&gt;按下功能鍵時，自動把訊息串往上捲、載入較早的訊息（則數上限預設 50，面板或選項都能改，10–500）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;可拖拉的按鈕&lt;/td&gt;
&lt;td&gt;右下角的 AI 按鈕可以拖到順手的位置，位置會記住&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  怎麼用
&lt;/h2&gt;

&lt;h3&gt;
  
  
  一、安裝擴充
&lt;/h3&gt;

&lt;p&gt;在 Google 擴充功能商店裡直接進行安裝：&lt;/p&gt;

&lt;p&gt;&lt;a href="https://chromewebstore.google.com/detail/lets-chat-ai/cjhllfpcohiiclodggnfemcchidlgaaj" rel="noopener noreferrer"&gt;Let's Chat AI&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  二、接上你自己的 n8n（五步）
&lt;/h3&gt;

&lt;p&gt;GitHub 資料夾裡有兩份可直接匯入的設定檔，自行選要用 Gemini 還是 OpenAI 的 API：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/letswritetw/extension-lets-chat-ai/blob/master/n8n/google-chat-ai.gemini.workflow.json" rel="noopener noreferrer"&gt;google-chat-ai.gemini.workflow.json&lt;/a&gt;（Google Gemini）&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/letswritetw/extension-lets-chat-ai/blob/master/n8n/google-chat-ai.openai.workflow.json" rel="noopener noreferrer"&gt;google-chat-ai.openai.workflow.json&lt;/a&gt;（OpenAI）&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Gemini API 有免費額度可以使用，建議先用 Gemini 來測試這擴充功能有沒有滿足你的需求。&lt;/p&gt;

&lt;h4&gt;
  
  
  1. 拿一把自己的 AI 金鑰
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Gemini：到 &lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI Studio&lt;/a&gt; 按 Create API key。&lt;/li&gt;
&lt;li&gt;OpenAI：到 &lt;a href="https://platform.openai.com/api-keys" rel="noopener noreferrer"&gt;platform.openai.com&lt;/a&gt; 建立 secret key（&lt;code&gt;sk-…&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  2. 把設定檔匯入 n8n
&lt;/h4&gt;

&lt;p&gt;n8n 左上角 &lt;strong&gt;＋ → Import from File…&lt;/strong&gt;，選對應的 &lt;code&gt;*.workflow.json&lt;/code&gt;。&lt;br&gt;
匯入後畫面上有一張黃色便利貼，寫著這份的重點，忘記可以回去看。&lt;/p&gt;

&lt;h4&gt;
  
  
  3. 把金鑰貼進去
&lt;/h4&gt;

&lt;p&gt;打開 &lt;strong&gt;Call Gemini&lt;/strong&gt;（或 &lt;strong&gt;Call OpenAI&lt;/strong&gt;）節點 → Credential → &lt;strong&gt;Create New&lt;/strong&gt;，選 Header Auth：&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;Gemini&lt;/th&gt;
&lt;th&gt;OpenAI&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;code&gt;X-goog-api-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Authorization&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;值&lt;/td&gt;
&lt;td&gt;你的 Gemini token&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Bearer sk-…&lt;/code&gt;（&lt;code&gt;Bearer&lt;/code&gt; 後面有一個空格）&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  4. 打開開關
&lt;/h4&gt;

&lt;p&gt;workflow 右上角點擊 &lt;strong&gt;Publish&lt;/strong&gt;。&lt;/p&gt;

&lt;h4&gt;
  
  
  5. 複製網址，貼進擴充
&lt;/h4&gt;

&lt;p&gt;打開 &lt;strong&gt;Webhook&lt;/strong&gt; 節點，複製 &lt;strong&gt;Production URL&lt;/strong&gt;（長得像 &lt;code&gt;https://你的n8n/webhook/google-chat-ai&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;Chrome 工具列的擴充圖示 → &lt;strong&gt;選項&lt;/strong&gt; → 貼進「n8n Webhook URL」→ 儲存（會跳權限請求，允許）→ 按&lt;strong&gt;測試連線&lt;/strong&gt;，看到「連線成功」就完成了。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;公司內網的 n8n 只有 http 沒有 https？&lt;/strong&gt; 可以。擴充接受 &lt;code&gt;http://&lt;/code&gt;，只是不加密，&lt;br&gt;
所以只適合沒有對外、在信任的內網裡的 n8n。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  三、開始整理
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;打開 Google Chat，進入某個 Space 或 DM。需要更早的訊息可以先手動往上捲一點。&lt;/li&gt;
&lt;li&gt;點 &lt;strong&gt;Let's Chat AI&lt;/strong&gt; 的按鈕開啟面板。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;打一句指示&lt;/strong&gt;，或選&lt;strong&gt;整理討論 / 整理工作項目 / 產生 Jira Draft&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;確認「即將送出 N 則訊息」→ 送出。&lt;/li&gt;
&lt;li&gt;看結果。Jira Draft 可按「複製 Jira Draft」拿純文字。&lt;/li&gt;
&lt;/ol&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;Gemini 版&lt;/th&gt;
&lt;th&gt;OpenAI 版&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;code&gt;google-chat-ai.gemini.workflow.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;google-chat-ai.openai.workflow.json&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;呼叫的 API&lt;/td&gt;
&lt;td&gt;&lt;code&gt;generativelanguage.googleapis.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;api.openai.com/v1/chat/completions&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;預設模型&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemini-3.5-flash-lite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt-5.6-luna&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;金鑰 header&lt;/td&gt;
&lt;td&gt;&lt;code&gt;X-goog-api-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Authorization: Bearer sk-…&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;其餘節點（&lt;code&gt;Webhook → Prepare Prompt → Call → Shape Result → Respond&lt;/code&gt;）完全一樣。&lt;/p&gt;

&lt;p&gt;想改整理的口氣，改 &lt;strong&gt;Prepare Prompt&lt;/strong&gt; 節點裡的 prompt 就好，別動最後 &lt;strong&gt;Respond&lt;/strong&gt; 回的&lt;br&gt;
&lt;code&gt;{ "success": true, "result": { … } }&lt;/code&gt; 形狀，那是擴充在讀的。&lt;/p&gt;




&lt;h2&gt;
  
  
  原始碼
&lt;/h2&gt;

&lt;p&gt;Let's Chat AI 擴充功能：&lt;a href="https://chromewebstore.google.com/detail/lets-chat-ai/cjhllfpcohiiclodggnfemcchidlgaaj" rel="noopener noreferrer"&gt;https://chromewebstore.google.com/detail/lets-chat-ai/cjhllfpcohiiclodggnfemcchidlgaaj&lt;/a&gt;。&lt;/p&gt;

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

&lt;p&gt;請大家多多支持，安裝來用、推薦給朋友、回報問題、給個評價都好，你的一個小小動作，對本站都是大大的鼓勵。&lt;/p&gt;

</description>
      <category>n8n</category>
      <category>googlechat</category>
      <category>openai</category>
      <category>gemini</category>
    </item>
    <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>
  </channel>
</rss>
