Local-first Discord bot for running Codex CLI jobs across multiple repo-scoped sessions.
- Install prerequisites:
- Bun
- Node.js (required for the PTY bridge helper used by
node-pty) - Codex CLI (
codex) available inPATH(or setCODEX_COMMAND) - A Discord application + bot token
- Bot invited to your server with
botandapplications.commandsscopes
- Install dependencies:
bun install- Bootstrap local config:
bun run setupRequired values in .env:
DISCORD_TOKENDISCORD_APP_ID- Either
REPO_ALLOWLIST=/abs/path/repo-a,/abs/path/repo-borREPO_ALLOWLIST_FILE=./config/repos.local.json
Required values in config/repos.local.json:
- Add at least one allowed repo path to the
reposarray.
Optional but recommended:
DISCORD_GUILD_IDfor fast guild-only command registration during developmentCODEX_COMMANDandCODEX_ARGSifcodexis not directly runnableCODEX_APPROVAL_POLICY(on-requestdefault, ornever)PASTEBIN_DEV_KEYfor Pastebin upload support on long outputsPASTEBIN_USER_KEYif you want pastes attached to your Pastebin accountPASTEBIN_PRIVACY(0public,1unlisted,2private),PASTEBIN_EXPIRE,PASTEBIN_FORMAT,PASTEBIN_NAME_PREFIXPASTEBIN_TRIGGER_CHARSto control when uploads are used
- Start the bot:
bun run startThis project is intentionally private in package.json to prevent accidental npm publishing.
Other users can deploy directly from the repository:
git clone <your-repo-url>
cd discord-codex-orchestrator
bun install
bun run setup
bun run startFor a built entrypoint instead of source execution:
bun run start:distDetailed steps: docs/DEPLOY_NON_PUBLISH.md
- Run
/new repo:<allowed repo name/path> branch:<optional>in a text channel. - Open the created thread and run
/ask prompt:<text>(thread-only). - Use
/steer prompt:<correction>in the session thread to course-correct a running/next step. - If Codex asks for any interactive input (approval, selection, password, text), respond in the same thread:
/approve allow:true|falsefor yes/no approval prompts/respond text:"..." submit:true|false secret:true|falsefor text/password/choice input/send-key key:enter|tab|up|down|esc|ctrl-cfor control/navigation input
- Use
/updatesin the session thread when you want a current progress snapshot (pull-based, low spam). - Use
/active-threadsto get open session thread links with repo/branch/runtime summary. - Manage sessions with
/list,/switch,/fork,/pause,/resume,/stop,/status.
/ask, /steer, /approve, /respond, /send-key, and /updates are thread-only and do not accept sessionid.
- Local-first execution: jobs run on your machine; nothing is hosted remotely.
- Safety first: only repos in allowlist are runnable; secret-like text is redacted.
- Session model: one Discord thread maps to one session for clear context boundaries.
- Concurrency model: each session is serialized (queue), while sessions run in parallel.
- Persistence: SQLite (
bun:sqlite, WAL mode) stores sessions/messages/jobs/heartbeats and restores sessions after restart. - Execution model: Bun app runtime with a Node PTY bridge (
scripts/codex-pty-proxy.mjs) for reliablenode-ptybehavior. - Command model: each
/askor/steerlaunches a fresh Codex PTY command process (instead of keeping one long-lived PTY shell). - Interaction model: structured detector (ANSI-stripped + redacted context) captures approval, confirm, choice, password, and text-input prompts.
- Update model: bot avoids continuous output spam; use
/updatesfor on-demand progress and output tail. - Operator visibility: every reply includes metadata footer (
session/runner/thread/repo@branch/status).
-
Why hybrid (
Bun + Node helper)? -
node-ptyis not fully reliable under Bun runtime in this workflow, so PTY execution is delegated to Node while the app remains Bun-based. -
Reference: Bun issue #4290
-
When to move to full Node runtime:
-
If this is production-critical and you want the simplest runtime surface, migrate fully to Node (
better-sqlite3+ Node process APIs) and remove Bun runtime coupling. -
If you want minimal migration effort now, keep the current hybrid architecture.
bun run setupbun run devbun run startbun run start:distbun run buildbun run typecheckbun run lintbun run test
ENOENTfor codex: setCODEX_COMMANDto the full executable path.- Slash commands not appearing globally yet: set
DISCORD_GUILD_IDfor immediate guild registration. - Long outputs: configure
PASTEBIN_DEV_KEY; if Pastebin upload is unavailable, the bot attaches a.mdfile in-thread as fallback. - Jobs timing out: raise
EXECUTION_TIMEOUT_MSin.envfor longer Codex tasks. - Need interactive approvals/prompts: set
CODEX_APPROVAL_POLICY=on-request(default) and restart the bot. - Startup says another process is running: stop old
bun run src/index.tsinstances; only one orchestrator instance should run per DB. - PTY commands appear hung with no output: verify
nodeis installed and reachable inPATH(the PTY bridge runs via Node).