Route all AI coding assistant interactions to Feishu. Approve, pick options, and send commands from your phone — multi-device, no terminal babysitting required.
| Pain Point | How Agent Notifier Solves It |
|---|---|
| Claude / Codex needs confirmations, permissions — you're chained to the terminal | Feishu interactive cards push in real time; tap once from phone, desktop, or tablet |
| You want mobile access but there's no official app | Feishu is your multi-platform app — iOS / Android / Mac / Windows / Web |
| Self-hosted push needs a server, domain, and DNS setup? | Feishu's long-polling mode connects directly — no public IP or domain required |
| Enterprise approval workflows are painful? | Feishu enterprise custom apps get approved in minutes; works for individuals too, completely free |
| Running tasks across multiple terminals — notifications are a mess? | Multi-terminal parallel routing keeps each terminal's interactions isolated and independent |
| Scenario | Card Color | Description |
|---|---|---|
| Permission confirmation | 🟠 Orange | Allow / Allow for session / Deny + text input |
| AskUserQuestion single-select | 🟠 Orange | Dynamic option buttons + Other + text input |
| AskUserQuestion multi-part | 🟠 Orange | Q1 → Q2 → Q3 sent one card at a time |
| Task complete | 🟢 Green | Summary, duration, tokens + text input |
| Abnormal exit | 🔴 Red | Error details + text input |
| Live execution summary | 🔵 Blue | Patches the same card in place for the current task |
Notifications — Feishu interactive cards / task completion & failure alerts / live execution summaries / session duration & token stats / local audio alerts
Interactions — Button clicks flow back to the terminal / text input flows back to the terminal / multi-terminal parallel routing / shared entry point for both Claude and Codex
git clone <repo-url>
cd agent_notifierEdit .env (automatically created from .env.example on first install):
FEISHU_APP_ID=your_app_id_here
FEISHU_APP_SECRET=your_app_secret_here
FEISHU_CHAT_ID=your_chat_id_hereFor step-by-step instructions on creating and approving a Feishu custom app, see Feishu Setup Guide below.
bash install.shThe install script handles everything automatically:
- Checks dependencies (Node.js, npm, python3)
- Cleans up previous configuration (runs
uninstall.shinternally) - Installs Node.js dependencies
- Creates
.envfrom.env.example(if it doesn't exist) - Writes Claude Code hooks to
~/.claude/settings.json - Injects
claude/codexshell wrapper functions - Starts the Feishu listener and registers it for auto-start on boot
Running
install.shmultiple times is safe — it cleans up before reinstalling each time.
source ~/.zshrc
# or source ~/.bashrcclaude
# or
codexTo temporarily stop the Feishu listener and Codex watcher started by install.sh, while keeping hooks, shell functions, .env, and auto-start configuration:
bash stop-services.sh
# or
npm run services:stopTo fully uninstall and remove configuration:
bash uninstall.shThe uninstall script cleans up:
- Stops and removes the Feishu listener service (launchd / systemd / crontab)
- Terminates background processes (feishu-listener, codex-watcher, codex-session-watcher, pty-relay)
- Removes hooks from
~/.claude/settings.json - Removes shell function injections from
~/.zshrc/~/.bashrc - Cleans up runtime files (session-state, pid, log, /tmp buffer files)
.envandnode_modules/are preserved. Delete them manually if you want a full cleanup.
| Platform | Service Management | Auto-Start on Boot |
|---|---|---|
| macOS | launchd (~/Library/LaunchAgents/) |
RunAtLoad + KeepAlive |
| Linux (with systemd user session) | systemd user service | systemctl --user enable |
| Linux (no systemd, e.g. pure SSH) | nohup + crontab @reboot |
crontab fallback |
macOS:
# Check status
launchctl print gui/$(id -u)/com.agent-notifier.feishu-listener
# Stop
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/com.agent-notifier.feishu-listener.plist
# Start
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.agent-notifier.feishu-listener.plistLinux (systemd):
systemctl --user status agent-notifier-feishu
systemctl --user restart agent-notifier-feishu
journalctl --user -u agent-notifier-feishu -f# Feishu custom app
FEISHU_APP_ID=your_app_id_here
FEISHU_APP_SECRET=your_app_secret_here
FEISHU_CHAT_ID=your_chat_id_here
# Default host (optional)
# DEFAULT_AGENT_HOST=claude
# CODEX_BIN=codex
# Explicitly specify tmux pane (optional)
# CLAUDE_TMUX_TARGET=claude:0.0
# Live summary (optional)
# FEISHU_LIVE_CAPTURE=1
# FEISHU_LIVE_DEBOUNCE_MS=3000
NOTIFICATION_ENABLED=true
# NOTIFICATION_EXPIRE_HOURS=12
# ENABLE_ESC_BUTTON=true
SOUND_ENABLED=trueAccepted values:
1/true: Enable all capture modestools: Tool / command summariesoutput: Assistant output contentresults: Tool execution result summaries- Combine as needed:
tools,output,results
Codex output is sourced from ~/.codex/sessions/*.jsonl, not inferred from terminal text.
Log in to the Feishu Open Platform and create an enterprise custom app.
Copy the credentials from the app dashboard and add them to .env.
Turn on the Bot feature under App Capabilities.
No public IP or domain needed.
card.action.trigger
im:messageim:message:send_as_botim:chat:readonly
After publishing, add the bot to your target group chat.
install.sh starts both the Feishu callback listener and the Codex interactive-card watcher:
- Linux systemd user:
agent-notifier-feishu.service,agent-notifier-codex-watcher.service - macOS launchd:
com.agent-notifier.feishu-listener,com.agent-notifier.codex-watcher - Linux without systemd: nohup processes plus crontab
@rebootentries
stop-services.sh / npm run services:stop only stops these persistent services. It does not remove hooks, shell functions, .env, node_modules, or auto-start configuration.
bash install.sh # Install (auto-cleans old config → reinstalls and starts services)
bash stop-services.sh # Stop persistent services while keeping installation config
bash uninstall.sh # Uninstall (stops services → cleans up config)
npm run services:stop # Same as bash stop-services.shnpm run feishu-listener # Run in foreground
npm run feishu-listener:start # Start in background with nohup
npm run feishu-listener:stop # Stop background processnpm run codex-watcher
npm run codex-watcher:start
npm run codex-watcher:stop- Claude Hooks fire events →
src/apps/claude-hook.jsbuilds cards → Feishu listener receives callbacks → input injected back into the local terminal
pty-relay.pyestablishes a terminal bridge →src/apps/codex-watcher.jshandles interactive cards →src/apps/codex-session-watcher.jsreads session files →src/apps/codex-live.jshandles live summary cards
To route Feishu input back to Claude / Codex, the project supports several injection methods:
| Method | Use Case |
|---|---|
| tmux | Recommended — run claude / codex inside a tmux session |
| PTY relay | Non-tmux environments; pty-relay.py sets up a FIFO injection channel automatically |
| Explicit tmux pane | CLAUDE_TMUX_TARGET=claude:0.0 |
Injection priority: CLAUDE_TMUX_TARGET > auto-detected tmux pane > FIFO relay > pty master direct write > TIOCSTI fallback
Handled automatically by install.sh. For manual setup, add to ~/.claude/settings.json:
{
"hooks": {
"Stop": [{ "hooks": [{ "type": "command", "command": "node /path/to/hook-handler.js" }] }],
"Notification": [{ "matcher": "permission_prompt|idle_prompt|elicitation_dialog", "hooks": [{ "type": "command", "command": "node /path/to/hook-handler.js" }] }],
"StopFailure": [{ "hooks": [{ "type": "command", "command": "node /path/to/hook-handler.js" }] }],
"PostToolUse": [{ "matcher": "Bash|Write|Edit|NotebookEdit", "hooks": [{ "type": "command", "command": "node /path/to/live-handler.js" }] }]
}
}# Run tests
bun test tests/
python3 -m py_compile pty-relay.py
# Send test cards
node scripts/send-codex-feishu-test-cards.js --pts /dev/pts/<N>
npm run ask:e2e:cardRecommended manual checks:
- Claude completion card sends correctly
- Codex text input / approval / single-select / multi-select all flow back to the terminal
- Codex live cards patch in place for the same task and create new cards for new tasks
- Long text is properly chunked
- In PTY raw mode, Enter sends
\r(CR), not\n(LF) - Completion cards include a text input field for easy follow-up conversation
im.message.patchstrips input fields, so completion cards are always sent as new messages while in-progress cards use patch- Keep sensitive config in
.env— do not commit it
If you're looking to contribute or extend the project, start with:
docs/ai_rules.mddocs/ai_docs/README.mdsrc/apps/claude-hook.jssrc/apps/codex-live.jssrc/apps/codex-watcher.jssrc/channels/feishu/feishu-interaction-handler.js





