iLink Hub
iLink-compatible multiplexer hub for WeChat ClawBot — connect one WeChat account to multiple AI agent backends running on different machines or workspaces, with zero client-side code changes.
The Problem
WeChat ClawBot's iLink API enforces an exclusive lock: only one process can poll getupdates at a time. If you run Recursive on your Mac, Recursive on a server, and OpenClaw on your laptop — they all fight for the same connection, and only one wins.
The Solution
iLink Hub is a transparent iLink proxy:
[WeChat User]
↕ real iLink protocol
[iLink Hub] ← the sole connection holder
↕ emulated iLink API (same HTTP endpoints, same protocol)
┌───────────────┐ ┌────────────────────┐ ┌────────────────┐
│Recursive (Mac)│ │Recursive (Server) │ │OpenClaw (etc.) │
│base_url=hub │ │base_url=hub │ │base_url=hub │
│token=vhub_abc │ │token=vhub_def │ │token=vhub_xyz │
└───────────────┘ └────────────────────┘ └────────────────┘
Clients don't need any code changes — just point WEIXIN_BASE_URL at the Hub and use a virtual token. The Hub handles multiplexing, routing, and token mapping transparently.
Features
- iLink-compatible API — any existing iLink client works out-of-the-box
- Multi-backend routing — route messages to different backends via WeChat commands
- Context-token mapping — real context tokens never leak to clients; persisted across restarts
- QR code login — scan once, token saved to DB
- Multi-database — SQLite (default), PostgreSQL, MySQL via
DATABASE_URL - Full persistence — client registrations, routing state, and context mappings survive restarts
- Web admin panel — manage clients and copy config at
/hub/ui - Admin auth — protect
/hub/endpoints withILINK_ADMIN_TOKENenv var - Bounded queues — per-client message buffer capped at 200 to prevent OOM
- Prometheus metrics — counters and gauges at
/metrics - Friendly fallback — when all backends are offline, WeChat users get an instant reply
- Pre-built binaries — download from GitHub Releases (Linux/macOS/Windows), no Rust required
- Health checks — auto-marks offline clients after 90s idle
- CLI bridge (
ilink-hub-bridge) — connect as a Hub backend and run a local CLI per message (docs/bridge/README.md) - Docker support — single-command deployment, multi-arch image (amd64 + arm64)
Desktop app (Tauri)
A Tauri 2 desktop shell lives under desktop/ilink-hub-desktop/: it embeds the same run_serve runtime as ilink-hub serve (default listen 127.0.0.1:8765, SQLite under the OS app data dir). The root crate stays out of any workspace with this app, so cargo build / cargo test at the repo root are unchanged.
Prebuilt installers (.dmg / .msi / .deb, filenames prefixed with ilink-hub-desktop-) are attached to GitHub Releases when a v* tag is published (same workflow as CLI binaries). User-facing install notes: docs — 安装(桌面版). Dev commands and data paths: desktop/ilink-hub-desktop/README.md. Roadmap: docs/desktop-tauri-roadmap.md.
Quick Start
Option A: Pre-built Binary (fastest)
# Linux x86_64
&&
# macOS Apple Silicon
&&
# macOS Intel
&&
Windows: download
ilink-hub-windows-x86_64.exefrom Releases.
Option B: Cargo (requires Rust)
# Start Hub (QR login runs inline on first start if no token in DB)
# Open web admin panel
# Visit http://your-hub.example.com:8765/hub/ui
# Register each backend (CLI or via the web UI)
# Outputs:
# WEIXIN_BASE_URL=http://your-hub.example.com:8765
# WEIXIN_TOKEN=vhub_xxxxxxxxxxxxxxxx
Option C: Docker Compose
# docker-compose.yml
services:
ilink-hub:
image: ghcr.io/jeffkit/ilink-hub:latest
restart: unless-stopped
ports:
- "8765:8765"
volumes:
- ilink-hub-data:/data
environment:
DATABASE_URL: sqlite:/data/ilink-hub.db
# ILINK_TOKEN: your-token # Optional: skip QR login if you have a token
volumes:
ilink-hub-data:
# First-time iLink bind: follow logs for the QR code
Option D: PostgreSQL backend
DATABASE_URL=postgres://user:pass@localhost/ilink_hub
WeChat Commands
Send these from WeChat to control the Hub:
| Command | Effect |
|---|---|
/list |
List all registered backends and their status |
/use <name> |
Switch active backend (e.g. /use mac-home) |
/broadcast <text> |
Send a message to all online backends |
/status |
Show Hub status (online/total clients) |
Quote-reply routing (multi-session)
If you quote-reply to a bot message, iLink includes structured ref_msg data. The Hub:
- Records each backend
sendmessageby its outboundclient_id(ilink-hub:…). - When the real iLink
getupdatesstream returns the bot copy (message_type == 2) with the sameclient_id, the Hub indexes that message’s per-itemmsg_id(and top-levelmessage_id). - Your next user message that quotes that bot line is routed to the same backend (or the same Hub command, e.g.
/list), overriding the current/useselection for that turn. Explicit/…hub commands in the new text still take priority over quote routing.
Operational note: routing depends on iLink echoing your bot sends on the upstream poll. If your tenant never echoes them, the index stays empty and quote routing has nothing to match.
Optional display label: by default, the Hub appends a short footer (— workspace or — workspace · label) to each client sendmessage text only when more than one backend is registered (so single-backend setups stay clean). Set ILINKHUB_OUTBOUND_ORIGIN_LABEL=0 / false / off to disable, or 1 / true / on to always append (even with one client).
Configuring Clients
Recursive
# ~/.recursive/config.toml
[]
= "http://your-hub.example.com"
= "vhub_xxxxxxxxxxxxxxxx"
Or via environment:
WEIXIN_BASE_URL=http://your-hub.example.com
WEIXIN_TOKEN=vhub_xxxxxxxxxxxxxxxx
Any wechatbot-based Rust SDK
let bot = new;
OpenClaw
# ~/.openclaw/openclaw.json
ilink-hub-bridge (local CLI)
Run a local command (Claude Code, Cursor Agent, Codex, etc.) for each routed WeChat text message — same iLink virtual-token flow as other backends. Usage guide (Chinese): bridge/USAGE. Quick echo path: 5-minute try. Full options: docs/bridge/README.md and docs/bridge/examples/.
# Default — no WEIXIN_TOKEN and no cred file yet: POST /hub/register, saves ~/.ilink-hub/bridge-credentials.json (ILINK_ADMIN_TOKEN if Hub requires it). If the file exists but is corrupt/empty, bridge errors instead of overwriting — use --force-register or delete the file.
WEIXIN_BASE_URL=http://127.0.0.1:8765
# Optional — Hub client QR pairing instead: add --pair
# Optional — explicit vtoken: WEIXIN_TOKEN=vhub_xxx …
Hub API Reference
The Hub exposes the full iLink API surface plus Hub-specific management endpoints:
iLink-compatible endpoints (same as ilinkai.weixin.qq.com)
| Method | Path | Description |
|---|---|---|
POST |
/ilink/bot/getupdates |
Long-poll for messages (30s timeout) |
POST |
/ilink/bot/sendmessage |
Send reply (context_token auto-translated) |
POST |
/ilink/bot/sendtyping |
Send typing indicator |
POST |
/ilink/bot/getconfig |
Get typing ticket |
POST |
/ilink/bot/getuploadurl |
Get CDN upload URL |
Authentication: Same as real iLink — Authorization: Bearer <vtoken> header.
Hub management endpoints
| Method | Path | Description |
|---|---|---|
POST |
/hub/register |
Register a new backend client |
GET |
/hub/clients |
List all registered clients (includes vtoken) |
GET |
/hub/ui |
Web admin panel (browser UI) |
GET |
/metrics |
Prometheus-format metrics |
GET |
/health |
Health check |
Admin auth: Set ILINK_ADMIN_TOKEN=<secret> on the Hub. Then pass Authorization: Bearer <secret>
when calling /hub/register or /hub/clients. If the env var is unset, these endpoints are open (suitable
for local dev / private networks).
Architecture
ilink-hub/
├── src/
│ ├── ilink/
│ │ ├── types.rs — Complete iLink protocol types (mirrors ilinkai.weixin.qq.com)
│ │ ├── upstream.rs — Real iLink poller (exponential backoff, auto-reconnect)
│ │ └── login.rs — QR login flow (terminal QR rendering)
│ ├── hub/
│ │ ├── registry.rs — Client registry (vtoken management)
│ │ ├── router.rs — Message routing + WeChat command parser
│ │ ├── queue.rs — Per-client queues + context_token mapping
│ │ └── health.rs — Background health checker
│ ├── server/
│ │ └── routes.rs — iLink-compatible HTTP handlers
│ ├── store/
│ │ └── mod.rs — sqlx database layer (SQLite/PostgreSQL/MySQL)
│ └── main.rs — CLI: serve / login / register / clients
├── Dockerfile — Multi-stage build
└── .github/workflows/ci.yml
Message flow
WeChat sends message
↓
Hub polls real iLink getupdates → receives InboundMessage
↓
Router: parse WeChat command or determine target client
↓
Map real context_token → virtual context_token (stored in DB)
↓
Push to target client's queue (notify waiting getupdates long-poll)
↓
Client's getupdates returns the message
↓
Client processes, sends sendmessage with virtual context_token
↓
Hub resolves virtual → real context_token
↓
Hub forwards sendmessage to real iLink
↓
WeChat receives reply ✓
Security Recommendations
- Deploy behind HTTPS — use a reverse proxy (Nginx, Caddy) with TLS
- Restrict
/hub/admin endpoints — add IP allowlist or Bearer token auth to admin routes - Use PostgreSQL for production — SQLite works but isn't suited for high-concurrency deployments
- Rotate virtual tokens periodically — re-register clients with a new name to get fresh vtokens
- Keep Hub on private network — only expose port 8765 if needed; ideally put Nginx in front
Nginx example
server {
listen 443 ssl;
server_name hub.example.com;
# Only allow your backend IPs to access admin endpoints
location /hub/ {
allow 192.168.1.0/24;
deny all;
proxy_pass http://localhost:8765;
}
# iLink API open to registered clients
location /ilink/ {
proxy_pass http://localhost:8765;
proxy_set_header Host $host;
}
location /health {
proxy_pass http://localhost:8765;
}
}
Comparison with Similar Projects
| Project | Protocol for clients | Multi-machine | Standalone |
|---|---|---|---|
| iLink Hub (this) | ✅ iLink-compatible | ✅ Yes | ✅ Yes |
| OpeniLink Hub | ❌ Custom WebSocket/SDK | ✅ Yes | ✅ Yes |
| HermesClaw | ❌ Local proxy only | ❌ No | ✅ Yes |
| wechat-clawbot | HTTP webhook | ✅ Yes | ✅ Yes |
| OpenClaw bindings | ❌ OpenClaw-specific | ❌ Same machine | ✅ Yes |
License
MIT © 2026 jeffkit