{"id":50642799,"url":"https://github.com/jeffkit/ilink-hub","last_synced_at":"2026-06-07T10:00:56.437Z","repository":{"id":362714692,"uuid":"1259974573","full_name":"jeffkit/ilink-hub","owner":"jeffkit","description":"iLink-compatible multiplexer hub for WeChat ClawBot — connect one WeChat account to multiple AI agent backends","archived":false,"fork":false,"pushed_at":"2026-06-05T14:42:19.000Z","size":329,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-05T16:23:26.771Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jeffkit.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-05T03:27:15.000Z","updated_at":"2026-06-05T14:42:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jeffkit/ilink-hub","commit_stats":null,"previous_names":["jeffkit/ilink-hub"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/jeffkit/ilink-hub","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffkit%2Filink-hub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffkit%2Filink-hub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffkit%2Filink-hub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffkit%2Filink-hub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jeffkit","download_url":"https://codeload.github.com/jeffkit/ilink-hub/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jeffkit%2Filink-hub/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34016490,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-07T02:00:07.652Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-07T10:00:23.608Z","updated_at":"2026-06-07T10:00:56.425Z","avatar_url":"https://github.com/jeffkit.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# iLink Hub\n\n**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.\n\n[![CI](https://github.com/jeffkit/ilink-hub/actions/workflows/ci.yml/badge.svg)](https://github.com/jeffkit/ilink-hub/actions)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n---\n\n## The Problem\n\nWeChat 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.\n\n## The Solution\n\niLink Hub is a **transparent iLink proxy**:\n\n```\n[WeChat User]\n      ↕ real iLink protocol\n[iLink Hub]  ← the sole connection holder\n      ↕ emulated iLink API (same HTTP endpoints, same protocol)\n  ┌───────────────┐  ┌────────────────────┐  ┌────────────────┐\n  │Recursive (Mac)│  │Recursive (Server)  │  │OpenClaw (etc.) │\n  │base_url=hub   │  │base_url=hub        │  │base_url=hub    │\n  │token=vhub_abc │  │token=vhub_def      │  │token=vhub_xyz  │\n  └───────────────┘  └────────────────────┘  └────────────────┘\n```\n\n**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.\n\n---\n\n## Features\n\n- **iLink-compatible API** — any existing iLink client works out-of-the-box\n- **Multi-backend routing** — route messages to different backends via WeChat commands\n- **Context-token mapping** — real context tokens never leak to clients; persisted across restarts\n- **QR code login** — scan once, token saved to DB\n- **Multi-database** — SQLite (default), PostgreSQL, MySQL via `DATABASE_URL`\n- **Full persistence** — client registrations, routing state, and context mappings survive restarts\n- **Web admin panel** — manage clients and copy config at `/hub/ui`\n- **Admin auth** — protect `/hub/` endpoints with `ILINK_ADMIN_TOKEN` env var\n- **Bounded queues** — per-client message buffer capped at 200 to prevent OOM\n- **Prometheus metrics** — counters and gauges at `/metrics`\n- **Friendly fallback** — when all backends are offline, WeChat users get an instant reply\n- **Pre-built binaries** — download from GitHub Releases (Linux/macOS/Windows), no Rust required\n- **Health checks** — auto-marks offline clients after 90s idle\n- **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))\n- **Docker support** — single-command deployment, multi-arch image (amd64 + arm64)\n\n### Desktop app (Tauri)\n\nA **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).\n\n---\n\n## Quick Start\n\n### Option A: Pre-built Binary (fastest)\n\n```bash\n# Linux x86_64\ncurl -Lo ilink-hub https://github.com/jeffkit/ilink-hub/releases/latest/download/ilink-hub-linux-x86_64\nchmod +x ilink-hub \u0026\u0026 sudo mv ilink-hub /usr/local/bin/\n\n# macOS Apple Silicon\ncurl -Lo ilink-hub https://github.com/jeffkit/ilink-hub/releases/latest/download/ilink-hub-macos-aarch64\nchmod +x ilink-hub \u0026\u0026 sudo mv ilink-hub /usr/local/bin/\n\n# macOS Intel\ncurl -Lo ilink-hub https://github.com/jeffkit/ilink-hub/releases/latest/download/ilink-hub-macos-x86_64\nchmod +x ilink-hub \u0026\u0026 sudo mv ilink-hub /usr/local/bin/\n```\n\n\u003e Windows: download `ilink-hub-windows-x86_64.exe` from [Releases](https://github.com/jeffkit/ilink-hub/releases).\n\n### Option B: Cargo (requires Rust)\n\n```bash\ncargo install ilink-hub\n\n# Start Hub (QR login runs inline on first start if no token in DB)\nilink-hub serve --addr 0.0.0.0:8765\n\n# Open web admin panel\n# Visit http://your-hub.example.com:8765/hub/ui\n\n# Register each backend (CLI or via the web UI)\nilink-hub register --hub-url http://your-hub.example.com:8765 \\\n  --name mac-home --label \"Mac Home\"\n# Outputs:\n#   WEIXIN_BASE_URL=http://your-hub.example.com:8765\n#   WEIXIN_TOKEN=vhub_xxxxxxxxxxxxxxxx\n```\n\n### Option C: Docker Compose\n\n```yaml\n# docker-compose.yml\nservices:\n  ilink-hub:\n    image: ghcr.io/jeffkit/ilink-hub:latest\n    restart: unless-stopped\n    ports:\n      - \"8765:8765\"\n    volumes:\n      - ilink-hub-data:/data\n    environment:\n      DATABASE_URL: sqlite:/data/ilink-hub.db\n      # ILINK_TOKEN: your-token  # Optional: skip QR login if you have a token\n\nvolumes:\n  ilink-hub-data:\n```\n\n```bash\ndocker compose up -d\n# First-time iLink bind: follow logs for the QR code\ndocker compose logs -f ilink-hub\n```\n\n### Option D: PostgreSQL backend\n\n```bash\nDATABASE_URL=postgres://user:pass@localhost/ilink_hub ilink-hub serve\n```\n\n---\n\n## WeChat Commands\n\nSend these from WeChat to control the Hub:\n\n| Command | Effect |\n|---------|--------|\n| `/list` | List all registered backends and their status |\n| `/use \u003cname\u003e` | Switch active backend (e.g. `/use mac-home`) |\n| `/broadcast \u003ctext\u003e` | Send a message to all online backends |\n| `/status` | Show Hub status (online/total clients) |\n\n---\n\n## Configuring Clients\n\n### Recursive\n\n```toml\n# ~/.recursive/config.toml\n[weixin]\nbase_url = \"http://your-hub.example.com\"\ntoken = \"vhub_xxxxxxxxxxxxxxxx\"\n```\n\nOr via environment:\n```bash\nWEIXIN_BASE_URL=http://your-hub.example.com\nWEIXIN_TOKEN=vhub_xxxxxxxxxxxxxxxx\nrecursive weixin\n```\n\n### Any `wechatbot`-based Rust SDK\n\n```rust\nlet bot = WeChatBot::new(BotOptions {\n    base_url: Some(\"http://your-hub.example.com\".to_string()),\n    token: \"vhub_xxxxxxxxxxxxxxxx\".to_string(),\n    ..Default::default()\n});\n```\n\n### OpenClaw\n\n```yaml\n# ~/.openclaw/openclaw.json\n{\n  \"channels\": {\n    \"weixin\": {\n      \"base_url\": \"http://your-hub.example.com\",\n      \"token\": \"vhub_xxxxxxxxxxxxxxxx\"\n    }\n  }\n}\n```\n\n### ilink-hub-bridge (local CLI)\n\nRun 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/).\n\n```bash\ncp docs/bridge/examples/echo.example.yaml ./ilink-hub-bridge.yaml\n# 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.\nWEIXIN_BASE_URL=http://127.0.0.1:8765 ilink-hub-bridge --config ./ilink-hub-bridge.yaml\n# Optional — Hub client QR pairing instead: add --pair\n# Optional — explicit vtoken: WEIXIN_TOKEN=vhub_xxx …\n```\n\n---\n\n## Hub API Reference\n\nThe Hub exposes the full iLink API surface **plus** Hub-specific management endpoints:\n\n### iLink-compatible endpoints (same as `ilinkai.weixin.qq.com`)\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `POST` | `/ilink/bot/getupdates` | Long-poll for messages (30s timeout) |\n| `POST` | `/ilink/bot/sendmessage` | Send reply (context_token auto-translated) |\n| `POST` | `/ilink/bot/sendtyping` | Send typing indicator |\n| `POST` | `/ilink/bot/getconfig` | Get typing ticket |\n| `POST` | `/ilink/bot/getuploadurl` | Get CDN upload URL |\n\n**Authentication:** Same as real iLink — `Authorization: Bearer \u003cvtoken\u003e` header.\n\n### Hub management endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `POST` | `/hub/register` | Register a new backend client |\n| `GET` | `/hub/clients` | List all registered clients (includes vtoken) |\n| `GET` | `/hub/ui` | Web admin panel (browser UI) |\n| `GET` | `/metrics` | Prometheus-format metrics |\n| `GET` | `/health` | Health check |\n\n**Admin auth:** Set `ILINK_ADMIN_TOKEN=\u003csecret\u003e` on the Hub. Then pass `Authorization: Bearer \u003csecret\u003e`\nwhen calling `/hub/register` or `/hub/clients`. If the env var is unset, these endpoints are open (suitable\nfor local dev / private networks).\n\n---\n\n## Architecture\n\n```\nilink-hub/\n├── src/\n│   ├── ilink/\n│   │   ├── types.rs      — Complete iLink protocol types (mirrors ilinkai.weixin.qq.com)\n│   │   ├── upstream.rs   — Real iLink poller (exponential backoff, auto-reconnect)\n│   │   └── login.rs      — QR login flow (terminal QR rendering)\n│   ├── hub/\n│   │   ├── registry.rs   — Client registry (vtoken management)\n│   │   ├── router.rs     — Message routing + WeChat command parser\n│   │   ├── queue.rs      — Per-client queues + context_token mapping\n│   │   └── health.rs     — Background health checker\n│   ├── server/\n│   │   └── routes.rs     — iLink-compatible HTTP handlers\n│   ├── store/\n│   │   └── mod.rs        — sqlx database layer (SQLite/PostgreSQL/MySQL)\n│   └── main.rs           — CLI: serve / login / register / clients\n├── Dockerfile             — Multi-stage build\n└── .github/workflows/ci.yml\n```\n\n### Message flow\n\n```\nWeChat sends message\n  ↓\nHub polls real iLink getupdates → receives InboundMessage\n  ↓\nRouter: parse WeChat command or determine target client\n  ↓\nMap real context_token → virtual context_token (stored in DB)\n  ↓\nPush to target client's queue (notify waiting getupdates long-poll)\n  ↓\nClient's getupdates returns the message\n  ↓\nClient processes, sends sendmessage with virtual context_token\n  ↓\nHub resolves virtual → real context_token\n  ↓\nHub forwards sendmessage to real iLink\n  ↓\nWeChat receives reply ✓\n```\n\n---\n\n## Security Recommendations\n\n- **Deploy behind HTTPS** — use a reverse proxy (Nginx, Caddy) with TLS\n- **Restrict `/hub/` admin endpoints** — add IP allowlist or Bearer token auth to admin routes\n- **Use PostgreSQL for production** — SQLite works but isn't suited for high-concurrency deployments\n- **Rotate virtual tokens periodically** — re-register clients with a new name to get fresh vtokens\n- **Keep Hub on private network** — only expose port 8765 if needed; ideally put Nginx in front\n\n### Nginx example\n\n```nginx\nserver {\n    listen 443 ssl;\n    server_name hub.example.com;\n\n    # Only allow your backend IPs to access admin endpoints\n    location /hub/ {\n        allow 192.168.1.0/24;\n        deny all;\n        proxy_pass http://localhost:8765;\n    }\n\n    # iLink API open to registered clients\n    location /ilink/ {\n        proxy_pass http://localhost:8765;\n        proxy_set_header Host $host;\n    }\n\n    location /health {\n        proxy_pass http://localhost:8765;\n    }\n}\n```\n\n---\n\n## Comparison with Similar Projects\n\n| Project | Protocol for clients | Multi-machine | Standalone |\n|---------|---------------------|---------------|------------|\n| **iLink Hub** (this) | ✅ iLink-compatible | ✅ Yes | ✅ Yes |\n| OpeniLink Hub | ❌ Custom WebSocket/SDK | ✅ Yes | ✅ Yes |\n| HermesClaw | ❌ Local proxy only | ❌ No | ✅ Yes |\n| wechat-clawbot | HTTP webhook | ✅ Yes | ✅ Yes |\n| OpenClaw bindings | ❌ OpenClaw-specific | ❌ Same machine | ✅ Yes |\n\n---\n\n## License\n\nMIT © 2026 jeffkit\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeffkit%2Filink-hub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjeffkit%2Filink-hub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjeffkit%2Filink-hub/lists"}