{"id":50942140,"url":"https://github.com/openclaw/crabline","last_synced_at":"2026-06-17T16:01:43.146Z","repository":{"id":344756248,"uuid":"1181202413","full_name":"openclaw/crabline","owner":"openclaw","description":"A cli that uses Vercel's Chat SDK to allow testing message channels against OpenClaw","archived":false,"fork":false,"pushed_at":"2026-06-15T22:23:20.000Z","size":411,"stargazers_count":25,"open_issues_count":2,"forks_count":9,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-16T00:10:26.613Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openclaw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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},"funding":{"github":["moltbot"]}},"created_at":"2026-03-13T21:28:14.000Z","updated_at":"2026-06-09T14:38:44.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/openclaw/crabline","commit_stats":null,"previous_names":["openclaw/multipass","openclaw/crabline"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/openclaw/crabline","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openclaw%2Fcrabline","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openclaw%2Fcrabline/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openclaw%2Fcrabline/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openclaw%2Fcrabline/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openclaw","download_url":"https://codeload.github.com/openclaw/crabline/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openclaw%2Fcrabline/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34453446,"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-17T02:00:05.408Z","response_time":127,"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-17T16:00:43.471Z","updated_at":"2026-06-17T16:01:43.140Z","avatar_url":"https://github.com/openclaw.png","language":"TypeScript","funding_links":["https://github.com/sponsors/moltbot"],"categories":[],"sub_categories":[],"readme":"# 🧪 crabline\n\n![crabline banner](docs/assets/readme-banner.jpg)\n\nDeterministic messaging-provider tests for OpenClaw.\n\n`crabline` is config-driven, CI-friendly, and deliberately has no `openclaw` dependency. It now models the full OpenClaw messaging matrix: `bluebubbles`, `discord`, `feishu`, `googlechat`, `imessage`, `irc`, `line`, `matrix`, `mattermost`, `msteams`, `nextcloudtalk`, `nostr`, `signal`, `slack`, `synologychat`, `telegram`, `tlon`, `twitch`, `webchat`, `whatsapp`, `zalo`, `zalouser`.\n\nThis project used to be called `multipass`, but was renamed to `crabline` to avoid conflicts with Canonical Multipass.\n\nThe current shape is:\n\n- built-in `loopback` provider for local development and contract tests\n- built-in `discord` provider\n- built-in `feishu` provider\n- built-in `googlechat` provider\n- built-in `mattermost` provider\n- built-in `msteams` provider\n- built-in `slack` provider\n- built-in `telegram` provider\n- built-in `whatsapp` provider\n- built-in `zalo` provider\n- adapter-backed providers for `matrix` and `imessage`\n- `script` bridge for the remaining OpenClaw messaging channels\n- webhook-backed recorder mode for Slack `watch` / `webhook`\n- interactions-webhook + gateway-backed recorder mode for Discord\n- recorder-backed watch mode for Telegram, WhatsApp, Feishu, Google Chat, Mattermost, Microsoft Teams, Zalo, Matrix, and iMessage\n- nonce-based `send`, `roundtrip`, `agent`, `probe`, `run`, `watch`, `doctor`\n- text output by default, stable `--json` for automation\n- core provider model aligned with Vercel Chat SDK concepts\n\n## Install\n\n```bash\npnpm install\npnpm build\npnpm verify\n```\n\nRun locally:\n\n```bash\npnpm dev fixtures --config fixtures/examples/crabline.example.yaml\npnpm dev roundtrip loopback-roundtrip --config fixtures/examples/crabline.example.yaml\n```\n\n## Quality Gate\n\nLocal and CI use the same gate:\n\n```bash\npnpm verify\n```\n\nThat enforces:\n\n- `oxlint` with strict correctness/suspicious rules plus import and Vitest checks\n- `tsc --noEmit` under `strict`, `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`\n- Vitest coverage with global thresholds of 80% for statements, lines, and functions\n- `oxfmt --check` formatting\n\nGitHub Actions runs the same `pnpm verify` flow on pushes to `main` and pull requests, and uploads the coverage artifact.\n\n## Config\n\nConfig file search order:\n\n1. `--config \u003cpath\u003e`\n2. `./crabline.yaml`\n3. `./crabline.yml`\n4. `./crabline.json`\n\nTop-level shape:\n\n```yaml\nconfigVersion: 1\nuserName: crabline\nproviders:\n  provider-id:\n    adapter: loopback | script | slack | discord | feishu | googlechat | mattermost | msteams | telegram | whatsapp | zalo | matrix | imessage\n    platform: required only when adapter is script\nfixtures:\n  - id: string\n    provider: string\n    accountId: string?\n    mode: probe | send | roundtrip | agent\n    target:\n      id: string\n      channelId: string?\n      threadId: string?\n      behavior: echo | agent | sink?\n    inboundMatch:\n      author: assistant | user | system | any\n      strategy: contains | exact | regex\n      nonce: contains | exact | ignore\n      pattern: string?\n    timeoutMs: number\n    retries: number\n    tags: string[]\n    env: string[]\n    notes: string?\n```\n\nProvider ids are local profile names; fixtures reference them through `provider`.\nBuilt-in adapters infer their platform from `adapter`. Script bridge providers need\n`platform` so Crabline knows which OpenClaw channel they exercise. Credentials\nstay in env, never in fixtures.\n\nFor per-channel secrets, external setup, and smoke CI profile guidance, see\n[Channel Setup](docs/channel-setup.md).\n\n## Support Matrix\n\n- `ready`: `loopback`, built-in `slack`, built-in `discord`, built-in `feishu`, built-in `googlechat`, built-in `mattermost`, built-in `msteams`, built-in `telegram`, built-in `whatsapp`, built-in `zalo`, adapter-backed `matrix`, adapter-backed `imessage`\n- `bridge`: `bluebubbles`, `irc`, `line`, `nextcloudtalk`, `nostr`, `signal`, `synologychat`, `tlon`, `twitch`, `webchat`, `zalouser`\n- Plugin-backed in OpenClaw, available through the bridge: `line`, `nextcloudtalk`, `nostr`, `synologychat`, `tlon`, `twitch`, `zalouser`\n- Recommended bridge-only path today: `bluebubbles`, `irc`, `signal`, `webchat`\n\nTelegram notes:\n\n- Use one bot plus one real Telegram user identity for two-way tests.\n- Do not model Telegram roundtrip as bot-to-bot; Telegram bots do not receive messages from other bots, and Bot API delivery is update-queue/webhook based rather than arbitrary history fetch.\n- Best operator path: DM-first, then group/topic once DM roundtrip is stable.\n- For unattended automation, drive the user side with MTProto (for example Telethon), not a second bot.\n\nTelegram provider options:\n\n```yaml\nproviders:\n  telegram:\n    adapter: telegram\n    env:\n      - TELEGRAM_BOT_TOKEN\n    telegram:\n      mode: webhook # auto | webhook | polling\n      recorder:\n        path: ./.crabline/recorders/telegram.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8790\n        path: /telegram/webhook\n        publicUrl: https://example.ngrok.app/telegram/webhook # optional\n```\n\nTelegram targets use Chat SDK thread ids: `telegram:{chatId}` or `telegram:{chatId}:{messageThreadId}`. Raw `target.id` values are encoded automatically.\n\nWhatsApp provider options:\n\n```yaml\nproviders:\n  whatsapp:\n    adapter: whatsapp\n    env:\n      - WHATSAPP_ACCESS_TOKEN\n      - WHATSAPP_APP_SECRET\n      - WHATSAPP_PHONE_NUMBER_ID\n      - WHATSAPP_VERIFY_TOKEN\n    whatsapp:\n      recorder:\n        path: ./.crabline/recorders/whatsapp.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8789\n        path: /whatsapp/webhook\n        publicUrl: https://example.ngrok.app/whatsapp/webhook # optional\n```\n\nWhatsApp targets use Chat SDK thread ids: `whatsapp:{phoneNumberId}:{userWaId}`. Raw `target.id` values are encoded from `WHATSAPP_PHONE_NUMBER_ID` or `whatsapp.phoneNumberId`.\n\nFeishu provider options:\n\n```yaml\nproviders:\n  feishu:\n    adapter: feishu\n    env:\n      - FEISHU_APP_ID\n      - FEISHU_APP_SECRET\n    feishu:\n      recorder:\n        path: ./.crabline/recorders/feishu.jsonl\n```\n\nFeishu uses the Chat SDK Lark adapter and WebSocket transport. Targets use `lark:{chatId}:{rootId}`. Raw `oc_*` chat ids are encoded automatically; raw `ou_*` user ids are treated as DM targets.\n\nGoogle Chat provider options:\n\n```yaml\nproviders:\n  googlechat:\n    adapter: googlechat\n    env:\n      - GOOGLE_CHAT_CREDENTIALS\n    googlechat:\n      googleChatProjectNumber: \"1234567890\"\n      recorder:\n        path: ./.crabline/recorders/googlechat.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8792\n        path: /googlechat/webhook\n        publicUrl: https://example.ngrok.app/googlechat/webhook # optional\n```\n\nGoogle Chat raw space targets use `spaces/...` and are encoded as `gchat:spaces/...`. Thread replies use `target.channelId` plus `target.threadId`, encoded as `gchat:{spaceName}:{base64urlThreadName}`.\n\nMattermost provider options:\n\n```yaml\nproviders:\n  mattermost:\n    adapter: mattermost\n    env:\n      - MATTERMOST_BASE_URL\n      - MATTERMOST_BOT_TOKEN\n    mattermost:\n      recorder:\n        path: ./.crabline/recorders/mattermost.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8793\n        path: /mattermost/webhook\n        publicUrl: https://example.ngrok.app/mattermost/webhook # optional\n```\n\nMattermost raw channel ids are encoded as `mattermost:{base64urlChannelId}`. Thread replies use `target.channelId` plus `target.threadId`, encoded as `mattermost:{base64urlChannelId}:{base64urlRootPostId}`. User DMs can set `target.metadata.targetType: user`.\n\nMicrosoft Teams provider options:\n\n```yaml\nproviders:\n  msteams:\n    adapter: msteams\n    env:\n      - TEAMS_APP_ID\n      - TEAMS_APP_PASSWORD\n    msteams:\n      recorder:\n        path: ./.crabline/recorders/msteams.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8791\n        path: /msteams/webhook\n        publicUrl: https://example.ngrok.app/msteams/webhook # optional\n```\n\nMicrosoft Teams raw conversation targets require `target.metadata.serviceUrl` so Crabline can encode Chat SDK thread ids as `teams:{conversationId}:{serviceUrl}`. Encoded `teams:` ids are passed through.\n\nZalo provider options:\n\n```yaml\nproviders:\n  zalo:\n    adapter: zalo\n    env:\n      - ZALO_BOT_TOKEN\n      - ZALO_WEBHOOK_SECRET\n    zalo:\n      recorder:\n        path: ./.crabline/recorders/zalo.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8794\n        path: /zalo/webhook\n        publicUrl: https://example.ngrok.app/zalo/webhook # optional\n```\n\nZalo targets use Chat SDK thread ids: `zalo:{chatId}`. Raw `target.id` values are encoded automatically.\n\nDiscord provider options:\n\n```yaml\nproviders:\n  discord:\n    adapter: discord\n    env:\n      - DISCORD_BOT_TOKEN\n    discord:\n      applicationId: \"123456789012345678\" # optional; auto-discovered from bot token when omitted\n      publicKey: \"0123456789abcdef...\" # optional; auto-discovered from bot token when omitted\n      recorder:\n        path: ./.crabline/recorders/discord.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8788\n        path: /discord/interactions\n        publicUrl: https://example.ngrok.app/discord/interactions # optional\n```\n\nDiscord fixture targeting rules:\n\n- Guild channels: set `target.metadata.guildId` and either a raw channel id or a fully encoded `discord:guild:channel[:thread]` id.\n- DMs: omit `target.metadata.guildId`; `target.id` is treated as the user id.\n- Quote Discord snowflakes in YAML so they stay strings.\n\nDiscord metadata defaults to token-only setup. When `applicationId` or `publicKey` are omitted, `crabline` fetches them from Discord using the bot token on first connect.\n\nDiscord `watch` and `roundtrip` start the local interactions server plus a Discord Gateway listener. `publicUrl` is optional for local gateway-driven receive tests, but needed if you want Discord itself to hit your interactions endpoint from outside your machine.\n\nSlack provider options:\n\n```yaml\nproviders:\n  slack:\n    adapter: slack\n    slack:\n      recorder:\n        path: ./.crabline/recorders/slack.jsonl\n      webhook:\n        host: 127.0.0.1\n        port: 8787\n        path: /slack/events\n        publicUrl: https://example.ngrok.app/slack/events # optional but useful\n```\n\n`watch` (alias: `webhook`) starts the local Slack webhook listener and tails the recorded inbound JSONL stream. `roundtrip` and `agent` also start the webhook listener on demand, and will reuse an already-running listener on the configured port.\n\nMatrix provider options:\n\n```yaml\nproviders:\n  matrix:\n    adapter: matrix\n    env:\n      - MATRIX_BASE_URL\n      - MATRIX_ACCESS_TOKEN\n    matrix:\n      baseURL: https://matrix.example.com\n      recorder:\n        path: ./.crabline/recorders/matrix.jsonl\n```\n\niMessage provider options:\n\n```yaml\nproviders:\n  imessage:\n    adapter: imessage\n    env:\n      - IMESSAGE_API_KEY\n      - IMESSAGE_SERVER_URL\n    imessage:\n      local: false\n      serverUrl: https://imessage-gateway.example.com\n      recorder:\n        path: ./.crabline/recorders/imessage.jsonl\n```\n\nMatrix and iMessage `watch` tail the local recorder stream. There is no webhook listener for Matrix; iMessage uses the adapter gateway listener under the hood.\n\n## Example fixtures\n\nSee [fixtures/examples/crabline.example.yaml](fixtures/examples/crabline.example.yaml).\n\nFull OpenClaw bridge matrix example:\n\n[openclaw-bridge.yaml](fixtures/examples/openclaw-bridge.yaml)\n\nLoopback:\n\n```bash\npnpm dev roundtrip loopback-roundtrip --config fixtures/examples/crabline.example.yaml\npnpm dev agent loopback-agent --config fixtures/examples/crabline.example.yaml\n```\n\nSlack:\n\n```bash\nSLACK_BOT_TOKEN=xoxb-... \\\nSLACK_SIGNING_SECRET=... \\\npnpm dev probe slack-agent --config fixtures/examples/crabline.example.yaml\n\nSLACK_BOT_TOKEN=xoxb-... \\\nSLACK_SIGNING_SECRET=... \\\npnpm dev watch slack-agent --config fixtures/examples/crabline.example.yaml\n```\n\nDiscord:\n\n```bash\nDISCORD_BOT_TOKEN=... \\\npnpm dev probe discord-agent --config fixtures/examples/crabline.example.yaml\n\nDISCORD_BOT_TOKEN=... \\\npnpm dev watch discord-agent --config fixtures/examples/crabline.example.yaml\n```\n\nTelegram:\n\n```bash\nTELEGRAM_BOT_TOKEN=... \\\npnpm dev probe telegram-dm --config fixtures/examples/crabline.example.yaml\n\nTELEGRAM_BOT_TOKEN=... \\\npnpm dev watch telegram-dm --config fixtures/examples/crabline.example.yaml\n```\n\nFor true Telegram two-way verification, point `target.id` at the dedicated human test account, not another bot.\n\nWhatsApp:\n\n```bash\nWHATSAPP_ACCESS_TOKEN=... \\\nWHATSAPP_APP_SECRET=... \\\nWHATSAPP_PHONE_NUMBER_ID=... \\\nWHATSAPP_VERIFY_TOKEN=... \\\npnpm dev probe whatsapp-dm --config fixtures/examples/crabline.example.yaml\n\nWHATSAPP_ACCESS_TOKEN=... \\\nWHATSAPP_APP_SECRET=... \\\nWHATSAPP_PHONE_NUMBER_ID=... \\\nWHATSAPP_VERIFY_TOKEN=... \\\npnpm dev watch whatsapp-dm --config fixtures/examples/crabline.example.yaml\n```\n\nFeishu:\n\n```bash\nFEISHU_APP_ID=... \\\nFEISHU_APP_SECRET=... \\\npnpm dev probe feishu-chat --config fixtures/examples/crabline.example.yaml\n\nFEISHU_APP_ID=... \\\nFEISHU_APP_SECRET=... \\\npnpm dev watch feishu-chat --config fixtures/examples/crabline.example.yaml\n```\n\nMattermost:\n\n```bash\nMATTERMOST_BASE_URL=https://mattermost.example.com \\\nMATTERMOST_BOT_TOKEN=... \\\npnpm dev probe mattermost-channel --config fixtures/examples/crabline.example.yaml\n\nMATTERMOST_BASE_URL=https://mattermost.example.com \\\nMATTERMOST_BOT_TOKEN=... \\\npnpm dev watch mattermost-channel --config fixtures/examples/crabline.example.yaml\n```\n\nMicrosoft Teams:\n\n```bash\nTEAMS_APP_ID=... \\\nTEAMS_APP_PASSWORD=... \\\npnpm dev probe msteams-channel --config fixtures/examples/crabline.example.yaml\n\nTEAMS_APP_ID=... \\\nTEAMS_APP_PASSWORD=... \\\npnpm dev watch msteams-channel --config fixtures/examples/crabline.example.yaml\n```\n\nGoogle Chat:\n\n```bash\nGOOGLE_CHAT_CREDENTIALS='{\"client_email\":\"...\",\"private_key\":\"...\"}' \\\npnpm dev probe googlechat-space --config fixtures/examples/crabline.example.yaml\n\nGOOGLE_CHAT_CREDENTIALS='{\"client_email\":\"...\",\"private_key\":\"...\"}' \\\npnpm dev watch googlechat-space --config fixtures/examples/crabline.example.yaml\n```\n\nZalo:\n\n```bash\nZALO_BOT_TOKEN=... \\\nZALO_WEBHOOK_SECRET=... \\\npnpm dev probe zalo-chat --config fixtures/examples/crabline.example.yaml\n\nZALO_BOT_TOKEN=... \\\nZALO_WEBHOOK_SECRET=... \\\npnpm dev watch zalo-chat --config fixtures/examples/crabline.example.yaml\n```\n\nMatrix:\n\n```bash\nMATRIX_BASE_URL=https://matrix.example.com \\\nMATRIX_ACCESS_TOKEN=... \\\npnpm dev probe matrix-agent --config fixtures/examples/crabline.example.yaml\n\nMATRIX_BASE_URL=https://matrix.example.com \\\nMATRIX_ACCESS_TOKEN=... \\\npnpm dev watch matrix-agent --config fixtures/examples/crabline.example.yaml\n```\n\niMessage:\n\n```bash\nIMESSAGE_SERVER_URL=https://imessage-gateway.example.com \\\nIMESSAGE_API_KEY=... \\\npnpm dev probe imessage-agent --config fixtures/examples/crabline.example.yaml\n\nIMESSAGE_SERVER_URL=https://imessage-gateway.example.com \\\nIMESSAGE_API_KEY=... \\\npnpm dev watch imessage-agent --config fixtures/examples/crabline.example.yaml\n```\n\nScript bridge:\n\n```bash\nOPENCLAW_URL=http://127.0.0.1:18789 \\\nOPENCLAW_TOKEN=secret \\\npnpm dev probe slack-openclaw-demo --config fixtures/examples/crabline.example.yaml\n```\n\nFull bridge matrix bootstrap:\n\n```bash\nOPENCLAW_URL=http://127.0.0.1:18789 \\\nOPENCLAW_TOKEN=secret \\\npnpm dev providers --config fixtures/examples/openclaw-bridge.yaml\n```\n\n## Commands\n\n```bash\ncrabline providers\ncrabline fixtures\ncrabline probe \u003cfixture|provider\u003e\ncrabline send \u003cfixture\u003e\ncrabline roundtrip \u003cfixture\u003e\ncrabline agent \u003cfixture\u003e\ncrabline run \u003cfixture...\u003e\ncrabline watch \u003cfixture\u003e\ncrabline webhook \u003cfixture\u003e  # alias of watch\ncrabline doctor\n```\n\n## Script adapter contract\n\n`script` providers receive JSON on stdin and must emit JSON on stdout.\n\n`probe` input:\n\n```json\n{\n  \"fixture\": { \"...\": \"fixture config\" },\n  \"provider\": {\n    \"id\": \"slack-openclaw\",\n    \"manifestPath\": \"...\",\n    \"config\": { \"...\": \"provider config\" }\n  }\n}\n```\n\n`probe` output:\n\n```json\n{\n  \"healthy\": true,\n  \"details\": [\"token ok\", \"channel reachable\"]\n}\n```\n\n`send` output:\n\n```json\n{\n  \"accepted\": true,\n  \"messageId\": \"123\",\n  \"threadId\": \"slack:C123:thread\"\n}\n```\n\n`waitForInbound` output:\n\n```json\n{\n  \"message\": {\n    \"id\": \"456\",\n    \"author\": \"assistant\",\n    \"sentAt\": \"2026-03-13T21:00:00.000Z\",\n    \"text\": \"ACK mp-demo-...\",\n    \"threadId\": \"slack:C123:thread\"\n  }\n}\n```\n\nor:\n\n```json\n{ \"timeout\": true }\n```\n\n## Add a provider\n\n1. Add a configured provider instance under `providers`.\n2. Use a built-in adapter when one exists; otherwise use `adapter: script` for bridge-based real E2E.\n3. Set `platform` only for `adapter: script`.\n4. Add one or more fixtures that point at stable demo accounts/targets.\n5. Run `crabline doctor`, `crabline probe`, then `crabline run ...`.\n\n## Current scope\n\n- Real built-in providers: `loopback`, built-in `slack`, built-in `discord`, built-in `feishu`, built-in `googlechat`, built-in `mattermost`, built-in `msteams`, built-in `telegram`, built-in `whatsapp`, built-in `zalo`, adapter-backed `matrix`, adapter-backed `imessage`\n- Real external bridge: `script` for the full OpenClaw channel matrix\n- Not implemented yet: richer recorder compaction/query tooling, live-model response generation\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenclaw%2Fcrabline","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenclaw%2Fcrabline","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenclaw%2Fcrabline/lists"}