{
  "markdown": "<div align=\"center\">\n\n# 🌉 cursor-chat-bridge <sub><sup>(Telegram, Discord, GitHub)</sup></sub>\n\n### Drive the **Cursor** agent from your phone — over Telegram, Discord, or GitHub.\n\nSay _\"start remote chat mode\"_ (in any language) and Cursor posts a summary + question to a\nper-conversation thread at the end of every turn, waits for your reply, and **auto-continues** —\nlooping until you stop it. Step away from the keyboard; keep shipping from your phone.\n\n[![npm version](https://img.shields.io/npm/v/cursor-telegram-chat?style=for-the-badge&color=cb3837&logo=npm)](https://www.npmjs.com/package/cursor-telegram-chat)\n[![GitHub stars](https://img.shields.io/github/stars/udah1/cursor-chat-bridge?style=for-the-badge&logo=github&color=f5c518)](https://github.com/udah1/cursor-chat-bridge/stargazers)\n[![license](https://img.shields.io/npm/l/cursor-telegram-chat?style=for-the-badge&color=blue)](./LICENSE)\n[![Made for Cursor](https://img.shields.io/badge/built%20for-Cursor-000000?style=for-the-badge)](https://cursor.com)\n\n[![npm downloads](https://img.shields.io/npm/dm/cursor-telegram-chat?color=cb3837&logo=npm)](https://www.npmjs.com/package/cursor-telegram-chat)\n[![node](https://img.shields.io/node/v/cursor-telegram-chat?logo=node.js&logoColor=white)](https://nodejs.org)\n[![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white)](https://www.typescriptlang.org)\n[![CI](https://img.shields.io/github/actions/workflow/status/udah1/cursor-chat-bridge/publish.yml?branch=master&logo=githubactions&logoColor=white&label=publish)](https://github.com/udah1/cursor-chat-bridge/actions/workflows/publish.yml)\n[![Glama MCP server](https://glama.ai/mcp/servers/udah1/cursor-chat-bridge/badges/score.svg)](https://glama.ai/mcp/servers/udah1/cursor-chat-bridge)\n\n<img src=\"docs/architecture.png\" alt=\"cursor-chat-bridge round-trip flow\" width=\"760\">\n\n<br/>\n\n<table>\n  <tr>\n    <td align=\"center\" width=\"50%\">\n      <img src=\"docs/screenshot-channels.png\" alt=\"A channel per session in the chat app\" width=\"280\"><br/>\n      <sub><b>A channel per session</b> — one per Cursor conversation</sub>\n    </td>\n    <td align=\"center\" width=\"50%\">\n      <img src=\"docs/screenshot-session.png\" alt=\"The agent posts summaries you reply to from your phone\" width=\"280\"><br/>\n      <sub><b>Reply from your phone</b> — the agent picks up where it left off</sub>\n    </td>\n  </tr>\n</table>\n\n<br/>\n\n<a href=\"https://youtu.be/MYDUyqirx2c\">\n  <img src=\"https://img.youtube.com/vi/MYDUyqirx2c/maxresdefault.jpg\" alt=\"Watch the cursor-chat-bridge demo on YouTube\" width=\"640\">\n</a>\n<br/>\n<sub>▶ <b>Watch the demo</b> (YouTube)</sub>\n\n</div>\n\n---\n\n## Table of contents\n\n- [Why](#why)\n- [Features](#features)\n- [Channels at a glance](#channels-at-a-glance)\n- [Quick start](#quick-start)\n- [How it works](#how-it-works)\n- [Image attachments](#image-attachments)\n- [Voice messages (speech-to-text)](#voice-messages-speech-to-text)\n- [Read receipts (acknowledgments)](#read-receipts-acknowledgments)\n- [Configuration](#configuration)\n- [Environment overrides](#environment-overrides)\n- [The wait loop](#the-wait-loop-stop-hook)\n- [Push notifications (ntfy)](#push-notifications-ntfy)\n- [Per-platform setup](#per-platform-setup)\n- [Writing a new adapter](#writing-a-new-adapter)\n- [Security](#security)\n- [Verification status](#verification-status)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Why\n\nYou kick off a task in Cursor, then need to leave your desk. Normally the agent stalls the moment\nit needs a decision. **cursor-chat-bridge** turns any chat app into a remote control: the agent\nreports back and asks its questions in a thread you can answer from your phone, and it resumes on\nits own the instant you reply — no laptop required.\n\n- **A thread per conversation.** Every Cursor chat maps to its own issue / channel / topic —\n  even multiple chats in the same workspace stay separate.\n- **Hands-free loop.** Replies are re-injected automatically; you don't touch Cursor to continue.\n- **Pluggable channels.** Telegram, Discord, and GitHub Issues today — add your own in ~100 lines.\n- **Safe by default.** Remote replies are treated as untrusted; destructive actions require an\n  explicit confirmation sent back through the thread.\n- **Proxy-friendly.** GitHub and Discord tunnel through TLS-intercepting corporate proxies.\n\n## Features\n\n| Capability | What it does |\n|---|---|\n| **Phone-first** | Answer the agent from the GitHub / Discord / Telegram mobile app, with native push. |\n| **Auto-resume** | A `stop` hook waits for your reply and re-injects it as a `followup_message`. |\n| **Per-session isolation** | Keyed by Cursor's `conversation_id`; no cross-talk between chats. |\n| **Long, cheap waits** | One ~60-min blocking window per re-arm ⇒ minimal paid turns while idle. |\n| **Off when you type** | A `beforeSubmitPrompt` hook disables the loop the moment you type in Cursor (set `stopRemoteChatOnLocalMessage: false` to keep it on). |\n| **Token-authed local API** | The daemon's control API is loopback-only and token-guarded. |\n| **Optional ntfy push** | Get a phone alert even on GitHub (which never notifies you of your own posts). |\n| **Image attachments** | Send a photo from your phone; it's saved locally and the agent opens it with its Read tool. |\n| **Voice → text** | Optional speech-to-text (OpenAI or local): a voice note reaches the agent as transcribed text. |\n| **Adapter SDK** | Implement one `TransportAdapter` interface to support any channel. |\n| **Update-aware** | On activation it checks npm and offers to update when a newer release is out. |\n\n## Channels at a glance\n\n| Adapter | Status | Model | Mobile push |\n|---|---|---|---|\n| **Telegram** _(default)_ | ✅ code complete, unit-tested | A **forum topic per session** via a bot | ✅ native |\n| **Discord** | ✅ working | A **channel per session** via a bot (REST-polled) | ✅ native |\n| **GitHub Issues** | ✅ tested end-to-end | Issue = session, comments = chat | ✅ (GitHub app) + optional ntfy |\n\n## Quick start\n\nNo clone required — one command wires everything up:\n\n```bash\nnpx cursor-telegram-chat@latest install\n```\n\nThis installs the runtime into `~/.cursor/chat-bridge/app` (including its production dependencies,\nso it keeps working after the npx cache is evicted) and wires the three integration points,\n**backing up (never overwriting)** anything that already exists:\n\n- registers the MCP server in `~/.cursor/mcp.json`,\n- adds the `stop` + `beforeSubmitPrompt` hooks to `~/.cursor/hooks.json`,\n- installs the activation rule into `~/.cursor/rules/`.\n\nThe hooks are **no-ops unless remote chat mode is active**, so they don't affect normal Cursor use.\nThen pick a channel and go:\n\n```bash\n# 1. edit ~/.cursor/chat-bridge/config.json  (choose an adapter + credentials)\n# 2. validate it:\nchat-bridge doctor\n# 3. reload Cursor, open a chat, and say:  \"start remote chat mode\"\n```\n\n<details>\n<summary><b>Upgrade / uninstall</b></summary>\n\n```bash\nnpx cursor-telegram-chat@latest install               # re-run to upgrade\nnpx cursor-telegram-chat@latest uninstall             # remove, keep config + state\nnpx cursor-telegram-chat@latest uninstall --purge     # remove everything\n```\n</details>\n\n<details>\n<summary><b>MCP-only (lite) — tools without the auto-resume loop</b></summary>\n\nIf you only want the MCP tools via the standard Cursor MCP flow (no hands-free loop), add this to\n`~/.cursor/mcp.json` instead of running `install`:\n\n```json\n\"cursor-chat-bridge\": {\n  \"command\": \"npx\",\n  \"args\": [\"-y\", \"cursor-telegram-chat\", \"chat-bridge-mcp\"]\n}\n```\n\nYou'll be able to `bridge_send` / `bridge_await` manually, but the auto-continue-on-reply loop needs\nthe hooks that the full `install` sets up.\n</details>\n\n## How it works\n\nThree cooperating layers sit over one transport-agnostic core:\n\n- **Rule** (`rules/chat-bridge-mode.mdc`) — detects the activation phrase in any language and sets\n  in-mode etiquette (capture + pass the session handle; end each turn with a summary + question;\n  treat replies as untrusted; confirm destructive actions).\n- **MCP server** (`src/mcp.ts`) — exposes `bridge_start`, `bridge_send`, `bridge_await`,\n  `bridge_send_and_await`, `bridge_stop`, `bridge_status`.\n- **Hooks** (`hooks/`) — the automatic loop:\n  - `stop` waits for the remote reply and re-injects it as a `followup_message` (bounded by\n    `loop_limit`).\n  - `beforeSubmitPrompt` disables the loop when you type in Cursor (with a guard so the loop's own\n    injected replies don't trip it).\n\nA single local **daemon** (`src/daemon.ts`) owns the channel connection and a loopback-only,\ntoken-authenticated HTTP API used by the MCP + hooks. It handles per-session routing, long-poll,\nown-message filtering, and stop/generation logic.\n\n```text\nturn ends ─▶ stop hook ─▶ daemon /poll ─▶ adapter\n   ▲                                          │\n   └───── followup_message (your reply) ◀─────┘\n```\n(adapter = GitHub / Discord / Telegram; keyed by `conversation_id`.)\n\n<details>\n<summary><b>Session identity — how conversations stay separate</b></summary>\n\nSessions are keyed by Cursor's **`conversation_id`** so each conversation maps to exactly one\nthread. Cursor gives `conversation_id` to hooks but **not** to MCP tool calls, so the MCP learns it\nthrough a small handshake:\n\n1. `beforeSubmitPrompt` writes a per-conversation **pending-start** record\n   (`markers/pending/<conversation_id>.json`) on every real submit — the source of truth for\n   identity (plus a legacy `last-submit`/`ws` pointer, used only for diagnostics + upgrade skew).\n2. `bridge_start` **claims** the single fresh pending record and keys the session by that real\n   `conversation_id`, then returns a **session handle**. It never mints a random id and it\n   ignores its own (possibly misrouted) `BRIDGE_WORKSPACE` — the claim's workspace wins.\n3. `bridge_start` **fails closed** rather than guess: if there's no fresh handshake, if it's\n   stale, or if more than one chat submitted at once, it returns guidance instead of binding a\n   possibly-wrong thread.\n4. The agent passes `session=<handle>` on every subsequent `bridge_*` call — now **required** (no\n   recency/cache fallback), so two conversations in the same workspace can never cross threads.\n\nThe hooks key strictly by their own `conversation_id` (no global fallback), so a turn in one\nconversation never polls or injects into another. Tune the freshness window with\n`handshakeFreshMs` (config or `BRIDGE_HANDSHAKE_FRESH_MS`, default 600000ms); `chat-bridge doctor`\nreports MCP-process workspace bindings, pending/claim health, and version skew.\n</details>\n\n<details>\n<summary><b>Troubleshooting: the session stops after a few minutes</b></summary>\n\nThe hands-free wait (default **60 min**) comes from the `stop` hook's re-arm loop, **not** from\n`bridge_await` (which only polls ~50s per call). Cursor kills a `stop` hook after a short,\nundocumented ceiling unless `~/.cursor/hooks.json` sets a large `timeout` — the installer sets\n`timeout: 3660`. If your session stops after a couple of minutes:\n\n1. **Using the MCP-only (lite) setup?** It has no hooks, so there's no auto-resume. Run the full\n   `npx cursor-telegram-chat@latest install`.\n2. **Fully quit and reopen Cursor** after installing — a reload doesn't always reload `hooks.json`.\n3. Confirm `~/.cursor/hooks.json` has a `stop` hook with `timeout: 3660` (re-running the latest\n   `install` fixes an older one).\n4. Still killed early on your Cursor build? Shrink each wait window so it re-arms sooner: set\n   `\"stopWindowMin\": 5` in `~/.cursor/chat-bridge/config.json` (the 60-min total is `stopBudgetMin`).\n</details>\n\n## Image attachments\n\nSend a photo (or an image file) in the chat thread and the agent can see it:\n\n1. The adapter captures the attachment on the message (Discord `attachments`, Telegram `photo` /\n   image `document`).\n2. The daemon downloads the bytes to `~/.cursor/chat-bridge/media/<session>/` and appends a note to\n   the message text with the local path.\n3. The agent opens that path with its **Read** tool — so the image reaches the model as vision input.\n\n> **Behind a corporate TLS proxy:** Discord's `cdn.discordapp.com` is often blocked while\n> `media.discordapp.net` is allowed. The Discord adapter automatically rewrites attachment URLs to\n> the `media` host, so downloads work on such networks.\n\n## Voice messages (speech-to-text)\n\nSend a **voice note** (Telegram) or an **audio attachment** (Discord) and the agent receives a text\ntranscription as if you'd typed it — **off by default**. Enable it under `stt` in the config:\n\n```jsonc\n\"stt\": {\n  \"enabled\": true,\n  \"provider\": \"openai\",     // \"openai\" (OpenAI-compatible via baseUrl) or \"local\"\n  \"tryLocalSttFirst\": false,// true = force the local transcriber even if a cloud provider is set\n  \"apiKeyCommand\": \"…\",     // or \"apiKey\", or env BRIDGE_STT_API_KEY\n  \"language\": \"auto\",       // auto-detect, or force \"he\" / \"en\"\n  \"keepAudio\": true         // false = delete the audio after transcribing\n}\n```\n\n- **`local` provider** (offline, recommended for sensitive audio): set `localBin`/`localArgs` to a CLI\n  that prints the transcript to stdout (e.g. `whisper.cpp`). The configured `provider` is always\n  respected — the bridge never silently falls back to `local`. To force the local transcriber even\n  when a cloud provider is set, use `\"tryLocalSttFirst\": true` (or `BRIDGE_STT_TRY_LOCAL_FIRST=1`).\n- Transcription runs **asynchronously** in the daemon (never blocks the poll window); the transcript\n  is delivered on the next reply cycle. See [`docs/stt-plan.md`](docs/stt-plan.md) for the full design.\n\n## Read receipts (acknowledgments)\n\nWithout this, sending a message from your phone is silent until the agent finishes a whole turn — you\ncan't tell whether it arrived at all. Turn it on and the **daemon** replies the moment a message\nlands, before any slow work (attachment downloads, transcription). **Off by default:**\n\n```jsonc\n\"ack\": {\n  \"enabled\": true,\n  \"language\": \"auto\",                     // \"auto\" matches you; force with \"en\" or \"he\"\n  \"adapters\": [\"discord\", \"telegram\"],    // bot-identity channels only (see below)\n  \"echoTranscript\": true,                 // repeat back what a voice note said\n  \"maxTranscriptChars\": 200\n}\n```\n\nWhat you get, per message kind:\n\n| You send | The bridge replies |\n|---|---|\n| text | _Got it, on it…_ |\n| an image | _Got the image, taking a look…_ |\n| a voice note | _Got your voice message, transcribing…_ then _Got your request: \"…\"_ once transcribed |\n| `stop` | _Remote chat session stopped._ |\n\nThe ack comes from the daemon, not the agent — the agent doesn't even see your message until its\nnext turn begins, so an agent-authored ack would cost an LLM turn and arrive late.\n\nOn `\"auto\"` the reply matches the language you wrote in: Hebrew text gets a Hebrew reply, and for a\nvoice note the transcriber's own detected language decides. Messages with nothing to go on (an\nuncaptioned image, a bare `stop`) reuse the last language you used in that session, defaulting to\nEnglish.\n\n> **Timing:** the daemon only reads the channel while a poll is in flight — that is, while the agent\n> is awaiting a reply or the `stop` hook is waiting. Messages sent while the agent is **mid-turn**\n> aren't seen (and so aren't acked) until it finishes. They're never lost, just delayed.\n\n> **Why not GitHub:** there the agent comments under *your own* account, so an ack would read as you\n> replying to yourself. You can still opt in by adding `\"github\"` to `adapters`.\n\n## Configuration\n\n`~/.cursor/chat-bridge/config.json` (edits to this file are picked up automatically on the next\npoll — no daemon restart needed; only `mcp.json` env changes and adapter credentials need a restart):\n\n```jsonc\n{\n  \"activeAdapter\": \"telegram\",\n  \"pollIntervalMs\": 10000,   // check for replies every N ms (lower = snappier, more API calls)\n  \"minPollIntervalMs\": 2000, // floor for the above; lower it if you set pollIntervalMs < 10000\n  \"stopBudgetMin\": 60,       // wait budget (mins); resets on every reply\n  \"stopWindowMin\": 60,       // mins per window (keep < hooks.json timeout)\n  \"caCertPath\": \"\",          // corporate CA bundle (PEM) if behind a TLS proxy\n  \"requireConfirmForDestructive\": true,\n  \"stopRemoteChatOnLocalMessage\": true, // typing in Cursor turns remote mode off (set false to keep it on)\n  \"adapters\": {\n    \"github\": {\n      \"owner\": \"you\",\n      \"repo\": \"cursor-bridge-inbox\",\n      \"tokenCommand\": \"gh auth token --user you\"\n    },\n    \"discord\":  { \"botToken\": \"\", \"channelId\": \"\", \"allowedUserIds\": [] },\n    \"telegram\": { \"botToken\": \"\", \"chatId\": \"\", \"allowedUserIds\": [] }\n  },\n  \"ack\": { \"enabled\": false, \"language\": \"auto\" } // read receipts; see above\n}\n```\n\n> `caCertPath` is usually empty — leave it unless things fail. Some corporate networks intercept\n> HTTPS with their own root cert that Node doesn't trust, so requests fail with `TypeError: fetch\n> failed` while `curl` works. If you hit that, point `caCertPath` (or `BRIDGE_CA_CERT`) at your\n> machine's CA bundle (PEM); `doctor`, the daemon, and the update check all honor it.\n>\n> ntfy push is **off by default** and isn't part of this file — enable it only via `BRIDGE_NTFY_*`\n> env vars (see below).\n\n## Environment overrides\n\nSet these in the `env` block of the `cursor-chat-bridge` entry in `~/.cursor/mcp.json` (or the\nshell) to override `config.json` without editing it. All namespaced `BRIDGE_*`. A change needs a\ndaemon restart (`chat-bridge shutdown`) to affect a running daemon.\n\n| Env var | Overrides | Example |\n|---|---|---|\n| <sub><code>BRIDGE_PLATFORM</code></sub> | `activeAdapter` | `github` \\| `telegram` \\| `discord` |\n| <sub><code>BRIDGE_POLL_INTERVAL</code></sub> | poll interval (**seconds**) | `30` |\n| <sub><code>BRIDGE_STOP_BUDGET_MIN</code></sub> | wait budget, mins; resets on reply&nbsp;¹ | `60` |\n| <sub><code>BRIDGE_STOP_WINDOW_MIN</code></sub> | mins per blocking window&nbsp;² | `60` |\n| <sub><code>BRIDGE_CA_CERT</code></sub> | `caCertPath` | `/path/to/ca.pem` |\n| <sub><code>BRIDGE_GITHUB_REPO</code></sub> | github `owner/repo` | `you/inbox` |\n| <sub><code>BRIDGE_GITHUB_TOKEN</code></sub> | github token | `gho_…` |\n| <sub><code>BRIDGE_TELEGRAM_BOT_TOKEN</code></sub> | telegram bot token | — |\n| <sub><code>BRIDGE_TELEGRAM_CHAT_ID</code></sub> | telegram forum group id | — |\n| <sub><code>BRIDGE_TELEGRAM_ALLOWED_USER_IDS</code></sub> | whitelist (csv) | `123,456` |\n| <sub><code>BRIDGE_DISCORD_BOT_TOKEN</code></sub> | discord bot token | — |\n| <sub><code>BRIDGE_DISCORD_CHANNEL_ID</code></sub> | discord **channel** id&nbsp;³ | — |\n| <sub><code>BRIDGE_DISCORD_ALLOWED_USER_IDS</code></sub> | whitelist (csv) | `123,456` |\n| <sub><code>BRIDGE_WORKSPACE</code></sub> | per-window session key | `${workspaceFolder}` |\n| <sub><code>BRIDGE_NTFY_TOPIC</code></sub> | enable ntfy + set topic | `cursor-bridge-…` |\n| <sub><code>BRIDGE_NTFY_PRIORITY</code></sub> | push priority 0–5 (0 = off) | `2` |\n| <sub><code>BRIDGE_NTFY_SERVER</code></sub> | ntfy server base URL | `https://ntfy.sh` |\n\n<sub>¹ Also `stopBudgetMin` in `config.json` — the reliable knob, since the hook doesn't inherit the MCP entry's env.\n² Also `stopWindowMin` in `config.json`. Keep below the `stop` hook `timeout` in `~/.cursor/hooks.json`.\n³ Any existing text channel in your server; used to locate the server + category — a fresh channel is created per session alongside it (For example #General).</sub>\n\nPer-conversation platform can also be chosen at runtime: say _\"start remote chat in Telegram\"_ and\nthe agent passes `bridge_start(adapter: \"telegram\")` for that conversation only.\n\n## The wait loop (stop hook)\n\nWhile remote mode is active, the `stop` hook blocks at the end of each turn waiting for your reply.\nTwo knobs control it:\n\n- **`stopWindowMin`** (default **60**) — how long a single hook invocation blocks before it returns\n  a silent keep-alive and re-arms. Cursor caps `stop`-hook runtime at the `timeout` in\n  `~/.cursor/hooks.json` (default **3660s / 61 min**); probing showed no hidden cap below that, so\n  one ~60-min window means just **one paid keep-alive turn per hour** while you're away.\n- **`stopBudgetMin`** (default **60**) — the total time to keep waiting across re-arms. It **resets\n  on every reply**, so it's really \"keep waiting up to N minutes since your last message.\"\n\nThe loop ends when you reply, type in Cursor, send `stop` in the thread, or call `bridge_stop`.\n\n## Push notifications (ntfy)\n\nGitHub never notifies you about your **own** activity — and the agent posts as _you_ (self-mentions\nand self-assignment don't notify either). So to get a phone alert on the GitHub channel without a\nsecond account, cursor-chat-bridge can fire an out-of-band push via [ntfy](https://ntfy.sh) on\nevery summary. It's free, account-less, open-source, self-hostable, and deep-links to the issue.\n\nIt's **off by default**. Enable it via env on the MCP entry:\n\n1. Install the **ntfy** app (iOS/Android) or use the web app.\n2. Pick a long, unguessable topic (topics are public-by-obscurity) and **subscribe** to it.\n3. Set `BRIDGE_NTFY_TOPIC=cursor-bridge-<random>` in the `env` block of the `cursor-chat-bridge`\n   entry in `~/.cursor/mcp.json`.\n\n`BRIDGE_NTFY_PRIORITY` is the on/off dial: **0 = off (default)**, 1=min … 5=max. A push is sent only\nwhen priority ≥ 1 **and** a topic is set. Pushes are skipped for **Telegram** and **Discord**, which\nalready notify natively.\n\n## Per-platform setup\n\n<details>\n<summary><b>Telegram</b> — default, best chat UX</summary>\n\n1. Create a bot with **@BotFather** → bot token.\n2. Create a group, enable **Topics**, add the bot as admin with _Manage Topics_.\n3. Put the group `chatId` and your numeric `allowedUserIds` (whitelist) in config;\n   `activeAdapter: \"telegram\"`.\n\nObtain `chatId` / user ids via `getUpdates` pairing (send a message in the group, read the update).\nRequires the daemon to reach `api.telegram.org` — if a network blocks it, run the daemon on a host\nthat can, or use Discord/GitHub instead.\n</details>\n\n<details>\n<summary><b>Discord</b> — phone-first, works behind proxies</summary>\n\n1. Create a Discord **server** (use one you own) — the bot creates a channel per\n   session inside it.\n2. Create an app + **Bot** at <https://discord.com/developers/applications>. Under **Bot**, click\n   **Reset Token**, then **Copy** the revealed **bot token**.\n3. Under **Bot**, enable the **Message Content Intent**.\n4. Invite the bot: **OAuth2 → URL Generator**, scope `bot`, permissions **Manage Channels** + View\n   Channels + Send Messages + Read Message History (_Manage Channels is required_ — the bot creates\n   and deletes a channel per session). **Copy the Generated URL, open it in a new browser tab, and\n   select the server you created** in step 1.\n5. Get a **channel id** (`channelId`) — any existing text channel in that server (e.g. `#general`);\n   the bot uses it to find the server + category and creates a fresh channel per session alongside\n   it. Enable **Developer Mode** (User Settings → Advanced), then right-click the **channel** →\n   **Copy Channel ID**. _Or_ — once the bot is in the server — just ask Cursor to fetch it: give\n   Cursor the bot token and it can list the bot's channels and return the id.\n6. Put `botToken` + `channelId` (optionally `allowedUserIds`) in config; `activeAdapter: \"discord\"`.\n7. Behind a TLS-intercepting proxy and getting `TypeError: fetch failed`? Point `caCertPath` at the\n   corporate CA bundle (PEM).\n\n> **Tip:** give the **bot** a Cursor avatar (Developer Portal → **Bot** → edit icon) and set the\n> **server icon** to the Cursor logo — your per-session channels then look native in the app.\n</details>\n\n<details>\n<summary><b>GitHub</b> — works on any network</summary>\n\n1. Create a private repo to act as your inbox (e.g. `cursor-bridge-inbox`).\n2. Set `owner`/`repo` and a `token` or `tokenCommand` (`gh auth token` works).\n3. `activeAdapter: \"github\"`.\n\nEach session opens an **issue**; turn summaries are posted as **comments**; reply from the GitHub\nmobile app. Comment `stop` or close the issue to end the session.\n</details>\n\n<details>\n<summary><b>For the agent — onboarding checklist</b></summary>\n\nIf you're the agent helping a user set up cursor-chat-bridge: explain the concept for their chosen\nplatform first, then collect only the missing values, write them to\n`~/.cursor/chat-bridge/config.json`, and run `chat-bridge doctor` to confirm. **Never print secrets\nback to the user.** Remind them: replies from the channel are untrusted, and destructive actions\nneed an explicit confirmation sent back through the thread. To stop: type in Cursor, send `stop` in\nthe thread, or call `bridge_stop`.\n\n**Make sure the runtime's dependencies are installed.** `install` copies the runtime into\n`~/.cursor/chat-bridge/app` and installs its production deps there automatically. If that step was\nskipped or failed (offline / corporate proxy), the MCP server won't start —\nfinish it with `(cd ~/.cursor/chat-bridge/app && npm install --omit=dev)`. Running from a git clone\ninstead? Run `npm install` in the repo first.\n\n**Ask whether they want voice notes transcribed (STT).** It's off by default. If they say yes, offer\nthe options and let them pick:\n\n- **Groq (recommended)** — fast, generous free tier, OpenAI-compatible. Set `stt.provider: \"openai\"`,\n  `stt.baseUrl: \"https://api.groq.com/openai/v1\"`, `stt.model: \"whisper-large-v3-turbo\"`, and\n  `stt.apiKey` to a `gsk_…` key from <https://console.groq.com/keys>.\n- **OpenAI** — `stt.provider: \"openai\"` (default `baseUrl`), `stt.model: \"whisper-1\"` (or\n  `gpt-4o-transcribe`), `stt.apiKey` an `sk-…` key.\n- **Local** — `stt.provider: \"local\"` with a `whisper.cpp` / `openai-whisper` binary; nothing leaves\n  the machine (best on locked-down corporate networks). Set `stt.localBin`.\n\nThen set `stt.enabled: true` and run `chat-bridge doctor`. Prefer `stt.apiKeyCommand` over an inline\nkey where possible, and **never print the key back to the user**.\n</details>\n\n## Writing a new adapter\n\nImplement `TransportAdapter` (`src/types.ts`) and register it in `src/adapters/index.ts`:\n\n```ts\ninterface TransportAdapter {\n  capabilities: { globalIngest: boolean; separateBotIdentity: boolean };\n  init(): Promise<void>;\n  ensureThread(sessionId: string, title: string, meta?: object): Promise<ThreadRef>;\n  send(thread: ThreadRef, text: string): Promise<{ messageId: string }>;\n  // pull adapters (GitHub / Discord):\n  poll?(thread: ThreadRef, cursor?: string): Promise<PollResult>;\n  // push / global adapters (Telegram):\n  startIngest?(router: Router): Promise<() => void>;\n  stop?(thread: ThreadRef): Promise<void>;\n}\n```\n\n| Member | Required | Purpose |\n|---|---|---|\n| `capabilities` | ✅ | `globalIngest`: one stream for all sessions (Telegram) vs per-thread polling. `separateBotIdentity`: posts appear as a bot, not you. |\n| `init()` | ✅ | Validate credentials + connectivity. |\n| `ensureThread()` | ✅ | Create/lookup the per-session thread/channel; returns a `ThreadRef`. |\n| `send()` | ✅ | Post a message (handle the platform's length limits / chunking). |\n| `poll()` | pull adapters | Return new messages after `cursor`, filtered to allowed users. |\n| `startIngest()` | push adapters | Start a single global stream and route updates; return a stop fn. |\n| `stop()` | optional | Clean up (e.g. Discord deletes its per-session channel). |\n\n## Security\n\n- The loopback control API is **token-authenticated** — only local processes with the token (the\n  MCP + hooks) can drive the daemon.\n- Inbound messages are filtered by an `allowedUserIds` whitelist (Telegram/Discord).\n- Every remote reply is wrapped and marked **untrusted**; the rule forbids destructive actions\n  without an explicit confirmation sent back through the thread.\n- Tokens live in `~/.cursor/chat-bridge/config.json` (chmod 600) and are never committed. Prefer\n  `tokenCommand` over a stored token where possible.\n\n### Permissions — what the daemon needs (and why scanners flag it)\n\nDriving an agent from a chat app inherently needs some powerful capabilities. This package uses:\n\n- **Child processes** — to spawn the daemon, run your `tokenCommand` (e.g. `gh auth token`), the\n  local `whisper` binary (only if you choose local STT), and `npm install` inside the installer.\n- **Network access** — a loopback-only control server plus outbound calls to your chosen channel\n  (Telegram/Discord/GitHub), speech-to-text upload (if enabled), and an npm update check.\n- **Filesystem access** — read/write `~/.cursor/chat-bridge/` for config, media, and logs.\n\nBecause of these, supply-chain scanners (Socket, Snyk, etc.) show a lower **Supply Chain Security**\nscore than a pure-logic library — even though there are **no known vulnerabilities**. That score\nreflects *capability + package age* (this project is new, single-maintainer), not a detected exploit.\nFor transparency: there is **no `postinstall`/`install` script** — the only lifecycle script is\n`prepublishOnly` (a build that runs on the publisher's machine, never on your install). Everything\nnetwork/filesystem/process happens only when **you** run the daemon, and the control API is\nloopback-only and token-guarded. The source is MIT and auditable in this repo.\n\n## Verification status\n\nVerified end-to-end on-machine (no Cursor restart needed):\n\n- **GitHub adapter** — create issue, send, poll, own-message filtering, `stop` keyword, close-detection.\n- **Daemon** — token auth, long-poll, stop/generation, persistence.\n- **MCP server** — all tools over a real stdio JSON-RPC handshake.\n- **Hooks** — stop-loop `followup_message` injection, before-submit off-switch + injection guard,\n  instant no-op when inactive.\n- **Per-conversation routing** (`node scripts/e2e-conv.mjs`) — distinct conversations open distinct\n  issues; two conversations in the same workspace stay separate; re-activating a stopped session\n  opens a fresh thread; unconfigured channels return onboarding guidance.\n- **Unit tests** — `npm test` (routing, store semantics, message filtering).\n\n## Contributing\n\nIssues and PRs welcome. Local dev:\n\n```bash\nnpm install\nnpm run build        # tsc → dist/\nnpm run typecheck\nnpm test             # node --test\nnpm run dev:daemon   # run the daemon from source (tsx)\n```\n\nNew channels are the easiest contribution — implement one `TransportAdapter` (see above).\n\n## License\n\n[MIT](./LICENSE)\n",
  "bytes": 30363,
  "sha": "4c05493ac0e0bb5fb690c2349e65317f8fb17dc649e45b09b8ddf932953866fb",
  "repo_slug": "udah1/cursor-chat-bridge",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_udah1_cursor_chat_bridge_4673304d/readme"
}