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

Hermes Agent 現場導入教學手冊

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

12 份
現場精編教材與設定檔
4 支
ChatGPT 匯出與轉檔工具
5 大
現場手把手實作演練
3 Agent
協同生態(Hermes+AGY+OpenCode)
Module 1 | 架構方針

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

山姆大叔設計方針:將「資料存儲」、「認知運算」與「通訊互動」完全分開,資料 100% 掌握在自己手中,隨時可搬遷,不被任何單一雲端巨頭綁架。

1. Hermes Agent(筆電端大腦)

安裝於鶴仁筆電,扮演 24 小時數位特助與指揮官。

  • SOUL.md 定位:具備飯店營運思維、主動追蹤里程碑、精確輸出表格。
  • Cron 定時排程:早上 08:00 晨報、晚間 22:30 結算自動推播。
  • MCP 外部查證:可外接聯網查詢、計算與檔案處理工具。
  • 模型自由切換:DeepSeek / OpenCode / Claude 彈性路由。

2. Obsidian + Google Drive(第二大腦)

Vault 存放在 Google 雲端硬碟同步目錄,純文字 Markdown 格式。

  • 資料隨身同步:筆電、手機、辦公室電腦隨時存取最新筆記。
  • 分層資料庫:00_Inbox(收件箱)、Clippings(剪藏)、知識庫、專案庫。
  • ChatGPT 歷史入庫:歷史專案對話批次轉入 Obsidian 分類管理。
  • 完全可搬遷:即使未來換 Agent,所有知識產出依然完整留存。

3. Discord(行動端走動管理)

鶴仁在飯店現場巡場、開會走動時的最佳交辦與接收介面。

  • 隨時語音交辦:直接發送語音訊息,Hermes 自動轉文字消化。
  • 頻道分流管理:#一般(交辦問答)、#晨報(定時營運推播)。
  • 獨立專屬伺服器:一人一伺服器,Hermes bot 24/7 常駐。
  • 全平台免費 App:iOS / Android / Mac / Windows 全覆蓋。
💡 系統架構亮點:隨時可搬遷、極低維護成本
鶴仁的所有資料都在本地 Markdown 與雲端硬碟,模型調用走 API(月成本僅 $0–5),不需要每個月付 $20 被 OpenAI 封閉生態綁定。未來無論擴充 AntiGravity 或是 OpenCode,都能無縫串接!
Module 2 | 現場節奏

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

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

時間 單元 內容與教學重點 對應素材與工具
19:00–19:15 開場 架構方針與痛點診斷
說明 Hermes Agent 能幫鶴仁做什麼(對照他現有十幾個混亂的 ChatGPT 對話串,從被動問答升級為主動特助)。
00_README.md
19:15–20:00 實作① 筆電環境安裝與模型路由
安裝 Hermes Agent、配置 API Key(OpenCode / DeepSeek)、測試模型連線與基礎指令。
06_安裝步驟一頁紙.md
20:00–20:40 實作② 第二大腦與擴充工具建置
Obsidian vault 建立 + Google 雲端磁碟同步目錄 + 瀏覽器擴充安裝(必裝 3 個 + 選配 3 個)。
06 Step 6 + 擴充清單
20:40–21:15 實作③ SOUL 人格注入與 4 大 Skills 實測
匯入 01_SOUL.md + 4 個營運專屬 Skill + 示範 Cron 晨報(08:00)與晚報(22:30)定時排程。
01–05 素材檔
21:15–21:40 實作④ ChatGPT 歷史對話一鍵匯出與入庫
鶴仁瀏覽器 Console 執行 chatgpt_export_all.js → 下載 3 檔 → Google Drive → Hermes 批次轉檔入 Obsidian。
09_ChatGPT對話匯出教學.md + 工具包
21:40–22:00 實作⑤ ⭐ Discord 通訊介面安裝與手機聯動
安裝手機/電腦 Discord App + 建立「鶴仁營運助理」伺服器 + 邀請 Hermes bot 加入 + 實測 @Hermes 手機交辦與晨報頻道接收。
11_Discord安裝設定教學.md
22:00–22:30 收尾 總體驗收、Q&A 與雲端搬遷說明
驗證 6 項核心功能、解答鶴仁實務疑問、說明未來升級與雲端伺服器搬遷路徑。
現場驗收清單

