Lark Events skill

Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.).

by larksuite·MIT license·★ 17,534 Stars on the repo·GitHub ↗

Use now

Files of Lark Events

larksuite/main1 file shown
SKILL.md
Show the full text160 lines

Lark Events

Prerequisite: Read ../lark-shared/SKILL.md first for authentication, --as user/bot switching, Permission denied handling, 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|auto Identity for the session (see lark-shared)

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

  1. lark-cli event list --json → pick a legal key. --domain <d> narrows to one domain; the domains are application, approval, board, card, im, minutes, task, vc. An unknown domain fails with the valid set listed in the hint.
  2. lark-cli event schema <key> --json → read resolved_output_schema + jq_root_path to determine field paths
  3. lark-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 / --timeout per 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)
1---
2name: lark-event
3version: 1.0.0
4description: "Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses."
5metadata:
6 requires:
7 bins: ["lark-cli"]
8 cliHelp: "lark-cli event --help"
9---
10 
11# Lark Events
12 
13> **Prerequisite:** Read [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md) first for authentication, `--as user/bot` switching, `Permission denied` handling, and safety rules.
14 
15## Core commands
16 
17| Command | Purpose |
18|------|------|
19| `lark-cli event list [--json]` | List all subscribable EventKeys |
20| `lark-cli event schema <EventKey> [--json]` | Show an EventKey's params and output schema |
21| `lark-cli event consume <EventKey> [flags]` | Blocking consume; events → stdout NDJSON |
22| `lark-cli event status [--json] [--fail-on-orphan]` | Inspect the local bus daemon status |
23| `lark-cli event stop [--all] [--force]` | Stop the bus daemon |
24 
25 
26## Common flags
27 
28| Flag | Description |
29|---|---|
30| `--param key=value` / `-p` | Business params (repeatable; comma-separated for multi-value). Unknown keys fail with valid names listed inline |
31| `--jq <expr>` | jq expression to filter / transform each event; empty output skips the event |
32| `--max-events N` | Exit after N events. Default 0 = unlimited |
33| `--timeout D` | Exit after duration D (e.g. `30s`, `2m`). Default 0 = no timeout. Whichever of `--max-events` / `--timeout` fires first wins |
34| `--output-dir <dir>` | Write each event as a file (relative paths only; prevents traversal) |
35| `--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 |
36| `--as user\|bot\|auto` | Identity for the session (see lark-shared) |
37 
38 
39## Examples
40 
41```bash
42# Default: stream every event for the key (no filter, no projection)
43lark-cli event consume im.message.receive_v1 --as bot
44 
45# List every EventKey of one domain (the authoritative, always-current catalog)
46lark-cli event list --domain vc --json
47 
48# Grab one sample event to inspect payload shape
49lark-cli event consume im.message.receive_v1 --max-events 1 --timeout 30s --as bot
50 
51# Run for 10 minutes then auto-exit
52lark-cli event consume im.message.receive_v1 --timeout 10m --as bot
53 
54# Consume multiple EventKeys concurrently (one shape per process, no dispatcher)
55lark-cli event consume im.message.receive_v1 --as bot > receive.ndjson &
56lark-cli event consume im.message.reaction.created_v1 --as bot > reaction.ndjson &
57wait
58 
59```
60 
61## Call flow
62 
631. `lark-cli event list --json` → pick a legal key. `--domain <d>` narrows to one domain; the domains are `application`, `approval`, `board`, `card`, `im`, `minutes`, `task`, `vc`. An unknown domain fails with the valid set listed in the hint.
642. `lark-cli event schema <key> --json` → read `resolved_output_schema` + `jq_root_path` to determine field paths
653. `lark-cli event consume <key> [--jq '<expr>']` → consume
66 
67## Subprocess contract
68 
69### Ready marker
70 
71`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`.
72 
73### stdin EOF = graceful exit
74 
75`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:
76 
77- Feed stdin a source that never EOFs: `< <(tail -f /dev/null)`
78- Or run bounded: `--max-events N` / `--timeout D`
79 
80### Exit codes & reason
81 
82On exit, the last stderr line is `[event] exited — received N event(s) in Xs (reason: ...)`.
83 
84| exit code | reason | Trigger |
85|---|---|---|
86| 0 | `reason: limit` | `--max-events` reached |
87| 0 | `reason: timeout` | `--timeout` reached |
88| 0 | `reason: signal` | Ctrl+C / SIGTERM / stdin EOF (stdin EOF applies to unbounded runs only) |
89| 1 | JSON error envelope on stderr | Lark API business failure during pre-consume setup (for example subscription create/delete) |
90| 2 | JSON error envelope on stderr (no `exited` line) | Validation failure (unknown EventKey, bad `--param` / `--jq`, another bus already connected) |
91| 3 | JSON error envelope on stderr | Auth failure (missing token, missing scopes) |
92| 4 / 5 | JSON error envelope on stderr | Network / internal failure (bus startup, handshake, file I/O) |
93 
94Startup 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.
95 
96Orchestrators should treat `reason: limit/timeout/signal` (all exit 0) as "business completion" and non-zero as "failure".
97 
98### Never `kill -9`
99 
100**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.
101 
102### One consume, one EventKey (multi-key = multi-shell)
103 
104The command takes exactly one positional argument; `k1,k2` and wildcards are unsupported. Listening to N keys means N subprocesses — this is **intentional**:
105 
106- One shape per process stdout; no dispatcher logic required in the AI
107- Fault isolation (one key failing doesn't affect others)
108- Independent `--as` / `--jq` / `--max-events` / `--timeout` per key
109 
110All N consumers share a single bus daemon (UDS local IPC), so the overhead is small
111 
112## Writing jq via schema
113 
114`event schema <key> --json` is the source of truth for writing `--jq`. Four things to look at:
115 
116**(1) Where fields start** — see `jq_root_path`
117 
118- Value `"."` → fields are at the top level, write `.chat_id`
119- Value `".event"` → fields are inside a V2 envelope, write `.event.chat_id`
120 
121**(2) Field list and types** — see `resolved_output_schema.properties.<name>`
122 
123Each field carries `type` / `description`, and some also have `format`. Snippet (from `event schema im.message.receive_v1 --json`):
124 
125```json
126{
127 "chat_id": {"type":"string", "format":"chat_id", "description":"Chat ID, prefixed with oc_"},
128 "sender_id": {"type":"string", "format":"open_id", "description":"Sender open_id, prefixed with ou_"},
129 "create_time": {"type":"string", "format":"timestamp_ms", "description":"Send time as ms-epoch string"}
130}
131```
132 
133**(3) Field semantics** — see the `format` tag
134 
135Lark-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.
136 
137**(4) Decoded state** — read the field's `description`
138 
139`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`.
140 
141**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.)
142 
143**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.
144 
145---
146 
147**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`**.
148 
149## Topic index
150 
151| Topic | Reference | Coverage |
152|------------|------------------------------------------------------------------------------|---|
153| Application | [`references/lark-event-application.md`](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 |
154| Approval | [`references/lark-event-approval.md`](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 |
155| IM | [`references/lark-event-im.md`](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`](../lark-im/references/lark-im-card-action-reply.md) |
156| Task | [`references/lark-event-task.md`](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 |
157| VC | [`references/lark-event-vc.md`](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` |
158| Minutes | [`references/lark-event-minutes.md`](references/lark-event-minutes.md) | Catalog of 1 Minutes EventKey (`minutes.minute.generated_v1`) + field reference + source type semantics (meeting only) |
159| Whiteboard | [`references/lark-event-whiteboard.md`](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) |
160 

Discussion

Alternatives

API and interface designGuides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.Coding · MITContext7Pulls up-to-date, version-specific library docs and code examples into the prompt so the AI stops inventing old APIs.Coding · MITContext7 Documentation LookupFetch up-to-date documentation and code examples for any library, framework, SDK, CLI tool, or cloud service. Use whenever the user asks about a specific library — even well-known ones like React, Next.js, Prisma, Express, Tailwind, Django, or Spring Boot — because training data may not reflect recent API changes or version updates. Always use for: API syntax questions, configuration options, version migration issues, "how do I" questions mentioning a library name, debugging that involves library-specific behavior, setup instructions, and CLI tool usage. Use even when you think you know the answer. Do not rely on training data for API details, signatures, or configuration options — they are frequently out of date. Prefer this over web search for library documentation.Coding · MITAdaptyv Bio Foundry APIHow to use the Adaptyv Bio Foundry API and Python SDK for protein experiment design, submission, and results retrieval. Use this skill whenever the user mentions Adaptyv, Foundry API, protein binding assays, protein screening experiments, BLI/SPR assays, thermostability assays, or wants to submit protein sequences for experimental characterization. Also trigger when code imports `adaptyv`, `adaptyv_sdk`, or `FoundryClient`, or references `foundry-api-public.adaptyvbio.com`.Science · MIT