| name | hermes-feishu-streaming-card |
| description | Stream Hermes Gateway agent responses to Feishu/Lark as live-updating interactive cards with thinking process, tool calls, and in-card interactions |
| triggers | ["install hermes feishu streaming card plugin","configure feishu streaming cards for hermes","set up lark cards for hermes gateway","stream hermes responses to feishu cards","add interactive feishu cards to hermes agent","troubleshoot hermes feishu card not updating","debug hermes feishu streaming card sidecar","configure feishu card interactions for hermes"] |
Hermes Feishu Streaming Card
Skill by ara.so — Hermes Skills collection.
The Hermes Feishu Streaming Card plugin transforms Hermes Agent Gateway's Feishu/Lark responses into continuously-updating interactive cards. Instead of fragmented gray text messages, you get a single card showing thinking process, tool calls, final answers, authorization requests, option selections, and execution stats — all in one place.
What It Does
This plugin solves key pain points when connecting Hermes to Feishu:
- Unified streaming experience:
thinking.delta, answer.delta, tool.updated, and message.completed all update the same Feishu card
- In-card interactions: Hermes approval/clarify choices render as Feishu buttons (callback mode) or numbered options (localhost/private sidecar)
- Long content stability: Markdown tables and code blocks split at structure boundaries to avoid raw markdown rendering
- Multi-bot/profile support: Multiple Feishu bots, multiple Hermes profiles, group chat bindings, routing diagnostics
- Sidecar-only architecture: Independent process handles Feishu API calls, state machine, retries, health checks
- Fail-open design: Hermes hook failures don't break your gateway; original text messages serve as fallback
Installation
Prerequisites
- Python 3.9+
- Hermes Gateway v0.13.0+ (or date-based version
v2026.4.23+)
- Active Feishu/Lark bot with app credentials
Quick Install
pip install hermes-feishu-streaming-card
hermes-feishu-card doctor --hermes-dir ~/.hermes/hermes-agent
hermes-feishu-card install --hermes-dir ~/.hermes/hermes-agent --yes
From Release Package
wget https://github.com/baileyh8/hermes-feishu-streaming-card/releases/latest/download/hermes-feishu-streaming-card.tar.gz
tar -xzf hermes-feishu-streaming-card.tar.gz
cd hermes-feishu-streaming-card
pip install .
hermes-feishu-card install --hermes-dir ~/.hermes/hermes-agent --yes
Installation State Management
The installer creates manifest and backup files in ~/.hermes-feishu-card/:
hermes-feishu-card doctor --explain
hermes-feishu-card repair --hermes-dir ~/.hermes/hermes-agent --yes
hermes-feishu-card restore --hermes-dir ~/.hermes/hermes-agent --yes
hermes-feishu-card uninstall --hermes-dir ~/.hermes/hermes-agent --yes
Configuration
Basic Setup
Create or update ~/.hermes-feishu-card/config.yaml:
feishu:
- app_id: cli_a1b2c3d4e5f6g7h8
app_secret: ${FEISHU_APP_SECRET_1}
verification_token: v1a2b3c4d5e6f7g8h9i0
encrypt_key: ${FEISHU_ENCRYPT_KEY_1}
bot_name: "Production Bot"
sidecar:
host: 127.0.0.1
port: 8765
log_level: INFO
card:
title: "🤖 Hermes Agent"
thinking_emoji: "💭"
answer_emoji: "💬"
tool_emoji: "🔧"
show_footer_stats: true
thinking_mode: append_block
interaction_mode: auto
content:
main_content_chunk_chars: 3500
code_fence_preserve: true
table_header_repeat: true
feishu_table_row_limit: 50
Multi-Bot Configuration
feishu:
- app_id: cli_prod_bot
app_secret: ${FEISHU_PROD_SECRET}
verification_token: ${FEISHU_PROD_TOKEN}
encrypt_key: ${FEISHU_PROD_ENCRYPT}
bot_name: "Production Bot"
- app_id: cli_test_bot
app_secret: ${FEISHU_TEST_SECRET}
verification_token: ${FEISHU_TEST_TOKEN}
encrypt_key: ${FEISHU_TEST_ENCRYPT}
bot_name: "Test Bot"
bindings:
chats:
oc_a1b2c3d4e5f6g7h8:
profile_id: thinking
bot_name: "Production Bot"
oc_x9y8z7w6v5u4t3s2:
profile_id: research
bot_name: "Test Bot"
Interaction Modes
card:
interaction_mode: auto
Key Commands
Sidecar Management
hermes-feishu-card start
hermes-feishu-card status
hermes-feishu-card stop
hermes-feishu-card logs
hermes-feishu-card logs --tail 50 --follow
Diagnostics
hermes-feishu-card doctor --explain --hermes-dir ~/.hermes/hermes-agent
hermes-feishu-card doctor --json --hermes-dir ~/.hermes/hermes-agent
hermes-feishu-card doctor --explain
Bot Testing
hermes-feishu-card bots test
hermes-feishu-card bots test --app-id cli_a1b2c3d4e5f6g7h8
hermes-feishu-card bots test --all
hermes-feishu-card bots test --profile-id thinking
Smoke Testing
hermes-feishu-card smoke-feishu-card \
--chat-id oc_a1b2c3d4e5f6g7h8 \
--bot-name "Production Bot" \
--profile-id thinking
hermes-feishu-card smoke-feishu-card \
--chat-id oc_a1b2c3d4e5f6g7h8 \
--test-message "Complex query with\n**markdown** and `code`"
Working with the Sidecar
Health Check Endpoints
The sidecar exposes health check endpoints:
curl http://localhost:8765/health
curl http://localhost:8765/health.routing
curl http://localhost:8765/health.routing.profiles
Example response:
{
"status": "healthy",
"sidecar_version": "3.6.5",
"uptime_seconds": 3600,
"bots_configured": 2,
"chats_bound": 5,
"profiles": {
"thinking": {
"bot_name": "Production Bot",
"bound_chats": 3,
"last_route": "2026-06-24T10:30:00Z",
"last_route_error": null
}
}
}
Event Flow
The sidecar receives events from Hermes hooks and manages card lifecycle:
import requests
def emit_event(event: dict):
"""Send event from Hermes hook to sidecar"""
try:
response = requests.post(
"http://localhost:8765/events",
json=event,
timeout=1.0
)
return response.status_code == 200
except Exception as e:
print(f"[hermes-feishu-card] hook failed: {e}")
return False
Event types:
message.started: Create new card
thinking.delta: Update thinking section
answer.delta: Update answer section
tool.updated: Show tool call progress
message.completed: Finalize card
interaction.requested: Add approval/clarify buttons
interaction.selected: Record user choice
Real Code Examples
Custom Card Template
from hermes_feishu_card.card_builder import CardBuilder
def create_custom_card():
"""Build a custom Feishu card"""
builder = CardBuilder(
title="Custom Agent",
thinking_emoji="🧠",
answer_emoji="✨",
tool_emoji="⚙️"
)
builder.update_thinking(
"Analyzing user request...",
mode="append_block"
)
builder.update_tool(
tool_name="web_search",
status="running",
input_data={"query": "latest news"}
)
builder.update_answer(
"Based on the search results, here's what I found..."
)
builder.update_footer(
elapsed_sec=2.5,
tokens_used=1234,
model_name="gpt-4"
)
return builder.build()
Handling Long Content
from hermes_feishu_card.content_splitter import split_markdown_aware
def send_long_content(content: str, max_chunk: int = 3500):
"""Split long content intelligently"""
chunks = split_markdown_aware(
content,
max_chunk_chars=max_chunk,
preserve_code_fence=True,
repeat_table_header=True
)
for i, chunk in enumerate(chunks):
print(f"Chunk {i+1}/{len(chunks)}: {len(chunk)} chars")
Custom Interaction Handler
from hermes_feishu_card.interaction import InteractionManager
async def handle_custom_approval(interaction_id: str, choices: list):
"""Custom approval flow"""
manager = InteractionManager(
sidecar_url="http://localhost:8765"
)
await manager.request_interaction(
interaction_id=interaction_id,
interaction_type="approval",
choices=choices,
message_id="msg_123",
user_id="ou_456"
)
while True:
result = await manager.get_interaction(interaction_id)
if result and result.get("selected_choice") is not None:
return result["selected_choice"]
await asyncio.sleep(0.5)
Profile-Specific Bot Routing
from hermes_feishu_card.routing import resolve_bot_for_profile
def get_bot_for_message(profile_id: str, chat_id: str):
"""Determine which bot to use"""
config = load_config()
chat_binding = config.get("bindings", {}).get("chats", {}).get(chat_id)
if chat_binding and chat_binding.get("profile_id") == profile_id:
bot_name = chat_binding["bot_name"]
return next(
bot for bot in config["feishu"]
if bot["bot_name"] == bot_name
)
return config["feishu"][0]
Common Patterns
Streaming Delta Aggregation
class StreamingCardHook:
def __init__(self):
self.answer_buffer = {}
self.thinking_buffer = {}
def on_answer_delta(self, message_id: str, delta: str):
"""Aggregate answer deltas"""
if message_id not in self.answer_buffer:
self.answer_buffer[message_id] = ""
self.answer_buffer[message_id] += delta
emit_event({
"type": "answer.delta",
"message_id": message_id,
"delta": delta,
"accumulated": self.answer_buffer[message_id]
})
Fail-Open Error Handling
def safe_emit_event(event: dict) -> bool:
"""Emit event with fail-open behavior"""
try:
response = requests.post(
"http://localhost:8765/events",
json=event,
timeout=1.0
)
if response.status_code != 200:
print(f"[hermes-feishu-card] sidecar returned {response.status_code}")
return False
return True
except requests.exceptions.Timeout:
print("[hermes-feishu-card] sidecar timeout (continuing)")
return False
except Exception as e:
print(f"[hermes-feishu-card] hook failed: {e}")
return False
Media/Attachment Handling
def format_attachments(message_locals: dict) -> str:
"""Format media/file attachments for card"""
attachments = []
if "attachments" in message_locals:
attachments.extend(message_locals["attachments"])
if "media_files" in message_locals:
attachments.extend(message_locals["media_files"])
if "files" in message_locals:
attachments.extend(message_locals["files"])
summary_parts = []
for att in attachments:
if isinstance(att, dict):
name = att.get("name") or att.get("file_name") or "file"
size = att.get("size", 0)
summary_parts.append(f"📎 {name} ({size} bytes)")
else:
summary_parts.append(f"📎 {att}")
return "\n".join(summary_parts)
Troubleshooting
Card Not Appearing
hermes-feishu-card status
hermes-feishu-card bots test --all
hermes-feishu-card doctor --explain --hermes-dir ~/.hermes/hermes-agent
hermes-feishu-card doctor --explain
tail -f ~/.hermes/hermes-agent/logs/gateway.log | grep hermes-feishu-card
Card Stuck in "Thinking"
This often happens after Hermes v0.17.0+ where streaming callbacks moved to _run_agent_inner:
hermes-feishu-card doctor --explain --hermes-dir ~/.hermes/hermes-agent
hermes-feishu-card install --hermes-dir ~/.hermes/hermes-agent --yes
Multiple Gray Messages After Card
The plugin should suppress Hermes native message resend, but if you still see duplicates:
card:
suppress_native_resend: true
content:
fallback_to_completed: true
Interaction Buttons Not Working
card:
interaction_mode: auto
card:
interaction_mode: callback
callback_base_url: https://your-public-domain.com
curl https://your-public-domain.com/health
Long Tables Showing Raw Markdown
content:
table_header_repeat: true
feishu_table_row_limit: 50
main_content_chunk_chars: 3500
Multiple Bots/Profiles Routing Wrong
curl http://localhost:8765/health.routing.profiles
cat ~/.hermes-feishu-card/config.yaml
hermes-feishu-card smoke-feishu-card \
--chat-id oc_your_chat_id \
--profile-id thinking \
--bot-name "Production Bot"
hermes-feishu-card logs | grep "route_error"
Upgrade Broke Installation
hermes-feishu-card doctor --explain
hermes-feishu-card repair --hermes-dir ~/.hermes/hermes-agent --yes
hermes-feishu-card restore --hermes-dir ~/.hermes/hermes-agent --yes
hermes-feishu-card install --hermes-dir ~/.hermes/hermes-agent --yes
Hermes Version Compatibility
Supported versions:
- Hermes v0.13.0+ (uses
gateway_run_013_plus)
- Hermes v0.14.x (uses
gateway_run_013_plus)
- Hermes v0.15.x (uses
gateway_run_013_plus)
- Hermes v0.17.x (uses
gateway_run_013_plus, hooks _run_agent_inner)
- Date-based v2026.4.23+ (uses
gateway_run_013_plus)
- Date-based v2026.6.19+ (uses
gateway_run_013_plus, hooks _run_agent_inner)
Older versions (v2026.4.23 to v2026.4.x) use legacy_gateway_run.
hermes-feishu-card doctor --explain --hermes-dir ~/.hermes/hermes-agent
Feishu Thread/Topic Replies
Since v3.6.4, initial cards in Feishu threads use reply API:
Cron Job Cards
Since v3.6.4, cron jobs can deliver cards:
jobs:
- id: daily-report
schedule: "0 9 * * *"
agent: reporting
deliver: "feishu:oc_your_chat_id"
Advanced Configuration
Custom Sidecar Port
sidecar:
host: 127.0.0.1
port: 9876
Debug Logging
sidecar:
log_level: DEBUG
log_file: ~/.hermes-feishu-card/sidecar.log
tail -f ~/.hermes-feishu-card/sidecar.log
Performance Tuning
card:
patch_interval_sec: 0.1
content:
main_content_chunk_chars: 4000
Environment Variables
export FEISHU_APP_SECRET_1="your-app-secret"
export FEISHU_ENCRYPT_KEY_1="your-encrypt-key"
export FEISHU_PROD_SECRET="prod-secret"
export FEISHU_PROD_TOKEN="prod-token"
export HERMES_FEISHU_CARD_CONFIG=~/custom/config.yaml
export HERMES_HOME=~/.hermes/hermes-agent
Resources