🎭 現場示範腳本(實作③ 現場演練步驟)

  1. 驗證 SOUL 生效:貼上 01_SOUL.md 後,輸入 你是誰?你的工作職責是什麼? → 確認回答為「鶴仁的飯店營運特助」。
  2. 測試會議速記萃取:丟入一段雜亂的會議對話 → 自動觸發 02_meeting_action_extractor 產出包含負責人、期限、優先級的四欄行動矩陣。
  3. 測試籌備里程碑追蹤:輸入 3 樓客房天花板驗收沒過,廠商延後 5 天交場 → 觸發 03_hotel_milestone_tracker 自動計算對試營運日期的衝擊並標記紅色警戒。
  4. 測試 Cron 排程:匯入 04/05 JSON 設定,說明明天早上 08:00 Discord 晨報頻道將自動推播第一份晨間營運摘要。
📅 Google 行事曆一鍵匯入(08_Google日曆匯入.md)
現場教學行程已整理為標準日曆格式,包含活動時間(2026-08-19 19:00–22:30)、地點(Block & Cafe)與攜帶清單,可直接點擊或掃描加入手機 Google 日曆。
Module 3 | 工具武裝

瀏覽器擴充工具配置清單

鶴仁日常在筆電端瀏覽網頁、分析資料與處理資訊時的 6 大利器(3 必裝 + 3 選配)。

🌟 必裝 3 大擴充套件

現場必裝
1. Obsidian Web Clipper

官方網頁剪藏工具,一鍵將飯店法規、競品報導、裝潢素材存入 Clippings 資料夾。

2. Voyager

AI 輔助分頁管理與網頁內容理解,快速提煉長篇採購規格書與市場調研。

3. Gemini 對話匯出擴充

一鍵將 Gemini 深度分析、營運對話結構化匯出成 Markdown 存入筆記庫。

💡 選配 3 大影音與學習擴充

視需求安裝
1. YouTube Dubbing

國際飯店管理、米其林餐飲海外教學影片即時 AI 繁體中文配音。

2. YouTube to Notebook

一鍵將 YouTube 演講、教學影片轉為結構化逐字稿與重點筆記。

3. 螢幕錄影工具(Screen Recorder)

快速錄製飯店 PMS 系統操作或異常回報影片,方便交辦給 Hermes 或同仁。

Module 4 | 落地安裝

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

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

1
檢查基礎環境(Git Bash / Node / Python)
Windows 筆電確認具備 Git Bash 終端環境,以便執行命令列與套件管理。
終端機指令
git --version && node -v && python --version
2
安裝與啟動 Hermes Agent
下載並安裝 Hermes Agent Desktop / CLI,初始化工作空間。
安裝指令
npm install -g hermes-agent-cli
# 或解壓縮 Hermes Agent Desktop 執行檔進行安裝
3
設定 API Provider 與模型路由
配置山姆大叔提供的 OpenCode / DeepSeek API 金鑰,設定為預設模型。
設定範例
hermes config set model.default "deepseek-chat"
hermes config set provider.opencode.key "YOUR_API_KEY"
4
建立 Obsidian Vault 於 Google 雲端硬碟
在 Google 雲端磁碟同步資料夾內建立 鶴仁第二大腦/,並建立 00_InboxClippings知識庫專案庫 等基礎目錄。
5
匯入 01_SOUL.md 與 4 大 Skills
將角色定義與專屬營運技能注入 Hermes Agent 設定目錄,立即賦予特助專業能力。
6
安裝瀏覽器擴充套件
安裝 Obsidian Web Clipper、Voyager 與 Gemini 匯出工具,打通知識捕捉管道。
Module 5 | 人格注入

角色設定:鶴仁專屬 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 6 | 專屬技能

飯店營運 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 7 | 歷史對話資產

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

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

① 鶴仁匯出(1分鐘)

登入 ChatGPT → 按 F12 打開 Console → 貼上 chatgpt_export_all.js → Enter 執行,自動下載完整對話 JSON 與索引。

② 放進共享資料夾

將下載的 JSON 檔案放入 Google 雲端硬碟共享資料夾中,路徑同步給 Hermes。

③ Hermes 批次轉檔

Hermes 執行 convert_chatgpt_to_obsidian.py,依專案自動分組建立資料夾與索引,完美存入 Obsidian!

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()
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,開源)
Module 8 | 通訊首選 山姆大叔首選推薦

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

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

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

📱

手機/電腦全平台

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

即時推播不漏接

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

🎙️

