Spectrum + Hermes Agent 接入 iMessage 全記錄:從零到能用的踩坑實錄

Spectrum + Hermes Agent 接入 iMessage 全記錄:從零到能用的踩坑實錄

作者: Elric
日期: 2026-07-19
標籤: iMessage Spectrum Hermes Agent Bun TypeScript Docker 踩坑


前言

想把 iMessage 接入 AI 智能體?目前市面上幾乎沒有現成的方案——iMessage 是 Apple 的封閉生態,不像 Telegram/WhatsApp 有公開 Bot API。本文記錄了使用 Photon Spectrum Cloud 雲端橋接 + Hermes Agent 的回覆引擎,完整打通 iMessage → AI 回覆的全過程,以及途中遇到的所有坑。


架構總覽

1
2
3
4
5
6
7
8
9
10
11
12
iPhone iMessage

[Photon Spectrum Cloud] ← 雲端橋接,不需 Mac 長期在線

[自托管服務器 /opt/spectrum-bot]
├── Bun 1.3.14 運行時
├── spectrum-ts@11.2.0 SDK
└── index.ts 自訂邏輯

[Hermes Agent] ← 用 hermes -z 處理訊息並回覆

iMessage 回覆送到手機

第一步:環境準備

服務器信息

  • OS: Ubuntu 22.04 (Docker 宿主機)
  • IP: YOUR_SERVER_IP
  • Hermes Agent: v0.18.2(已預裝)
  • Node.js: v22.22.2

安裝 Bun

Photon Spectrum 的 SDK spectrum-ts 官方推薦使用 Bun 運行時:

1
2
3
4
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc
bun --version
# → 1.3.14

坑 #1: 如果服務器之前裝過舊版 Bun,記得 source ~/.bashrc 或重開終端,否則 PATH 沒更新。


第二步:創建 Spectrum Cloud 項目

註冊 Photon Spectrum

前往 app.photon.codes 註冊帳號。

坑 #2: 網站有 Cloudflare 人機驗證,從服務器的無頭瀏覽器很難通過!
解決方案: 用本地瀏覽器(你的 Mac/PC)手動訪問註冊,然後複製 Project ID 和 Secret。

獲取憑證

註冊成功後,在 Photon Dashboard 創建一個新項目,拿到:

1
2
SPECTRUM_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
SPECTRUM_PROJECT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

這兩個值就是你的 iMessage 橋接鑰匙。


第三步:初始化項目

1
2
3
4
mkdir -p /opt/spectrum-bot
cd /opt/spectrum-bot
bun init -y
bun add spectrum-ts@11.2.0

坑 #3: spectrum-ts 會依賴 @spectrum-ts/core,這個庫自帶 Zod v4,而 Zod v4 的 TypeScript 類型定義和 tsc 有兼容性問題。
現象: 跑 tsc --noEmit 會報 50+ 個類型錯誤。
處理: 完全忽略——Bun 運行時無視這些錯誤,能正常執行。

配置環境變量

1
2
3
4
cat > /opt/spectrum-bot/.env << 'EOF'
SPECTRUM_PROJECT_ID=你的ID
SPECTRUM_PROJECT_SECRET=你的SECRET
EOF

第四步:編寫 Bot(踩坑密集區)

初版:照搬 README(出事了)

1
2
3
4
5
// ❌ 錯誤示範 — 這份代碼不會工作
for await (const [space, message] of app.messages) {
console.log(`收到: ${message.text}`); // undefined
console.log(`來自: ${message.from.name}`); // undefined
}

這是我們遇到的第一個重大坑。

坑 #4: message.text 不存在

Spectrum 的 Message 類型沒有 text 屬性!訊息內容在 message.content 中。

正確結構:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ✅ Message 類型 (簡化)
interface Message {
content: Content; // ← 內容在這裡!
sender?: User; // ← 不是 from!
// ...
}

// Content 是聯合類型
interface TextContent {
type: "text";
text: string; // 文字內容在 content.text
}
interface MarkdownContent {
type: "markdown";
markdown: string;
}

正確取文字:

1
2
3
4
const contentType = message.content.type;
const text = contentType === "text" ? message.content.text
: contentType === "markdown" ? message.content.markdown
: JSON.stringify(message.content);

