This skill handles "reading chat history / managing groups / message interaction". Sending new messages goes through feishu-cli-msg; building cards goes through feishu-cli-card。
Which path to choose
| Scenario | Which path |
|---|---|
| View group messages within a time window (including topic replies, name resolution, card parsing) | scripts/fetch_chat_history.py(one command does it all) |
| View the latest page of group chat messages (v1.27.1+ automatically expands all topics by default) | msg history single call |
| View private chat history | msg history --user-email or --user-id |
| Find group | msg search-chats --query |
| View a single message / merged forward | msg get / msg mget |
| View all replies in a topic | msg thread-messages <thread_id> |
| Search messages by keyword | search messages(belongs to feishu-cli-search) |
| Group member management, change group name | chat update / chat member ... |
| Reaction / Pin / Delete | msg reaction / msg pin / msg delete |
Authentication
Distinguished by command type:
- Read type(
msg history/list/get/mget/thread-messages/resource-download): after login, automatically loads User Token from~/.feishu-cli/token.json, falls back to App Token if not logged in (requires the Bot to be in the group).External group Bots are usually not in the group, reading external groups requires login first:feishu-cli auth check --scope "im:message:readonly im:message.group_msg:get_as_user" feishu-cli auth login --domain chat --recommend - User Token required(
reaction add/remove/list、pin/unpin/pins、search-chats、chat get/update/delete、chat member list/add/remove): errors out directly if not logged in. - Write type · default Bot identity(
chat create、chat link): defaults to App Token, no login required; when explicitly--user-access-tokenswitches to User (created as the person themselves). msg delete: defaults to App Token, used for the Bot to recall messages it sent within 24 hours; when passing--user-access-tokenor the environment variable, the admin recall scenario can be used.
End-to-end: pull the complete chat history for a time window
The most common need—"pull out all messages from group X in the last 24 hours"—involves pagination, topic expansion,
name resolution, recalled messages, rich text, and card rendering at the same time. Each of these steps has more than one pitfall (see
references/output-quirks.md), a single CLI cannot solve it all, so it is wrapped into a script:
# 默认最近 24 小时
python3 skills/feishu-cli-chat/scripts/fetch_chat_history.py oc_xxxxxxxx --since 24h
# 自定义时间窗 / 输出目录 / Bot 显示名
python3 skills/feishu-cli-chat/scripts/fetch_chat_history.py oc_xxxxxxxx \
--start 2026-05-20T00:00:00 --end 2026-05-22T00:00:00 \
--output-dir /tmp/my_chat \
--bot-name "你的 Bot 显示名"
# 不展开线程(更快,但话题群会丢回复)
python3 skills/feishu-cli-chat/scripts/fetch_chat_history.py oc_xxx --since 24h --no-thread
Output 4 files to --output-dir(default /tmp/lark_chat/):
| File | Content |
|---|---|
history.json |
Main message raw JSON + partial sender_names returned by Feishu |
threads.json |
Each thread_id → complete reply list |
names.json |
Merged open_id / app_id → name mapping |
timeline.txt |
Readable timeline: main messages in ascending order + thread replies indented by 4 spaces (└─ marker) |
Default script behavior:
msg historyusesByCreateTimeAsc + start-time + end-time, paginates by page_token untilhas_more=false;- collects all non-empty
thread_id, callsmsg thread-messagesfor each tid to expand (does not pass-o json, because it is JSON by default and has anti-error added); - Three-level fallback for name resolution:
mentions[].name→sender_namesAPI field →user infocall (external users will get 41050, silently skipped); - Bot app_id(
cli_xxx) mapped separately to--bot-namethe provided name; - Handle simultaneously during rendering: recalled messages (content is a string, not JSON), dual post structure, system template placeholders, interactive v2 card recursion.
If the script is unusable, see the "manually pull group messages" section below, understand what each step does, then fall back to jq + bash.
Single call: common read commands
# 群聊一页历史(v1.27.1+ 默认自动展开线程:一次拉根 + 所有话题回复)
feishu-cli msg history --container-id oc_xxx --container-id-type chat --page-size 50 -o json
# 关闭自动展开(仅拉根消息,更快)
feishu-cli msg history --container-id oc_xxx --container-id-type chat \
--page-size 50 --expand-threads=false -o json
# 调整展开规模(默认 per=50, total=500,与 lark-cli 对齐)
feishu-cli msg history --container-id oc_xxx --container-id-type chat \
--threads-per-page 30 --threads-total-limit 300 -o json
# 私聊:邮箱或 open_id 自动反查 P2P chat_id
feishu-cli msg history --user-email user@example.com --page-size 50 -o json
feishu-cli msg history --user-id ou_xxx --page-size 50 -o json
# 时间窗内全部消息(升序 + 翻页)
feishu-cli msg history --container-id oc_xxx --container-id-type chat \
--start-time $(date -v-24H +%s) --end-time $(date +%s) \
--sort-type ByCreateTimeAsc --page-size 50 -o json
# 单条 / 批量消息详情
feishu-cli msg get <message_id> -o json
feishu-cli msg mget --message-ids <id1,id2>
# 话题回复(注意:thread-messages 不接受 -o json,默认就是 JSON 输出)
feishu-cli msg thread-messages <thread_id> --page-size 50 --sort ByCreateTimeAsc
JSON output with automatic thread expansion (v1.27.1+)
msg history -o json Three new top-level fields are added, aligned with the official lark-cli +chat-messages-list :
| Field | Description |
|---|---|
thread_replies |
{thread_id: [reply, ...]}, ASC order, excluding the root message |
thread_has_more |
{thread_id: true} indicates that this thread has not been fully pulled within the threads-per-page limit |
thread_replies_card_texts |
card_texts extraction for interactive cards within the thread |
sender_names The field simultaneously merges mentions names from three sources: the main message + merge_forward + thread_replies, significantly improving the name resolution rate for external users (measured in a topic group: from 8% → 42%).
Senders still unresolved are usually真正跨租户外部用户且本话题内无人 @过他——This is a Feishu OpenAPI limitation; contact basic_batch does not return it, and the official lark-cli cannot get it either.
Search and locate
feishu-cli msg search-chats --query "项目群" -o json # 搜群
feishu-cli search messages "关键词" --chat-type p2p_chat -o json
feishu-cli search messages "关键词" --chat-ids oc_xxx -o json
Searching messages belongs to feishu-cli-search; this skill calls it incidentally during reading tasks.
Message interaction
feishu-cli msg reaction add <message_id> --emoji-type THUMBSUP
feishu-cli msg reaction remove <message_id> --reaction-id <reaction_id>
feishu-cli msg reaction list <message_id>
feishu-cli msg pin <message_id>
feishu-cli msg unpin <message_id>
feishu-cli msg pins --chat-id <chat_id>
feishu-cli msg delete <message_id> # Bot 自撤回
feishu-cli msg delete <message_id> --user-access-token u-xxx # 管理员撤回
Group chat management
feishu-cli chat get oc_xxx # 外部群可能 232033(看下方"外部群操作")
feishu-cli chat update oc_xxx --name "新群名"
feishu-cli chat member list oc_xxx # 默认 auto;外部群推荐 --as bot
feishu-cli chat member add oc_xxx --id-list ou_xxx,ou_yyy
feishu-cli chat member remove oc_xxx --id-list ou_xxx
feishu-cli chat create --name "项目群" --user-ids ou_xxx,ou_yyy
feishu-cli chat delete oc_xxx
chat member list/add/remove supports --as bot|user|auto:
auto(default): User Token preferred, fallback to Bot Tokenbot: force Bot Token,recommended for external groups(precondition: the App has enabled "external sharing capability" + the Bot is in the group)user: force User Token
External group operations (must-read)
Whenever you encounter 232033 errors or want to pull the complete member list of an external group,read first references/external-chat.md。
New in v1.27.2+:
msg historysupports the--as bot|user|autoflag (default auto = User preferred, fallback to Bot)msg historyautomatically pulls a chat member list once for a group chat container, with the result attached at the top level in thechat_membersfield- The field value is the
[{member_id, member_id_type, name, tenant_key}, ...]complete member list (including group nicknames) - There is also a
chat_members_notehint field telling the Agent how to use it (to avoid mismatches)
- The field value is the
msg historyFor eachitems[*]output, inject thesender_namefield, eliminating the need to look up thesender_namesdictionarychat member list/add/removeall support--as bot|user|auto- Error code translation:
chat get/update/delete/link/memberwhen the command receives 232033/232011/232006, automatically print a Chinese solution
⚠️ Major pitfall: in external groups, chat_members[*].member_id and items[*].sender.id aredifferent namespaces,
neveruse member_id to look up the sender's name. See references/external-chat.md the "Major pitfall" section.
Pull the complete member list of an external group (typical usage)
# 切到对外共享 App + Bot Token,从 msg history 顺带拉
FEISHU_APP_ID=cli_xxx FEISHU_APP_SECRET=xxx \
feishu-cli msg history --container-id oc_xxx --container-id-type chat \
--page-size 1 --as bot --expand-threads=false -o json \
| jq '.chat_members[] | select(.tenant_key != "<my_tenant>") | .name'
# → 输出所有外部商家名字(即群昵称),可直接喂给名字规范检查脚本
In one sentence: for external groups (external=true), "group info/members/config" APIs are disabled by default and require:
- The App has enabled "external sharing capability" (Feishu Open Platform → App → Credentials & Basic Info)
- This App's Bot has joined this group
If you have another App with external sharing capability enabled, just switch temporarily:
# 临时切 App(不写盘)
FEISHU_APP_ID=cli_对外共享App \
FEISHU_APP_SECRET=xxx \
feishu-cli chat member list oc_xxx --as bot
The returned name field is the user'sgroup nickname in that group(if not set, falls back to the global name), can be used directly for scenarios such as name specification checks.
Determine whether the chat is an external group:feishu-cli msg search-chats --query "..." -o json look at the returned external field.
For the full path/error codes/troubleshooting, see references/external-chat.md。
Pitfall quick reference (important)
Glance over this before writing code, otherwise you can easily get stuck for 30 minutes. For the detailed version, see references/output-quirks.md。
| Pitfall | One-sentence workaround |
|---|---|
thread-messages adding -o json throws an error |
Do not pass; it is JSON output by default |
thread-messages returns PascalCase |
Use d.get("items") or d.get("Items") to support both sets of keys |
history time in seconds,thread-messages time in milliseconds |
start_sec vs start_sec * 1000, pass them respectively |
Recalled message body.content is a literal string |
try: json.loads(...) wrap it, and on failure display it directly as a string |
| post content has two structures | Support both {zh_cn:{title,content}} and flat {title,content} |
system message template contains {from_user} placeholder |
Fill with other fields of the same object (join list with commas) |
Bot sender cli_xxx ≠ sender_names in ou_xxx |
map separately to a known bot name |
External group chat get/member list/... 232033 |
Required reading references/external-chat.md; switch to an App with external sharing enabled + --as bot |
Cross-enterprise user info 41050 |
silently skipped, rely on mentions[].name as a fallback |
msg history does not expand threads by default |
Topic groups must additionally call thread-messages to expand, otherwise replies are lost |
Name reverse-resolution strategy
In external groups, the Feishu backend does not return names for most senders. Sorted by reliability + cost:
mentions[](carried by the message):{"id":"ou_xxx","id_type":"open_id","name":"张三","key":"@_user_1"}, can also usekeyto replace the@_user_Nplaceholder in text messages.Not subject to external tenant isolation restrictions, preferred.sender_names(top level of history/thread-messages): a subset of Feishu reverse resolution, low coverage, use as a starting point.user info <ou_xxx>: cross-enterprise users always return41050 no user authority error, almost all external groups hit this, silently skipped.- Bot
cli_xxx: map separately to a bot name you know in advance (the script's--bot-name)。
Output handling
- Dump JSON to a temporary file before analyzing, to avoid long messages flooding the screen;
- the text content is in
body.content, parse the JSON string bymsg_type(except recalled messages, see above); mentionsthe field contains both the open_id → name mapping +@_user_Nkey → name mapping, more complete thansender_names, prefer it.
Card messages (interactive)
msg history by default includes --card-content-type user, which yields v2 schema JSON + the extracted card_texts.
The script already covers v2 schema recursion (column_set / form / collapsible_panel / action /
button / img / note), for manual parsing see references/output-quirks.md §8.
When you need the "original cardDSL" or the "OAPI rendered version", switch:
feishu-cli msg history --container-id oc_xxx --card-content-type raw -o json # 平台内部 cardDSL
feishu-cli msg history --container-id oc_xxx --card-content-type rendered -o json # OAPI 渲染版/降级版
Reference
scripts/fetch_chat_history.py— an executable script for end-to-end group message fetchingreferences/output-quirks.md— all API quirks such as JSON keys / time units / error codes / card parsing