語音訊息隨手交辦

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

🛡️

伺服器架構常駐

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

💰

個人使用完全免費

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

📁

頻道分流結構分明

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

🚀 現場 4 步驟安裝與配置指南

1
Step 1:下載與安裝 Discord(手機 + 筆電)
手機端:App Store(iOS)或 Google Play(Android)搜尋「Discord」並下載安裝。
電腦端:前往 https://discord.com/download 下載 Windows 桌面版安裝。
註冊帳號:以 Email 註冊、設定密碼並完成手機號碼簡訊驗證。
2
Step 2:建立專屬伺服器(或加入山姆大叔伺服器)
可依照現場需求選擇自建專屬伺服器(推薦)或加入現有伺服器:
  1. 點擊 Discord 介面左側欄最上方的 「+」(新增伺服器)。
  2. 選擇 「建立自己的伺服器」 → 命名為 鶴仁營運助理鶴仁特助空間
  3. 建立後系統會自動生成 #一般 頻道,並建議手動新增一個 #晨報 文字頻道。
  1. 由山姆大叔現場發送伺服器邀請連結(或提供 QR Code)。
  2. 鶴仁手機/電腦點擊邀請連結 → 點擊「接受邀請」即可加入。
3
Step 3:邀請 Hermes Agent(bot)加入伺服器
⚠️ 此步驟由山姆大叔在 Hermes 設定端授權,鶴仁只需在介面確認:
1. 山姆大叔將 Hermes bot 授權連結加入鶴仁的伺服器。
2. 伺服器右側成員名單出現 Hermes Agent,頭像顯示綠色 「在線」 狀態即代表連線成功!
3. 確認 Hermes bot 擁有 #一般#晨報 頻道的讀寫與發言權限。
4
Step 4:日常使用與交辦指令範例
現場由鶴仁拿起手機,在 Discord 頻道中直接實測發送指令:
#一般 頻道(交辦範例)
@Hermes 幫我整理今天的早會筆記,產出行動清單
@Hermes 華邑酒店的廠商合約進度如何?
@Hermes 請根據最新客房營收數據,分析週末住房率異常原因
#晨報 頻道(自動推播)
# 每天早上 08:00 定時由 Hermes cron 自動推播:
[晨間營運摘要]
1. 華邑酒店籌備剩餘 42 天,天花板驗收待追蹤(逾期 2 天)
2. 今日待辦事項:工務合約簽核、主管會報材料確認
3. 風險預警:週末預估滿房,布草備品庫存偏低

📊 Hermes 支援通訊平台綜合比較表

通訊平台 推薦評級 優勢與適用情境 限制與考量
Discord 首選 ⭐ 全平台原生 App、支援語音訊息、伺服器架構頻道分流、cron 排程推播極度穩定、完全免費。 首次建立伺服器需 2 分鐘基礎設定。
Telegram 備用 輕量、啟動極快、Bot 生態系成熟、API 串接簡單。 部分企業網路會封鎖、台灣主流使用者較少。
Signal 隱私備用 端到端高強度隱私加密。 Bot 支援較受限,鶴仁日常營運暫無極端保密需求。
LINE 不建議 台灣大眾最慣用通訊軟體。 LINE Official Account Bot 設定極度繁瑣、推播訊息有額度收費限制。
📄 點擊展開:查看 11_Discord安裝設定教學.md 原始檔案內容
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 9 | 決策評估

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

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

⚡ 30 秒核心結論
Codex 是「寫程式的 Agent」,Hermes 是「辦事的 Agent」。
鶴仁要的是「丟訊息 → 自動辦事、追蹤、推播」,而不是「打開黑色終端機 → 看程式碼跑腳本」。

📊 本質定位對比

面向 Hermes Agent(本次導入) Codex(ChatGPT Plus)
目標使用者 一般使用者/營運管理者 專業開發者/工程師
操作介面 Discord / Telegram 等通訊軟體 CLI 終端機 + IDE 編輯器
交辦方式 「傳訊息/語音」即可(手機走動管理) 打字下程式指令、看終端輸出
主要任務 整理、追蹤、回報、排程、推播 寫程式碼、除錯、重構程式庫
主動性 cron 定時主動推播(晨報/提醒) ❌ 被動等待使用者下指令
學習成本 極低(像傳訊息給真人特助) 高(需懂 terminal / 專案環境)
中文支援 ✅ 繁體中文原生優化 ⚠️ 程式語境與英文為主
經驗沉澱 ✅ Skills 知識庫自動累積 較弱(每次 session 需重給 context)

