Local Feishu long-connection bot for controlling Codex CLI sessions in configured project directories. The bot receives slash commands from Feishu, starts local Codex processes through a PTY, persists session state under .code-bot/, and returns status or log tails back to the chat.
Requirements:
- Node.js 20+
- Codex CLI installed and available on
PATHto start sessions - A Feishu/Lark app with bot capability enabled and SDK long-connection credentials
Feishu app setup:
- Enable bot capability for the app.
- Use SDK long-connection/WebSocket event handling; no public callback URL is needed for this mode.
- Subscribe to the
im.message.receive_v1event. - Grant the bot message receive/send permissions required by Feishu for this event and replies.
- Add the bot to group chats before using group commands.
Run:
npm install
mkdir -p .code-bot
cp config.example.json .code-bot/config.jsonEdit .code-bot/config.json:
- Set
feishu.appIdandfeishu.appSecret. - Set
restrictUserstotrueonly when you want to limit allowed Feishu user open IDs, then fillallowedUsers. - Set
restrictChatIdstotrueonly when you want to limit allowed group chat IDs, then fillallowedChatIds. - With either switch left as
false, that dimension is unrestricted. - Replace project entries with your local repositories. Use absolute paths for clarity.
- Keep
output.directMaxCharsandoutput.chunkSizeas reserved config schema fields; current Feishu replies do not enforce these limits. - Keep
codex.commandascodexwhen the CLI is onPATH, or set it to an absolute executable path. - Use
codex.defaultArgsfor arguments shared by all sessions, andprojects[].codexArgsfor project-specific Codex arguments. - Set
logLeveltoerror,info, ordebugto control bot logging from config.LOG_LEVELstill overrides it when you need a temporary runtime override. - When Codex is authenticated with a ChatGPT account, model availability can differ from API usage. In this repository,
gpt-5.4is verified to work, whilegpt-5.4-mediumreturns a Codex CLI400 invalid_request_error. If model support is unclear, omit--modeland let Codex use its default account-compatible model. - Configure
notifications.enabled,notifications.idleMs, andnotifications.maxFinalCharsto control proactive completion messages. Notifications are enabled by default.notifications.failureTailCharsis reserved config for failure diagnostics. - If Codex CLI is missing when the bot starts, the Feishu gateway still connects. Starting a session with
/newwill report the Codex start failure in chat.
Start the bot:
npm run devThe bot reads .code-bot/config.json from the repository root and writes local state, events, and session logs under .code-bot/.
In Feishu chat:
/projects
/new <project>
/status
/tail 80
After /new <project>, plain text messages are sent to the active Codex session. You can also send explicit input with:
/send <text>
With completion notifications enabled, plain text messages receive an immediate acknowledgement, then the bot sends a second Feishu message when Codex's final answer is detected. Use /tail or /rawtail to inspect process output while a task is running.
/help
/projects
/use <project>
/new [project]
/send <text>
/status
/current
/tail [n]
/rawtail [n]
/model [model] [reasoning]
/stop
/sessions
/resume [session] [project]
/approve <id>
/reject <id>
/upgrade
/restart
Notes:
/projectsnow prefers an interactive Feishu project selector card when projects are configured./use <project>remains the direct text fallback and still selects a project without starting Codex./new [project]starts a new Codex session for the named project, or for the currently selected project. If the chat already has a running session, the bot stops it first and then starts the new one. The bot will try to capture the Codex native session ID so the session can be resumed later./statusreturns the local bot/session summary and a Codex-native status block. For running sessions it asks Codex for a freshstatus; if that is unavailable, it falls back to the most recent cached Codex status or marks it unavailable./modelnow prefers an interactive Feishu card with a model selector and, when all listed models share compatible reasoning levels, a reasoning selector./model <model> [reasoning]remains the direct text fallback. In both forms, it saves chat/project defaults and switches the running session when one is active. Saved selections override project model args for future/newand/resumecommands./currentreturns a Feishu card snapshot of the current Codex TUI viewport. It uses the configured terminal snapshot dimensions and does not accept a line count.- On bot restart, the last running session for each chat is marked interrupted and automatically resumed when a Codex native session ID was captured.
/sessionslists recent sessions. Session status is shown ascurrent,resumable, ornot-resumable./resumeopens a project-scoped session selector card when a project is selected./resume <session> [project]directly resumes a code_bot or Codex native session./tail [n]returns a readable progress summary for the active Codex session. It still accepts and validates an optional count with a default of 80, but that count applies to the sanitized PTY fallback path. When structured Codex observation data is available,/tailreturns the structured summary instead./rawtail [n]returns raw PTY terminal output for exact debugging. It accepts an optional count and defaults to 80./stopstops the current session immediately./approve <id>and/reject <id>are reserved for future approval-gated actions./upgradelets an admin user install the latest configured branch and restart the bot through pm2. See Self Upgrade below./restartlets an admin user install dependencies, build, and restart the bot through pm2 from the current local checkout without fetching or merging the configured branch.- Resume limits: old sessions without a captured Codex native ID cannot be resumed by code_bot session ID; stop the active session before resuming another one; when you pass an explicit
project, it must match the session history.
/upgrade lets an admin user pull the latest configured branch, install dependencies, build, and restart the bot through pm2.
/restart uses the same config and admin list, but restarts from the current local checkout. It does not fetch, checkout, merge, or require a clean worktree.
Config:
"upgrade": {
"enabled": true,
"adminUsers": ["ou_admin_open_id"],
"pm2ProcessName": "code-bot",
"remote": "origin",
"branch": "main"
}Run the bot under pm2:
npm run build
pm2 start dist/src/index.js --name code-botInstall macOS login startup for the PM2-managed bot:
npm run pm2:startup:dry-run
npm run pm2:startup:installThe installer builds the project, starts or restarts the code-bot PM2 process, runs pm2 save, writes a user LaunchAgent under ~/Library/LaunchAgents/, and loads it with launchctl. On the next login, launchd runs pm2 resurrect to restore the saved PM2 process list.
To remove the login startup hook without stopping the current PM2 process:
npm run pm2:startup:uninstallOptional overrides:
node scripts/setup-pm2-startup.mjs --process-name code-bot-prod --entry dist/src/index.js
node scripts/setup-pm2-startup.mjs --pm2-path /absolute/path/to/pm2Codex state is isolated per checkout. The bot always starts Codex with <projectRoot>/.code-bot/codex-home, initializes that directory by copying only $HOME/.codex/config.toml when the local file is missing, and links auth.json to $HOME/.codex/auth.json when no project-local auth file exists. This reuses the user's Codex login while keeping sessions, hooks.json, and hook scripts isolated per checkout.
The command refuses to run on a dirty worktree and uses fast-forward-only git updates.
npm test
npm run build