# 配置

每通道的凭证设置不同。下面按通道分别说明。config 文件（有的话）在 `~/.forge-hub/state/{channel}/`：微信 `account.json`、Telegram `config.json`、飞书走 `lark-cli` 系统 keychain、iMessage 无 config。

不配的通道在 hub 启动时会 skip，不影响其他通道（crash isolation）。启动日志会明确写 `⏭ 通道跳过: ...`，也不会计入“已加载通道”。

**每通道底层协议/API 都走官方**（forge-hub 做 CC 侧 bridge，不逆向不绕开官方）：

| 通道 | 底层协议/API（官方开放） | forge-hub 做什么 | 用户拿凭证方式 |
|---|---|---|---|
| Telegram | [Telegram Bot API](https://core.telegram.org/bots/api) | 直接调 Bot API（fetch + 长轮询），不依赖 grammy；和 [anthropics/telegram](https://github.com/anthropics/claude-plugins-official/tree/main/external_plugins/telegram) 官方单通道 plugin 平行 | [@BotFather](https://t.me/BotFather) token |
| iMessage | macOS chat.db + AppleScript | 读 chat.db 写 CC channel（和 [anthropics/imessage](https://github.com/anthropics/claude-plugins-official/tree/main/external_plugins/imessage) 平行） | Full Disk Access 权限（无 token）|
| 飞书 | [Lark Open API](https://open.larksuite.com/document) | shell out 到 [`@larksuite/cli`](https://www.npmjs.com/package/@larksuite/cli) 做 IO，自己写 CC channel 层（官方 [openclaw-lark](https://github.com/larksuite/openclaw-lark) 只对接 OpenClaw 不接 CC）| `lark-cli config init` |
| 微信 | Tencent iLink HTTP | 自己写 iLink client 调 API（官方 [openclaw-weixin](https://github.com/Tencent/openclaw-weixin) 只对接 OpenClaw 不接 CC） | [`@tencent-weixin/openclaw-weixin-cli`](https://www.npmjs.com/package/@tencent-weixin/openclaw-weixin-cli) 扫码拿 token |
| Homeland | 本地 HTTP + SSE | Dashboard / 本机 Operator 的本地通道，无外部平台依赖 | 无需凭证 |

---

> Homeland 本身没有外部凭证，但 Dashboard 里的本机 Operator 入口仍走 allowlist。首次想从浏览器发消息时，先执行：`fh hub allow homeland local://operator Operator`

## 微信（ClawBot / iLink，腾讯官方协议）

ClawBot + iLink 是微信官方开放的个人号 bot 接入协议（API base `https://ilinkai.weixin.qq.com`）。forge-hub 的 `wechat.ts` 插件直接调这个 API——**不逆向、不模拟、不走第三方付费服务**。

**推荐：一键配置**

```bash
fh hub setup wechat
```

终端显示二维码，微信扫码确认后自动写入凭证、allowlist、approval owner——零手工编辑。

**手动路径**（二选一）：

```bash
# 路径 A：Tencent 官方 CLI
npx -y @tencent-weixin/openclaw-weixin-cli@latest install

# 路径 B：社区 npm 包 claude-code-wechat
npx claude-code-wechat setup
```

手动拿到 token 后写入 config：

```bash
mkdir -p ~/.forge-hub/state/wechat
cat > ~/.forge-hub/state/wechat/account.json <<EOF
{
  "baseUrl": "https://ilinkai.weixin.qq.com",
  "token": "<FROM_SETUP_CLI>"
}
EOF
```

> 如果你不需要 hub-level 多实例路由 + 统一审批，**直接跑 `@tencent-weixin/openclaw-weixin-cli` 或 `claude-code-wechat` 作为独立 MCP channel 就够了**——forge-hub 的定位是整合 5 通道（4 个外部通道 + 1 个本地 Homeland）到一个 hub，不是替代单通道方案。

**Allowlist**（哪些微信号能跟 bot 对话）：

```bash
fh hub allow wechat <对方的_wechat_sender_id>@im.wechat <昵称>
```

第一条加到 allowlist 后这个 bot 才会路由对方的消息进 Claude Code。`<sender_id>` 你可以从微信发条消息给 bot，看 hub log 里的 inbound entry 拿到。

---

## Telegram（Bot API）

**前置**：用 [@BotFather](https://t.me/BotFather) 创建一个 bot，拿到 bot_token。完整 Bot API 文档：<https://core.telegram.org/bots/api>。

```bash
mkdir -p ~/.forge-hub/state/telegram
cat > ~/.forge-hub/state/telegram/config.json <<EOF
{
  "bot_token": "<BotFather_GIVEN_TOKEN>"
}
EOF
```

**代理（中国大陆需要）**：Telegram API 在境内通常不通，Hub 会读 `https_proxy` / `http_proxy` env var。加到 launchd plist 见 [加 env 到 Hub 进程](#加-env-到-hub-进程)（替换 `7897` 为你实际的代理端口）。

**Allowlist**：你跟自己 bot 发条消息，从 hub log 看到 Telegram chat_id（一串数字），然后：

```bash
fh hub allow telegram <chat_id> <昵称>
```

---

## 飞书 / Lark

**前置**：

1. 装 lark-cli：`npm install -g @larksuite/cli`（[npm 包](https://www.npmjs.com/package/@larksuite/cli) · [lark-cli GitHub](https://github.com/larksuite/cli)）
2. 在[飞书开发者后台](https://open.feishu.cn/app)（国内）/ [Lark Developer](https://open.larksuite.com/app)（海外）创建企业自建应用，拿到 `app_id` 和 `app_secret`
3. 给应用加权限：im:message 收发、event:订阅 im 事件等（权限列表见 [飞书开放平台文档](https://open.larksuite.com/document)）

**飞书开发者后台的三个常见坑**（按顺序踩）：

- **`im.message.receive_v1` 事件下的"关联权限"必须在该事件面板单独点开**——只在"权限管理"开 `im:message` 不够。漏开的话飞书不推 `im.message.receive_v1`，hub 收不到任何消息。
- **机器人能力要在"应用能力" → "机器人" 里启用**——否则用户在飞书里搜不到该应用的对话入口，也就没法给 bot 发消息。
- **改事件 / 权限后必须创建并发布新版本**——只"保存配置"不会立即生效。第一次配完能跑 `lark-cli auth status` 但收不到事件，多半是这条。

**配凭证**：用 lark-cli 自己的 config init（写 keychain）：

```bash
echo -n '<APP_SECRET>' | lark-cli config init \
  --app-id <APP_ID> \
  --app-secret-stdin \
  --brand feishu

# 验证
lark-cli auth status
```

> ⚠️ **重要**：必须用 `lark-cli config init` 写入凭证，**不要**用 `security add-generic-password` 直接写——lark-cli 用 keytar 内部 ACL，直接写的 keychain 它读不到。

**Hub 这边不需要 config.json**（lark-cli 内部管理凭证）。Hub 启动时 feishu 通道会自动调 `lark-cli auth status` 检查。

**Allowlist**：自己用飞书私聊给 bot 发消息，从 hub log 看 inbound 的 sender_id：

```bash
fh hub allow feishu <id> <昵称>
```

飞书 ID 有两种前缀——用户的 open_id 是 `ou_` 前缀，群聊的 chat_id 是 `oc_` 前缀。Allowlist 支持两种授权粒度：

- **个人授权**（`ou_` user open_id）：只有这一个人能和 Claude 说话。安全面最小。
- **群授权**（`oc_` group chat_id）：这个群里**所有现在和未来的成员**都能和 Claude 说话。

> ⚠️ **群授权的安全语义**：把 `oc_` 群 ID 加进 allowlist，等于授权**整个群**——不是你现在认识的那几个群友。如果群主后续拉了新人进群（包括陌生人），**新人也自动有权限**和 Claude 说话。
>
> 适合**你完全信任群主**的小群（家人 / 核心团队 / 你自己建的群）；陌生人可能被拉进来的群（开源讨论群 / 大规模社区群），建议还是用 `ou_` 个人授权一个一个加。

Hub 出站时自动按前缀路由：`ou_` 走 `--user-id` 私聊路径，`oc_` 走 `--chat-id` 回群。

---

## iMessage（macOS only）

**前置**：

1. 给你的 terminal app（Terminal.app / iTerm / Ghostty / 等）开 **Full Disk Access**：
   - 系统设置 → 隐私与安全性 → 完全磁盘访问
   - 加上你的 terminal app
   - 否则 hub 读不了 `~/Library/Messages/chat.db`

2. 给 macOS Messages app 配置好账号（iMessage account / iCloud）

**配置**：iMessage 通道**没有** `config.json` —— 用 macOS 原生 chat.db 拉消息 + AppleScript 发消息，自动检测自身地址。

```bash
mkdir -p ~/.forge-hub/state/imessage
# 不需要写 config 文件，hub 会自动启动 iMessage 通道
```

**Allowlist**：

```bash
fh hub allow imessage <对方的电话号 / Apple ID> <昵称>
# 例：
fh hub allow imessage +13235551234 "妈"
fh hub allow imessage your-friend@icloud.com "朋友"
```

---

## 高级 / 共通配置

### 远程审批通道选择

`~/.forge-hub/hub-config.json`：

```json
{
  "port": 9900,
  "host": "127.0.0.1",
  "approval_channels": ["wechat", "feishu"],
  "approval_push_mode": "parallel",
  "agent_name": "Forge",
  "search_index": false
}
```

- `approval_channels`: 哪些通道收到审批请求会 push
- `approval_push_mode`: `parallel` (default, 并发推) 或 `sequential` (按顺序，第一个成功就停)
- `agent_name` (string, 可选): 出站消息的发送者名称。优先级：`HUB_AGENT_NAME` env > `agent_name` config > `"Forge"`
- `search_index` (boolean, 可选, default `false`): 启用 FTS5 全文搜索索引。启用后 `appendHistory` 同步写入 SQLite（`~/.forge-hub/search.db`），提供毫秒级关键字搜索（`GET /search`）

每个审批通道还需要显式 owner：

```bash
fh hub owner wechat <id或昵称>
fh hub owner feishu <id或昵称>
```

owner 会写入该通道的 `state/<channel>/allowlist.json`：

```json
{
  "allowed": [{ "id": "<平台原生ID>", "nickname": "用户" }],
  "approval_owner_id": "<平台原生ID>"
}
```

普通聊天仍按整个 allowlist 放行；审批回复只接受 `approval_owner_id` 对应的人。

### 语音支持矩阵

入站 = 用户 → Claude，出站 = Claude → 用户：

| 通道 | 入站 | 出站 |
|------|------|------|
| **微信** | ✅ iLink 内置 ASR，自动送 agent 文本 | 🟡 mp3 文件附件（非原生语音条，iLink 协议限制） |
| **Telegram** | 🟡 Hub 下载 ogg → 配 `FORGE_HUB_ASR_HOOK` 走 ASR 识别成文本；没配则给 agent `[语音] ${path}` 占位 | ✅ ogg/opus 原生语音条 |
| **飞书** | 🟡 Hub 下载 audio → 配 ASR hook 识别成文本（参考 `examples/feishu-stream-asr.ts` 接飞书原生 `stream_recognize`，或用 Whisper / 其他）| ✅ ogg/opus 原生语音条 |
| **iMessage** | 🟡 attachment `.caf` 路径 → 配 ASR hook 识别成文本 | ❌ 未实现（capability 里未声明） |

**出站设计**：Hub 层统一合成 ogg/opus（`ffmpeg libopus`）发 TG / 飞书 → 原生语音条。微信因 iLink 协议不接 silk 原生语音，降级为 mp3 附件。iMessage 未实现。

**入站识别**：只有微信平台**自带** ASR（iLink 送 text）。其他通道需用户配 `FORGE_HUB_ASR_HOOK` 接自选 ASR 服务（Whisper / MiniMax / 本地 whisper.cpp / 飞书 speech_to_text 等）——详见下方 [ASR hook](#asr-hook可选--语音入站识别)。未配 hook 时 agent 只看到 `[语音] ${path}` 占位，能自觉 pivot 到请用户打字。

---

### ASR hook（可选 / 语音入站识别）

不配 = 用户给 bot 发语音时 agent 只看到 `[语音] ${path}` 占位，读不到内容（只能 pivot 让用户打字）。

**架构**：和 TTS hook 对称——用户自写 shell 脚本，Hub 调它。Hub 不绑定任何 ASR provider（Whisper / MiniMax / 飞书 speech_to_text / 本地 whisper.cpp / 都行）。

**Hook 协议**：

- 参数：
  - `$1` = 音频文件绝对路径（`.ogg` / `.caf` / `.mp3` / `.wav` 等，各通道原生格式不同）
  - `$2` = 期望输出的纯文本文件路径（UTF-8）
  - `$3` = 通道名（`telegram` / `feishu` / `imessage`——按通道分流 ASR provider）
- 责任：调 ASR 服务，把识别到的文字写到 `$2`
- 成功判定：exit 0 **且** `$2` 非空

**启用**：
```bash
export FORGE_HUB_ASR_HOOK=/path/to/your-asr-hook.sh
```
加到 launchd plist 的 EnvironmentVariables（见 [加 env 到 Hub 进程](#加-env-到-hub-进程)）。

**参考示例**：见 [examples/asr-hook.sh](examples/asr-hook.sh)——含 OpenAI Whisper / 本地 whisper.cpp / per-channel 分流 三种模式。

**Channel plugin 自带 ASR**：某些通道插件未来可能实现 `asrTranscribe` method 自带 ASR 能力（参考 [channel-plugin-guide.md](hub-docs/channel-plugin-guide.md)）。Hub 默认**优先**用 plugin 自带的，plugin 没实现才 fallback 到 `FORGE_HUB_ASR_HOOK`。

**强制覆盖 plugin 自带**（想走 hook 而不是 plugin 自带）：`hub-config.json` 加

```json
{
  "asr_plugin_override": {
    "feishu": "hook"
  }
}
```

**关闭某通道 ASR**（agent 只拿 `[语音] ${path}` 占位）：

```json
{
  "asr_plugin_override": {
    "imessage": "none"
  }
}
```

---

### TTS hook（可选 / 语音发送功能）

不配 = 语音发送不可用。详细协议见 `hub-server/tts.ts` 顶部注释。

**Hook 协议**：

- 参数：`$1 = text（UTF-8）`，`$2 = 期望输出的 mp3 绝对路径`
- 责任：你的 hook 负责调你选的 TTS 服务，把合成的 **mp3 binary** 写到 `$2`
- 成功判定：exit 0 **且** `$2` 文件存在
- Hub 随后用 ffmpeg 把 mp3 转 ogg 发出去

**实现提示**：不是 `curl -o $OUTPUT`——多数 TTS API 返回 JSON 包含 base64 编码的 audio，需要 decode 再写文件。例子：

```bash
#!/bin/bash
TEXT="$1"
OUTPUT="$2"
# 伪代码——按你的 TTS provider API shape 改
curl -sS -X POST https://api.example.com/tts \
  -H "Authorization: Bearer $MY_TTS_API_KEY" \
  -d "{\"text\": \"$TEXT\"}" \
  | jq -r '.audio_base64' | base64 -d > "$OUTPUT"
test -s "$OUTPUT"  # 确认非空
```

**启用**：`export FORGE_HUB_TTS_HOOK=/path/to/your-hook.sh`，加到 launchd plist（见 [加 env 到 Hub 进程](#加-env-到-hub-进程)）。

### 锁定暗号（panic button）

```bash
fh hub set-lock-phrase "我的紧急暗号"
```

之后任何通道收到内容**完全等于**这个暗号的消息，hub 立刻冻结所有通道。`fh hub unlock` 解锁。

### Hub API token（可选，HTTP 鉴权）

```bash
export HUB_API_TOKEN=$(openssl rand -hex 32)
```

设了之后 Hub 的受保护端点都要求 token。CLI / 脚本走 `Authorization: Bearer <token>`，WebSocket 走 `?token=<token>`；浏览器 Dashboard 的静态页仍可直接打开，但第一次读取实例、审批或 Homeland 数据时会提示输入 token，成功后由 Hub 写入 HttpOnly cookie 供后续 API / SSE 复用。公开健康检查只保留 `/health` 的最小状态；详细 `/status` 需要鉴权。不设 = 默认信任 localhost（单用户 Mac 可以，多用户 / 容器场景必设）。

token 的读取顺序是：先读进程环境变量 `HUB_API_TOKEN`，再读 `~/.forge-hub/api-token` 文件。`forge-hub install` 如果在环境里看到 `HUB_API_TOKEN`，会把它同步写入 `~/.forge-hub/api-token`（chmod 600），这样 Hub、`fh` CLI 和 Claude Code 启动的 `hub-client` 即使不继承同一份 shell env，也能读到同一个 token。

手动轮换 token 时，如果 launchd plist 里也设置了 `HUB_API_TOKEN`，它会优先于文件；要么同步更新 plist 和 `api-token` 文件，要么移除 plist 里的 env，只保留 `api-token` 文件作为单一来源。

Env 加到 launchd plist 见 [加 env 到 Hub 进程](#加-env-到-hub-进程)。

### 环境变量参考

Hub Server 读取的主要环境变量：

| 环境变量 | 说明 | 默认值 |
|----------|------|--------|
| `HUB_AGENT_NAME` | 出站消息的发送者名称。优先级最高，覆盖 `hub-config.json` 的 `agent_name` | — (fallback 到 config 或 `"Forge"`) |
| `FORGE_HUB_DIR` | Hub 运行时根目录 | `~/.forge-hub` |
| `FORGE_HUB_CHANNELS_DIR` | 通道插件目录 | `$FORGE_HUB_DIR/channels` |
| `HUB_API_TOKEN` | HTTP / WebSocket 鉴权 token | — (不设 = 信任 localhost) |
| `FORGE_HUB_ASR_HOOK` | ASR 识别 shell 脚本路径 | — |
| `FORGE_HUB_TTS_HOOK` | TTS 合成 shell 脚本路径 | — |
| `FORGE_HUB_DEV` | 设为 `1` 启用热加载 | — |
| `FORGE_HUB_AUTH_MODE` | allowlist 二次确认方式（`prompt` / `touchid` / `none`） | `prompt` |
| `FORGE_FFMPEG_PATH` | 显式 ffmpeg 路径（优先于 PATH 搜索） | — |
| `FORGE_LARK_CLI` | 显式 lark-cli 路径（优先于 PATH 搜索） | — |
| `https_proxy` / `http_proxy` | 代理（Telegram 等需要） | — |

---

### 加 env 到 Hub 进程

Hub 由 launchd 启动，**不继承你 shell 的 env**——代理 / TTS_HOOK / HUB_API_TOKEN 都必须进 plist 的 `EnvironmentVariables`。

> [!IMPORTANT]
> **PATH 默认也不包含 Homebrew**——launchd 默认 PATH = `/usr/bin:/bin:/usr/sbin:/sbin`，所以 `ffmpeg` / `lark-cli` / 其他装在 `/opt/homebrew/bin`（Apple Silicon）或 `/usr/local/bin`（Intel Mac）的工具**找不到**。
>
> 如果你要用 TG/飞书语音（需要 ffmpeg）或飞书通道（需要 lark-cli），plist 的 `EnvironmentVariables` 里**必须**包含：
> ```xml
> <key>PATH</key>
> <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
> ```
> 或者显式指向二进制：`FORGE_FFMPEG_PATH=/opt/homebrew/bin/ffmpeg` 和 `FORGE_LARK_CLI=/opt/homebrew/lib/node_modules/@larksuite/cli/bin/lark-cli`（这两个 env 优先级高于 PATH 搜索）。

```bash
# 1. 编辑 plist
vim ~/Library/LaunchAgents/com.forge-hub.plist
# 在 <dict> 里加一段：
#   <key>EnvironmentVariables</key>
#   <dict>
#     <key>HUB_API_TOKEN</key>
#     <string>xxxxxx</string>
#     <key>https_proxy</key>
#     <string>http://127.0.0.1:7897</string>
#   </dict>

# 2. 重载让 launchd 读新 env
launchctl bootout gui/$(id -u)/com.forge-hub
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.forge-hub.plist

# 3. 验证
fh hub status   # 如果设了 HUB_API_TOKEN 且 CLI 没读到会 401
```

---

## 验证 / Troubleshoot

```bash
fh hub status            # Hub 健康 + 通道加载情况
fh hub channels          # 已加载哪些通道
fh hub allowlist <ch>    # 看某通道当前 allowlist
fh hub peers             # 在线 instance + 各自 subscribe 的通道

# 单通道 send 测试
fh hub send wechat <id_or_nickname> "测试"
fh hub send telegram <chat_id> "测试"
fh hub send feishu ou_xxxxx "测试"
fh hub send imessage +1234567890 "测试"
```

通道挂了看：
- `~/.forge-hub/hub.log` 和 `~/.forge-hub/hub-stderr.log` —— Hub server 全局
- 各 channel 的 polling/connect 错会在那里出现
