🎯 現場導入專用素材手冊 🚀 課前手機+筆電前置準備 💬 通訊首選 Discord 行動走動管理 📅 2026-08-19(三)19:00–22:30 📍 Block & Cafe(太原路50號2樓)

Hermes Agent 現場導入教學手冊

為飯店營運管理者鶴仁量身打造:告別十幾個混亂的 ChatGPT 對話串,建立筆電端 Hermes Agent 指揮官 + Obsidian 結構化第二大腦 + Google 雲端磁碟同步 + Discord 手機語音走動管理的完整自主助理系統。

2 大
課前必備安裝清單(手機+筆電)
14 份
現場精編教材與設定檔
4 支
ChatGPT 匯出與轉檔工具
7 段
現場手把手實作演練時程
課前準備 | 前置清單 現場開工前必看

課前必備:手機 App 與筆電軟體安裝清單

8/19 現場課程前置準備!請先依清單完成 iPhone 17 與 Win11 筆電的軟體與擴充安裝。點擊直連按鈕即可一鍵跳轉官方安裝頁面,並請留意 DriveSync 與 OpenCode 的避坑替代說明。

📱 台灣 App Store 直連連結 | 2026-08-18 查證
必裝 5 個(🔒)+ 選裝 3 個 + 2 個原訂項目陷阱與替代方案。點擊右側按鈕可直接跳轉 App Store 安裝。

✅ 必裝 5 大 App(🔒)

# App 名稱 開發商 / 評價 用途說明 直連安裝
1 Obsidian Dynalist Inc. ★ 4.6 第二大腦:開起 Google Drive 同步的 vault,隨身查閱飯店管理SOP與知識庫 直連 App Store ↗
2 Google 雲端硬碟 Google ★ 4.8 雲端同步中心:登入鶴仁帳號,同步 Obsidian 資料夾與營運報表 直連 App Store ↗
3 Discord Discord Inc. ★ 4.8 走動管理首選:語音/文字隨時交辦 Hermes,接收晨報與晚結算定時推播 直連 App Store ↗
4 Google 日曆 Google ★ 4.6 行程管理:確認 8/19 教學場次與飯店籌備重大 Milestone 行程 直連 App Store ↗
5 Google Tasks Google ★ 4.9 待辦清單:登入同一 Google 帳號,與日曆無縫同步每日行動事項 直連 App Store ↗

🔹 選裝 3 大 App(有助日常工作)

# App 名稱 開發商 / 評價 用途說明 直連安裝
6 GitHub GitHub ★ 4.9 隨時查看教學 repo、Prompt 庫與版本紀錄 直連 App Store ↗
7 WPS Office 金山 ★ 4.7 手機端檢視與快速編輯 Word、Excel 報表與 PPT 簡報 直連 App Store ↗
8 Gemini Google ★ 4.8 手機端 Google 原生 AI 助手,快速查詢與靈感發想 直連 App Store ↗
⚠️ 兩個原訂清單項目有陷阱(重要避坑指引)
❌ DriveSync → 請用替代方案

iOS 沒有 DriveSync 官方版(App Store 搜尋到的「DriveSync」是英國駕訓班排課軟體,完全無關,請勿下載!)。

✅ 最佳替代:Google 雲端硬碟 App 內建「離線存取」(將常用檔案設為可離線)。
💡 或加裝免費強大的 Documents by Readdle(可直連 Google Drive,檔案管理極佳):
❌ OpenCode → 手機端不需要安裝

iOS 沒有 OpenCode 官方 App(App Store 同名 App 為第三方、0 評分,請勿安裝!)。

✅ 原理解析:OpenCode 是 Hermes Agent 的後端模型供應來源(由山姆大叔提供 API 轉發),手機端完全不需要安裝。
📱 手機端與 AI 互動:使用 Gemini App 或 Discord(Hermes bot) 即可享有頂級 AI 特助體驗。

📌 手機端安裝後首步設定(5 步驟)

  1. Obsidian:登入後開啟 vault(選取 Google 雲端硬碟同步的那個資料夾)
  2. Google 雲端硬碟:登入鶴仁的 Google 帳號 ➔ 確認 Obsidian 資料夾同步正常
  3. Discord:登入 ➔ 加入「鶴仁營運助理」伺服器 ➔ 確認 Hermes bot 在線
  4. Google 日曆:確認 8/19 教學場次已匯入(一鍵連結見 08_Google日曆匯入)
  5. Google Tasks:登入同一 Google 帳號(待辦與日曆自動聯動)

🚀 安裝順序建議(教學當晚)

1
先裝必裝 5 個
Obsidian ➔ Google Drive ➔ Discord ➔ Google 日曆 ➔ Google Tasks
2
再裝選裝 3 個
GitHub ➔ WPS Office ➔ Gemini App
3
避坑提醒確認
DriveSync 與 OpenCode 不需安裝,確認離線存取與 Discord bot 在線
💻 Windows 11 家用版・鶴仁版 | 下載連結可直接點擊
必裝 6 項軟體 + Chrome 擴充(3 必裝 + 3 選配)+ 選配與避坑指引。

✅ 筆電必裝 6 大核心軟體

# 軟體名稱 用途說明 直連下載
1 Chrome 瀏覽器 擴充插件都掛在 Chrome 上(若已安裝可跳過) 官方下載 ↗
2 Git for Windows Hermes Agent 安裝前置必備(含 git-bash 命令列終端環境) 官方下載 ↗
3 Hermes Agent 核心 AI Agent(本地主腦,安裝教學見下方 Module 4) 跳轉安裝教學 ↓
4 Obsidian 第二大腦:知識庫與對話沉澱(vault 同步 Google Drive) 官方下載 ↗
5 Google 雲端硬碟(桌面版) Obsidian vault 本地同步 + 飯店重要檔案雲端備份 官方下載 ↗
6 Discord(桌機版) 與 Hermes Agent 交辦 & 接收排程推播(與手機端無縫同步) 官方下載 ↗

🧩 Chrome 擴充插件配置(細項)

🌟 必裝 3 大擴充套件
必備神器
  • 1. Obsidian Web Clipper 直連安裝 ↗
    網頁一鍵擷取入 Obsidian 知識庫,自動去除廣告雜訊與排版格式。
  • 2. Voyager 直連安裝 ↗
    AI 瀏覽助手(需授予網站存取權),協助快速分析網頁與長文摘要。
  • 3. Gemini 對話匯出 商店搜尋
    於 Chrome 線上應用程式商店搜尋「Gemini export」安裝,匯出對話存檔。
🔹 選配 3 大擴充套件
工作效率加分
  • 4. YouTube Dubbing 選配
    外語影片自動即時語音翻譯配音,吸收國際飯店案例零語言門檻。
  • 5. YouTube to Notebook 選配
    YouTube 影片一鍵轉換為結構化筆記與重點摘要。
  • 6. 螢幕錄影(如 Loom 或類似工具) 選配
    教學、操作記錄與異常問題一鍵錄影回報。
📌 補充建議與 Python 避坑指引(山姆大叔特別交代)
🔹 WPS Office(選配):相容 Word/Excel/PPT ➔ 官方下載 (wps.com)
🔹 7-Zip(選配):解壓縮 ZIP/RAR 工具 ➔ 官方下載 (7-zip.org)
❌ Python 不需要另外手動安裝 —— Hermes Agent 安裝器會自動處理(自帶獨立 uv + Python 隔離環境),無須擔心環境衝突!

🚀 筆電安裝順序流程(教學當晚推薦)

1
Chrome 瀏覽器 ➔ Git for Windows ➔ Hermes Agent
先備妥瀏覽器與 Git Bash 終端環境,隨後執行 Hermes 一鍵安裝腳本。
2
Obsidian ➔ Google 雲端硬碟桌面版 ➔ Discord 桌機版
建立第二大腦知識庫資料夾、開啟雲端同步,並登入 Discord 連結 Hermes bot。
3
Chrome 擴充 3 必裝 ➔ 選配軟體隨意
啟用 Obsidian Web Clipper、Voyager 與 Gemini 對話匯出,完成全套武裝!
Module 1 | 系統藍圖

三層核心架構:資料、大腦與通訊分層解耦

鶴仁的 AI 營運特助系統採用「三層解耦」設計:資料永遠在自己手裡,大腦可彈性升級,通訊介面隨時可換。

第一層 · 資料層 📁

Obsidian + Google 雲端

純 Markdown 格式第二大腦,透過 Google 雲端磁碟自動跨裝置同步(手機/筆電),本機永久保存,不依賴任何單一 AI 平台。

  • 知識庫、SOP、會議記錄結構化沉澱
  • 雙向連結(Wiki Links)形成網狀記憶
第二層 · 大腦層 🧠

Hermes Agent(筆電端)

常駐於鶴仁筆電的自主 AI 智能體。擁有專屬 SOUL 人設、4 大飯店營運 Skills、Cron 定時推播排程與檔案讀寫能力。

  • 後端模型串接 OpenCode API 高性價比模型
  • 主動執行晨報情報整理與晚間營運結算
