Bridge Feishu group chats to opencode TUI sessions with real-time two-way messaging.
- Real-time bridging — Messages sent in Feishu arrive in your opencode TUI instantly, and agent replies stream back as live-updating cards.
- Interactive cards — Agent questions and permission requests appear as clickable Feishu cards. Answer or approve directly from the chat — no need to switch to the TUI.
- WebSocket connection — Uses Feishu's long-lived WebSocket mode. No webhook polling, no public IP required.
- SSE streaming — Consumes the opencode SSE event stream and debounces card updates to stay within rate limits.
- Conversation memory — SQLite-backed per-thread history is prepended to each message, giving the agent context across turns.
- Session auto-discovery — Finds and binds to the latest opencode TUI session for a working directory. Survives restarts.
- Graceful recovery — Reconnects to the opencode server with exponential backoff (up to 10 attempts) on startup.
- Extensible channel layer —
ChannelPlugininterface lets you add Slack, Discord, or any other platform without touching core logic. - File and image support — Handles image and file messages from Feishu (not just text). Downloads attachments to
${OPENCODE_CWD}/.opencode-lark/attachments/and forwards the local path to opencode for analysis. 50 MB size limit, streaming download, filename sanitization included.
Feishu client
↕ WebSocket
Feishu Open Platform
↕ WebSocket
opencode-lark (this project)
↕ HTTP API + SSE
opencode server (localhost:4096)
↕ stdin/stdout
opencode TUI
opencode serveruns the HTTP server. Useopencode attachin a separate terminal to view the session in TUI.
Inbound (Feishu → TUI): Feishu sends a message over WebSocket. opencode-lark normalizes it, resolves the bound session, prepends conversation history, then POSTs to the opencode API. The TUI sees the message immediately.
Outbound (TUI → Feishu): opencode-lark subscribes to the opencode SSE stream. As the agent produces text, TextDelta events accumulate and a debounced card update fires. Once SessionIdle arrives, the final card is flushed to Feishu.
| Message Type | Supported | Notes |
|---|---|---|
text |
✅ | Plain text messages |
post |
✅ | Rich text / multi-paragraph messages |
image |
✅ | Photos and screenshots — downloaded and saved locally |
file |
✅ | Documents, code files, etc. — downloaded and saved locally |
audio / video / sticker |
❌ | Logged and skipped |
Downloaded files are saved to ${OPENCODE_CWD}/.opencode-lark/attachments/ (falls back to the system temp directory if that path isn't writable).
Note: Bun is the required runtime — this project uses
bun:sqlitewhich is Bun-only.
# Global install
npm install -g opencode-lark
# or
bun add -g opencode-larkOr clone and run from source:
git clone https://github.com/guazi04/opencode-lark.git
cd opencode-lark
bun installGet up and running in 5 minutes. You'll need a Feishu Open Platform app with bot capability — see Feishu App Setup below for the detailed walkthrough if you haven't created one yet.
- Bun (required runtime — this project uses
bun:sqlitewhich is Bun-only) - opencode installed locally
- A Feishu Open Platform app with credentials (see Feishu App Setup)
1. Install
bun add -g opencode-lark
# or: npm install -g opencode-lark2. Start opencode server
OPENCODE_SERVER_PORT=4096 opencode serve3. Start opencode-lark
In a second terminal:
opencode-larkOn first run with no configuration, an interactive setup wizard guides you through:
- Entering your Feishu App ID and App Secret (masked input)
- Validating the opencode server connection
- Saving credentials to a
.envfile
The service starts automatically after setup completes.
Tip: To re-run the wizard later, use
opencode-lark init.To configure manually instead, create a
.envfile withFEISHU_APP_IDandFEISHU_APP_SECRETbefore starting.
4. Send a test message
Send any message to your Feishu bot. On first contact it auto-discovers the latest TUI session and replies:
Connected to session: ses_xxxxx
After that, Feishu and the TUI share a live two-way channel. To attach the TUI:
opencode attach http://127.0.0.1:4096 --session {session_id}The session_id is shown in opencode-lark's startup logs (e.g. Bound to TUI session: ... → ses_xxxxx).
- Open Feishu Open Platform
- Click Create App → Create Internal App
- Fill in app name and description, then confirm
Navigate to App Features → Bot and enable the bot capability.
Navigate to Credentials & Basic Info to find:
- App ID → set as
FEISHU_APP_ID - App Secret → set as
FEISHU_APP_SECRET
You'll need these in Step 6 to configure opencode-lark.
Navigate to Development Config → Permissions & Scopes and add the following:
| Permission | Scope Identifier | Purpose | Required |
|---|---|---|---|
| 获取与发送单聊、群组消息 | im:message |
Send messages & update cards | ✅ |
| 获取用户发给机器人的单聊消息 | im:message.p2p_msg:readonly |
Receive direct messages | ✅ |
| 获取群组中所有消息 | im:message.group_msg |
Receive all group messages | ✅ |
| 获取群组中 @机器人的消息 | im:message.group_at_msg:readonly |
Receive group messages that @mention the bot | ✅ |
| 获取与上传图片或文件资源 | im:resource |
Handle message attachments | ✅ |
| 创建并发布卡片 | cardkit:card:write |
Render interactive cards (questions, permissions) | ✅ |
Navigate to App Release → Version Management & Release, create a version and submit for review. After approval, add the bot to your workspace.
Note: Internal apps in trial status can be used by app administrators immediately without review for testing.
Before configuring event subscriptions, start opencode-lark so Feishu can detect the WebSocket connection.
-
Install and configure:
# Install globally bun add -g opencode-lark # or: npm install -g opencode-lark # Or run from source # git clone https://github.com/guazi04/opencode-lark.git # cd opencode-lark && bun install
-
Start opencode server in one terminal:
OPENCODE_SERVER_PORT=4096 opencode serve
-
Start opencode-lark in another terminal:
opencode-lark
The interactive setup wizard will guide you through entering credentials and validating the server connection. If running from source:
bun run devKeep this running while you configure event subscriptions in the next step.
Tip: To see messages in real-time in the TUI, open a third terminal and attach to the session:
opencode attach http://127.0.0.1:4096 --session {session_id}The
session_idis shown in opencode-lark's startup logs (e.g.Bound to TUI session: ... → ses_xxxxx).
Navigate to Development Config → Event Subscriptions and:
- Select Long Connection (WebSocket) mode — no public IP required
- Add the following event:
| Event Name | Event Identifier | Purpose | Required |
|---|---|---|---|
| 接收消息 | im.message.receive_v1 |
Receive all user messages | ✅ |
⚠️ Important: opencode-lark must be running (Step 6) before you can save Long Connection mode. If you see "应用未建立长连接", go back to Step 6 and ensure the app is running.
Navigate to Development Config → Event Subscriptions → Callback Subscription (回调订阅) — this is a separate section from Event Subscription above.
- Select Long Connection (WebSocket) mode
- Add the following callback:
| Callback Name | Callback Identifier | Purpose | Required |
|---|---|---|---|
| 卡片回传交互 | card.action.trigger |
Receive card button clicks (question answers, permission replies) | ✅ |
⚠️ Important: This is required for interactive cards (questions & permissions). Without it, clicking card buttons shows error200340.Event Subscription and Callback Subscription are two separate settings. You must configure both.
| Symptom | Likely Cause | Fix |
|---|---|---|
| Bot doesn't receive messages | WebSocket not enabled or wrong subscription | Check event subscription, ensure Long Connection mode is selected |
| "Invalid App ID or Secret" | Wrong credentials in .env | Double-check App ID and App Secret from Step 3 |
| Messages received but no reply | opencode server not running | Ensure opencode server is running: OPENCODE_SERVER_PORT=4096 opencode serve |
| Card not updating in real-time | Rate limit or debounce delay | Normal behavior — updates are debounced to stay within Feishu rate limits |
Error 200340 when clicking card buttons |
Callback subscription not configured | Go to Callback Subscription (回调订阅) → select Long Connection → add card.action.trigger |
| "应用未建立长连接" when saving Long Connection mode | App not running — Feishu requires an active WebSocket connection before saving | Start opencode-lark first (Step 6), then save the setting in Feishu console |
| Variable | Required | Default | Description |
|---|---|---|---|
FEISHU_APP_ID |
yes | Feishu App ID | |
FEISHU_APP_SECRET |
yes | Feishu App Secret | |
OPENCODE_SERVER_URL |
no | http://localhost:4096 |
opencode server URL |
FEISHU_WEBHOOK_PORT |
no | 3001 |
HTTP webhook fallback port (only needed if not using WebSocket for card callbacks) |
OPENCODE_CWD |
no | process.cwd() |
Override session discovery directory |
FEISHU_VERIFICATION_TOKEN |
no | Event subscription verification token | |
FEISHU_ENCRYPT_KEY |
no | Event encryption key |
opencode-lark.jsonc (gitignored; copy from opencode-lark.example.jsonc):
(also supports opencode-feishu.jsonc for backward compatibility)
Supports ${ENV_VAR} interpolation and JSONC comments. If no config file is found, the app builds a default config from .env values directly.
opencode-lark pairs with lark-openapi-mcp to give the opencode agent direct access to Feishu's cloud document ecosystem — read/write docs, upload files, query Bitable tables, and search the wiki, all from within a single conversation.
| Category | Capabilities |
|---|---|
| Docs (docx) | Create doc, write Markdown, read Markdown, get raw text content, search docs, import file as doc |
| Drive | Upload local file to cloud drive, download file from cloud drive |
| Bitable | Create Bitable app, manage tables, list fields, record CRUD |
| Messaging (im) | List chats the bot belongs to, get chat members |
| Wiki | Search wiki nodes, get node details |
When calling tools with user_access_token (i.e. useUAT: true), enable the following scopes in Permissions & Scopes in the Feishu Open Platform console:
| Scope | Purpose |
|---|---|
drive:drive |
Cloud drive read/write |
docx:document |
Document read/write |
bitable:app |
Bitable CRUD |
im:chat:readonly |
Query chat/group info |
wiki:wiki:readonly |
Wiki search and read |
Add a lark-mcp entry to the mcp section of your opencode config (~/.config/opencode/config.json):
{
"mcp": {
"lark-mcp": {
"type": "local",
"command": ["node", "/path/to/lark-openapi-mcp/dist/cli.js", "mcp", "-a", "${FEISHU_APP_ID}", "-s", "${FEISHU_APP_SECRET}", "--oauth", "--token-mode", "user_access_token", "-l", "zh"],
"enabled": true
}
}
}Note: Pass
--token-mode user_access_tokento call tools on behalf of a user (required for drive uploads, wiki operations, etc.). Replace/path/to/lark-openapi-mcpwith the actual install path after cloning/installing the package.
src/
├── index.ts # Entry point, 9-phase startup + graceful shutdown
├── types.ts # Shared type definitions
├── channel/ # ChannelPlugin interface, ChannelManager, FeishuPlugin
├── feishu/ # Feishu REST client, CardKit, WebSocket, message dedup
├── handler/ # MessageHandler (inbound pipeline) + StreamingBridge (SSE → cards)
├── session/ # TUI session discovery, thread→session mapping, progress cards
├── streaming/ # EventProcessor (SSE parsing), SessionObserver, SubAgentTracker
├── cron/ # CronService (scheduled jobs) + HeartbeatService
├── utils/ # Config loader, logger, SQLite init, EventListenerMap
bun run dev # Watch mode, auto-restart on changes
bun run start # Production mode
bun run test:run # Run all tests (vitest)
bun run build # Compile TypeScript to dist/Note: Use
bun run test:runrather thanbun test. The latter picks up bothsrc/anddist/test files;vitestis configured to scope tosrc/only.
See CONTRIBUTING.md for guidelines on issues, pull requests, and code style.
MIT © 2026 opencode-lark contributors