A TypeScript service that forwards tool notifications from OpenAI Codex and OpenCode into a Discord channel and lets you reply from Discord threads back to each tool.
The project is designed for distribution: it includes a reusable HTTP bridge, Discord integration, accompanying integration artifacts (Codex notify script and OpenCode plugin), automated tests, and setup documentation.
- Minimal Fastify HTTP API secured with a shared bearer token.
- Discord bot that posts rich embeds per tool update and spins up a thread per conversation.
- Reply queue so Codex/OpenCode can poll for Discord responses.
- Ready-to-use integration artifacts:
integrations/codex-notify/notify.mjsfor Codexnotifyhook.integrations/opencode-plugin/index.tsOpenCode plugin.
- Vitest coverage for the HTTP API.
- Node.js 18.18 or newer (uses built-in
fetch, ESM, and Discord.js v14). - A Discord bot with
MESSAGE CONTENT INTENTand permission to manage threads in the destination guild. - Hosted Codex CLI with
notifyconfigured. - OpenCode CLI (>= 0.3) with plugin support.
- Go to the Discord Developer Portal and create (or reuse) an application.
- Under Bot, add a bot user and copy its token for
DISCORD_TOKEN. - Enable Message Content Intent (and optionally Presence intent) in the bot settings.
- Generate an invite under OAuth2 → URL Generator with the
botscope and permissions to send messages, create/manage threads, add reactions, and read history. - Invite the bot to your guild using the generated URL.
- Enable developer mode in Discord (Settings → Advanced), right-click the target channel, and copy its ID for
DISCORD_CHANNEL_ID. - Ensure the bot role in that channel can send messages, start threads, and react.
npm install
cp .env.example .env
# Fill in your Discord token, channel ID, and bridge token
npm run buildTo run in development:
npm run devTo start the compiled service:
npm start| Name | Description |
|---|---|
DISCORD_TOKEN |
Discord bot token |
DISCORD_CHANNEL_ID |
Guild text channel to receive updates |
BRIDGE_AUTH_TOKEN |
Shared secret for HTTP integrations |
BRIDGE_HOST |
Optional host binding (default 0.0.0.0) |
BRIDGE_PORT |
Optional port (default 8787) |
LOG_LEVEL |
Pino log level (default info) |
npm testThe suite (tests/http-server.test.ts) exercises the HTTP API (authorization, validation, draining replies).
Codex can invoke an external script for every agent turn. Use the provided script:
Install via one-liner:
curl -fsSL https://raw.githubusercontent.com/adam-paterson/codex-opencode-notifier/main/scripts/install-codex-notify.sh | bash(use ASSUME_YES=1 in front of the command for non-interactive environments)
The installer copies notify.mjs to ~/.codex/notify.mjs (override with DEST=...), prints the snippet to add to ~/.codex/config.toml, and reminds you to restart Codex.
notify = ["node", "/Users/you/.codex/notify.mjs", "https://your-bridge.example.com", "your-shared-token"]notify.mjs expects the notification JSON on argv[2]. Command signature:
node notify.mjs <bridgeUrl> <authToken> '<JSON_PAYLOAD>'Install via one-liner:
curl -fsSL https://raw.githubusercontent.com/adam-paterson/codex-opencode-notifier/main/scripts/install-opencode-plugin.sh | bash(set ASSUME_YES=1 if running non-interactively)
By default the plugin lands in ~/.config/opencode/plugin/discord-bridge.ts (override with DEST=...) and a readme is placed alongside it. Restart OpenCode after setting:
export DISCORD_BRIDGE_URL="https://your-bridge.example.com"
export DISCORD_BRIDGE_TOKEN="your-shared-token"All routes (except /health) require the Authorization: Bearer <BRIDGE_AUTH_TOKEN> header.
| Method & Path | Description |
|---|---|
GET /health |
Liveness probe |
POST /events |
Accepts a tool event (Codex/OpenCode) |
POST /replies/drain |
Drains queued Discord replies for a thread |
GET /queue/snapshot |
Debug view of outstanding replies |
{
"id": "evt_123",
"source": "codex",
"type": "agent-turn-complete",
"title": "Turn complete",
"body": "Codex finished processing the task.",
"createdAt": "2025-09-18T10:02:22.123Z",
"threadId": "thread_abc",
"metadata": {
"sessionId": "abc",
"user": "alice"
}
}{
"source": "opencode",
"threadId": "session-123"
}Response:
{
"replies": [
{
"id": "reply_456",
"source": "opencode",
"threadId": "session-123",
"body": "Acknowledged, running tests now.",
"postedAt": "2025-09-18T10:05:00.000Z",
"metadata": {
"discordMessageId": "129991234567890"
}
}
]
}- Host behind HTTPS (e.g., via reverse proxy, Fly.io, Render, etc.).
- Store
BRIDGE_AUTH_TOKENas a secret. Rotate periodically. - Discord threads auto-archive after 24h (configurable in
ThreadAutoArchiveDuration). Adjust if you expect long-running conversations.
- Persist reply queue in Redis/Postgres for multi-instance deployments.
- Add signature verification to incoming Codex/OpenCode payloads.
- Add metrics and structured audit logs.
- Support slash-commands (e.g.,
/bridge status).
MIT