第三層 · 通訊層 ⭐ 首選 💬

Discord 手機/桌面介面

跨平台(iOS/Android/Win/Mac)即時通訊軟體。鶴仁透過 Discord 手機 App 隨時以語音/文字交辦任務、接收排程情報。

  • 走動管理最便利,手機 App 隨手錄音
  • Hermes bot 伺服器後台常駐,推播零延遲
Module 2 | 現場節奏

3.5 小時現場教學流程(7 段時間表)

8/19(三)19:00–22:30 @ Block & Cafe(太原路50號2樓)。包含開場、5 大落地實作演練與收尾 Q&A。

時間 單元 內容與教學重點 對應素材與工具
19:00–19:20 開場說明 觀念破冰:從十幾個 ChatGPT 對話串到三層解耦 Agent;展示 Hermes 成果與走動管理願景 00_README.md
10_Hermes_vs_Codex評估.md
19:20–19:50 實作演練① 基礎環境 + 安裝 Hermes:確認 Win11 筆電環境、安裝 Hermes Agent、驗證 CLI 啟動 06_安裝步驟一頁紙.md
19:50–20:20 實作演練② Obsidian + 雲端同步:建立專屬 Vault、掛載 Google Drive 同步、安裝必備外掛(Web Clipper 等) 06_安裝步驟一頁紙.md
20:20–20:50 實作演練③ SOUL 注入 + 4 大營運技能實測:匯入 SOUL.md、測試會議速記提取、華邑里程碑追蹤與 Cron 定時 01_SOUL.md
02_~05_ Skills
20:50–21:30 實作演練④ ChatGPT 專案對話匯出 + 批次轉檔閉環:控制台腳本 1 分鐘匯出 JSON、Python 批次轉為結構化 Markdown 筆記 09_ChatGPT對話匯出教學.md
ChatGPT匯出工具/
21:30–22:00 實作演練⑤ Discord 通訊介面串接:建立專屬伺服器、邀請 Hermes bot、設定 #一般 與 #晨報 頻道、手機實測語音交辦 11_Discord安裝設定教學.md
22:00–22:30 收尾確認 驗收清單確認 + Q&A:完成現場 5 大驗收勾選、交代日常運維與後續擴充 07_現場教學流程.md
Module 3 | 落地安裝

筆電安裝步驟一頁紙(6 大步驟)

現場帶著鶴仁在筆電端依序執行的 6 個關鍵落地步驟。

1
檢查基礎環境(Git Bash / Node / Python)
Windows 筆電確認具備 Git Bash 終端環境,以便執行命令列與套件管理。
git --version
2
一鍵安裝 Hermes Agent
開啟 Git Bash 終端機,貼上官方一鍵安裝指令:
bash
curl -fsSL https://raw.githubusercontent.com/NousResearch/Hermes-Agent/main/install.sh | bash
3
設定後端 LLM API(OpenCode / DeepSeek)
填入由山姆大叔準備的高性價比 API 金鑰與 Base URL。
4
安裝 Obsidian & 建立 Google Drive 同步 Vault
於 Google 雲端硬碟建立 鶴仁第二大腦 資料夾,並以此路徑作為 Obsidian Vault。
5
注入專屬 SOUL.md 人設與 4 大 Skills
將素材包內的 01_SOUL.md 與 02~05 技能檔複製進 Hermes 設定目錄。
6
設定 Discord Bot 走動通訊介面
將 Hermes 連接至 Discord 伺服器,手機安裝 Discord App 完成隨身串接。
Module 4 | 人格注入

角色設定:鶴仁專屬 SOUL.md

定義 Hermes Agent 的專業人設:飯店營運特助、數據導向、主動追蹤、格式結構化。

01_SOUL.md
# 核心身份 (Identity)
你是「星級飯店營運與新案籌備高階特助」,專屬服務於飯店管理者陳鶴仁(鶴仁)。你的首要任務是降低管理者的行政摩擦力,將零散的會議紀錄、現場巡視口述與營運數據,自動轉化為結構化的決策資訊、公文與行動清單。

# 核心目標 (Core Objectives)
1. **資訊降噪與公文結構化:** 將口語化的輸入快速轉譯為符合集團規範的「主管呈報」或「跨部門正式文件」。
2. **籌備專案追蹤:** 擔任「華邑酒店」等新案籌備的專案管理樞紐,長期記憶各節點(如廠商合約、現場驗收、採購進度),並能主動盤點逾期風險。
3. **營運數據洞察:** 快速解析飯店營運指標(住房率 Occ%、平均房價 ADR、客房收益 RevPAR、餐飲營收),識別異常波動並提供具體改善建議。
4. **會議行動轉化:** 從雜亂的討論中提煉結論,並拆解為「部門/負責人/期限」的清晰行動矩陣。

# 行為準則 (Behavioral Guidelines)
- **極簡高效:** 省去所有客套話、問候與前言,第一句話直接給出核心結論或最終產出物。
- **產業語境:** 熟練使用飯店業標準術語,並具備前檯、房務、餐飲與工務跨部門協調的實務邏輯。
- **排版優化:** 考量管理者常透過手機(通訊軟體)閱讀,必須大量使用 Markdown 清單與表格,確保資訊高度可掃描(Scannable)。
- **主動釐清:** 若鶴仁交辦的任務缺乏關鍵資訊(如:會議負責人未定、籌備時間線缺失),請直接列出 1-3 個簡短的問題向他確認,嚴禁自行捏造事實。

# 預設工作流模式 (Default Workflows)
偵測到以下任務意圖時,請自動切換對應輸出框架:
- **[主管回報模式]:** 輸出「背景與現況 -> 核心問題 -> 建議方案 (含 A/B 兩案對比) -> 預期效益與資源需求」。
- **[會議管理模式]:** 輸出「決策摘要 -> 跨部門待辦清單 (Table 呈現:事項 / 負責部門 / 壓線日期) -> 潛在風險」。
- **[籌備專案模式]:** 提取歷史對話中的「華邑酒店」進度,對比當前任務,輸出「進度更新 -> 延遲警示 -> 需高管裁決事項」。
- **[數據決策模式]:** 針對提供的數據,輸出「核心洞察 -> 異常紅燈指標 -> 現場營運微調建議」。

# 輸出限制 (Output Constraints)
- 全程使用繁體中文。
- 拒絕輸出「希望這對您有幫助」、「總結來說」等廢話結尾。產出完畢即停止。
Module 5 | 專屬技能

飯店營運 4 大 Skills 與 Cron 定時推播

包含會議速記提取、華邑籌備里程碑追蹤、晨間 08:00 簡報與晚間 22:30 營運結算。

02_meeting_action_extractor.yaml
name: meeting_action_extractor
description: 將口語化或零散的飯店會議筆記,結構化解析為決策摘要、跨部門行動矩陣(Table)及高風險警示。
triggers:
  - 會議記錄
  - 早會整理
  - 跨部門協調
  - 交辦事項
inputs:
  raw_text:
    type: string
    description: 原始會議速記、口述摘要或語音轉文字內容。
    required: true

workflow:
  1. 資訊過濾:剔除閒聊與重複資訊,擷取核心決策點。
  2. 責任歸屬拆解:將待辦事項對齊到標準飯店部門(前檯 Front Office / 房務 Housekeeping / 餐飲 F&B / 工務 Engineering / 財務 Finance / 業務 Sales)。
  3. 時間線與風險評估:標註具體截止日期;若未提供具體時間,標記為「待確認」並列入追蹤。
  4. 格式化輸出:產生決策重點與標準 Markdown 表格。

output_template: |
  **📋 會議決策核心**
  * [列出 1-3 點核心決策]

  **🎯 跨部門行動矩陣**
  | 事項編號 | 交辦事項 / 任務內容 | 負責部門 | 主責人員 | 完成期限 | 優先級 |
  | :--- | :--- | :--- | :--- | :--- | :--- |
  | 1 | [任務簡述] | [部門] | [姓名/職稱] | [YYYY-MM-DD] | 高/中/低 |

  **⚠️ 潛在風險與待確認事項**
  * [列出資訊不明確或跨部門卡點項目]
03_hotel_milestone_tracker.yaml
name: hotel_milestone_tracker
description: 追蹤新飯店籌備期的工程進度、硬體驗收、物料採購與廠商合約節點,輸出進度報告並警示延誤風險。
triggers:
  - 籌備進度
  - 華邑酒店進度
  - 工程驗收
  - 採購追蹤
inputs:
  project_name:
    type: string
    description: 專案名稱(預設:華邑酒店籌備專案)。
    default: "華邑酒店籌備專案"
  update_notes:
    type: string
    description: 最新現場巡查紀錄、廠商反饋或進度更新。
    required: true

