Connect a Feishu bot to OpenCode or Codex with:
- Feishu persistent connection mode
- interactive project picker from your configured projects root
- per-chat directory binding
- per-chat backend session reuse
This project is useful when you want to talk to different local codebases from Feishu without manually switching directories in a terminal.
- A user sends a message to the Feishu bot.
- If the chat is not bound yet, the bridge scans
PROJECTS_ROOTand sends a project picker card. - After a project is selected, the chat is bound to that directory.
- The bridge starts or reuses one backend session for that chat and directory.
- Later messages in the same chat continue in the same session.
- Feishu message events via persistent connection
- Feishu card action events via persistent connection
- project selection buttons with pagination
- fallback text commands for switching and searching
- one backend session per Feishu chat
- Node.js 22+
opencodeorcodexinstalled and available inPATH- a Feishu self-built app with bot capability enabled
git clone https://github.com/fusae/feishu-opencode-bridge.git
cd feishu-opencode-bridge
npm install
cp .env.example .envEdit .env:
FEISHU_APP_ID=cli_xxx
FEISHU_APP_SECRET=xxx
FEISHU_DOMAIN=Feishu
FEISHU_VERIFICATION_TOKEN=
FEISHU_ENCRYPT_KEY=
PROJECTS_ROOT=/path/to/projects
PROJECT_PAGE_SIZE=12
STATE_FILE_PATH=./data/state.json
GROUP_REQUIRE_MENTION=true
BRIDGE_BACKEND=opencode
OPENCODE_SERVER_HOSTNAME=127.0.0.1
OPENCODE_SERVER_PORT=4096
OPENCODE_SERVER_USERNAME=opencode
OPENCODE_SERVER_PASSWORD=
OPENCODE_SYSTEM_PROMPT=You are helping with the currently bound project. Give the answer first, then the necessary detail.
CODEX_COMMAND=codex
CODEX_MODEL=
CODEX_PROFILE=
CODEX_SANDBOX=workspace-writeNotes:
PROJECTS_ROOTis scanned for first-level subdirectories.GROUP_REQUIRE_MENTION=truemeans the bot only reacts when mentioned in group chats.BRIDGE_BACKEND=opencode|codexselects the backend.OPENCODE_SERVER_PASSWORDis optional. If set, the bridge uses HTTP Basic Auth when talking toopencode serve.CODEX_SANDBOXis only used whenBRIDGE_BACKEND=codex.
Development:
npm run devProduction:
npm run build
npm startIn the Feishu developer console:
- Create a self-built app.
- Enable
Bot. - Enable events and callbacks.
- Choose persistent connection mode.
- Subscribe to:
im.message.receive_v1card.action.trigger
- Grant message receive/send permissions required by your app.
No separate public webhook endpoint is required for this bridge.
/switch Open the project picker again
/status Show the currently bound directory
/reset Reset the current backend session for this chat
/session List sessions for the current project
/session current
/session new
/session use <id|index>
/session delete <id|index>
/next Next page in the project picker
/prev Previous page in the project picker
/search x Filter projects by keyword
Command notes:
/switch: clear the current chat binding and reopen the project picker/status: show the current project directory and active session id/reset: drop the active session binding for the chat and let the next message start a fresh session/session: list recent sessions in the current project/session current: show the currently active session id/session new: create and switch to a new session in the current project/session use <id|index>: switch to an existing session by full id, id prefix, or list index/session delete <id|index>: delete a session by full id, id prefix, or list index/nextand/prev: paginate the project picker/search x: filter the project picker by keyword
Example session flow:
/session
/session new
/session use 2
/session delete ses_xxx
Runtime state is stored in:
data/state.json
It keeps:
- chat -> directory binding
- chat -> session binding
- pending selector state
- processed message/card tokens for deduplication
- one Feishu chat maps to one project directory
- one Feishu chat maps to one backend session inside that directory
- messages in the same chat are processed serially to preserve session order
- project picker uses buttons, with text-command fallback still available
If the bot does not reply:
- confirm the Feishu app is using persistent connection mode
- confirm both
im.message.receive_v1andcard.action.triggerare subscribed - confirm the selected backend CLI is installed and callable from the shell
- confirm
FEISHU_APP_IDandFEISHU_APP_SECRETare correct - inspect bridge logs from
npm run dev
If replies are slow:
- the bridge waits for the selected backend to finish before replying
- messages from the same chat are queued in order
- large repositories or tool-heavy prompts may take noticeably longer