A Discord bot that gives your server a shared coding agent. Connect a GitHub
repo, type /code <task> in any channel, and AnyWareCode works in a thread —
streaming progress, taking mid-task instructions from anyone in the thread —
then opens a pull request.
/code <task> spawns a thread, agent works, streams progress, opens a PR
/ask <question> repo-aware Q&A, read-only
/connect llm connect your LLM (Anthropic key, Claude subscription, or compatible provider)
/connect github connect GitHub repos
/setup show connection status and usage
/repo set pick the active repo for a channel
/status running and queued tasks
/cancel stop the task in this thread
/config role choose who may invoke the agent (default: admins)
Discord ⇄ apps/bot (discord.js + fastify + orchestrator)
│ Drizzle → Supabase (Postgres)
│ dockerode → ephemeral container per task (apps/runner)
│ │ Claude Agent SDK, git clone/push
│ └ egress only via allowlist proxy
└ octokit → GitHub App (installation tokens, PRs, merges)
Each Discord server connects its own LLM credential (bring your own key).
Supported: Anthropic API key, Claude Pro/Max subscription token (claude setup-token),
or any Anthropic-compatible endpoint (DeepSeek, LiteLLM proxy, etc.).
Besides /code and /ask, you can @mention the bot anywhere with plain
language. It reads the recent conversation and decides what to do:
- Chat — questions answerable from the conversation get a normal reply. Open to everyone; costs one small LLM call on the server's credential (rate-limited per guild, doesn't touch the monthly task cap).
- Explicit task —
@AnyWareCode fix the login 500starts a coding task immediately (same thread + PR flow as/code). Requires the same role as/code(/config role). - Inferred task — tag the bot after a discussion without giving a direct
command and it proposes the task it inferred, with Run / Dismiss buttons.
Run re-checks permissions and caps for whoever clicks; proposals expire
after
CHAT_PROPOSAL_TTL_MINUTES(default 60). - In a finished task thread, asking for more changes iterates on that thread's existing PR.
Only explicit @ mentions trigger it — replies to bot messages, @everyone,
and @here are ignored. Mentions inside an active task thread are forwarded
to the running agent like any other reply.
- Node.js >= 22 —
node --version - Docker Desktop running
corepack enable(provides pnpm)
- Go to https://discord.com/developers/applications → New Application.
- Name it (e.g. "AnyWareCode Dev").
- Bot tab → Add Bot → copy the Token → this is
DISCORD_TOKEN. - Same page → enable Message Content Intent under Privileged Gateway Intents.
- OAuth2 → General → copy Client ID → this is
DISCORD_CLIENT_ID. - OAuth2 → URL Generator: scopes =
bot+applications.commands; permissions = Send Messages, Create Public Threads, Send Messages in Threads, Embed Links, Read Message History. - Copy the generated URL → open it → add the bot to your test server.
GitHub needs to redirect back to the bot after app installation. For local dev, use a free Cloudflare Tunnel:
# Install once
brew install cloudflared
# Run before starting the bot (leave this terminal open)
cloudflared tunnel --url http://localhost:3000Copy the https://something.trycloudflare.com URL — this is your PUBLIC_URL.
The tunnel URL changes each run. Update
PUBLIC_URLin.envand the GitHub App's Setup URL if it changes.
-
Fill in:
- GitHub App name:
AnyWareCode(or any name; the slug in the URL =GITHUB_APP_SLUG) - Homepage URL: your
PUBLIC_URLfrom Step 2 - Callback URL:
{PUBLIC_URL}/github/user-callback— used by/link github(provenance identity linking). Also click Generate a new client secret: copy the Client ID →GITHUB_CLIENT_IDand the secret →GITHUB_CLIENT_SECRET. (Leave both env vars unset to disable linking.) - Setup URL:
{PUBLIC_URL}/github/setup— check Redirect on update - Webhook → Active: check and set:
- Webhook URL:
{PUBLIC_URL}/github/webhook - Webhook secret: a random string (>=16 chars) →
GITHUB_WEBHOOK_SECRETin.env. (Leaving both unset disables webhook features: issue feed, auto-review, ship-log auto-post, proactive previews.)
- Webhook URL:
- GitHub App name:
-
Repository permissions:
- Contents: Read & write
- Pull requests: Read & write
- Issues: Read-only
- Deployments: Read-only
- Commit statuses: Read-only
- Checks: Read-only
- Metadata: Read-only (required automatically) Then under Subscribe to events: check Issues, Pull request, Deployment status, Installation (so GitHub-side uninstalls clean up the Discord-side links). (Existing installations must approve any permission change from their installation settings page.)
A server can link multiple installations — its members' personal accounts and any orgs.
/connect githubalways offers an "install on another account or org" link; GitHub's own picker lists the orgs you admin. Unlink with/connect github remove:<login>. -
Where can this GitHub App be installed?: Any account
-
Click Create GitHub App.
-
On the app page:
- Copy App ID →
GITHUB_APP_ID - Copy the slug from the URL (
/apps/your-slug) →GITHUB_APP_SLUG - Scroll down → Generate a private key → download
.pemfile - Open the
.pem, copy contents →GITHUB_APP_PRIVATE_KEY(replace literal newlines with\nfor the env file)
- Copy App ID →
- Go to https://supabase.com → New project.
- Once created: Project Settings → Database → Connection string.
- Copy the Session pooler URI → this is
DATABASE_URL. - Set
DATABASE_SSL=true.
cp .env.example .envOpen .env and fill in:
DISCORD_TOKEN= # from Step 1
DISCORD_CLIENT_ID= # from Step 1
GITHUB_APP_ID= # from Step 3
GITHUB_APP_SLUG= # from Step 3 (e.g. anywarecode)
GITHUB_APP_PRIVATE_KEY= # contents of the .pem, with \n for newlines
PUBLIC_URL= # https://something.trycloudflare.com from Step 2
STATE_SECRET= # openssl rand -base64 24
CREDENTIAL_SECRET= # openssl rand -base64 48
DATABASE_URL= # Supabase Session pooler URI from Step 4
DATABASE_SSL=true
# Leave these for dev:
RUNNER_NETWORK=
RUNNER_HTTPS_PROXY=Generate the secrets:
openssl rand -base64 24 # paste as STATE_SECRET
openssl rand -base64 48 # paste as CREDENTIAL_SECRETcorepack enable
pnpm install
# Build the runner Docker image (from repo root)
docker build -f apps/runner/Dockerfile -t anywarecode-runner .
# Start the bot (tsx watch — auto-reloads on file changes)
pnpm devThe bot will:
- Run DB migrations against Supabase automatically
- Register slash commands with Discord
- Log
Logged in as AnyWareCode#xxxxwhen ready
Runner changes:
pnpm devonly reloads the bot. After editing anything inapps/runner/orpackages/shared/, rebuild the runner image:docker build -f apps/runner/Dockerfile -t anywarecode-runner .
With the bot running in your test server:
- The bot posts a welcome message with Connect GitHub and Connect LLM buttons.
- Click Connect GitHub → install the GitHub App on a repo.
- Click Connect LLM → pick a provider:
- Claude subscription: run
claude setup-tokenlocally, paste the token - Anthropic API key: paste your
sk-ant-api-...key - Other provider: paste base URL + key + model name
- Claude subscription: run
- Run
/repo setin a channel → pick a repo. - Type
/code fix the typo in README— done.
pnpm -r typecheck # TypeScript check across all packages
pnpm -r test # run all tests (vitest)
pnpm dev # bot with hot reload
pnpm --filter @anywarecode/bot test gates # single test file
pnpm --filter @anywarecode/bot db:generate # after editing db/schema.tscp .env.example .env # fill in all values
docker compose up -d --buildBuilds and starts: bot, runner image, egress proxy. DB is Supabase — no local Postgres. Bot runs migrations and registers commands automatically on boot.
- MCP extensions (
/connect mcp): setMCP_HOST_ALLOWLISTto the hostnames admins may attach, AND add a matching regex line for each host toinfra/egress-proxy/filter, then rebuild the proxy (docker compose up -d --build egress-proxy) — otherwise the connection dies at the proxy. - Discord Premium Apps (second billing rail): create the SKUs in the
dev portal (guild
subscription SKUs for Pro/Studio at the same prices as Razorpay, one
consumable SKU for job packs) and set
DISCORD_SKU_PRO,DISCORD_SKU_STUDIO,DISCORD_SKU_PACK. Payouts require a US/UK/EU developer entity. Unset = the rail stays inert; Razorpay keeps working.
| Package | Role |
|---|---|
packages/shared |
NDJSON protocol between bot and runner (TaskSpec in, RunnerEvent out) |
apps/bot |
Discord, Supabase, GitHub App, container orchestration, HTTP server |
apps/runner |
Baked Docker image; clones repo, runs Claude Agent SDK, pushes branch |
infra/egress-proxy |
tinyproxy allowlist — Anthropic + GitHub only (production) |
Security notes:
- Tokens travel via container stdin, never env vars (
docker inspectexposes env). - Runner has dropped capabilities, memory/CPU/PID limits, AutoRemove.
- In production, runner containers can only reach
api.anthropic.com+ GitHub (egress proxy). - Each guild's LLM credential is encrypted at rest (AES-256-GCM, per-guild key).
- GitHub install links are HMAC-signed + single-use DB nonces (unforgeable, unreplayable).