Skip to content

Feature expansion: raw API passthrough + 10 new command groups, full CRUD, WebSocket events - #22

Open
seahyc wants to merge 90 commits into
yjwong:mainfrom
seahyc:main
Open

Feature expansion: raw API passthrough + 10 new command groups, full CRUD, WebSocket events#22
seahyc wants to merge 90 commits into
yjwong:mainfrom
seahyc:main

Conversation

@seahyc

@seahyc seahyc commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Major expansion of the lark-cli with 20 top-level commands (up from 12), covering the full Lark Open Platform surface.

New command groups

  • lark api — Raw API passthrough for all 2500+ Lark endpoints (GET/POST/PUT/PATCH/DELETE + discovery via lark api list)
  • lark approval — Approval workflows (list/get/approve/reject/transfer/cancel/cc)
  • lark attendance — Clock-in/attendance records
  • lark events — Real-time WebSocket event subscription (NDJSON streaming with type/regex filters)
  • lark meetings — Video conferencing (search, notes, transcript, recording)
  • lark schema — API schema introspection via llms-docs
  • lark slides — Presentation management (create/get/slide create-delete/media upload)
  • lark wiki — Wiki space and node management (spaces/create/move/delete/get/children)

Expanded existing commands

  • lark task — Full CRUD: create, update, complete, reopen, assign, subtasks, comments, reminders, tasklists
  • lark bitable — Full CRUD: table/field/record management, batch ops, search
  • lark chat — Group management: create, update, delete, members add/remove, pin/unpin, dm lookup, list-dms
  • lark msg — Search across chats, batch get, forward, merge-forward, edit/recall with --as user

Infrastructure

  • --as user support across all message/chat commands (send, history, recall as your identity, not bot)
  • --format global flag: pretty (default), json, ndjson, table, csv
  • Auto port cleanup on auth login retry (no more manual kill on port 9999)
  • P2P DM discovery workaround (chat dm, chat list-dms) since Lark's API doesn't expose P2P chat lists
  • 11 OAuth scope groups (up from 8): added approval, slides, attendance
  • 25 unit tests for core helpers (parseTimeArg, scopes, path normalization)
  • 12 Claude Code skill files (symlinked) with full command documentation

Lark API limitations documented

  • msg edit and msg forward are bot-only (tenant token required by Lark)
  • chat create requires bot identity (im:chat:create is tenant-scope only)
  • Approval scope names (approval:approval) may need adjustment per tenant

Test plan

  • go build ./cmd/lark/ builds cleanly
  • go test ./... -short — 25 tests pass
  • lark api list returns Lark API module index
  • lark task list --format table renders ASCII table
  • lark msg search "test" --limit 3 returns search results
  • lark chat dm "Francis Goh" returns open_id + P2P chat_id
  • lark chat list-dms enumerates recent DMs
  • lark wiki spaces returns wiki space list
  • lark approval --help / lark slides --help / lark attendance --help show subcommands
  • lark events --help shows subscribe subcommand
  • lark schema modules --format table renders module list

seahyc and others added 8 commits March 2, 2026 11:19
- lark sheet style: apply bold formatting to a cell range
- lark sheet resize: set pixel widths per column (JSON map or --all/--cols)
- lark sheet add-tab: add a new tab to an existing spreadsheet

Also refactors the repeated first-sheet resolution logic into a shared
resolveSheetID helper, used by read, write, style, and resize commands.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds task list, create, complete, and delete commands for Lark Tasks v2 API.
Includes tasks scope group and skill documentation.
… flags

- sheets: add write, create, add-tab, style, resize command docs
- documents: remove duplicate sheets section, add doc image command
- calendar: add missing --no-notify flag to cal update
- contacts: add missing --page-size flag to list-dept
Add `lark doc upload` for uploading files to Lark Drive via
POST /drive/v1/files/upload_all (multipart, 20MB limit).
Add drive:drive write scope to documents scope group.
- sheet write --auto-type: auto-convert date strings (YYYY-MM-DD) to
  Excel serial numbers and numeric strings to numbers
- sheet style --format: apply number/date format to cell ranges
  (e.g., "yyyy-MM-dd", "#,##0", "0.00%")