坑 #5: message.from 不存在

發送者是 message.sender 而不是 message.from

而且 User 類型非常簡潔:

1
2
3
4
5
interface User {
readonly __platform: string; // 平台標識
readonly id: string; // 用戶 ID(iMessage 裡是電話號碼)
readonly kind?: "agent"; // 可選
}

沒有 namedisplayName 等字段,只有 id。iMessage 場景下 id 就是對方的手機號。

正確用法:

1
const senderId = message.sender?.id || "unknown";  // → +852xxxxxxxx

第五步:接入 Hermes Agent(又一個坑)

坑 #6: hermes chat -q 輸出思考過程

一開始我用:

1
2
// ❌ 錯誤
const reply = await $`hermes chat -q "問題" -Q --cli`.text();

hermes chat 是交互模式,即使加上 -Q(安靜模式),仍然會輸出 Agent 的內部推理和工具調用計劃,而不是最終答案。

實際輸出是這樣的:

1
2
3
Let me search past sessions for any Messenger-related context first,
then check what capabilities are available regarding message handling
in Hermes Agent docs/skills to give you an accurate, grounded...

這顯然不是你想讓 iMessage 收到的回覆。

✅ 正確方案:用 hermes -z

1
2
hermes -z "你的問題"
# → 我叫 Hermes Agent,由 Nous Research 創造,你的智能助手。

-z 是 top-level 的單輪查詢模式,直接返回純文字答案,沒有多餘輸出,速度也更快。

最後的 Bot 代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { Spectrum } from "spectrum-ts";
import { imessage } from "spectrum-ts/providers/imessage";
import { $ } from "bun";

const app = await Spectrum({
projectId: process.env.SPECTRUM_PROJECT_ID!,
projectSecret: process.env.SPECTRUM_PROJECT_SECRET!,
providers: [imessage.config()],
});

console.log("🤖 Spectrum -> Hermes iMessage bridge running...");

for await (const [space, message] of app.messages) {
await space.responding(async () => {
const senderId = message.sender?.id || "unknown";
const contentType = message.content.type;
const text = contentType === "text" ? message.content.text
: contentType === "markdown" ? message.content.markdown
: JSON.stringify(message.content);

console.log(`📩 iMessage from ${senderId}: ${text}`);

// 關鍵一行:用 hermes -z 獲取 AI 回覆
const hermesReply = await $`hermes -z ${`[iMessage from ${senderId}]: ${text}`}`.text();
const cleanReply = hermesReply.trim();

console.log(`🤖 Hermes replies: ${cleanReply.slice(0, 200)}`);

await message.reply(cleanReply);
});
}

第六步:啟動和維護

啟動 Bot(後臺運行)

1
2
cd /opt/spectrum-bot
bun run index.ts &

建議用 Hermes Agent 的 cron 或 process 管理來保持長期運行。

驗證是否正常

發送一條 iMessage 到自己的號碼,觀察 Bot 日誌:

1
2
📩 iMessage from +852xxxxxxxx: 你好
🤖 Hermes replies: 你好!我是 Hermes Agent,有什麼可以幫你的?

踩坑總結

# 錯誤寫法 正確寫法
1 類型錯誤(Zod v4) 試圖修復 tsc 錯誤 忽略,Bun 能跑
2 Cloudflare 驗證 用服務器瀏覽器註冊 本地瀏覽器手動註冊
3 message.text 直接取 text message.content.text
4 message.from message.from.name message.sender?.id
5 User 無 name message.sender.name 只用 sender.id
6 Hermes 輸出推理過程 hermes chat -q hermes -z

結語

打通 iMessage + AI 的鏈路雖然折騰,但一旦跑通,體驗非常順暢。Photon Spectrum Cloud 解決了 iMessage 橋接最頭痛的「需要 Mac 長期在線」問題,而 Hermes Agent 提供了強大的 AI 處理能力。

後續可以擴展的功能:

  • 多輪對話上下文(用 hermes --resume 保持 session)
  • 指令系統(特定前綴觸發不同技能)
  • 圖片/附件處理

如果你也遇到了類似問題,或者有更好的方案,歡迎交流討論!