https://github.com/jeffkit/ilink-hub
iLink-compatible multiplexer hub for WeChat ClawBot — connect one WeChat account to multiple AI agent backends
https://github.com/jeffkit/ilink-hub
Last synced: 4 days ago
JSON representation
iLink-compatible multiplexer hub for WeChat ClawBot — connect one WeChat account to multiple AI agent backends
- Host: GitHub
- URL: https://github.com/jeffkit/ilink-hub
- Owner: jeffkit
- Created: 2026-06-05T03:27:15.000Z (6 days ago)
- Default Branch: main
- Last Pushed: 2026-06-05T14:42:19.000Z (6 days ago)
- Last Synced: 2026-06-05T16:23:26.771Z (6 days ago)
- Language: Rust
- Size: 321 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# 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.
[](https://github.com/jeffkit/ilink-hub/actions)
[](LICENSE)
---
## The Problem
WeChat ClawBot's [iLink API](https://ilinkai.weixin.qq.com) 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 with `ILINK_ADMIN_TOKEN` env 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`](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/`](desktop/ilink-hub-desktop/): it embeds the same [`run_serve`](src/runtime/serve.rs) 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. See [`desktop/ilink-hub-desktop/README.md`](desktop/ilink-hub-desktop/README.md) for `npm run tauri dev` / `tauri build`. Longer-term notes: [`docs/desktop-tauri-roadmap.md`](docs/desktop-tauri-roadmap.md).
---
## Quick Start
### Option A: Pre-built Binary (fastest)
```bash
# Linux x86_64
curl -Lo ilink-hub https://github.com/jeffkit/ilink-hub/releases/latest/download/ilink-hub-linux-x86_64
chmod +x ilink-hub && sudo mv ilink-hub /usr/local/bin/
# macOS Apple Silicon
curl -Lo ilink-hub https://github.com/jeffkit/ilink-hub/releases/latest/download/ilink-hub-macos-aarch64
chmod +x ilink-hub && sudo mv ilink-hub /usr/local/bin/
# macOS Intel
curl -Lo ilink-hub https://github.com/jeffkit/ilink-hub/releases/latest/download/ilink-hub-macos-x86_64
chmod +x ilink-hub && sudo mv ilink-hub /usr/local/bin/
```
> Windows: download `ilink-hub-windows-x86_64.exe` from [Releases](https://github.com/jeffkit/ilink-hub/releases).
### Option B: Cargo (requires Rust)
```bash
cargo install ilink-hub
# Start Hub (QR login runs inline on first start if no token in DB)
ilink-hub serve --addr 0.0.0.0:8765
# Open web admin panel
# Visit http://your-hub.example.com:8765/hub/ui
# Register each backend (CLI or via the web UI)
ilink-hub register --hub-url http://your-hub.example.com:8765 \
--name mac-home --label "Mac Home"
# Outputs:
# WEIXIN_BASE_URL=http://your-hub.example.com:8765
# WEIXIN_TOKEN=vhub_xxxxxxxxxxxxxxxx
```
### Option C: Docker Compose
```yaml
# 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:
```
```bash
docker compose up -d
# First-time iLink bind: follow logs for the QR code
docker compose logs -f ilink-hub
```
### Option D: PostgreSQL backend
```bash
DATABASE_URL=postgres://user:pass@localhost/ilink_hub ilink-hub serve
```
---
## WeChat Commands
Send these from WeChat to control the Hub:
| Command | Effect |
|---------|--------|
| `/list` | List all registered backends and their status |
| `/use ` | Switch active backend (e.g. `/use mac-home`) |
| `/broadcast ` | Send a message to all online backends |
| `/status` | Show Hub status (online/total clients) |
---
## Configuring Clients
### Recursive
```toml
# ~/.recursive/config.toml
[weixin]
base_url = "http://your-hub.example.com"
token = "vhub_xxxxxxxxxxxxxxxx"
```
Or via environment:
```bash
WEIXIN_BASE_URL=http://your-hub.example.com
WEIXIN_TOKEN=vhub_xxxxxxxxxxxxxxxx
recursive weixin
```
### Any `wechatbot`-based Rust SDK
```rust
let bot = WeChatBot::new(BotOptions {
base_url: Some("http://your-hub.example.com".to_string()),
token: "vhub_xxxxxxxxxxxxxxxx".to_string(),
..Default::default()
});
```
### OpenClaw
```yaml
# ~/.openclaw/openclaw.json
{
"channels": {
"weixin": {
"base_url": "http://your-hub.example.com",
"token": "vhub_xxxxxxxxxxxxxxxx"
}
}
}
```
### 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](https://jeffkit.github.io/ilink-hub/bridge/USAGE.html). **Quick echo path:** [5-minute try](https://jeffkit.github.io/ilink-hub/bridge/quick-try.html). Full options: [`docs/bridge/README.md`](docs/bridge/README.md) and [`docs/bridge/examples/`](docs/bridge/examples/).
```bash
cp docs/bridge/examples/echo.example.yaml ./ilink-hub-bridge.yaml
# 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 ilink-hub-bridge --config ./ilink-hub-bridge.yaml
# 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 ` 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=` on the Hub. Then pass `Authorization: Bearer `
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
```nginx
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