- doc info: get file/folder metadata (owner, timestamps, URL)
- doc mkdir: create folders in Lark Drive
- doc list: now includes created_time, modified_time, owner_id
- Add development workflow to CLAUDE.md
- Update skills for all new capabilities
# Conflicts:
#	internal/cmd/sheet.go
#	skills/sheets/SKILL.md
- Add --link flag to doc append and doc update for creating hyperlinks
- Add doc trash command to move files to Drive trash
- Add doc delete command for removing blocks by ID
- Add doc update command for modifying existing block content
- Add DeleteDriveFile and DeleteWithBody API methods
- Update documents skill with new commands and auth docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Lark PATCH block API has strict format requirements and may return
"invalid param" errors. Document the delete + append workaround.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@seahyc seahyc changed the title feat: add tasks, drive upload, sheet ops, and skill updates feat: add doc block ops, drive upload/trash, sheet styles, tasks support Mar 2, 2026
seahyc and others added 6 commits March 2, 2026 19:17
- Add BaseResponse.Err() method, replacing 61 inline error checks across 14 API files
- Add PaginateWith[T] generic helper, replacing 8 pagination loops
- Add ClampPageSize helper, replacing 7 page size clamping patterns
- Add config.LoadTimezone() helper, replacing 4 duplicate timezone loading blocks
- Extract buildBlocks/setHeadingField in doc.go, deduplicating append/update commands

Net reduction of ~140 lines while improving consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…delete

- Add `doc outline` command showing heading structure with block IDs
- Add `doc move` command to relocate blocks within a document
- Add `--after <block-id>` flag to `doc append` for stable positioning
- Fix `doc update` for all block types (use update_text_elements field)
- Fix `--index 0` not prepending (remove omitempty from JSON tag)
- Fix bulk delete failing when deleting parent+child blocks together

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…trol

Default render mode is 'value' (computed values via valueRenderOption=ToString).
Options: value (default), formula (raw =formulas), formatted (display strings).
…ills

Add Lark API error code table (99991663, 99991664, 99991400, etc.) with
exact remediation steps. Agent now tells users who needs to act and what
to do instead of fumbling with workarounds like CSV export or copy-paste.
Agent now checks auth status first to distinguish tool-missing vs
not-authenticated vs no-file-access before attempting any command.
@seahyc seahyc changed the title feat: add doc block ops, drive upload/trash, sheet styles, tasks support feat: doc/sheet enhancements, tasks support, and agent error handling Mar 4, 2026
seahyc and others added 14 commits March 5, 2026 17:10
- Add `lark msg edit` command (PUT /im/v1/messages/:id) for in-place
  message editing instead of recall+resend