workflow:
  1. 里程碑分類:將更新歸類至四大模組(1. 現場硬體/工務驗收 2. 營運物料採購 3. 證照與法規驗收 4. 人員培訓與試營運)。
  2. 延誤與瓶頸識別:比對關鍵路徑(Critical Path),偵測是否會影響預定開幕/試營運日期。
  3. 行動建議產出:針對卡點提供 1-2 個替代方案或需向上層呈報裁決的具體事項。

output_template: |
  **🏨 【{{project_name}}】籌備進度看板**
  * **當前狀態評估:** [正常推進 / 輕度延遲 / 嚴重卡點]
  * **本期完成節點:** [列出已完成事項]

  **🚧 關鍵路徑追蹤表**
  | 籌備模組 | 當前關鍵任務 | 廠商/負責窗口 | 預定驗收日 | 狀態 | 風險評估 |
  | :--- | :--- | :--- | :--- | :--- | :--- |
  | 工務驗收 | [如:大廳地毯樣式確認] | [廠商/工務] | [YYYY-MM-DD] | 進行中 | [影響試營運/低] |

  **🚨 需高管裁決事項 (Action Required)**
  * [列出需鶴仁或總經理拍板的決策點]
04_morning_briefing_tracker.md
# 定時 Skill:晨間營運摘要(morning-briefing-tracker)

- **觸發**:Cron 每日 08:00|手動關鍵詞:`晨報、今日早報、晨會準備、早安簡報`
- **聚合**:未結案行動清單(今日到期/逾期)+華邑籌備當週驗收節點+昨日營運數據(Occ%/ADR/RevPAR)+早會需高管確認事項
- **Cron 配置**:

```json
{
  "name": "Daily Hotel Morning Briefing",
  "schedule": "0 8 * * *",
  "timezone": "Asia/Taipei",
  "skill": "morning-briefing-tracker",
  "prompt": "請整合今日日期、當前所有未結案跨部門待辦及華邑酒店籌備最新進度,產出今日晨間營運摘要與重點提醒推播。"
}
```

- **輸出模板**:☀️ 晨間營運與籌備簡報 → 🔥 Top Priorities(P0 卡點 1-2 項)→ 🏨 華邑關鍵節點+風險警示 → 📋 跨部門待追蹤清單(表格)→ 💡 早會提示

---
05_evening_settlement_tracker.md
# 定時 Skill:晚間營運結算(evening-settlement-tracker)

- **觸發**:Cron 每日 22:30 或 23:00(夜審前/值班經理交班)|手動關鍵詞:`晚報、今日營收、夜班交接、晚間結算`
- **聚合四維度**:當日核心營收(Occ%/ADR/RevPAR+餐飲宴會)+現場異常與重大客訴(免單/升等補償、設施故障)+夜間交班巡檢(遲到 VIP、MOD 注意事項)+明日營運預警(預估住房率、大團進出、人力佈署)
- **Cron 配置**:

```json
{
  "name": "Daily Hotel Evening Settlement",
  "schedule": "30 22 * * *",
  "timezone": "Asia/Taipei",
  "skill": "evening-settlement-tracker",
  "prompt": "請整合今日各部門回報之營運數據(住房率、ADR、RevPAR、餐飲營收)、客訴與現場異常紀錄,產出晚間營運結算與夜班交班簡報。"
}
```

- **輸出模板**:🌙 晚間營運結算與交班簡報 → 📊 當日核心營收指標(對比預算標註超標/落後)→ ⚠️ 現場異常與客訴 → 🛎️ 夜班交班要點(VIP/巡檢)→ 🔮 明日營運預警

> 晨間+晚間形成完整閉環:早上主動準備決策視角,晚間結算當日績效與風險,搭配會議速記與籌備追蹤技能全面解放行政負擔。

---
Module 6 | 歷史對話資產

ChatGPT 專案對話匯出與 Hermes 批次轉檔閉環

鶴仁在 ChatGPT 中累積了十幾個重要對話串(華邑酒店籌備、數據分析、公文草稿等)。透過本工具包,1 分鐘完整匯出專案對話,並自動批次轉為 Obsidian 結構化筆記!

步驟一 🌐

Chrome 控制台匯出

在 ChatGPT 網頁按下 F12,貼上 chatgpt_export_all.js,一鍵將專案對話打包下載為 JSON 檔。

步驟二 ⚡

Hermes 批次轉檔

指令呼叫 convert_chatgpt_to_obsidian.py,自動按主題拆分、生成 YAML Frontmatter 與 Wiki Links。

步驟三 📚

自動沉澱至第二大腦

所有對話化為 Obsidian Clippings/ 與 知識庫/ 正式筆記,Hermes 隨時讀取調用!

09_ChatGPT對話匯出教學.md
# ChatGPT 對話匯出教學(給鶴仁,現場實作)

> 目的:把鶴仁現有 ChatGPT 專案對話完整導出,轉存進 Obsidian 第二大腦。
> 方案:② 瀏覽器一鍵匯出 + ③ Hermes 批次轉檔(最佳解,保留專案分組)

## 三步驟流程

### ① 鶴仁匯出(1 分鐘,在他的瀏覽器)
1. 開啟 https://chatgpt.com 並登入
2. 按 `F12` → **Console** 分頁
3. 複製 `chatgpt_export_all.js` 全部內容 → 貼進 Console → Enter
4. 等待跑完(進度列顯示 ✅),會自動下載 3 個檔:
   - `chatgpt_all_conversations.json`(完整對話,含專案)
   - `chatgpt_conversations_index.json`(索引)
   - `chatgpt_all_conversations.md`(人讀版)
5. **跑完前不要重新整理頁面!**

> ⚠️ 若為免費帳號遇到限流:改用 `chatgpt_export_projects_only.js`(保守模式)分次跑,再用 `merge_exports.py` 合併。

### ② 放進共享資料夾
- 把下載的 JSON 檔放進 Google 雲端硬碟共享資料夾(告訴 Hermes 路徑即可)

### ③ Hermes 批次轉檔(自動)
- Hermes 執行 `convert_chatgpt_to_obsidian.py`:
  - 依**專案分組**建立資料夾(華邑籌備/數據分析/…)
  - 每篇對話 → 一筆 Obsidian 筆記(`YYYYMMDD|主題.md`)
  - 自動產生 `_00_匯出總覽.md` 索引
- 輸出:`Obsidian/Clippings/ChatGPT匯出_鶴仁/`

## 檔案清單(本工具包 `ChatGPT匯出工具/`)

| 檔名 | 用途 |
|---|---|
| `chatgpt_export_all.js` | 一鍵匯出全部(一般+專案),1.5–4s 隨機延遲 |
| `chatgpt_export_projects_only.js` | 僅匯出專案對話,8–15s 保守模式(免費帳號用) |
| `merge_exports.py` | 多次匯出合併成單一檔 |
| `convert_chatgpt_to_obsidian.py` | Hermes 批次轉檔核心(JSON → Obsidian 筆記) |

## 注意事項
- 匯出在鶴仁自己的瀏覽器 session 內執行(使用他自己的登入 token),不需 API Key
- 官方匯出(Settings → Data controls → Export)可並行申請當全量備份,但**不含專案分組**(最多 7 天)
- 工具來源:GitHub `24kchengYe/chatgpt-exporter`(MIT,開源)
chatgpt_export_all.js
/**
 * ChatGPT One-Click Full Export (Regular + Project conversations)
 * ================================================================
 * Usage:
 * 1. Open https://chatgpt.com and log in
 * 2. F12 → Console
 * 3. Paste this entire script, press Enter
 * 4. Wait for completion — files download automatically
 *
 * DO NOT refresh the page while the script is running!
 */

(async () => {
  const PAGE_SIZE = 100;
  // Random delay 1.5~4s to mimic human behavior
  const randomDelay = () => 1500 + Math.random() * 2500;

  console.log("🔑 获取 access token...");
  const sessionResp = await fetch("/api/auth/session");
  const sessionData = await sessionResp.json();
  const token = sessionData.accessToken;
  if (!token) { console.error("❌ 无法获取 token,请确保已登录"); return; }
  console.log("✅ Token 获取成功");

  const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

  async function apiGet(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
      try {
        const resp = await fetch(url, { headers });
        if (resp.status === 429) { console.warn("⚠️ 限流,等15秒..."); await sleep(15000); continue; }
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        return await resp.json();
      } catch (e) {
        if (i === retries - 1) throw e;
        console.warn(`⚠️ 重试 ${i+1}/${retries}: ${e.message}`);
        await sleep(2000);
      }
    }
  }

  // ==========================================
  // Step 1: Get regular conversation list
  // ==========================================
  console.log("\n📋 [1/4] 获取常规对话列表...");
  let allConversations = [];
  let offset = 0;

  while (true) {
    const data = await apiGet(`/backend-api/conversations?offset=${offset}&limit=${PAGE_SIZE}&order=updated`);
    const items = data.items || [], total = data.total || 0;
    if (items.length === 0) break;
    allConversations.push(...items.map(c => ({ ...c, _source: "常规对话" })));
    console.log(`  已获取 ${allConversations.length}/${total} 条`);
    offset += PAGE_SIZE;
    if (offset >= total) break;
    await sleep(randomDelay());
  }
  console.log(`✅ 常规对话: ${allConversations.length} 条`);

  // ==========================================
  // Step 2: Get project (Gizmos) conversations
  // ==========================================
  console.log("\n📂 [2/4] 获取项目列表...");
  let projects = [];
  try {
    const sidebarData = await apiGet("/backend-api/gizmos/snorlax/sidebar");
    projects = (sidebarData.items || []).map(item => ({
      id: item.gizmo?.id || item.id,
      name: item.gizmo?.display?.name || item.title || "未命名项目",
    }));
    console.log(`✅ 发现 ${projects.length} 个项目: ${projects.map(p => p.name).join(", ")}`);
  } catch (e) {
    console.warn(`⚠️ 获取项目列表失败: ${e.message},跳过项目对话`);
  }

  for (const project of projects) {
    console.log(`\n  📂 项目「${project.name}」...`);
    let cursor = "0";
    let projectConvCount = 0;

    while (cursor !== null && cursor !== undefined) {
      try {
        const data = await apiGet(`/backend-api/gizmos/${project.id}/conversations?cursor=${cursor}`);
        const items = data.items || [];
        if (items.length === 0) break;

        const existingIds = new Set(allConversations.map(c => c.id));
        for (const item of items) {
          if (!existingIds.has(item.id)) {
            allConversations.push({ ...item, _source: `项目: ${project.name}` });
            projectConvCount++;
          }
        }

        cursor = data.cursor || null;
        await sleep(randomDelay());
      } catch (e) {
        console.warn(`  ⚠️ 项目「${project.name}」获取失败: ${e.message}`);
        break;
      }
    }
    console.log(`    ✅ 新增 ${projectConvCount} 条对话`);
  }

  console.log(`\n✅ 总计: ${allConversations.length} 条对话(含项目)`);

  // ==========================================
  // Step 3: Download full conversation details
  // ==========================================
  console.log("\n📥 [3/4] 下载对话内容...");
  const allDetails = [];
  let success = 0, failed = 0;

  for (let i = 0; i < allConversations.length; i++) {
    const conv = allConversations[i];
    const title = conv.title || "Untitled";
    const source = conv._source || "";

    try {
      const detail = await apiGet(`/backend-api/conversation/${conv.id}`);
      if (detail) {
        detail._source = source;
        allDetails.push(detail);
      } else {
        throw new Error("返回数据为空");
      }
      success++;
      if ((i+1) % 10 === 0 || i === allConversations.length - 1)
        console.log(`  [${i+1}/${allConversations.length}] ${title.slice(0,35)} (${source}) ✅`);
    } catch (e) {
      console.warn(`  [${i+1}/${allConversations.length}] ${title.slice(0,35)}... ❌ ${e.message}`);
      failed++;
    }
    await sleep(randomDelay());
  }

  // ==========================================
  // Step 4: Generate export files
  // ==========================================
  console.log("\n📝 [4/4] 生成导出文件...");

  function extractMessages(convData) {
    if (!convData || !convData.mapping) return [];
    const mapping = convData.mapping;
    let rootId = null;
    for (const [id, node] of Object.entries(mapping)) { if (!node.parent) { rootId = id; break; } }
    if (!rootId) return [];
    const messages = [], queue = [rootId];
    while (queue.length > 0) {
      const node = mapping[queue.shift()];
      if (!node) continue;
      const msg = node.message;
      if (msg && msg.content && msg.content.parts) {
        const role = msg.author?.role || "unknown";
        const text = msg.content.parts.filter(p => typeof p === "string").join("\n").trim();
        if (text && ["user","assistant","system","tool"].includes(role))
          messages.push({ role, content: text, model: msg.metadata?.model_slug || "" });
      }
      if (node.children?.length > 0) queue.push(node.children[0]);
    }
    return messages;
  }

  function convToMarkdown(d) {
    const title = d.title || "Untitled";
    const source = d._source || "常规对话";
    let ct = "unknown";
    try { if (d.create_time) ct = new Date(d.create_time * 1000).toISOString().slice(0,16).replace("T"," "); } catch(e) {}
    const msgs = extractMessages(d);
    let md = `# ${title}\n\n**来源**: ${source} | **日期**: ${ct}\n\n`;
    for (const m of msgs) {
      const label = {user:"👤 User",assistant:"🤖 Assistant",system:"⚙️ System",tool:"🔧 Tool"}[m.role]||m.role;
      md += `\n## ${label}${m.model?` (${m.model})`:""}\n\n${m.content}\n\n`;
    }
    return md;
  }

  const allDataJson = JSON.stringify(allDetails, null, 2);

  const indexData = allConversations.map(c => {
    let ct = null, ut = null;
    try { if (c.create_time) ct = new Date(c.create_time * 1000).toISOString(); } catch(e) {}
    try { if (c.update_time) ut = new Date(c.update_time * 1000).toISOString(); } catch(e) {}
    return { id: c.id, title: c.title, source: c._source, create_time: ct, update_time: ut };
  });
  const indexJson = JSON.stringify(indexData, null, 2);

  let allMd = `# ChatGPT 对话导出(含项目)\n\n导出时间: ${new Date().toISOString()}\n共 ${allDetails.length} 条对话\n\n---\n\n`;
  for (const d of allDetails) allMd += convToMarkdown(d) + "\n\n---\n\n";

  function dl(content, filename, type="application/json") {
    const a = document.createElement("a");
    a.href = URL.createObjectURL(new Blob([content], {type}));
    a.download = filename;
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
  }

  dl(allDataJson, "chatgpt_all_conversations.json");
  await sleep(500);
  dl(indexJson, "chatgpt_conversations_index.json");
  await sleep(500);
  dl(allMd, "chatgpt_all_conversations.md", "text/markdown");

  console.log("\n" + "=".repeat(50));
  console.log(`🎉 导出完成! 成功: ${success}, 失败: ${failed}`);
  console.log(`📁 已下载 3 个文件(含 ${projects.length} 个项目的对话)`);
  console.log("=".repeat(50));
})();
chatgpt_export_projects_only.js
/**
 * ChatGPT 项目对话导出(仅项目)
 * 只抓项目里的对话,间隔长,避免限流
 */
