diff --git a/affordance/im.md b/affordance/im.md new file mode 100644 index 000000000..87ec76dea --- /dev/null +++ b/affordance/im.md @@ -0,0 +1,343 @@ +# im +> skill: lark-im + +## chat.members create +Add users or bots to an existing chat by id. + +### Avoid when +- Creating a new chat with initial members → use [[+chat-create]] with --users/--bots +- Only need to see who is already in the chat → use [[+chat-members-list]] + +### Prerequisites +- chat_id (oc_xxx) from [[+chat-search]], [[+chat-list]], or [[+chat-create]] output +- member open_ids (ou_xxx) from contact +search-user + +### Examples + +**Add two users to a chat** +```bash +lark-cli im chat.members create --chat-id --data '{"id_list":["",""]}' +``` + +## chat.members delete +Remove users or bots from a chat. + +### Avoid when +- Only reviewing membership before removal → use [[+chat-members-list]] first + +### Prerequisites +- chat_id (oc_xxx) and the member open_ids, both visible in [[+chat-members-list]] output + +### Examples + +**Remove one user from a chat** +```bash +lark-cli im chat.members delete --chat-id --data '{"id_list":[""]}' +``` + +## chat.members get +Page through the raw member list of a chat. + +### Avoid when +- Normal member listing → use [[+chat-members-list]]; it buckets users[]/bots[], paginates, and surfaces truncations[] + +### Prerequisites +- chat_id (oc_xxx) from [[+chat-search]] or [[+chat-list]] + +### Examples + +**Fetch one raw member page** +```bash +lark-cli im chat.members get --chat-id +``` + +## chat.members bots +Check whether the calling bot itself is in the chat. + +### Avoid when +- Listing which bots are members → use [[+chat-members-list]] --member-types bot + +### Prerequisites +- chat_id (oc_xxx); call with bot identity (--as bot) + +### Examples + +**Check the calling bot's membership** +```bash +lark-cli im chat.members bots --chat-id --as bot +``` + +## messages forward +Forward an existing message unchanged to another chat, user, or thread. + +### Avoid when +- Need to send new text, markdown, image, or file content → use [[+messages-send]] +- Need to reply under an existing message → use [[+messages-reply]] +- Need to read messages before forwarding → use [[+chat-messages-list]] or [[+messages-search]] + +### Prerequisites +- message_id from [[+chat-messages-list]], [[+messages-search]], or [[+messages-mget]] +- receive_id_type must match the target id, usually chat_id for group chats + +### Examples + +**Forward one message to a chat** +```bash +lark-cli im messages forward --message-id --receive-id-type chat_id --data '{"receive_id":""}' +``` + +## messages delete +Recall (delete) a sent message. + +### Avoid when +- Fixing content → there is no edit-by-recall; send a corrected message with [[+messages-send]] or reply with [[+messages-reply]] + +### Prerequisites +- message_id from [[+chat-messages-list]] or [[+messages-mget]] +- bot identity can only recall messages the bot itself sent; recall also fails after the tenant's recall window expires + +### Examples + +**Recall a message** +```bash +lark-cli im messages delete --message-id +``` + +## messages merge_forward +Merge-forward multiple messages from one chat as a single combined message. + +### Avoid when +- Forwarding a single message → use [[messages forward]] +- Forwarding a whole thread → use [[threads forward]] + +### Prerequisites +- message_ids all from the same source chat, via [[+chat-messages-list]] +- receive_id_type matching the target id + +### Examples + +**Merge-forward two messages to a chat** +```bash +lark-cli im messages merge_forward --receive-id-type chat_id --data '{"receive_id":"","message_id_list":["",""]}' +``` + +## messages read_users +List who has read a message you sent. + +### Avoid when +- Checking a message's content or reactions → use [[+messages-mget]] + +### Prerequisites +- message_id of a message sent by the current identity; user_id_type decides the id form in the response + +### Examples + +**List readers of a message** +```bash +lark-cli im messages read_users --message-id --user-id-type open_id +``` + +## reactions create +Add an emoji reaction to a message. + +### Avoid when +- Replying with content → use [[+messages-reply]]; reactions carry no text + +### Prerequisites +- message_id from [[+chat-messages-list]], [[+messages-search]], or [[+messages-mget]] +- emoji_type is a fixed enum key (e.g. THUMBSUP, OK); it is not free-form text + +### Examples + +**Add a thumbs-up reaction** +```bash +lark-cli im reactions create --message-id --data '{"reaction_type":{"emoji_type":"THUMBSUP"}}' +``` + +## reactions delete +Remove a reaction you previously added. + +### Avoid when +- Removing someone else's reaction → not possible; only the reaction creator can delete it + +### Prerequisites +- reaction_id from [[reactions list]] or the [[reactions create]] response + +### Examples + +**Delete a reaction** +```bash +lark-cli im reactions delete --message-id --reaction-id +``` + +## reactions list +List reactions on a single message, optionally filtered by emoji type. + +### Avoid when +- Fetching reactions for many messages at once → use [[reactions batch_query]] +- Reading messages with reactions attached → [[+messages-mget]] already enriches reactions + +### Prerequisites +- message_id from [[+chat-messages-list]] or [[+messages-mget]] + +### Examples + +**List reactions on a message** +```bash +lark-cli im reactions list --message-id +``` + +## reactions batch_query +Fetch reactions for several messages in one call. + +### Avoid when +- Only one message → use [[reactions list]] +- Reading messages together with reactions → [[+messages-mget]] enriches automatically + +### Prerequisites +- one or more message_ids from [[+chat-messages-list]], each wrapped as a query entry + +### Examples + +**Query reactions for two messages** +```bash +lark-cli im reactions batch_query --data '{"queries":[{"message_id":""},{"message_id":""}]}' +``` + +## pins create +Pin a message in its chat. + +### Avoid when +- Personal bookmark rather than chat-visible pin → use [[+flag-create]] + +### Prerequisites +- message_id from [[+chat-messages-list]] or [[+messages-search]] +- the calling identity must be in the chat that contains the message + +### Examples + +**Pin a message** +```bash +lark-cli im pins create --data '{"message_id":""}' +``` + +## pins delete +Unpin a previously pinned message. + +### Avoid when +- Removing a personal bookmark → use [[+flag-cancel]] + +### Prerequisites +- message_id of the pinned message, from [[pins list]] + +### Examples + +**Unpin a message** +```bash +lark-cli im pins delete --message-id +``` + +## pins list +List pinned messages in a chat. + +### Avoid when +- Listing normal (non-pinned) history → use [[+chat-messages-list]] + +### Prerequisites +- chat_id (oc_xxx) from [[+chat-search]] or [[+chat-list]] + +### Examples + +**List pins in a chat** +```bash +lark-cli im pins list --chat-id +``` + +## images create +Upload a local image and get an image_key for later use. + +### Avoid when +- Sending an image message directly → use [[+messages-send]] --image ; it uploads and sends in one step + +### Prerequisites +- a local image file; the returned image_key is what other APIs accept + +### Examples + +**Upload an image for reuse** +```bash +lark-cli im images create --data '{"image_type":"message"}' --file ./picture.png +``` + +## threads forward +Forward an entire thread (topic) to another chat, user, or thread. + +### Avoid when +- Forwarding a single message → use [[messages forward]] +- Reading the thread before forwarding → use [[+threads-messages-list]] + +### Prerequisites +- thread_id (omt_xxx) from [[+threads-messages-list]] or thread fields in [[+chat-messages-list]] output +- receive_id_type matching the target id + +### Examples + +**Forward a thread to a chat** +```bash +lark-cli im threads forward --thread-id --receive-id-type chat_id --data '{"receive_id":""}' +``` + +## chats get +Fetch raw chat metadata by id. + +### Avoid when +- Finding a chat or its id → use [[+chat-search]] (by keyword) or [[+chat-list]] (my chats); reach for this raw call only for fields the shortcuts don't surface + +### Examples + +**Fetch chat metadata** +```bash +lark-cli im chats get --chat-id +``` + +## chats update +Update raw chat settings. + +### Avoid when +- Renaming or changing the description → use [[+chat-update]]; this raw call is for settings the shortcut doesn't cover (permissions, membership approval, etc.) + +### Examples + +**Update chat join permission** +```bash +lark-cli im chats update --chat-id --data '{"join_message_visibility":"only_owner"}' +``` + +## chats create +Create a chat via the raw API. + +### Avoid when +- Normal chat creation → use [[+chat-create]]; it handles member invites, chat mode, and owner in one step + +### Examples + +**Create a bare chat** +```bash +lark-cli im chats create --data '{"name":"project chat"}' +``` + +## chats link +Generate a share link for a chat. + +### Avoid when +- Only need the chat id or basic info → use [[+chat-search]] or [[chats get]] + +### Prerequisites +- chat_id (oc_xxx); link validity is controlled by validity_period in --data + +### Examples + +**Get a chat share link** +```bash +lark-cli im chats link --chat-id --data '{"validity_period":"week"}' +``` diff --git a/internal/affordance/affordance_im_test.go b/internal/affordance/affordance_im_test.go new file mode 100644 index 000000000..698cf515d --- /dev/null +++ b/internal/affordance/affordance_im_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package affordance + +import ( + "encoding/json" + "os" + "testing" +) + +// The 21 im raw-API methods that affordance/im.md must cover: 17 first-batch +// methods plus 4 "prefer the shortcut" entries. Keys follow the parsed heading +// form (spaces become dots), same as TestFor's fixture keys. +var imAffordanceMethods = []string{ + "chat.members.create", "chat.members.delete", "chat.members.get", "chat.members.bots", + "messages.forward", "messages.delete", "messages.merge_forward", "messages.read_users", + "reactions.create", "reactions.delete", "reactions.list", "reactions.batch_query", + "pins.create", "pins.delete", "pins.list", + "images.create", + "threads.forward", + "chats.get", "chats.update", "chats.create", "chats.link", +} + +type parsedAffordance struct { + UseWhen []string `json:"use_when"` + AvoidWhen []string `json:"avoid_when"` + Prerequisites []string `json:"prerequisites"` + Examples []struct { + Command string `json:"command"` + } `json:"examples"` +} + +// TestForIMRealFile parses the real affordance/im.md through the production +// parser and asserts coverage plus depth on the showcase method. +func TestForIMRealFile(t *testing.T) { + prev := mdSource + t.Cleanup(func() { SetSource(prev) }) + SetSource(os.DirFS("../../affordance")) + + for _, m := range imAffordanceMethods { + raw, ok := For("im", m) + if !ok { + t.Errorf("For(\"im\", %q) ok=false, want an overlay section in affordance/im.md", m) + continue + } + var a parsedAffordance + if err := json.Unmarshal(raw, &a); err != nil { + t.Errorf("%s: overlay is not valid affordance JSON: %v", m, err) + continue + } + if len(a.UseWhen) == 0 { + t.Errorf("%s: missing lead paragraph (use_when)", m) + } + if len(a.AvoidWhen) == 0 { + t.Errorf("%s: missing Avoid when section", m) + } + } + + // Showcase depth: messages forward (spec §3.3, mirrors the tech-plan diff). + raw, ok := For("im", "messages.forward") + if !ok { + t.Fatal("messages.forward overlay missing") + } + var fwd parsedAffordance + if err := json.Unmarshal(raw, &fwd); err != nil { + t.Fatalf("messages.forward overlay invalid: %v", err) + } + if len(fwd.AvoidWhen) < 3 { + t.Errorf("messages.forward: want >=3 avoid_when entries, got %d", len(fwd.AvoidWhen)) + } + if len(fwd.Prerequisites) < 2 { + t.Errorf("messages.forward: want >=2 prerequisites, got %d", len(fwd.Prerequisites)) + } + if len(fwd.Examples) < 1 || fwd.Examples[0].Command == "" { + t.Errorf("messages.forward: want >=1 fenced example command") + } +}