- Replace fragile strconv.Unquote in unescapeString with manual
  replacement for \n, \t, \r, \\ - the old approach silently failed
  on the entire string if any unrecognized escape was present (e.g.
  fish shell's \!)
- Strip fish shell's \! escape injection
- Auto-upgrade --msg-type text to post when mentions (@{), bold (**)
  or links ([) are detected, preventing broken formatting
- Add PatchWithTenantToken and PutWithTenantToken to API client

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Table creation now automatically fills cells with header and row
content via follow-up API calls, instead of requiring manual
per-cell append commands. Includes rate limiting (200ms delay)
and retry logic for API reliability.
- Table --table-header/--table-row now auto-fills cell content after
  creation instead of leaving cells empty
- Add move and outline commands to SKILL.md
- Add --after flag, callout/table-cell block types, and markdown/table
  entries to "Which Command to Use" table
- Change GetUser from tenant_access_token to user_access_token (fixes
  API error 41050 "no user authority error")
- Add Name field to OutputMessageSender
- Resolve sender names via GetUser in msg history with deduplication
  cache (one API call per unique sender, not per message)
Adds --file flag to lark msg send for sending file messages (PDF, PPTX,
DOCX, XLSX, MP4, etc.) via the Lark IM file upload API (/im/v1/files).
File type is inferred from extension; falls back to stream for unknown
types. Multiple files send as separate messages. Cannot be combined with
--text or --image. Updates skill docs with usage and 30MB size limit.
…ttedValue

sheet write: auto-calculate end range from values dimensions instead of
bare "A1" which Lark v2 API rejects. Convert bool values to 1/0 since
Lark API doesn't accept bool cell type.

sheet read: default render option to UnformattedValue for raw numeric
values instead of ToString which loses precision.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Try common types (docx, sheet, bitable, folder, file, doc, mindnote,
slides, wiki) in order instead of defaulting to "file" which fails for
most document tokens.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add --include-bodies flag to sync command for fetching full email bodies
during sync. Improve cache schema and search to support body content.
Update email skill docs with new capabilities.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
seahyc added 9 commits April 16, 2026 15:16
- chat dm scans recent messages to find the P2P chat_id with the target user,
  not just the open_id (Lark's API has no direct user→P2P lookup endpoint)
- chat list-dms enumerates recent DMs by scanning message history,
  filtering on is_p2p_chat=true and deduping by chat_id (Lark's /im/v1/chats
  endpoint only returns group chats, never P2P)
- Workaround for Lark's missing P2P chat list/lookup APIs
Replace the long-polling stub in SubscribeEvents with a real implementation
that speaks Lark's long-connection event protocol (pbbp2 binary frames over
wss://). The endpoint URL is fetched via POST /callback/ws/endpoint using the
configured AppID/AppSecret; heartbeats, multi-frame message reassembly, and
exponential-backoff reconnect (max 60s) are handled inline.

The Frame/Header protobuf types are vendored from
github.com/larksuite/oapi-sdk-go/v3/ws/pbbp2.pb.go so no runtime dependency on
the full SDK is required — only gorilla/websocket and gogo/protobuf.

Events are emitted as-is (one JSON object per line). Filters (--type, --match)
are applied to the raw payload; --compact remains accepted for back-compat but
is a no-op since frames already arrive compact.
New top-level 'lark approval' command wraps the /approval/v4/ API:
- list: query the current user's pending approval tasks
- get <instance-code>: fetch full instance detail (form, timeline, tasks)
- approve|reject <task-id>: act on a pending task (with optional --comment)
- transfer <task-id> --to <user>: reassign a task to another approver
- cancel <instance-code>: cancel an instance the user started
- cc <instance-code> --to <user,user>: cc additional users

Adds 'approval' scope group (approval:approval, approval:instance:write,
approval:task:write) and registers the command in root.go. Includes the
skills/lark-approval/SKILL.md guide.
New top-level 'lark slides' command wraps the /slides/v1/ API:
- create: new empty presentation with optional --folder
- get <id>: fetch XML presentation metadata
- slide create <id>: append a slide via --xml or --xml-file (supports stdin)
- slide delete <id> --slide-id: remove a slide
- media upload <file>: upload media to Drive, returns file_token for
  referencing in slide XML

Adds 'slides' scope group covering slides:presentation:* and
docs:document.media:upload + drive:drive for uploads. Registers the
command in root.go. Includes skills/lark-slides/SKILL.md guide.
New persistent --format flag on the root command, validated in
PersistentPreRun. output.JSON() switches on output.Format at emit time
so every subcommand picks it up automatically.

Formats:
- pretty (default) / json: indented JSON
- ndjson: compact one-line JSON (good for streaming and jq pipelines)
- table: ASCII table; detects array-of-objects, or a single object
  containing exactly one array-valued field, and renders that as rows
- csv: same detection as table, emits RFC 4180 CSV

Falls back to pretty JSON when the data shape can't be tabulated.
Errors always render as pretty JSON regardless of --format so tooling
gets a stable error envelope.
Introspects any Lark API path or dotted name by fetching the relevant
llms-docs file from open.larksuite.com and printing the matching section(s).

- 'lark schema <path-or-name>' — fetch + grep + print section
- 'lark schema modules' — list supported modules

Module map covers messaging, docs, calendar, meetings, approval,
attendance, tasks, mail, bot, bitable, contacts, feed, and group-chat.
Section matching splits on '##' headings so callers get the full
reference block for the matched endpoint.
New 'lark attendance list' fetches user_task records from
/attendance/v1/user_tasks/query. Defaults to the caller (resolved via
/authen/v1/user_info) and the last 7 days; --user, --after, --before,
--overtime cover targeted lookups.

Adds the 'attendance' scope group (attendance:task[:readonly]) and
registers the command in root. Includes a skill file with usage and
error-handling reference.
Extracts three pure helpers to make them testable and covers them with
25 table-driven tests:

- api.NormalizeAPIPath (new): handles /open-apis prefix collapse,
  relative path append, and absolute URL passthrough. raw.go now
  delegates to it, so the doubled-prefix regression is caught by a test.
- cmd.classifyMemberIdent (new): shape check extracted from
  resolveMembers (ou_* vs @ vs name).
- cmd.parseTimeArgE (new): errorable twin of parseTimeArg that covers
  unix, RFC3339, ISO-without-tz, date-only, and invalid inputs.

Also tests api.InferModule / searchNeedles / extractSections for the
schema command, and scopes.GetScopesForGroups / ParseGroups /
GetGroupForCommand / AllGroupNames.
Lark's docs sometimes list approval:instance:read / approval:task:read
instead of approval:approval[:readonly]. Add a pointer so the next caller
who sees SCOPE_ERROR knows where to adjust scopes.go.
@seahyc seahyc changed the title feat: comprehensive CLI enhancements — docs, sheets, messages, mail, tasks, drive Feature expansion: raw API passthrough + 10 new command groups, full CRUD, WebSocket events Apr 17, 2026
seahyc and others added 20 commits May 13, 2026 10:42
One command does resolve person -> fetch recent history -> optional reply.
Adds --compact mode that returns a parsed plain-text transcript with role
(me/them), sender name, and message text, optimized for agent consumption.
findP2PChatIDForUser matches the first P2P chat where FromID equals the
target open_id. When the target is the authenticated user, every DM they
sent matches, so the command would route the reply into an arbitrary
recent counterpart's chat. Fail fast with VALIDATION_ERROR instead.
Four ergonomic gaps that broke bulk-reorder of a Lark doc, all from
one real-world failure (reordering ~30 blocks across a single doc):

1. doc move now returns the NEW block_id, not the input. Lark's "move"
   is delete+recreate so the moved block always gets a fresh id;
   chaining `doc move ... --after $just-moved` always failed before
   because the saved id was stale. OutputDocumentMove gains
   `old_block_id` for callers that still want the original.

2. doc move now preserves table content. Previously the block-clone
   path copied a fixed list of leaf field pointers (Heading1..9,
   Bullet, Code, Quote, Image, etc.) and silently dropped Table +
   Children, so moving a table re-inserted an empty same-typed block
   with zero rows. Now the move detects table source blocks, extracts
   each cell's text before delete, and re-populates the cells of the
   freshly-created table after insert. Same path used for Callout.

3. New `doc move-range <doc> <start-block-id> <end-block-id> --after
   <target>` for bulk reordering of a contiguous slice of root
   children. Internally moves blocks one-by-one re-anchoring on each
   freshly-returned id, so a multi-block move is one command instead
   of N+ stale-id error chains.

4. doc append --markdown now auto-chunks markdown tables larger than
   6 rows into multiple consecutive tables (each repeating the header).
   Appending a 14-row table previously failed with an opaque
   `invalid param` from Lark's API; the chunked output renders
   indistinguishably to the reader and works reliably.

Cross-cutting: the HTTP client now retries Lark's rate-limit error
(error_code 99991663 / `99991400` / HTTP 429) on POST/PUT/PATCH/DELETE
with exponential backoff (1s, 2s, 4s, 8s, 16s; 5 attempts). Every
existing write call benefits — large doc rebuilds no longer trip the
rate limiter.

Verified end-to-end against the live Lark API on a throwaway doc:
all four bugs reproduce-and-fix demonstrated, plus a 20-call rapid
loop completes with zero failures.
- Add contact:user.email:readonly to the contacts scope bundle so
  lark contact get returns email (was previously gated by tenant
  admin policy, scope confirmed via Lark dev console).
- lark msg resource: infer --type from file-key prefix (img_*/file_*)
  so callers don't need to know whether something is image vs file
  vs video vs audio. --type is now an optional override.
- Sync lark-messages and lark-contacts skills to current CLI shape:
  document chat list-dms, chat delete, chat create --to shortcut;
  drop stale fields (job_title/department) from contact get example;
  note Glints tenant restrictions on list-dept.
- USAGE.md app-permission checklist: add the new email scope.
- msg history Long help previously said "default reads as bot" while
  the flag default has been "user" for a while; rewrite the prose to
  match the actual default.
- skill: split the upload cap (30MB files, 10MB images per Lark API)
  and cite the 234037 error code for the 100MB download cap so the
  reader can attribute limits to Lark rather than the CLI.
Lark's IMAP server occasionally returns 0 from SELECT in some
auth/idle states even when the server holds messages, causing sync
to silently no-op for days. Always go through GetAllUIDs() — a truly
empty mailbox returns an empty list and hits the up-to-date branch
cleanly.
Default to user identity. P2P chat resources require user token
since the bot isn't a chat member.
nameResolver batches+caches open_id->display-name lookups within a run,
falling back to the open_id when the contacts lookup fails or is empty.

DMCache persists a person->chat_id map in the config dir (dm_cache.json).
Lark exposes no read-only way to get a 1:1 chat_id from an open_id, so once
one becomes known (send response / history read / list-dms) we remember it,
letting later reads skip the send-to-discover step that would notify the
counterpart.
Decodes Lark message content deterministically into one clean plain-text
block per message so an agent can read a conversation without writing a
recursive JSON decoder:

  text -> text, a -> label (url), at -> @name (resolved), img/media/file ->
  [image|video|file <key>], post -> title+paragraphs, interactive -> [card]
  <title/summary>, recalled -> [recalled].

Senders and @-mentions resolve to display names; replies are marked with a
leading marker + indent and thread messages carry a thread:<short> tag.
Adds output.FormatText; pretty/json/ndjson/table/csv are unchanged.
Lark's search API silently ignores its from_ids/chat_ids parameters and
returns global results, so the CLI now matches meta_data.from_id /
meta_data.chat_id after fetching and prints a one-line stderr note when it
does. Scans up to 50 pages to fill --limit against the global stream, and
sets client_side_filtered in the JSON output. Also renders search hits as a
compact transcript under --format text.
…commands

- msg history / dm / msg search emit the transcript under --format text and
  resolve sender + mention names via the shared resolver.
- dm / chat dm consult the persistent DM cache before the recent-activity
  scan, cache any chat_id they learn (send response, history, list-dms), and
  return an actionable open_id + send hint when one is still unknown — never
  sending implicitly.
- Enable cobra.EnableTraverseRunHooks so the root --format hook is no longer
  shadowed by each group's scope-validation PersistentPreRun. Without this,
  output.Format stayed "pretty" for every grouped subcommand, so --format
  json/ndjson/text were silently dropped on e.g. msg history. Guarded by a
  regression test.
- SKILL.md documents the transcript format, the cache-backed DM read, and the
  client-side search-filter note.
Agent-readable UX: transcript format, DM cache, client-side search filters, name resolution
`lark msg send --file` sent every attachment as msg_type=file. Lark
rejects that for video/audio with code 230055 and stores them as
"nonsupport" ("This message type is currently not supported").

Map the upload file_type to the msg_type Lark expects:
  mp4/mov/m4v -> media (video)   opus -> audio (voice)   else -> file

Video also needs:
  - a cover image_key, or the message is still stored as nonsupport.
    Use --cover if given, else generate a plain placeholder (Lark draws
    its own play button over it).
  - its duration in the upload's `duration` form field, or the client
    renders 00:00. Parse it from the mp4 moov/mvhd box (pure Go).

Adds the --cover flag and threads durationMs through UploadFile(AsUser).
Adds 'lark mail filter' (list/create/delete) and 'lark mail folder'
(list/create) wrapping the mail-v1 user_mailbox rule and folder
endpoints. Filter create matches by sender — full addresses match
exactly (operator: is), @Domain entries match by contains — and moves
matched mail to a folder. The Open API has no auto-forward action, so
move-to-folder is the supported primitive; fan-out to a chat is left to
a separate folder-watching forwarder.

New 'mailrules' scope group (mail:user_mailbox.rule:write,
mail:user_mailbox.folder:write) kept separate from the IMAP 'mail' group
so read-only IMAP usage is unaffected.
The --add flag was defined but never used: login requested only the
named scope groups and relied on Lark's incremental authorization to
preserve existing grants, which unreliably dropped groups (observed
dropping the messages/im group when adding mailrules). Now --add
explicitly requests the union of currently-granted groups and the new
ones, so no permission is silently lost.
filter create now supports forward actions in addition to move-to-folder:
--forward-email (action type 12) and --forward-chat (type 13), both
repeatable and combinable with --folder. At least one action is now
required and --folder is optional. Although the published API docs mark
forwarding unsupported, the rule engine honours types 12/13.

Also adds 'mail folder delete' and documents the filter/folder commands
and action-type map in the lark-email skill.
lark auth login --manual: browser-free login for headless hosts. Prints the
consent URL, human approves on any device and pastes the localhost redirect
URL (or bare code) back on stdin; reuses the registered localhost:PORT/callback
redirect_uri and verifies OAuth state. No xdg-open, no reachable callback needed.

lark auth refresh: non-interactive access-token refresh for a keep-alive timer,
so Lark's short refresh-token window never lapses into a full re-login.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant