An Emacs package that wraps lark-cli to bring Lark/Feishu into Emacs. Transient menus for command dispatch, mu4e-style mail, dired-like drive browsing, org-table spreadsheets, and async execution throughout.
Short screen recordings of each domain in action.
Natural-language prompts drive the agent loop — skills routing, lark-cli tool calls, streamed markdown answers — via a local ACP agent (Claude Code), no API key.
lark-ai.mp4
Fetching a Lark doc as org-mode, images rendered from the persistent cache.
lark-doc.mp4
Group search and message history with inline image and video thumbnails.
lark-im.mp4
mu4e-style inbox and the magit-commit-style compose/reply panel.
lark-mail.mp4
Meeting search with notes and transcript rendering.
lark-meeting.mp4
Task lists and the org-agenda–style due-date view.
lark-task.mp4
- Emacs 29.1+
- lark-cli installed and on your
PATH - org-lark (available on MELPA) — used by the Docs domain to convert Lark Markdown to Org
- shell-maker (available on MELPA, includes
markdown-overlays) — the AI shell interface
npm install -g @larksuite/cliVerify it works:
lark-cli --help(straight-use-package
'(elark :type git :host github :repo "bbw9n/elark"
:files ("*.el" "core" "domain" "ai")))Clone this repo and add it to your load-path:
(add-to-list 'load-path "/path/to/elark")
(require 'elark)
(elark-setup-prefix-key) ;; binds C-c l(use-package elark
:load-path "/path/to/elark"
:config
(elark-setup-prefix-key))Add in packages.el from the source of github repo
(package! elark
:recipe (:host github :repo "bbw9n/elark" :branch "main"
:files ("*.el" "core" "domain" "ai")))Ensure to setup (elark-setup-prefix-key) in your config.el
(use-package! elark
:config
(elark-setup-prefix-key))This section walks you through initial configuration, authentication, and pulling up your calendar — all from within Emacs.
Before elark can talk to Lark, you need to register an app.
Run this in your terminal (or use M-x shell):
lark-cli config initThis is an interactive prompt that asks for:
- Brand: choose
feishu(China) orlark(international) - App ID & App Secret: from the Feishu Open Platform (or Lark Developer Console)
- Go to the console, create an app (or use an existing one)
- Copy the App ID and App Secret
- The CLI stores credentials locally in
~/.config/lark-cli/
If you prefer non-interactive setup:
echo "YOUR_APP_SECRET" | lark-cli config init \
--app-id cli_xxxxxxxxxxxx \
--app-secret-stdin \
--brand feishuVerify:
lark-cli config showNow authenticate your Lark user account. You can do this either from the terminal or from Emacs:
M-x elark-auth-login
Or use the keybinding: C-c l l then press L (Login).
This will:
- Call
lark-cli auth login --no-waitin the background - Open your browser to the Lark authorization page
- Poll automatically until you approve in the browser
- Show “Lark: login successful!” in the echo area
lark-cli auth login --domain calendarThe --domain flag requests scopes for a specific domain. Available
domains: calendar, task, im, mail, contact, docs, drive,
base, sheets, wiki, vc, approval, event, minutes, or all.
You can request multiple domains:
lark-cli auth login --domain calendar,task,imOr request everything:
lark-cli auth login --domain allFrom Emacs:
M-x elark-auth-status
Or: C-c l s
This shows your login status in the echo area. You should see something like “Lark: authenticated as Your Name (your-tenant)”.
From terminal:
lark-cli auth statusNow the fun part:
C-c l a
Or: M-x elark-calendar-agenda
This fetches today’s agenda and displays it in a *Lark Agenda* buffer:
Daily Standup Start: 2026-04-12 09:00 End: 2026-04-12 09:30 Location: Meeting Room A Organizer: Alice RSVP: accept Sprint Planning Start: 2026-04-12 10:00 End: 2026-04-12 11:00 Location: Room 301 Organizer: Bob RSVP: accept 1:1 with Manager Start: 2026-04-12 14:00 End: 2026-04-12 15:00 Organizer: Carol RSVP: tentative
| Key | Action |
|---|---|
RET | Open event detail |
g | Refresh (re-fetch from Lark) |
c | Create a new event |
d | Delete event at point |
y | Copy event ID to kill ring |
? | Open Calendar transient menu |
q | Quit buffer |
C-c l l (opens main dispatch) c (opens Calendar submenu)
Or directly: M-x elark-calendar-dispatch
The Calendar transient menu:
-- Lark Calendar commands -- Quick Actions a Agenda (today) n New event f Free/busy s Suggest time r RSVP Browse l List calendars e List events
| Key | Command | Description |
|---|---|---|
C-c l l | elark-dispatch | Main transient menu |
C-c l a | elark-calendar-agenda | Today’s agenda |
C-c l t | elark-tasks-list | My tasks |
C-c l m | elark-im-dispatch | Messenger menu |
C-c l e | elark-mail-inbox | Mail inbox |
C-c l d | elark-docs-dispatch | Docs menu |
C-c l f | elark-drive-list | Drive root listing |
C-c l k | elark-contacts-dispatch | Contacts menu |
C-c l s | elark-auth-status | Auth status |
C-c l i | elark-ai-ask | Ask AI (natural language) |
C-c l I | elark-ai-chat | AI chat buffer |
C-c l . | elark-ai-act | AI action at point |
The main transient menu provides access to all 12 Lark domains:
| Key | Domain | Key | Domain |
|---|---|---|---|
c | Calendar | s | Sheets |
m | Messenger | e | |
d | Docs | w | Wiki |
f | Drive | k | Contacts |
b | Base | v | Meetings |
t | Tasks | W | Whiteboard |
AI commands: i ask AI, I AI chat, . AI act at point.
AI workflows: 1 standup report, 2 meeting summary, 3 schedule meeting.
Auth: a status, L login, O logout, X identity switch, S schema.
| Command | Description |
|---|---|
elark-calendar-agenda | Show today’s agenda |
elark-calendar-events | List events (optional calendar ID) |
elark-calendar-list | List all calendars |
elark-calendar-create-event | Create event (transient form) |
elark-calendar-freebusy | Check free/busy for a user |
elark-calendar-suggest-time | Get meeting time suggestions |
elark-calendar-rsvp | RSVP to an event |
| Command | Description |
|---|---|
elark-tasks-list | List my tasks |
elark-tasks-create | Create task (transient form) |
elark-tasks-open | Open task detail (at point) |
elark-tasks-complete | Mark task complete (at point) |
elark-tasks-edit | Edit task title (at point) |
elark-tasks-delete | Delete task (at point) |
elark-tasks-subtask-create | Add subtask (at point) |
elark-tasks-tasklists | List task lists |
| Command | Description |
|---|---|
elark-im-chats | List chats |
elark-im-messages | Show message history for a chat |
elark-im-send | Send a message |
elark-im-reply | Reply to a message |
elark-im-react | Add reaction to a message |
elark-im-search | Search messages |
elark-im-group-create | Create group chat (transient form) |
elark-im-subscribe | Start real-time event subscription |
elark-im-unsubscribe | Stop event subscription |
mu4e-style interface with flag indicators and shr-rendered HTML bodies.
| Command | Description |
|---|---|
elark-mail-inbox | Show inbox |
elark-mail-read | Read mail (at point) |
elark-mail-compose | Compose new mail (transient form) |
elark-mail-reply | Reply to mail |
elark-mail-forward | Forward mail |
elark-mail-delete | Delete mail (at point) |
elark-mail-search | Search mail |
elark-mail-drafts | List drafts |
Keybindings in the mail list:
| Key | Action |
|---|---|
RET | Read message |
c | Compose new mail |
r | Reply |
f | Forward |
d | Delete |
/ | Search |
n | Next message |
p | Previous message |
g | Refresh |
| Command | Description |
|---|---|
elark-docs-search | Search documents |
elark-docs-fetch | Fetch document content (URL or token) |
elark-docs-create | Create document (transient form) |
elark-docs-update | Update document (transient form) |
elark-docs-media-download | Download media from a document |
elark-docs-media-insert | Insert media into a document |
elark-docs-media-preview | Preview media |
Dired-like file browser for Lark Drive. Owner names are resolved from contact open_ids automatically.
| Command | Description |
|---|---|
elark-drive-list | List root (or folder by token) |
elark-drive-open | Open item at point (RET) |
elark-drive-view-content | View doc/docx/sheet content (v) |
elark-drive-upload | Upload local file |
elark-drive-download | Download file at point |
elark-drive-mkdir | Create folder (+) |
elark-drive-copy | Copy file (C) |
elark-drive-move | Move/rename file (R) |
elark-drive-import | Import local file to Drive |
elark-drive-export | Export cloud doc to local |
Keybindings in the drive buffer:
| Key | Action |
|---|---|
RET | Open (descend into folder / show detail) |
v | View doc/docx/sheet content |
^ | Go up to parent folder |
w | Copy file token |
W | Copy file URL |
n/p | Next/previous file |
In the file detail buffer, press RET to view the content of
doc/docx/sheet files directly.
Spreadsheet data is displayed as editable org-mode tables.
| Command | Description |
|---|---|
elark-sheets-info | Show spreadsheet info |
elark-sheets-read | Read range as org table |
elark-sheets-write-table | Write org table back to sheet |
elark-sheets-append-table | Append rows from org table |
elark-sheets-create | Create spreadsheet (transient) |
elark-sheets-create-from-table | Create from org table at point |
elark-sheets-find | Find cells by text |
elark-sheets-export | Export as xlsx/csv |
In the sheet info buffer, press RET on a sheet line to read the first
100 rows as an org table.
| Command | Description |
|---|---|
elark-contact-get-user | Look up user by ID |
elark-contact-search | Search contacts by keyword |
elark-contact-resolve-name | Resolve open_id to name (cached) |
| Command | Description |
|---|---|
elark-meetings-search | Search meeting records |
elark-meetings-open | Open meeting detail (auto-fetches recording) |
elark-meetings-notes | Fetch meeting notes (summary, todos, chapters) |
elark-meetings-recording | Query recording for a meeting |
elark-meetings-download-recording | Download recording by minute token |
Meeting detail pages automatically check for recordings and show a clickable download link when available.
elark includes an AI-native orchestration layer that turns natural-language prompts into multi-step lark-cli execution plans. Instead of remembering which commands to chain together, you describe what you want and the AI figures out the steps.
- You type a prompt (e.g., “what’s on my plate today?”)
- elark selects the relevant lark-cli skill files (
SKILL.md) based on keywords in your prompt - The skills + your prompt are sent to an LLM, which returns a JSON execution plan
- The plan is shown in a review buffer — you can inspect, remove steps, or cancel
- On confirmation, elark executes the CLI commands (in parallel where possible), collecting results
- If the plan includes a synthesis step, results are sent back to the LLM for a human-readable summary
- The final output is rendered in a buffer
Write operations (create, update, delete, send) are flagged in the plan and require explicit confirmation before execution.
- An LLM backend: a local ACP agent such as Claude Code or Gemini CLI via acp.el (recommended, the default — no API key), gptel, or any OpenAI-compatible HTTP endpoint
- lark-cli skills installed in
~/.agent/skills/:
npx skills add larksuite/cli -g -yThe default backend drives a locally installed Agent Client Protocol agent — the same mechanism agent-shell uses — so the agent’s own login is reused and no API key is needed. Requires the acp.el package and an ACP agent binary:
npm install -g @zed-industries/claude-code-acp # Claude Code adapter;; Nothing to configure: `elark-ai-backend' defaults to `acp' and the
;; default agent is claude-code-acp.
;; To use Gemini CLI instead:
(setq elark-ai-acp-command "gemini"
elark-ai-acp-command-params '("--experimental-acp")
;; Gemini ignores the session _meta systemPrompt extension, so
;; send elark's system prompt inline with the user message:
elark-ai-acp-system-prompt-placement 'inline)The agent process is started on first use and kept alive;
M-x elark-ai-acp-shutdown kills it. Each LLM call runs in a fresh
ACP session, and the agent’s own tools are disabled — it acts purely
as the text engine behind elark’s agent loop.
(use-package gptel
:config
;; configure your preferred provider
(setq gptel-model 'claude-sonnet-4-20250514))
(setq elark-ai-backend 'gptel)(setq elark-ai-backend 'http
elark-ai-http-endpoint "https://api.openai.com/v1/chat/completions"
elark-ai-http-api-key "sk-...")Or set the ELARK_AI_API_KEY environment variable instead of hardcoding the key.
| Key | Command | Description |
|---|---|---|
C-c l i | elark-ai-ask | Natural-language prompt |
C-c l I | elark-ai-chat | Conversational AI buffer |
C-c l . | elark-ai-act | Context-aware action at point |
These are also available from the main dispatch (C-c l l) under the
“AI” and “AI Workflows” sections.
C-c l i "what's on my plate today?"
The AI generates a plan:
Lark AI -- Execution Plan
----------------------------------------
[0] Fetch today's agenda (read-only) [group 1]
lark-cli calendar +agenda
[1] Fetch open tasks (read-only) [group 1]
lark-cli task +get-my-tasks
[2] Summarize schedule and tasks (AI synthesis)
----------------------------------------
RET Execute x Remove step q Cancel
Steps 0 and 1 run in parallel (same group). After execution, the LLM synthesizes results into:
## 2026-04-13 Sunday ### Agenda | Time | Event | Organizer | Status | |-------------+--------------------+-----------+----------| | 09:00-09:30 | Daily Standup | Alice | accepted | | 14:00-15:00 | 1:1 with Manager | Carol | tentative| ### Open Tasks - [ ] Review PR #142 (due: today) - [ ] Update API docs (due: Apr 14) ### Notes - No scheduling conflicts - Free slots: 10:00-14:00, 15:00-18:00
Or use the dedicated command: M-x elark-ai-workflow-standup.
C-c l i "summarise my meetings this week"
The AI chains vc +search -> vc +notes -> synthesis, producing a
structured report of all meetings with their notes and action items.
Or: M-x elark-ai-workflow-meeting-summary.
C-c l i "set up a 30min sync with Alice next Tuesday afternoon"
The AI plan:
[0] Search contact "Alice" (read-only) [1] Find available time slots (read-only) [2] Check room availability (read-only) [3] Create calendar event CAUTION writes
Step 3 is marked as a side-effect and will prompt for confirmation before executing.
Or: M-x elark-ai-workflow-schedule.
When you’re in a Lark buffer (agenda, chat, task list, etc.), press
C-c l . to perform an AI action on the item at point. The AI
automatically knows the domain and which item you’re looking at.
For example, in the *Lark Agenda* buffer with point on an event:
C-c l . "reschedule to tomorrow at 3pm"
C-c l I opens a persistent AI chat buffer where you can ask follow-up
questions. Type your message and press C-c C-c to send.
You: how many meetings do I have tomorrow? ---------------------------------------- AI: Generated a plan. Review it in the plan buffer. (after execution) AI: You have 3 meetings tomorrow: - 09:00-09:30 Daily Standup - 11:00-12:00 Design Review - 15:00-16:00 Sprint Retro
| Command | Description |
|---|---|
elark-ai-workflow-standup | Today’s agenda + open tasks |
elark-ai-workflow-mee
9283
ting-summary | Summarise recent meetings |
elark-ai-workflow-schedule | Schedule a meeting with AI help |
These are shortcuts for common multi-step operations. They also appear
in the dispatch menu under “AI Workflows” (keys 1, 2, 3).
All options are under M-x customize-group RET elark.
| Variable | Default | Description |
|---|---|---|
elark-cli-executable | "lark-cli" | Path to the lark-cli binary |
elark-default-format | "json" | Output format (json/table/csv/pretty) |
elark-default-identity | "user" | Identity type (user/bot) |
elark-dry-run | nil | Pass –dry-run to destructive commands |
elark-im-message-limit | 50 | Messages to fetch in chat history |
elark-mail-page-size | 20 | Emails per page |
elark-calendar-default-days | 1 | Days in agenda view |
elark-ai-backend | acp | LLM backend (acp, gptel, or http) |
elark-ai-model | nil | Model ID (nil uses backend default) |
elark-ai-skills-directory | ~/.agents/skills/ | Path to lark-cli SKILL.md files |
elark-ai-skill-routing | llm | Skill picker: llm (ask the model) or keyword |
elark-ai-skills-log-lines | 10 | Top N lines of each skill shown in the debug log (nil = full) |
elark-ai-step-timeout | 1800 | Seconds before a plan step’s CLI call is killed (nil = none) |
elark-ai-execute-mode | confirm-writes | Plan execution: auto / confirm-writes / review |
elark-ai-auto-execute-commands | ("docs +create" "docs +update") | Write cmds that skip the confirm prompt |
elark-ai-http-endpoint | nil | URL for the http backend |
elark-ai-http-api-key | nil | API key (or set ELARK_AI_API_KEY) |
elark-ai-acp-command | "claude-code-acp" | ACP agent binary for the acp backend |
elark-ai-acp-command-params | nil | Extra args for the ACP agent (e.g. --experimental-acp) |
elark-ai-acp-cwd | "~" | Working directory for ACP sessions |
elark-ai-acp-system-prompt-placement | session | System prompt via session meta (session) or inline text (inline) |
lark-cli is not on your PATH. Either:
- Install it:
npm install -g @larksuite/cli - Set the full path:
(setq elark-cli-executable "/path/to/lark-cli")
Run M-x elark-auth-login or authenticate from terminal:
lark-cli auth login --domain all- Check you have calendar scopes:
lark-cli auth check --scope calendar:readonly - Try from terminal first:
lark-cli calendar +agenda - If the terminal works but Emacs doesn’t, enable debug logging:
(setq elark--debug t)and check the*elark-log*buffer
Some operations require specific scopes. Re-login with the needed domain:
lark-cli auth login --domain calendar,taskEnable debug logging to see all CLI invocations:
(setq elark--debug t)Then check *elark-log* buffer (C-x b *elark-log*) to see every command
that elark runs, including arguments and exit codes.
elark.el Entry point, defgroup, prefix keymap, load-path setup
core/
elark-core.el Process management, JSON parsing, CLI debug line stripping
elark-auth.el OAuth device-code login, status, identity switching
elark-transient.el Top-level transient dispatch menu
ui/
elark-ui.el Shared presentation helpers (loading spinner)
domain/
elark-calendar.el Calendar: agenda, events, create, free/busy, RSVP
elark-tasks.el Tasks: list, create, complete, subtasks, tasklists
elark-im.el Messenger: chats, messages, send, reply, events
elark-mail.el Mail: mu4e-style inbox, shr HTML rendering, compose
elark-docs.el Docs: search, fetch, create, update, media operations
elark-drive.el Drive: dired-like browser, upload/download, owner names
elark-sheets.el Sheets: org-table read/write, create, find, export
elark-contact.el Contacts: user lookup, search, name resolution cache
elark-meetings.el Meetings: search, notes, recording with download links
elark-whiteboard.el Whiteboard: create and update whiteboards
ai/
elark-ai.el AI: prompt -> plan -> execute -> synthesize -> present
elark-ai-skills.el AI: skill registry, loader, system prompt assembly
elark-ai-context.el AI: buffer context extraction for all domain modes
elark-ai-llm.el AI: LLM backend dispatch (gptel / acp / http)
elark-ai-acp.el AI: ACP agent backend (Claude Code, Gemini CLI, ... via acp.el)
test/
elark-*-test.el ERT tests for each module
demo/
elark-native-ui-demo.el Runnable magit-section / tabulated-list UI preview
elark-*.mp4 Domain screen-recording sources (hosted copies
are embedded in the Demos section)
All CLI calls funnel through elark--run-command (async) or
elark--run-command-sync. Output is --format json by default, parsed
with json-parse-string. CLI debug lines ([cmd +sub] prefixes and
tip lines) are automatically stripped before JSON parsing.
elark.el automatically adds core/, ui/, domain/, and ai/ to the
load-path at load time, so a single (add-to-list 'load-path "/path/to/elark")
is sufficient.