(async () => {
  console.log("🔑 获取 access token...");
  const sessionResp = await fetch("/api/auth/session");
  const { accessToken: token } = await sessionResp.json();
  if (!token) { console.error("❌ 无法获取 token"); return; }
  console.log("✅ Token 获取成功");

  const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  // 每次请求间隔 8~15 秒,非常保守
  const randomDelay = () => 8000 + Math.random() * 7000;

  async function apiGet(url, retries = 5) {
    for (let i = 0; i < retries; i++) {
      try {
        const resp = await fetch(url, { headers });
        if (resp.status === 429) {
          const wait = 30 + i * 15;  // 30s, 45s, 60s, 75s, 90s 递增等待
          console.warn(`⚠️ 限流,等 ${wait} 秒... (第${i+1}次)`);
          await sleep(wait * 1000);
          continue;
        }
        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
        return await resp.json();
      } catch (e) {
        if (i === retries - 1) throw e;
        console.warn(`⚠️ 重试 ${i+1}/${retries}: ${e.message}`);
        await sleep(10000);
      }
    }
  }

  // 1. 获取项目列表
  console.log("\n📂 [1/3] 获取项目列表...");
  const sidebarData = await apiGet("/backend-api/gizmos/snorlax/sidebar");
  const projects = (sidebarData.items || []).map(item => ({
    id: item.gizmo?.id || item.id,
    name: item.gizmo?.display?.name || item.title || "未命名项目",
  }));
  console.log(`✅ 发现 ${projects.length} 个项目: ${projects.map(p => p.name).join(", ")}`);

  if (projects.length === 0) { console.log("没有项目,退出"); return; }

  // 2. 获取项目内对话列表
  console.log("\n📋 [2/3] 获取项目对话列表...");
  let projectConversations = [];

  for (const project of projects) {
    console.log(`  📂 项目「${project.name}」...`);
    let cursor = "0";

    while (cursor !== null && cursor !== undefined) {
      try {
        const data = await apiGet(`/backend-api/gizmos/${project.id}/conversations?cursor=${cursor}`);
        const items = data.items || [];
        if (items.length === 0) break;

        for (const item of items) {
          projectConversations.push({ ...item, _projectName: project.name });
        }

        cursor = data.cursor || null;
        await sleep(randomDelay());
      } catch (e) {
        console.warn(`  ⚠️ 项目「${project.name}」获取失败: ${e.message}`);
        break;
      }
    }
  }

  console.log(`✅ 项目对话共 ${projectConversations.length} 条`);

  // 3. 逐条下载完整对话
  console.log(`\n📥 [3/3] 下载对话内容(每条间隔 8~15 秒)...`);
  const allDetails = [];
  let success = 0, failed = 0;

  for (let i = 0; i < projectConversations.length; i++) {
    const conv = projectConversations[i];
    const title = conv.title || "Untitled";
    const project = conv._projectName;

    console.log(`  [${i+1}/${projectConversations.length}] ${title} (${project})`);

    try {
      const detail = await apiGet(`/backend-api/conversation/${conv.id}`);
      if (detail) {
        detail._source = `项目: ${project}`;
        allDetails.push(detail);
        success++;
        console.log(`    ✅ 成功`);
      } else {
        throw new Error("返回数据为空");
      }
    } catch (e) {
      console.warn(`    ❌ 失败: ${e.message}`);
      failed++;
    }

    if (i < projectConversations.length - 1) {
      const delay = randomDelay();
      console.log(`    ⏳ 等待 ${(delay/1000).toFixed(1)} 秒...`);
      await sleep(delay);
    }
  }

  // 生成文件
  function extractMessages(convData) {
    if (!convData || !convData.mapping) return [];
    const mapping = convData.mapping;
    let rootId = null;
    for (const [id, node] of Object.entries(mapping)) { if (!node.parent) { rootId = id; break; } }
    if (!rootId) return [];
    const messages = [], queue = [rootId];
    while (queue.length > 0) {
      const node = mapping[queue.shift()];
      if (!node) continue;
      const msg = node.message;
      if (msg && msg.content && msg.content.parts) {
        const role = msg.author?.role || "unknown";
        const text = msg.content.parts.filter(p => typeof p === "string").join("\n").trim();
        if (text && ["user","assistant","system","tool"].includes(role))
          messages.push({ role, content: text, model: msg.metadata?.model_slug || "" });
      }
      if (node.children?.length > 0) queue.push(node.children[0]);
    }
    return messages;
  }

  let allMd = `# ChatGPT 项目对话导出\n\n导出时间: ${new Date().toISOString()}\n共 ${allDetails.length} 条项目对话\n\n---\n\n`;
  for (const d of allDetails) {
    const title = d.title || "Untitled";
    const source = d._source || "";
    let ct = "unknown";
    try { if (d.create_time) ct = new Date(d.create_time * 1000).toISOString().slice(0,16).replace("T"," "); } catch(e) {}
    const msgs = extractMessages(d);
    allMd += `# ${title}\n\n**来源**: ${source} | **日期**: ${ct}\n\n`;
    for (const m of msgs) {
      const label = {user:"👤 User",assistant:"🤖 Assistant",system:"⚙️ System",tool:"🔧 Tool"}[m.role]||m.role;
      allMd += `\n## ${label}${m.model?` (${m.model})`:""}\n\n${m.content}\n\n`;
    }
    allMd += "\n---\n\n";
  }

  function dl(content, filename, type="application/json") {
    const a = document.createElement("a");
    a.href = URL.createObjectURL(new Blob([content], {type}));
    a.download = filename;
    document.body.appendChild(a); a.click(); document.body.removeChild(a);
  }

  dl(JSON.stringify(allDetails, null, 2), "chatgpt_project_conversations.json");
  await sleep(500);
  dl(allMd, "chatgpt_project_conversations.md", "text/markdown");

  console.log("\n" + "=".repeat(50));
  console.log(`🎉 项目对话导出完成! 成功: ${success}, 失败: ${failed}`);
  console.log("📁 已下载 2 个文件:");
  console.log("  - chatgpt_project_conversations.json");
  console.log("  - chatgpt_project_conversations.md");
  console.log("=".repeat(50));
})();
convert_chatgpt_to_obsidian.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
convert_chatgpt_to_obsidian.py
================================
Hermes 批次轉檔:ChatGPT 匯出 JSON → Obsidian 筆記
(配合 24kchengYe/chatgpt-exporter 產出的 chatgpt_all_conversations.json)

用法:
    python convert_chatgpt_to_obsidian.py <匯出資料夾> [--out <Obsidian輸出資料夾>]

輸入 (匯出資料夾內):
    chatgpt_all_conversations.json    — 全部對話(含 mapping 訊息、_source 專案標籤)
    chatgpt_conversations_index.json  — 索引(id/title/source/create_time)[可選]

輸出 (預設):
    <Obsidian>/Clippings/ChatGPT匯出_鶴仁/
        <專案名>/
            YYYYMMDD|對話標題.md
        _00_匯出總覽.md                 — 各專案對話統計索引

特點:
    - 保留專案分組(_source: "项目: XX" / "常规对话")
    - 每篇對話一筆筆記:YAML frontmatter + 逐輪訊息(👤/🤖)
    - 輸出總覽索引,方便 Obsidian 彙整
"""
import json, os, re, sys, io, argparse
from datetime import datetime

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

def safe_name(s, maxlen=60):
    s = re.sub(r'[\\/:*?"<>|\n\r\t]', ' ', s).strip()
    s = re.sub(r'\s+', ' ', s)
    return s[:maxlen].rstrip('.')

def extract_messages(conv):
    """從 mapping 提取 [role, content, model] 清單(與 chatgpt_export_all.js 相同邏輯)"""
    mapping = conv.get('mapping') or {}
    root_id = next((i for i, n in mapping.items() if not n.get('parent')), None)
    if not root_id:
        return []
    out, queue = [], [root_id]
    while queue:
        node = mapping.get(queue.pop(0))
        if not node:
            continue
        msg = node.get('message')
        if msg and msg.get('content', {}).get('parts'):
            role = (msg.get('author') or {}).get('role', 'unknown')
            parts = msg['content']['parts']
            text = '\n'.join(p for p in parts if isinstance(p, str)).strip()
            if text and role in ('user', 'assistant', 'system', 'tool'):
                out.append({'role': role, 'content': text,
                            'model': (msg.get('metadata') or {}).get('model_slug', '')})
        if node.get('children'):
            queue.append(node['children'][0])
    return out

def fmt_date(ts):
    """ChatGPT create_time 是 Unix epoch 秒"""
    if not ts:
        return ''
    try:
        return datetime.fromtimestamp(float(ts)).strftime('%Y-%m-%d %H:%M')
    except Exception:
        return ''

def main():
    ap = argparse.ArgumentParser()
    ap.add_argument('src', help='ChatGPT 匯出資料夾(含 chatgpt_all_conversations.json)')
    ap.add_argument('--out', default=None,
                    help='Obsidian 輸出根資料夾(預設: <src> 上一層的 Obsidian 慣例路徑)')
    args = ap.parse_args()

    src_dir = args.src
    json_path = os.path.join(src_dir, 'chatgpt_all_conversations.json')
    if not os.path.exists(json_path):
        print(f'❌ 找不到 {json_path}')
        sys.exit(1)

    # 輸出資料夾:預設 G:/我的雲端硬碟/Obsidian/Clippings/ChatGPT匯出_鶴仁/
    if args.out:
        out_root = args.out
    else:
        vault = os.environ.get('OBSIDIAN_VAULT', r'G:/我的雲端硬碟/Obsidian')
        out_root = os.path.join(vault, 'Clippings', 'ChatGPT匯出_鶴仁')
    os.makedirs(out_root, exist_ok=True)

    convs = json.load(open(json_path, encoding='utf-8'))
    print(f'📄 讀取 {len(convs)} 篇對話')

    # 依 _source 分組:專案 → 對話清單
    groups = {}
    for c in convs:
        src = c.get('_source') or '常规对话'
        src = src.replace('项目: ', '').strip() if src.startswith('项目:') else src
        groups.setdefault(src, []).append(c)

    stats = {}
    total = 0
    for proj, items in sorted(groups.items()):
        proj_dir = os.path.join(out_root, safe_name(proj) or '未分類')
        os.makedirs(proj_dir, exist_ok=True)
        stats[proj] = len(items)
        for c in items:
            title = c.get('title') or 'Untitled'
            ts = fmt_date(c.get('create_time'))
            date_prefix = ts[:10].replace('-', '') if ts else '日期待確認'
            fname = f'{date_prefix}|{safe_name(title, 45)}.md'
            fpath = os.path.join(proj_dir, fname)
            msgs = extract_messages(c)
            label = {'user': '👤 User', 'assistant': '🤖 Assistant',
                     'system': '⚙️ System', 'tool': '🔧 Tool'}
            body = '\n\n'.join(
                f'## {label.get(m["role"], m["role"])}' +
                (f' ({m["model"]})' if m['model'] else '') + '\n\n' + m['content']
                for m in msgs)
            note = (f'---\ncreated: {ts or ""}\nsource: ChatGPT 匯出\nproject: {proj}\n'
                    f'tags: [ChatGPT匯出, 鶴仁]\n---\n\n'
                    f'# {title}\n\n> [!info] 對話資訊\n> - **專案**: {proj}\n'
                    f'> - **日期**: {ts or "待確認"}\n> - **訊息數**: {len(msgs)}\n\n{body}\n')
            with open(fpath, 'w', encoding='utf-8-sig') as f:
                f.write(note)
            total += 1
        print(f'  ✅ {proj}: {len(items)} 篇 → {proj_dir}')

    # 總覽索引
    overview = ['---\ncreated: 2026-08-18\nsource: ChatGPT 匯出\n'
                'tags: [ChatGPT匯出, 鶴仁, 索引]\n---\n',
                '# ChatGPT 對話匯出總覽(鶴仁)', '']
    overview.append('| 專案 | 對話數 |')
    overview.append('|---|---|')
    for proj, n in sorted(stats.items()):
        overview.append(f'| [[{safe_name(proj) or "未分類"}]] | {n} |')
    overview.append(f'\n**合計:{total} 篇對話**')
    with open(os.path.join(out_root, '_00_匯出總覽.md'), 'w', encoding='utf-8-sig') as f:
        f.write('\n'.join(overview) + '\n')

    print(f'\n🎉 完成!共 {total} 篇筆記 → {out_root}')

if __name__ == '__main__':
    main()
merge_exports.py
"""
Merge regular and project ChatGPT exports into a single file.

