Lark Event スキル
- インストール数 87.4万
lark-event は Claude のスキルで、ボットやイベントサブスクライバーを作るために、lark-cli event consume を使って、Feishu / Lark のリアルタイムのイベントを NDJSON としてストリーミングします。
インストールしたくない場合は、Skillselion MCP 経由で先に試せます。インストール不要、ディスクにも何も残りません。
1. Claude Code - リモートコネクタを追加:
claude mcp add --transport http skillselion https://mcp.skillselion.com/mcpClaude.ai・ChatGPT・Cursor の場合は、この URL をカスタムコネクタとして追加します。
https://mcp.skillselion.com/mcp2. エージェントに頼む:
Use Skillselion load_skill to load skill:open.feishu.cn#lark-event (Lark Event) and follow it.インストールが向いている場合
リアルタイムのイベントストリームを消費する、Lark のボットや、長時間稼働のイベントサブスクライバーを作る場合に向いています。
見送ったほうがよい場合
カレンダー、メール、シートの単発の操作には向きません。それらには専用の Lark スキルがあります。
lark-eventの主な内容
- lark-cli event consume で、Feishu / Lark のイベントを NDJSON としてストリーミングします
- IM のメッセージ、リアクション、チャットの変更、VC の会議終了、Minutes、ホワイトボードのイベントをカバーします
- --max-events / --timeout による実行の制限と、サブプロセス向けの stderr の ready マーカーの取り決めがあります
数字で見るlark-event
| 累計インストール数 | 873,616 (87.4万) | skills.shレジストリ |
|---|---|---|
| リポジトリ | ||
2026年11月9日時点のデータ(Skillselionカタログ同期)
lark-eventはClaude Codeで何ができますか?
スキルは無料です。イベントサブスクリプションを設定したLark/Feishuアプリと、lark-cli認証が必要です。
- 機能
- イベントのストリーミング · イベントのサブスクリプション · Webhookの処理 · ボットのランタイム
- ユースケース
- orchestration
- 実行環境
- ローカルで実行
- 料金
- 無料
lark-eventのドキュメントが説明する機能
stream events as NDJSON via `lark-cli event consume <EventKey>`
Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers.
lark-eventにはどんなファイルが含まれますか?
Lark Events
Prerequisite: Read `../lark-shared/SKILL.md` first for authentication,
--as user/botswitching,Permission deniedhandling, and safety rules.
Core commands
| Command | Purpose |
|---|---|
lark-cli event list [--json] | List all subscribable EventKeys |
lark-cli event schema <EventKey> [--json] | Show an EventKey's params and output schema |
lark-cli event consume <EventKey> [flags] | Blocking consume; events → stdout NDJSON |
lark-cli event status [--json] [--fail-on-orphan] | Inspect the local bus daemon status |
lark-cli event stop [--all] [--force] | Stop the bus daemon |
Common flags
| Flag | Description |
|---|---|
--param key=value / -p | Business params (repeatable; comma-separated for multi-value). Unknown keys fail with valid names listed inline |
--jq <expr> | jq expression to filter / transform each event; empty output skips the event |
--max-events N | Exit after N events. Default 0 = unlimited |
--timeout D | Exit after duration D (e.g. 30s, 2m). Default 0 = no timeout. Whichever of --max-events / --timeout fires first wins |
--output-dir <dir> | Write each event as a file (relative paths only; prevents traversal) |
--quiet | Suppress ready/exit markers and per-event stderr diagnostics, including drop warnings. This can hide event loss. AI should not use this — it removes readiness and integrity signals |
| `--as user\ | bot\ |
Examples
# Default: stream every event for the key (no filter, no projection)
lark-cli event consume im.message.receive_v1 --as bot
# List every EventKey of one domain (the authoritative, always-current catalog)
lark-cli event list --domain vc --json
# Grab one sample event to inspect payload shape
lark-cli event consume im.message.receive_v1 --max-events 1 --timeout 30s --as bot
# Run for 10 minutes then auto-exit
lark-cli event consume im.message.receive_v1 --timeout 10m --as bot
# Consume multiple EventKeys concurrently (one shape per process, no dispatcher)
lark-cli event consume im.message.receive_v1 --as bot > receive.ndjson &
lark-cli event consume im.message.reaction.created_v1 --as bot > reaction.ndjson &
wait
Call flow
lark-cli event list --json→ pick a legal key.--domain <d>narrows to one domain; the domains areapplication,approval,board,card,im,minutes,task,vc. An unknown domain fails with the valid set listed in the hint.lark-cli event schema <key> --json→ readresolved_output_schema+jq_root_pathto determine field pathslark-cli event consume <key> [--jq '<expr>']→ consume
Subprocess contract
Ready marker
event consume's stderr emits a fixed line [event] ready event_key=<key>. Parent processes should block on stderr until this line appears, then start reading stdout. Do not fall back to sleep.
stdin EOF = graceful exit
event consume treats stdin close as a shutdown signal (wired for AI subprocess callers). Bounded runs are exempt: when --max-events or --timeout is set (> 0), stdin EOF is ignored and the run exits only via its own bound, timeout, or SIGTERM. For unbounded runs, < /dev/null / nohup / systemd's default StandardInput=null will cause an immediate graceful exit (stderr reason: signal). To keep an unbounded run alive:
- Feed stdin a source that never EOFs:
< <(tail -f /dev/null) - Or run bounded:
--max-events N/--timeout D
Exit codes & reason
On exit, the last stderr line is [event] exited — received N event(s) in Xs (reason: ...).
| exit code | reason | Trigger |
|---|---|---|
| 0 | reason: limit | --max-events reached |
| 0 | reason: timeout | --timeout reached |
| 0 | reason: signal | Ctrl+C / SIGTERM / stdin EOF (stdin EOF applies to unbounded runs only) |
| 1 | JSON error envelope on stderr | Lark API business failure during pre-consume setup (for example subscription create/delete) |
| 2 | JSON error envelope on stderr (no exited line) | Validation failure (unknown EventKey, bad --param / --jq, another bus already connected) |
| 3 | JSON error envelope on stderr | Auth failure (missing token, missing scopes) |
| 4 / 5 | JSON error envelope on stderr | Network / internal failure (bus startup, handshake, file I/O) |
Startup and runtime failures emit a structured JSON envelope on stderr: {"ok":false,"error":{"type","subtype","param","message","hint",...}} (the envelope may also carry top-level identity / _notice siblings). Parse error.type / error.subtype to branch (e.g. missing_scope carries a missing_scopes list), error.param to find the offending flag, and error.hint for the recovery action — do not regex-match message text.
Orchestrators should treat reason: limit/timeout/signal (all exit 0) as "business completion" and non-zero as "failure".
Never kill -9
Avoid kill -9 on consume processes for EventKeys whose PreConsume registers a server-side subscription and unsubscribes on exit (minutes, vc, board keys): kill -9 skips the OAPI unsubscribe and leaks the server-side subscription (symptoms: "subscription already exists" on restart, duplicate event delivery). Keys whose subscription is a durable relation with no cleanup (task, approval keys) do not leak this way, but SIGTERM or closing stdin remains the right shutdown for every key.
One consume, one EventKey (multi-key = multi-shell)
The command takes exactly one positional argument; k1,k2 and wildcards are unsupported. Listening to N keys means N subprocesses — this is intentional:
- One shape per process stdout; no dispatcher logic required in the AI
- Fault isolation (one key failing doesn't affect others)
- Independent
--as/--jq/--max-events/--timeoutper key
All N consumers share a single bus daemon (UDS local IPC), so the overhead is small
Writing jq via schema
event schema <key> --json is the source of truth for writing --jq. Four things to look at:
(1) Where fields start — see jq_root_path
- Value
"."→ fields are at the top level, write.chat_id - Value
".event"→ fields are inside a V2 envelope, write.event.chat_id
(2) Field list and types — see resolved_output_schema.properties.<name>
Each field carries type / description, and some also have format. Snippet (from event schema im.message.receive_v1 --json):
{
"chat_id": {"type":"string", "format":"chat_id", "description":"Chat ID, prefixed with oc_"},
"sender_id": {"type":"string", "format":"open_id", "description":"Sender open_id, prefixed with ou_"},
"create_time": {"type":"string", "format":"timestamp_ms", "description":"Send time as ms-epoch string"}
}(3) Field semantics — see the format tag
Lark-defined semantic tags (not JSON Schema's standard format). Common values: open_id / chat_id / message_id / timestamp_ms / email. Purpose: distinguish "same string type, different meanings" fields so you can reverse-lookup via API or convert formats.
(4) Decoded state — read the field's description
event consume runs Process hooks that may pre-decode some payload fields (flattening V2 envelopes, rendering .content to plain text, etc.) — behavior differs from raw OAPI. Always read the field's description before writing jq, especially for generic field names like content / data / body / payload.
Why it matters: blindly applying fromjson to an already-decoded text field makes jq error on every event and silently drop it — the consumer looks alive but emits nothing, with only a single WARN line buried on stderr. (This is the general behavior: any jq runtime error skips the event with a one-line WARN; the loop does not abort.)
Don't shortcut the schema: when projecting event schema --json with jq, do not strip .description from properties — that's the field that tells you whether a field is already decoded. Dump the full property objects, not just keys.
Aside: --param's valid parameters also live in the schema — the params section lists name / type / required / enum / default / description; section missing = this key accepts no --param.
Topic index
| Topic | Reference | Coverage |
|---|---|---|
| Application | `references/lark-event-application.md` | Catalog of Application EventKeys, including application.bot.menu_v6 for custom bot menu push events + flattened event_key / operator fields + jq recipe |
| Approval | `references/lark-event-approval.md` | Catalog of 2 Approval EventKeys (approval.instance.status_changed_v4, approval.task.status_changed_v4) + optional/multi subscription_type pre-registration + user-auth subscription lifecycle + flat output field reference |
| IM | `references/lark-event-im.md` | Catalog of 12 IM EventKeys + shape notes (flat vs V2 envelope) + im.message.receive_v1 field gotchas (sender_id is open_id only; .content is plain text except for interactive cards) + common jq recipes (filter by chat_type / message_type / sender); for card.action.trigger see also `../lark-im/references/lark-im-card-action-reply.md` |
| Task | `references/lark-event-task.md` | Catalog of 1 Task EventKey (task.task.update_user_access_v2) + Native V2 envelope shape + task commit types + user/bot subscription notes |
| VC | `references/lark-event-vc.md` | Catalog of 7 VC EventKeys (meeting lifecycle participant_meeting_started/joined/ended_v1, vc.note.generated_v1, recording recording_started/transcript_generated/ended_v1) + field reference + source type semantics; the live list is always lark-cli event list --domain vc --json |
| Minutes | `references/lark-event-minutes.md` | Catalog of 1 Minutes EventKey (minutes.minute.generated_v1) + field reference + source type semantics (meeting only) |
| Whiteboard | `references/lark-event-whiteboard.md` | Catalog of 1 Board EventKey (board.whiteboard.updated_v1) + per-whiteboard subscription model (requires -p whiteboard_id=<token>) + payload field reference (whiteboard_id / operator_ids triple-id) |
Lark Application Events
This page covers Application-domain EventKeys supported by lark-cli event.
application.bot.menu_v6
Triggered when a user clicks a custom bot menu item whose response action is configured as a push event.
Listen as the bot identity:
lark-cli event consume application.bot.menu_v6 --as botFilter a specific menu event key:
lark-cli event consume application.bot.menu_v6 --as bot --jq 'select(.event_key == "start_eval")'Output is flattened at the top level:
| Field | Meaning |
|---|---|
type | Event type, always application.bot.menu_v6 |
event_id | Globally unique event ID from the event header |
timestamp | Event delivery time, preferring header.create_time |
app_id | App ID from the event header |
tenant_key | Tenant key from the event header |
event_key | Developer-defined menu event key, for example start_eval |
menu_timestamp | Menu click timestamp from the event body |
operator_id | Operator open_id alias |
operator_open_id | Operator open_id |
operator_union_id | Operator union_id |
operator_user_id | Operator user_id |
operator_name | Operator display name |
This EventKey has no --param; use --jq to filter by event_key or operator fields.
Approval Events
Prerequisite: Read `../SKILL.md` first for the
event consumeessentials (commands, subprocess contract, jq usage).
Key catalog (2)
| EventKey | Purpose |
|---|---|
approval.instance.status_changed_v4 | An approval instance status changed |
approval.task.status_changed_v4 | An approval task status changed |
Both keys use a Custom schema. The raw Lark schema 2.0 envelope is flattened: event metadata is exposed as type, event_id, and timestamp, while approval business fields are exposed at the top level.
Both keys carry a PreConsume hook that subscribes the current authorized user through the Approval subscription APIs before listening. The consumer intentionally does not unsubscribe on exit; the server-side Approval subscription relation remains until it is canceled outside event consume. These keys require --as user.
Listener and subscription selection
At the raw CLI level, each event consume process accepts exactly one EventKey. approval.instance.status_changed_v4 and approval.task.status_changed_v4 have different output shapes, so listening to both still means two processes.
For Approval only, subscription_type is an optional setup param used by PreConsume to register server-side Approval subscription relations before the local listener starts. It is not an output field, a local event filter, or a local subscription identity. The pushed event does not say which subscription relation caused delivery, and one business event can match both relations; deduplicate with event_id when needed.
subscription_type may be omitted, a single value, a comma-separated list, or a JSON string array:
# Omitted: register both INVOLVED_APPROVAL and MANAGED_APPROVAL for this EventKey
lark-cli event consume approval.instance.status_changed_v4 --as user
# Single relation
lark-cli event consume approval.instance.status_changed_v4 \
-p subscription_type=INVOLVED_APPROVAL \
--as user
# Explicit multi-relation registration for one local consumer
lark-cli event consume approval.task.status_changed_v4 \
-p subscription_type=INVOLVED_APPROVAL,MANAGED_APPROVAL \
--as user
# JSON array form; quote it for the shell
lark-cli event consume approval.task.status_changed_v4 \
-p 'subscription_type=["INVOLVED_APPROVAL","MANAGED_APPROVAL"]' \
--as user| Value | Meaning |
|---|---|
INVOLVED_APPROVAL | Receive events where the current user is the approval requester or approver |
MANAGED_APPROVAL | Receive events under approval definitions managed by the current user |
User-intent inference:
| User intent | EventKey(s) | subscription_type |
|---|---|---|
| Mentions approval instances, approval forms, approval order/status, or "instance status" | approval.instance.status_changed_v4 | infer from relation words below |
| Mentions approval tasks, approval todo items, approver operations, or "task status" | approval.task.status_changed_v4 | infer from relation words below |
| Says "approval status changes/events" without saying task vs instance | both EventKeys | infer from relation words below |
| Says "my approvals", "approvals involving me", "I requested/approved", "待我审批", "我发起/我参与" | requested EventKey(s) | INVOLVED_APPROVAL |
| Says "approvals I manage", "managed definitions", "definitions managed by me", "我管理的审批定义" | requested EventKey(s) | MANAGED_APPROVAL |
| Explicitly asks for both involved and managed, or says "all approval subscriptions" | requested EventKey(s), or both if EventKey is also ambiguous | omit subscription_type, or pass both values in one -p |
| Relation is ambiguous and the user wants broad coverage | requested EventKey(s), or both if EventKey is also ambiguous | omit subscription_type so PreConsume registers both |
If the user's wording omits the relation and broad listening is acceptable, omit subscription_type. Ask only when registering both relations would be materially harmful.
Scopes & auth
| EventKey | Scope | Auth |
|---|---|---|
approval.instance.status_changed_v4 | approval:instance:read | user |
approval.task.status_changed_v4 | approval:task:read | user |
Subscription behavior
Startup calls the endpoint for the selected EventKey:
POST /open-apis/approval/v4/instances/subscription
POST /open-apis/approval/v4/tasks/subscriptionFor each resolved subscription_type, PreConsume sends one request body:
{"subscription_type":"INVOLVED_APPROVAL"}If subscription_type is omitted, PreConsume sends two registration requests for that EventKey: one with INVOLVED_APPROVAL, then one with MANAGED_APPROVAL. If listening to both instance and task events, run two consumers; each consumer may omit subscription_type to register both relations for its own EventKey.
Do not start two consumers for the same Approval EventKey merely to split INVOLVED_APPROVAL and MANAGED_APPROVAL. The server push and flattened output are keyed by EventKey and cannot be distinguished by subscription relation.
Shutdown behavior:
event consume does not call the Approval unsubscribe APIs when it exits. This applies to graceful exit, Ctrl+C / SIGTERM, stdin EOF, --timeout, and --max-events.
To stop future delivery for a user, cancel the Approval subscription relation outside this consumer. The unsubscribe APIs are separate operations and are not called by event consume.
Output fields
Common fields:
| Field | Type | Description |
|---|---|---|
type | string | Event type |
event_id | string | Globally unique event ID; use for deduplication |
timestamp | string (timestamp_ms) | Event delivery time in milliseconds, taken from header.create_time |
Instance event fields:
| Field | Type | Description |
|---|---|---|
approval_code | string | Approval definition code; not a subscription dimension |
instance_code | string | Approval instance code |
external_id | string | Third-party approval instance id, when present |
status | string enum | PENDING, APPROVED, REJECTED, CANCELED, DELETED, REVERTED, OVERTIME_CLOSE, OVERTIME_RECOVER |
operate_time | string (timestamp_ms) | Status change time |
start_user | object | Instance starter user IDs, omitted when unavailable |
start_user.open_id | string (open_id) | Instance starter open_id, when present |
start_user.union_id | string (union_id) | Instance starter union_id, when present |
start_user.user_id | string (user_id) | Instance starter tenant user_id, when present |
Task event fields:
| Field | Type | Description |
|---|---|---|
approval_code | string | Approval definition code; not a subscription dimension |
instance_code | string | Approval instance code |
task_id | string | Approval task id |
external_id | string | Third-party approval external id, when present |
task_external_id | string | Third-party task external id, when emitted |
assigned_user | object | Task assignee or operator user IDs, omitted for automatic flows without an operator |
assigned_user.open_id | string (open_id) | Task assignee or operator open_id, when present |
assigned_user.union_id | string (union_id) | Task assignee or operator union_id, when present |
assigned_user.user_id | string (user_id) | Task assignee or operator tenant user_id, when present |
status | string enum | REVERTED, PENDING, APPROVED, REJECTED, TRANSFERRED, ROLLBACK, DONE, OVERTIME_CLOSE, OVERTIME_RECOVER |
operate_time | string (timestamp_ms) | Status change time |
Examples
# Stream approval instance updates broadly; registers both involved and managed relations
lark-cli event consume approval.instance.status_changed_v4 \
--as user
# Stream approval instance updates only for approvals involving the current user
lark-cli event consume approval.instance.status_changed_v4 \
-p subscription_type=INVOLVED_APPROVAL \
--as user
# Stream approval task updates for definitions managed by the current user
lark-cli event consume approval.task.status_changed_v4 \
-p subscription_type=MANAGED_APPROVAL \
--as user
# Broad approval status listening:
# run both EventKeys as separate processes; omit subscription_type so each registers both relations.
lark-cli event consume approval.instance.status_changed_v4 \
--as user > approval-instance.ndjson &
lark-cli event consume approval.task.status_changed_v4 \
--as user > approval-task.ndjson &
wait
# Listen to both involved and managed task subscriptions with one local consumer.
lark-cli event consume approval.task.status_changed_v4 \
-p subscription_type=INVOLVED_APPROVAL,MANAGED_APPROVAL \
--as user > approval-task.ndjson
# Project a compact approval-task record
lark-cli event consume approval.task.status_changed_v4 \
-p subscription_type=INVOLVED_APPROVAL \
--as user \
--jq '{event_id, task_id, status, at: .operate_time}'IM Events
Prerequisite: Read `../SKILL.md` first for the
event consumeessentials (commands, subprocess contract, jq usage).Heads-up for AI agents: this key's
.contentis NOT the raw OAPI payload shape your training data may suggest.lark-cliruns a Process hook (convertlib) that flattens the V2 envelope and pre-renders.contentto human-readable text fortext/post/image/file/audio/ etc. Onlyinteractive(cards) keeps the raw JSON string. Don't blindlyfromjson.
Key catalog (12)
| EventKey | Purpose |
|---|---|
im.message.receive_v1 | Receive IM messages |
im.message.message_read_v1 | User read a bot's p2p message (group messages don't fire this) |
im.message.reaction.created_v1 | Reaction added to a message |
im.message.reaction.deleted_v1 | Reaction removed from a message |
im.chat.updated_v1 | Chat settings changed (owner, avatar, name, permissions, etc.) |
im.chat.disbanded_v1 | Chat disbanded |
im.chat.member.bot.added_v1 | Bot added to a chat |
im.chat.member.bot.deleted_v1 | Bot removed from a chat |
im.chat.member.user.added_v1 | User joined a chat (including topic chats) |
im.chat.member.user.deleted_v1 | User left voluntarily or was removed |
im.chat.member.user.withdrawn_v1 | Pending chat invite withdrawn (inviter canceled; user never actually joined) |
card.action.trigger | Interactive card callback — button click, form submit, dropdown, etc. → see `lark-im-card-action-reply.md` |
Shape: All 12 events have a V2-enveloped raw payload.
lark-cliflattens two of them —im.message.receive_v1andcard.action.trigger— so their consumed output is flat (fields at.xxx). The other 10 are passed through as-is; use.event.xxxto access their fields.
Gotchas (im.message.receive_v1)
sender_id is open_id only: the event payload carries no display name. Call the contact API separately if you need the sender's name.
.content shape depends on message_type (this key uses a flat Custom schema; see `events/im/message_receive.go`):
| message_type | .content shape | How to read |
|---|---|---|
text / post / image / file / audio / sticker / share_chat / share_user / media / system | Human-readable text (convertlib-processed; @mentions resolved to display names) | Use .content directly |
interactive (card) | Raw card JSON string (structured actions can't be losslessly flattened) | `.content \ |
Do not blindly fromjson — for non-interactive messages it fails with jq: fromjson cannot be applied to "hello" because .content isn't JSON-encoded.
# text: .content is plain text — no fromjson needed
lark-cli event consume im.message.receive_v1 --as bot \
--jq 'select(.message_type=="text") | .content'
# interactive: .content is a JSON string — fromjson to parse
lark-cli event consume im.message.receive_v1 --as bot \
--jq 'select(.message_type=="interactive") | .content | fromjson'On-demand filter recipes
Default = no
--jq. Runlark-cli event consume im.message.receive_v1 --as botto see every message. The recipes below are only for cases where the user has asked to narrow the stream.
1. Filter by chat type (p2p vs group)
chat_type is an enum with values p2p / group.
# p2p only (direct messages)
lark-cli event consume im.message.receive_v1 --as bot \
--jq 'select(.chat_type=="p2p") | {from: .sender_id, msg: .content}'
# group only
lark-cli event consume im.message.receive_v1 --as bot \
--jq 'select(.chat_type=="group") | {chat: .chat_id, from: .sender_id, msg: .content}'2. Filter by message type
# text only — content is plain human-readable text
lark-cli event consume im.message.receive_v1 --as bot \
--jq 'select(.message_type=="text") | .content'
# interactive (card) only — parse the card body
lark-cli event consume im.message.receive_v1 --as bot \
--jq 'select(.message_type=="interactive") | .content | fromjson'3. Filter by sender (only one user's messages)
# example: only messages from the given open_id
lark-cli event consume im.message.receive_v1 --as bot\
--jq 'select(.sender_id=="ou_xxxxxxxxxxxxxxxxxxxxxxxxxx") | {msg_id: .message_id, text: .content}'Get your own open_id via lark-cli contact +get-user --as user; other users' via lark-cli contact +search-user.
Minutes Events
Prerequisite: Read `../SKILL.md` first for the
event consumeessentials (commands, subprocess contract, jq usage).
Key catalog (1)
| EventKey | Purpose |
|---|---|
minutes.minute.generated_v1 | A minute (妙记) has been generated |
This key uses a Custom schema (flat output at .xxx) and carries a PreConsume hook that auto-subscribes / unsubscribes via OAPI on first / last consumer.
Scopes & auth
| EventKey | Scope | Auth |
|---|---|---|
minutes.minute.generated_v1 | minutes:minutes.basic:read | user |
Requires --as user.
minutes.minute.generated_v1
Output fields
| Field | Type | Description |
|---|---|---|
type | string | Event type; always minutes.minute.generated_v1 |
event_id | string | Globally unique event ID; safe for deduplication |
timestamp | string (timestamp_ms) | Event delivery time (ms timestamp string) |
minute_token | string | Minute token |
title | string | Minute title (enriched via detail API) |
minute_source | object | Minute source metadata; only present when the source is a meeting |
minute_source.source_type | string | Source type; only present when the source is a meeting (value: meeting) |
minute_source.source_entity_id | string | Source entity ID (meeting ID); only present when the source is a meeting |
Enrichment & degradation
The Process hook calls GET /open-apis/minutes/v1/minutes/{minute_token} to enrich title. If the detail API fails, this field is left empty — the base fields (type, event_id, timestamp, minute_token, minute_source) are always present.
minute_source is populated from the event payload directly (not the detail API), so it survives enrichment failures. Note: minute_source is only present when the minute originates from a meeting; for other sources (e.g. recording, local upload) this field is absent.
Example
lark-cli event consume minutes.minute.generated_v1 --as user
# Project title and token only (skip events where enrichment failed)
lark-cli event consume minutes.minute.generated_v1 --as user \
--jq 'select(.title != "") | {minute_token, title}'
# Filter by source type
lark-cli event consume minutes.minute.generated_v1 --as user \
--jq 'select(.minute_source.source_type == "meeting") | {minute_token, title}'Task Events
Prerequisite: Read `../SKILL.md` first for the
event consumeessentials (commands, subprocess contract, jq usage).
Key catalog (1)
| EventKey | Purpose |
|---|---|
task.task.update_user_access_v2 | A visible task has been created, deleted, or updated |
This key uses a Native schema (V2 envelope; output rooted at .event) and carries a PreConsume hook that calls the Task event subscription API before listening.
Scopes & auth
| EventKey | Scope | Auth |
|---|---|---|
task.task.update_user_access_v2 | task:task:read | user, bot |
Supports --as user or --as bot.
--as user: receive task updates visible to the current user through authorship, assignment, following, or other access.--as bot: receive task updates for tasks the application is responsible for.
task.task.update_user_access_v2
Subscription behavior
On startup, event consume calls:
POST /open-apis/task/v2/task_v2/task_subscription?user_id_type=open_idThe Task subscription API has no matching unsubscribe endpoint in the current CLI metadata, so graceful exit has no cleanup call for this EventKey. Re-running the consumer repeats the subscribe call for the selected identity.
This EventKey is single-consumer per local bus subscription: start one event consume task.task.update_user_access_v2 process for a given app/profile/identity at a time.
Output fields (V2 envelope; root path .event)
| Field | Type | Description |
|---|---|---|
.header.event_id | string | Globally unique event ID; safe for deduplication |
.header.create_time | string (timestamp_ms) | Event creation time in milliseconds |
.event.event_types[] | string enum | Task commit types included in this event |
.event.task_guid | string (kind=task_guid) | Task GUID that changed |
Commit types:
task_assignees_update
task_completed_update
task_create
task_deleted
task_desc_update
task_followers_update
task_reminders_update
task_start_due_update
task_summary_updateExample
# Stream task update events for the current user
lark-cli event consume task.task.update_user_access_v2 --as user
# Sample one event for payload inspection
lark-cli event consume task.task.update_user_access_v2 \
--as user --max-events 1 --timeout 2m
# Project to a compact task-update record
lark-cli event consume task.task.update_user_access_v2 \
--as user \
--jq '{event_id: .header.event_id, task_guid: .event.task_guid, event_types: .event.event_types, timestamp: .header.create_time}'
# Consume as the app identity
lark-cli event consume task.task.update_user_access_v2 --as botVC Events
Prerequisite: Read `../SKILL.md` first for the
event consumeessentials (commands, subprocess contract, jq usage).
Key catalog (7)
| EventKey | Purpose |
|---|---|
vc.meeting.participant_meeting_started_v1 | A meeting the current user participates in has started |
vc.meeting.participant_meeting_joined_v1 | The current user has joined a meeting |
vc.meeting.participant_meeting_ended_v1 | A meeting the current user participates in has ended |
vc.note.generated_v1 | A note has been generated (meeting, recording, upload, etc.) |
vc.recording.recording_started_v1 | A recording_bean recording has started (Feishu software only) |
vc.recording.recording_transcript_generated_v1 | Recording_bean transcript items were generated (Feishu software only) |
vc.recording.recording_ended_v1 | A recording_bean recording ended and uploaded successfully (Feishu software only) |
All seven keys use a Custom schema (flat output) and carry a PreConsume hook that auto-subscribes / unsubscribes via OAPI on first / last consumer. All require --as user.
Scopes & auth
| EventKey | Scope | Auth |
|---|---|---|
vc.meeting.participant_meeting_started_v1 | vc:meeting.meetingevent:read | user |
vc.meeting.participant_meeting_joined_v1 | vc:meeting.meetingevent:read | user |
vc.meeting.participant_meeting_ended_v1 | vc:meeting.meetingevent:read | user |
vc.note.generated_v1 | vc:note:read | user |
vc.recording.recording_started_v1 | vc:recording:read | user |
vc.recording.recording_transcript_generated_v1 | vc:recording:read | user |
vc.recording.recording_ended_v1 | vc:recording:read | user |
Meeting participant events
Covered keys:
vc.meeting.participant_meeting_started_v1vc.meeting.participant_meeting_joined_v1vc.meeting.participant_meeting_ended_v1
Output fields
| Field | Type | Description |
|---|---|---|
type | string | Event type; one of the covered meeting participant EventKeys |
event_id | string | Globally unique event ID; safe for deduplication |
timestamp | string (timestamp_ms) | Event delivery time (ms timestamp string) |
meeting_id | string | Meeting ID |
topic | string | Meeting topic |
meeting_no | string | Meeting number |
start_time | string | Meeting start time in RFC3339, converted to the local timezone |
calendar_event_id | string | Calendar event ID associated with the meeting |
end_time | string | Meeting end time in RFC3339, converted to the local timezone; only present for vc.meeting.participant_meeting_ended_v1 |
Gotchas
start_time/end_timeare not the raw unix-seconds from OAPI — the Process hook converts them to local-timezone RFC3339. If the raw value is empty or non-numeric, the field is left empty.end_timeis emitted only forvc.meeting.participant_meeting_ended_v1.- No detail API call is made; all fields come from the event payload itself.
Example
lark-cli event consume vc.meeting.participant_meeting_started_v1 --as user
lark-cli event consume vc.meeting.participant_meeting_joined_v1 --as user
lark-cli event consume vc.meeting.participant_meeting_ended_v1 --as user
# Project meeting topic and end time only
lark-cli event consume vc.meeting.participant_meeting_ended_v1 --as user \
--jq '{meeting: .meeting_id, topic: .topic, ended: .end_time}'vc.note.generated_v1
Fires when a note is generated — not just from meetings, but also from realtime recordings and local file uploads.
Output fields
| Field | Type | Description |
|---|---|---|
type | string | Event type; always vc.note.generated_v1 |
event_id | string | Globally unique event ID; safe for deduplication |
timestamp | string (timestamp_ms) | Event delivery time (ms timestamp string) |
note_id | string | Note ID |
note_token | string | Note document token; may be empty if detail is not yet available |
verbatim_token | string | Verbatim document token; may be empty if detail is not yet available |
note_source | object | Source metadata; only present when source is a meeting |
note_source.source_type | string | Source type; only present when source is a meeting (value: meeting) |
note_source.source_entity_id | string | Source entity ID (meeting ID); only present when source is a meeting |
Source type semantics
source_type | Trigger |
|---|---|
meeting | Note generated from a meeting |
note_source (and its sub-fields) are only populated when source_type is meeting. For other sources the field is absent.
Example
lark-cli event consume vc.note.generated_v1 --as user
# Only notes with enriched tokens, skip incomplete ones
lark-cli event consume vc.note.generated_v1 --as user \
--jq 'select(.note_token != "") | {note_id, note_token, verbatim_token}'
# Filter to meeting-sourced notes only
lark-cli event consume vc.note.generated_v1 --as user \
--jq 'select(.note_source.source_type == "meeting") | {note_id, meeting_id: .note_source.source_entity_id}'Whiteboard Events
Prerequisite: Read `../SKILL.md` first for the
event consumeessentials (commands, subprocess contract, jq usage).
Key catalog (1)
| EventKey | Purpose |
|---|---|
board.whiteboard.updated_v1 | A whiteboard has been edited |
This key uses a Native schema (V2 envelope; output rooted at .event) and carries a PreConsume hook that auto-subscribes / unsubscribes via OAPI on first / last consumer.
Scopes & auth
| EventKey | Scope | Auth |
|---|---|---|
board.whiteboard.updated_v1 | board:whiteboard:node:read | user, bot |
Supports --as user or --as bot. The caller must have manage access to the target whiteboard, otherwise the subscribe OAPI returns 403 and event consume exits with an auth error before listening.
board.whiteboard.updated_v1
Per-whiteboard subscription
Unlike global event keys (e.g. minutes / im), this key subscribes per whiteboard: event consume calls POST /open-apis/board/v1/whiteboards/{whiteboard_id}/subscribe on startup with the whiteboard_id you pass via -p. Required parameter: -p whiteboard_id=<whiteboard_token>. Missing this param fails param validation up-front with required param "whiteboard_id" missing for EventKey board.whiteboard.updated_v1 before any subscription happens.
Whiteboard token can be obtained via the docs OAPI list document blocks: the block whose block_type=43 is a whiteboard, and block.token is the whiteboard token.
Output fields (V2 envelope; root path .event)
| Field | Type | Description |
|---|---|---|
.event.whiteboard_id | string (kind=whiteboard_id) | Whiteboard token |
.event.operator_ids[].open_id | string (kind=open_id) | Editor's open_id (ou_ prefix) |
.event.operator_ids[].union_id | string (kind=union_id) | Editor's union_id |
.event.operator_ids[].user_id | string (kind=user_id) | Editor's user_id (only present when the caller's app has the user_id-related contact scope granted by the OAPI side) |
operator_ids is an array — multi-user collaborative editing within one tick collapses into a single event with multiple entries.
Subscription lifecycle
| Phase | Behavior |
|---|---|
| Startup | event consume calls subscribe OAPI; on success stderr emits [event] consuming as ..., [event] running pre-consume setup..., [event] listening for events (key=board.whiteboard.updated_v1)..., then the AI-facing ready marker [event] ready event_key=board.whiteboard.updated_v1 |
| Running | Edits to the whiteboard stream as NDJSON to stdout |
Graceful exit (Ctrl+C / SIGTERM / --max-events / --timeout / stdin EOF) | event consume calls unsubscribe OAPI |
kill -9 | Skips unsubscribe → server-side subscription leaks, may cause subscription already exists or duplicate delivery on next consume. See SKILL.md "Never kill -9". |
Example
# Stream every edit on whiteboard <token> until Ctrl+C
lark-cli event consume board.whiteboard.updated_v1 \
-p whiteboard_id=<whiteboard_token> \
--as user
# Sample one event for payload inspection
lark-cli event consume board.whiteboard.updated_v1 \
-p whiteboard_id=<whiteboard_token> \
--as user --max-events 1 --timeout 2m
# Project to "edit summary": who edited which whiteboard
lark-cli event consume board.whiteboard.updated_v1 \
-p whiteboard_id=<whiteboard_token> \
--as user \
--jq '{whiteboard: .event.whiteboard_id, editors: (.event.operator_ids | map(.open_id))}'lark-eventに関連するスキルは?
Lark Event スキルのフォークは?
カタログにはLark Eventのコピーが1件あり、インストール数は合計47.8万です。これらはすべて、この元のリスティングに正規化されます。
- larksuite - インストール数47.8万
Lark Event スキルのよくある質問
Lark Event スキルはどんな人に向いていますか?
リアルタイムのイベントストリームを消費する、Lark のボットや、長時間稼働のイベントサブスクライバーを作る場合に向いています。 次に当てはまる場合は見送ってください:カレンダー、メール、シートの単発の操作には向きません。それらには専用の Lark スキルがあります。
lark-eventはどんなときに使いますか?
エージェントが、受信メッセージや会議終了の通知など、Feishu / Lark のリアルタイムのイベントを待ち受けたり、それに反応したりする必要があるときに使います。
lark-eventのバッジを追加する
このスキルがSkillselionに掲載されていることを開発者に示せます。READMEに貼り付けてください。