🎯 鶴仁 5 大實際需求逐項檢驗

鶴仁實際營運需求 Hermes Agent Codex
1. 早會筆記 → 行動清單 ✅ 丟語音/文字訊息直接出行動表格 ❌ 非其主要設計用途
2. 華邑酒店籌備里程碑追蹤 ✅ cron 主動盯逾期與衝擊預警 ❌ 無定時主動排程概念
3. 營運數據與住房率異常分析 ✅ 丟數據直接出結論與對策 ⚠️ 需開特定環境跑分析腳本
4. 主管呈報與公文草稿 ✅ 模板化結構輸出 ❌ 無此公文排版設計
5. 手機巡場時隨手交辦 Discord 通訊軟體直達 ❌ 必須綁定在電腦螢幕前

💰 成本、風險與生態比較

項目 Hermes + OpenCode ChatGPT Plus + Codex
月成本 ≈ $0–5 / 月(DeepSeek 實價用量) $20 / 月固定月租
綁定風險 資料在本地 Obsidian(完全可搬遷) 對話資料鎖在 OpenAI 封閉生態
上手速度 今晚 3.5 小時教學即可正式上線 鶴仁得先學會終端機與開發環境
生態擴充性 可自由接 AntiGravity / OpenCode 封閉單一平台

🤝 附:三 Agent 協同生態(山姆大叔實戰推薦方案)

未來若鶴仁有更龐大的自動化與知識庫維護需求,可採用山姆大叔的三 Agent 協同架構:

3-Agent 架構拓撲圖
Hermes Agent(資料引擎 · 指揮官)
   │ agentapi CLI            │ A2A bridge
   ▼                          ▼
AntiGravity(知識大腦 · Gemini3.7)   OpenCode(開發工兵 · DS雙備援)
   └──────┬─────── 共用知識庫 ───────┘
          ▼
     Obsidian 第二大腦(Google 雲端磁碟同步)
Agent 定位角色 核心強項與職責
Hermes Agent 指揮官/資料引擎 cron 排程定時推播、MCP 工具查證、Obsidian 自動寫入、Discord 跨平台通訊。
AntiGravity 知識大腦 Gemini 3.7 Flash 文筆潤飾、知識庫架構重整、自動化教學網頁部署。
OpenCode 開發工兵 DeepSeek V4 雙備援 API、後台寫程式碼、跑轉檔自動化腳本。
Module 10 | 現場驗收

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

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

✅ 現場實作驗收清單

  • Hermes Agent 筆電端安裝成功且啟動正常
  • API Provider 設定完成,模型連線測試成功
  • Obsidian Vault 建立在 Google 雲端硬碟同步目錄
  • 必裝 3 大瀏覽器擴充套件安裝並測試成功
  • 01_SOUL.md 成功匯入,特助身份測試生效
  • 4 大 Skills 匯入完成,會議速記與華邑追蹤實測成功
  • ChatGPT 歷史專案對話成功匯出並批次轉入 Obsidian
  • Discord App(手機+筆電)安裝完成並註冊登入
  • Discord 專屬伺服器建立完成,Hermes bot 顯示在線
  • 手機 Discord 實測發送 @Hermes 指令並收到回覆
  • 設定好 #晨報 頻道,Cron 08:00 定時排程已啟用

🎒 山姆大叔攜帶清單

  • 筆電 + 電源充電器(確認鶴仁與山姆大叔均備妥)
  • 本教學素材包(已上傳 Google 雲端硬碟,隨時可存取)
  • LLM API Key 金鑰(OpenCode / DeepSeek)
  • 現場網路確認(Block & Cafe Wi-Fi / 手機熱點備援)
  • ChatGPT 匯出工具包(4 支 JS/Py 工具檔案備妥)
  • 瀏覽器擴充套件清單(Web Clipper / Voyager / Gemini)
  • Discord 安裝包與 Hermes bot 邀請連結
Module 11 | 素材清單

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

所有檔案均保存在 G:/我的雲端硬碟/Hermes Agent Desktop HOME/鶴仁導入教學素材包/

檔案名稱 類型 用途說明與對應單元
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 加入、日常使用。
ChatGPT匯出工具/ 工具包 包含 chatgpt_export_all.jsconvert_chatgpt_to_obsidian.py 等 4 支核心工具。