Usage:
  python merge_exports.py --regular all.json --projects projects.json --output merged/

Or simply:
  python merge_exports.py  (uses default paths in current directory)
"""

import json
import os
import sys
import argparse
from datetime import datetime


def extract_messages(conv_data):
    """Extract ordered messages from conversation mapping tree."""
    if not conv_data or 'mapping' not in conv_data:
        return []
    mapping = conv_data['mapping']
    root_id = None
    for nid, node in mapping.items():
        if node.get('parent') is None:
            root_id = nid
            break
    if not root_id:
        return []
    messages = []
    queue = [root_id]
    while queue:
        node_id = queue.pop(0)
        node = mapping.get(node_id, {})
        msg = node.get('message')
        if msg and msg.get('content') and msg['content'].get('parts'):
            role = msg.get('author', {}).get('role', 'unknown')
            parts = [p for p in msg['content']['parts'] if isinstance(p, str)]
            text = '\n'.join(parts).strip()
            if text and role in ('user', 'assistant', 'system', 'tool'):
                model = msg.get('metadata', {}).get('model_slug', '')
                messages.append({'role': role, 'content': text, 'model': model})
        children = node.get('children', [])
        if children:
            queue.append(children[0])
    return messages


def main():
    parser = argparse.ArgumentParser(description='Merge ChatGPT exports')
    parser.add_argument('--regular', default='chatgpt_all_conversations.json',
                        help='Regular conversations JSON file')
    parser.add_argument('--projects', default='chatgpt_project_conversations.json',
                        help='Project conversations JSON file')
    parser.add_argument('--output', default='merged',
                        help='Output directory')
    args = parser.parse_args()

    # Load files
    all_conv = []
    if os.path.exists(args.regular):
        all_conv = json.load(open(args.regular, encoding='utf-8'))
        print(f"Regular conversations: {len(all_conv)}")
    else:
        print(f"Warning: {args.regular} not found, skipping")

    proj_conv = []
    if os.path.exists(args.projects):
        proj_conv = json.load(open(args.projects, encoding='utf-8'))
        print(f"Project conversations: {len(proj_conv)}")
    else:
        print(f"Warning: {args.projects} not found, skipping")

    # Deduplicate and merge
    existing_ids = set()
    for d in all_conv:
        cid = d.get('conversation_id') or d.get('id', '')
        existing_ids.add(cid)

    added = 0
    for d in proj_conv:
        cid = d.get('conversation_id') or d.get('id', '')
        if cid not in existing_ids:
            all_conv.append(d)
            existing_ids.add(cid)
            added += 1

    # Sort by create_time
    all_conv.sort(key=lambda x: x.get('create_time') or 0)
    print(f"Merged total: {len(all_conv)} (added {added} from projects)")

    # Create output directory
    os.makedirs(args.output, exist_ok=True)

    # Save merged JSON
    json_path = os.path.join(args.output, 'chatgpt_merged_all.json')
    with open(json_path, 'w', encoding='utf-8') as f:
        json.dump(all_conv, f, ensure_ascii=False, indent=2)
    print(f"Saved: {json_path}")

    # Save merged Markdown (using plain text separators to avoid conflicts)
    md_path = os.path.join(args.output, 'chatgpt_merged_all.md')
    lines = []
    lines.append(f'<!-- ChatGPT Full Export -->')
    lines.append(f'<!-- Exported: {datetime.now().strftime("%Y-%m-%d %H:%M")} -->')
    lines.append(f'<!-- Total: {len(all_conv)} conversations -->\n')

    for idx, d in enumerate(all_conv):
        title = d.get('title', 'Untitled')
        source = d.get('_source', 'Regular')
        ct = 'unknown'
        try:
            if d.get('create_time'):
                ct = datetime.fromtimestamp(d['create_time']).strftime('%Y-%m-%d %H:%M')
        except:
            pass

        lines.append(f'{"=" * 60}')
        lines.append(f'Conversation {idx+1}/{len(all_conv)}: {title}')
        lines.append(f'Source: {source} | Date: {ct}')
        lines.append(f'{"=" * 60}\n')

        msgs = extract_messages(d)
        for m in msgs:
            role_label = {'user': 'User', 'assistant': 'Assistant',
                          'system': 'System', 'tool': 'Tool'}.get(m['role'], m['role'])
            model_str = f" ({m['model']})" if m.get('model') else ''
            lines.append(f'--- {role_label}{model_str} ---\n')
            lines.append(m['content'])
            lines.append('')
        lines.append('')

    with open(md_path, 'w', encoding='utf-8') as f:
        f.write('\n'.join(lines))
    print(f"Saved: {md_path}")


if __name__ == '__main__':
    main()
Module 7 | 通訊首選 山姆大叔首選推薦

Discord 安裝與設定教學(鶴仁走動管理介面)

為什麼選 Discord?Hermes Agent 掛在 Discord 伺服器後台,手機 App 隨時文字/語音交辦、跨裝置同步、排程自動推播,是管理者「巡場走動管理」的最佳利器!

🌟 為什麼選 Discord 作為 Hermes 通訊介面?

📱

手機/電腦全平台

iOS、Android、Windows、Mac 皆有完整獨立 App,無論拿手機巡場或在辦公室開筆電,訊息毫秒級同步。

⚡

即時推播不漏接

Hermes 的 cron 排程(早上 08:00 晨報、晚間結算、合約催辦)直接主動推播進指定頻道,打開手機即見。

🎙️

語音訊息隨手交辦

巡場時不方便打字,鶴仁可直接錄音發出語音訊息,Hermes 自動轉文字消化並提取待辦事項。

🛡️

伺服器架構常駐

一人一專屬伺服器,Hermes bot 在雲端/筆電後台常駐,手機 App 即使滑掉關閉,排程與通知也照常運行。

💰

個人使用完全免費

無月租費用、無傳輸限制、無 Bot 呼叫上限,個人與小型團隊使用完全免費零負擔。

📁

頻道分流結構分明

建立 #一般(日常交辦問答)與 #晨報(定時情報接收),資訊井然有序不混雜。

🚀 Discord 落地 4 大實作步驟

1
下載並註冊 Discord
手機端至 App Store / Google Play 搜尋「Discord」下載;筆電端至 discord.com/download 下載安裝並登入同一帳號。
2
建立鶴仁專屬伺服器 & 規劃頻道
點擊左側「+」建立「鶴仁營運特助」伺服器,並建立兩個文字頻道:#一般(交辦與日常問答)與 #晨報(定時情報推播專用)。
3
邀請 Hermes Bot 機器人進駐
山姆大叔現場提供 Hermes Bot 邀請連結,鶴仁授權加入伺服器後,設定 #晨報 頻道 ID 給 Hermes 的 Cron 定時推播器。
4
現場實測:語音交辦 & 晨報推播模擬
鶴仁現場用手機 Discord 發送一則語音或文字訊息 @Hermes 幫我整理剛才工務會議的 3 個重點,驗收機器人即時提取並同步至 Obsidian。
11_Discord安裝設定教學.md
# Discord 安裝與設定教學(鶴仁版)

> 通訊軟體首選:**Discord**(山姆大叔推薦)
> 理由:Hermes Agent 掛在 Discord 伺服器後台,手機 App 隨時交辦、跨裝置同步、支援語音訊息,是「走動管理」最佳介面。

## 為什麼選 Discord

| 特性 | 說明 |
|---|---|
| 手機/電腦全平台 | iOS/Android/Windows/Mac 都有 App |
| 即時推播 | Hermes 的 cron 晨報、追蹤提醒直接送進頻道 |
| 語音訊息 | 鶴仁可以錄音交辦,Hermes 轉文字處理(適合巡場時) |
| 伺服器架構 | 一人一伺服器,Hermes bot 常駐,關閉 App 也照常運作 |
| 免費 | 個人使用完全免費 |

## Step 1:安裝 Discord

- **手機**:App Store(iOS)/Google Play(Android)搜尋「Discord」下載
- **電腦**:https://discord.com/download 下載安裝
- 註冊帳號(email+密碼+手機驗證)

## Step 2:建立伺服器(或加入山姆大叔的)

**方案 A:鶴仁自己建伺服器(推薦,獨立專用)**
1. Discord 左側欄「+」→ 建立伺服器
2. 選擇「建立自己的伺服器」→ 命名如「鶴仁營運助理」
3. 建立後自動有「一般」頻道

**方案 B:加入山姆大叔的伺服器**
1. 山姆大叔提供伺服器邀請連結
2. 點連結 → 接受邀請 → 進入伺服器

## Step 3:邀請 Hermes Agent(bot)加入

> ⚠️ 這步需山姆大叔/Hermes 設定端完成,鶴仁只需確認 bot 在線:
1. 山姆大叔把 Hermes bot 加入鶴仁的伺服器(或鶴仁加入 Hermes 所在的伺服器)
2. 伺服器出現 Hermes bot 成員,狀態「在線」即完成
3. 設定 bot 可讀寫的頻道(建議開「一般」+「晨報」兩個頻道)

## Step 4:日常使用

```
# 交辦(一般頻道)
「@Hermes 幫我整理今天的早會筆記,產出行動清單」
「@Hermes 華邑酒店的廠商合約進度如何?」

# 接收推播(晨報頻道)
每天早上 08:00 自動收到:營運摘要+待辦追蹤+籌備風險
```

## 驗收清單

- [ ] Discord App 安裝完成(手機+電腦)
- [ ] 帳號註冊成功
- [ ] 伺服器建立/加入完成
- [ ] Hermes bot 顯示在線
- [ ] 傳一則訊息給 Hermes 有回應
- [ ] 設定好「晨報」頻道接收每日推播

## 補充:Hermes 支援的其他通訊平台(備用)

| 平台 | 適合 |
|---|---|
| **Discord**(首選)| 全平台、語音、推播穩定 |
| Telegram | 輕量、bot 生態成熟 |
| Signal | 重視隱私(鶴仁暫不需要)|
| LINE | 台灣慣用但 bot 設定繁瑣 |
Module 8 | 決策評估

Hermes Agent vs Codex 深度評估(鶴仁決策指南)

問題:鶴仁該選「Hermes Agent + OpenCode」還是「ChatGPT Plus + Codex」?
核心結論:Hermes Agent 更適合 —— 鶴仁是飯店營運管理者,不是軟體工程師!

比較維度 🕊️ Hermes Agent + OpenCode 💻 ChatGPT Plus + Codex 對鶴仁(飯店營運者)的實際意義
核心定位 自主行動 Agent 程式碼生成工具 鶴仁需要的是「幫我管飯店的特助」,不是「幫我寫程式的工程師」
定時主動性 ✅ 內建 Cron 排程(08:00 晨報、22:30 結算) ❌ 無排程能力(被動等待提問) Hermes 主動推播營運情報,Codex 只能等你開電腦問它
通訊與走動管理 ✅ 直串 Discord(手機隨時語音交辦) ❌ 局限於 Web / 終端機介面 飯店巡場、會客、工程巡視時,手機隨時語音交辦
第二大腦整合 ✅ 直讀直寫 Obsidian Vault ⚠️ 需手動複製貼上或自寫腳本 對話沉澱、公文、會議速記自動化進第二大腦
月租成本 💰 依使用量計費(山姆大叔支援平價 API) 💳 每月固定 $20 USD(約 650 台幣) 用多少付多少,高性價比無固定包月壓力

🌐 完整架構:三 Agent 協同生態(山姆大叔 + 鶴仁)

鶴仁現場主力 🕊️

Hermes Agent

鶴仁筆電端專屬營運特助。負責日常交辦、會議速記、籌備追蹤、晨報推播與 Obsidian 寫入。

山姆大叔後勤 🚀

AntiGravity 2.0

架構設計與教學網頁部署。負責開發教學素材包、轉檔工具、維護 GitHub Pages 與第二大腦規則。

算力後盾 ⚡

OpenCode 服務端

山姆大叔住家工作站常駐。提供穩定、平價的 LLM API 轉發服務,作為 Hermes 的高性價比大腦算力。

10_Hermes_vs_Codex評估.md
# Hermes Agent vs Codex 評估(鶴仁決策參考)

> 問題:鶴仁該選「Hermes Agent + OpenCode」還是「ChatGPT Plus + Codex」?
> 結論:**Hermes Agent 更適合**——鶴仁是飯店管理者,不是工程師。

## 30 秒結論

Codex 是「寫程式的 Agent」,Hermes 是「辦事的 Agent」。鶴仁要的是「丟訊息→自動辦事」,不是「開終端機→寫程式」。

## 本質對比

| 面向 | **Hermes Agent** | **Codex(ChatGPT Plus)** |
|---|---|---|
| 目標使用者 | 一般使用者/管理者 | **開發者/工程師** |
| 操作介面 | **通訊軟體**(Telegram/Discord/LINE)| CLI 終端機+IDE |
| 交辦方式 | 「傳訊息」即可(手機走動管理)| 打字下指令、看程式輸出 |
| 主要任務 | 整理、追蹤、回報、排程、推播 | **寫程式、改程式碼** |
| 主動性 | ✅ cron 定時**主動推播**(晨報/追蹤)| ❌ 被動等指令 |
| 學習成本 | 低(像傳 Line 給助理)| 高(要懂 terminal/專案)|
| 中文支援 | ✅ 繁體中文原生 | ⚠️ 程式語境為主 |
| 經驗沉澱 | ✅ Skills 自動累積 | 較弱 |

## 鶴仁的 5 大需求逐項檢驗

| 鶴仁的需求 | Hermes | Codex |
|---|---|---|
| 早會筆記 → 行動清單 | ✅ 丟訊息就出表格 | ❌ 不是它的用途 |
| 華邑酒店籌備追蹤 | ✅ cron 主動盯逾期 | ❌ 無排程概念 |
| 營運數據異常分析 | ✅ 丟數據→結論 | ⚠️ 要開環境跑 |
| 主管呈報公文 | ✅ 模板化輸出 | ❌ 無此設計 |
| 手機巡場時交辦 | ✅ 通訊軟體直達 | ❌ 綁在電腦前 |

## 成本與風險

| 項目 | Hermes + OpenCode | ChatGPT Plus + Codex |
|---|---|---|
| 月成本 | ≈ $0–5(DeepSeek 用量)| **$20/月固定** |
| 綁定風險 | 資料在 Obsidian(可搬遷)| 對話鎖在 OpenAI 生態 |
| 上手速度 | 當晚教學即可用 | 鶴仁得先學 terminal |
| 擴充性 | 接 AntiGravity/其他 Agent | 封閉 |

## 誠實補充:Codex 的唯一優勢

- **寫程式能力頂級**:$20 含約 $700/月的 Codex 額度,開發任務非常划算
- 但鶴仁**沒有寫程式需求**——他的「程式」是公文、追蹤、分析,Hermes 全包

## 最終建議

> **Hermes Agent 為主**(本次教學導入),**OpenCode 當模型來源**(山姆大叔提供 API)。
> 若未來鶴仁想學自動化開發,**再加** Codex 當「開發工兵」由 Hermes 指揮——兩者不是二選一,是主從關係。

## 附:三 Agent 協同方案(山姆大叔實戰版)

```
Hermes Agent(資料引擎·指揮官)
   │ agentapi CLI            │ A2A bridge
   ▼                          ▼
AntiGravity(知識大腦·Gemini3.7)   OpenCode(開發工兵·DS雙備援)
   └──────┬─────── 共用知識庫 ───────┘
          ▼
     Obsidian 第二大腦(Google 雲端磁碟同步)
```

| Agent | 定位 | 強項 |
|---|---|---|
| **Hermes Agent** | 指揮官/資料引擎 | cron 排程、MCP 查證、Obsidian 寫入、跨平台推播 |
| **AntiGravity** | 知識大腦 | Gemini 3.7 Flash 文筆美化、知識整理、部署網頁 |
| **OpenCode** | 開發工兵 | DeepSeek V4 雙備援、寫程式、跑腳本 |

成本:≈ $0–5/月(DeepSeek/OpenCode API 用量),能力含排程+知識庫+開發+部署+推播全包。
影音專題 | 推薦觀看 YouTube 必看系列

參考資源:三師爸「AI Agent 基本功」播放清單

YouTube 知名創作者三師爸(Sense Bar)精編「AI Agent 基本功」完整 7 集系列。這是學習 AI Agent 必看的觀念打底、架構解析與實戰指南!

▶️ 一鍵順序播放(EP01 → EP07)
照教學建議順序自動連續播放,輕鬆搞懂 Agent 核心觀念!
▶️ 立即一鍵順播清單 ↗
⚠️ 重要提醒:YouTube 播放清單原始順序是倒序(EP07 在最上面)。觀看時請務必點擊上方的一鍵順播連結,或從 EP01 開始依序觀看。

📺 觀看順序(EP01 → EP07)與直連連結

集數 影片標題與核心主題 對應鶴仁導入 Hermes 情境建議 直連觀看
EP01 用 Agent 來學習 Agent:一個 GitHub repo,複製我的整套 AI 工作流到你的 Agent 直接套用山姆大叔準備好的 Hermes 環境設定與素材包 觀看 EP01 ↗
EP02 學習 Agent 必懂的核心觀念與初始化設定 理解 Agent 怎麼運作、大腦與工具如何分層(初次上手必看) 觀看 EP02 ↗
EP03 一句話讓 AI 幫你讀檔、寫程式、上網、做出成品 飯店公文草擬、簡報製作、數據分析的直覺用法 觀看 EP03 ↗
EP04 連接外部工具:MCP 與連接器一張地圖講清楚 Hermes 連接外部工具與資料庫(未來進階擴充) 觀看 EP04 ↗
EP05 三層一次講清楚:搞定 技能/全域/專案 Hermes 的 Skills 技能庫/全域設定/華邑專案 Context 觀看 EP05 ↗
EP06 跨 Agent、跨電腦協作同一個專案,設定觀念一次到位 Hermes + AntiGravity + OpenCode 三 Agent 協同生態 觀看 EP06 ↗
EP07 需求探索四象限法,釐清專案需求的最後一塊拼圖 鶴仁導入各項飯店系統前釐清需求(建議最後看) 觀看 EP07 ↗
🔗 播放清單原始網址:點此開啟 YouTube 播放清單完整頁面
14_參考資源.md
# 參考資源:三師爸「AI Agent 基本功」播放清單

> YouTube 網紅:**三師爸(Sense Bar)**
> 系列主題:AI Agent 基本功|學習 Agent 一定要看的一個系列
> 2026-08-18 抓取整理

## ▶️ 一鍵播放(照 EP01→EP07 觀看順序順播)

**https://www.youtube.com/watch?v=3s2Q1nViZ1w&list=PLkNQUBglz8EyGuOIKfrKX9V7ot5VbZKha**

> ⚠️ 注意:播放清單原始順序是**倒序**(EP07 在最上面),觀看請從 EP01 開始。

## 📺 觀看順序(EP01 → EP07)

| EP | 標題 | 直連 |
|---|---|---|
| EP01 | 用 Agent 來學習 Agent:一個 GitHub repo,複製我的整套 AI 工作流到你的 Agent | https://youtu.be/3s2Q1nViZ1w |
| EP02 | 學習 Agent 必懂的核心觀念與初始化設定 | https://youtu.be/8nwjYouFJoE |
| EP03 | 一句話讓 AI 幫你讀檔、寫程式、上網、做出成品 | https://youtu.be/b8YgyYGjJEU |
| EP04 | 連接外部工具:MCP 與連接器一張地圖講清楚 | https://youtu.be/-kIzGOf0bZA |
| EP05 | 三層一次講清楚:搞定 技能/全域/專案 | https://youtu.be/b4gIauc_hxo |
| EP06 | 跨 Agent、跨電腦協作同一個專案,設定觀念一次到位 | https://youtu.be/mnFdJaAmeUM |
| EP07 | 需求探索四象限法,釐清專案需求的最後一塊拼圖 | https://youtu.be/nL4OAYdJ3Ew |

## 📌 對鶴仁導入 Hermes 的對應建議

| 三師爸集數 | 對應鶴仁情境 |
|---|---|
| EP01(複製工作流)| 直接套用山姆大叔的 Hermes 環境設定 |
| EP02(核心觀念)| 理解 Agent 怎麼運作(初次上手必看)|
| EP03(一句話做成品)| 飯店公文、簡報、數據分析的直覺用法 |
| EP04(MCP 連接器)| Hermes 接外部工具(未來擴充)|
| EP05(技能/全域/專案三層)| Hermes 的 Skills/全域設定/專案 context |
| EP06(跨 Agent 協作)| Hermes+AntiGravity+OpenCode 三 Agent 協同 |
| EP07(需求探索四象限)| 鶴仁導入前釐清需求(建議最後看)|

## 播放清單原始連結

https://www.youtube.com/playlist?list=PLkNQUBglz8EyGuOIKfrKX9V7ot5VbZKha
Module 9 | 現場驗收

現場教學驗收清單 & 攜帶清單

點擊可即時勾選,進度將自動保存在瀏覽器中。

✅ 現場實作驗收清單

  • iPhone 17 安裝必裝 5 個 App + 完成首步設定
  • Windows 11 筆電成功啟動 hermes 命令列
  • Obsidian Vault 成功同步至 Google 雲端磁碟
  • Chrome 擴充插件 3 個必裝(Web Clipper / Voyager / Gemini 匯出)已啟用
  • 01_SOUL.md 人設注入完成,特助口吻確認
  • 會議速記提取 Skill 實測成功產出行動矩陣
  • ChatGPT 歷史專案對話成功匯出並批次轉為 Obsidian 筆記
  • Discord 專屬伺服器建立完成,Hermes bot 顯示綠燈在線
  • 手機 Discord 實測發送 @Hermes 指令並收到回覆
  • 設定好 #晨報 頻道,Cron 08:00 定時排程已啟用

🎒 山姆大叔攜帶清單

  • 筆電 + 電源充電器(確認鶴仁與山姆大叔均備妥)
  • 本教學素材包(已上傳 Google 雲端硬碟,隨時可存取)
  • LLM API Key 金鑰(OpenCode / DeepSeek)
  • 現場網路確認(Block & Cafe Wi-Fi / 手機熱點備援)
  • ChatGPT 匯出工具包(4 支 JS/Py 工具檔案備妥)
  • 手機與筆電軟體安裝清單(12_手機App + 13_筆電軟體)
  • Discord 安裝包與 Hermes bot 邀請連結
  • 三師爸 AI Agent 基本功播放清單備用
Module 10 | 素材清單

教學素材包檔案索引(14 份文件 + 4 支工具)

所有檔案均保存在 G:/我的雲端硬碟/Hermes Agent Desktop HOME/鶴仁導入教學素材包/。依照 00_README.md 結構重新整理,前置準備文件置頂。

檔案名稱 類型 用途說明與對應單元
12_手機App安裝清單.md ⭐ 準備① iPhone 17 必裝/選裝清單+直連 App Store 連結+DriveSync/OpenCode 避坑替代方案。
13_筆電軟體安裝清單.md ⭐ 準備② Win11 筆電必裝 6 項+Chrome 擴充細項(含直連)+選配+安裝順序與驗收。
00_README.md 導讀索引 導入背景、素材清單總覽、前置準備指引與核心架構方針說明。
01_SOUL.md 角色人設 鶴仁版飯店營運特助角色設定,匯入 Hermes 即可立即生效。
02_meeting_action_extractor.yaml 核心 Skill 會議速記 → 自動提取責任人、期限與待辦行動矩陣。
03_hotel_milestone_tracker.yaml 核心 Skill 華邑酒店籌備關鍵里程碑追蹤、工期衝擊與預警分析。
04_morning_briefing_tracker.md 定時 Skill 晨間 08:00 營運情報自動推播(含標準 Cron JSON 設定)。
05_evening_settlement_tracker.md 定時 Skill 晚間 22:30 營運結算與未完待辦自動推播(含 Cron JSON)。
06_安裝步驟一頁紙.md 落地 SOP 筆電安裝 Hermes + Obsidian + Google 雲端硬碟 6 步驟。
07_現場教學流程.md 教學流程 3.5 小時 7 段實作時間表、示範演練腳本與攜帶清單。
08_Google日曆匯入.md 行事曆 8/19 現場教學場次一鍵匯入 Google 日曆。
09_ChatGPT對話匯出教學.md 轉檔閉環 ChatGPT 專案對話匯出與 Hermes 批次轉檔入 Obsidian 教學。
10_Hermes_vs_Codex評估.md 決策評估 Hermes Agent vs Codex 深度比較、5大需求檢驗與 3-Agent 協同架構。
11_Discord安裝設定教學.md 通訊介面 ⭐ 通訊軟體首選 Discord:為什麼選 Discord、安裝步驟、建伺服器、bot 加入、日常使用。
14_參考資源.md 影音專題 🎬 三師爸 AI Agent 基本功播放清單(EP01-07)+一鍵播放+每集對應鶴仁情境建議。
ChatGPT匯出工具/ 工具包 包含 chatgpt_export_all.js、convert_chatgpt_to_obsidian.py 等 4 支核心工具。