Extend +add-comment to accept sheet URLs and wiki URLs that resolve
to sheets. Reuse --block-id with <sheetId>!<cell> format (e.g.
a281f9!D6) for sheet cell positioning.
Wiki links resolving to sheet type are handled by first calling
get_node, then redirecting to the sheet comment path with proper
parameter validation.
* feat(doc): add --file-view flag to +media-insert for file block rendering
The docx File block supports three render modes via view_type
(1=card, 2=preview inline player, 3=inline), but --type=file today
always creates with the default card view. Because view_type can only
be set at creation time (PATCH replace_file ignores it), callers
wanting an inline audio/video player had to abandon the shortcut and
reimplement the full 4-step orchestration manually.
Add --file-view card|preview|inline that threads into file.view_type
on block creation. Omitting the flag preserves the exact request body
that the shortcut sends today, so existing users are unaffected.
--file-view is rejected when combined with --type=image (images have
their own rendering) and when an unknown value is passed.
* refactor(doc): narrow view_type gate and relax file-view test
Address review feedback from automated reviewers on #419:
- Replace `fileViewType > 0` with an explicit 1|2|3 whitelist inside
buildCreateBlockData so a stray positive int cannot escape into the
request payload if a future caller bypasses Validate.
- Relax TestFileViewMapCoversDocumentedValues to assert only the
documented keys rather than full-map equality, so future aliases
(e.g. a "player" synonym for preview) do not falsely break the test.
No behaviour change for any existing --file-view input.
* test(doc): cover --file-view Validate contract and explicit card path
Pins down the two CLI guard branches (unknown --file-view value and
--file-view passed with --type!=file) that were previously only covered
indirectly through buildCreateBlockData. Also adds the --file-view card
case so the explicit view_type=1 payload (different from the legacy
file: {} shape when the flag is omitted) is locked in as part of the
public flag contract.
* fix: repair unit tests
Change-Id: I8c6bb69bfa22c9455a2cbb0f46b401e2cbe87762
---------
Co-authored-by: Nick Zhang <nickzhangcomes@users.noreply.github.com>
Co-authored-by: wangweiming <wangweiming@bytedance.com>
* feat(base): auto grant current user for bot create and copy
* fix(base): declare auto-grant permission scope
* Apply suggestion from @kongenpei
Co-authored-by: kongenpei <kongenpei.jojo@bytedance.com>
* Apply suggestion from @kongenpei
Co-authored-by: kongenpei <kongenpei.jojo@bytedance.com>
* style(base): format auth-specific scope declarations
* fix(base): use bitable permission target for auto-grant
---------
Co-authored-by: kongenpei <kongenpei.jojo@bytedance.com>
mediaBuffer.FileName() returned a hardcoded "media"+ext, so IM file
messages sent via URL displayed generic names like "media.pdf" instead
of the filename parsed from the URL. This regressed the pre-refactor
tempfile path which at least carried a unique basename.
Store fileNameFromURL(rawURL) on the buffer and return it from
FileName(). Split newMediaBuffer so the URL-to-filename wiring is
reachable from tests without going through the hardened download
transport.
Also lock in that the local upload branch keeps filepath.Base(filePath)
as file_name, so the URL fix cannot silently regress the local branch
later.
Change-Id: I729b217e9dc9237aeb89c2b89df86a37ad64a840
The /open-apis/im/v1/images and /open-apis/im/v1/files APIs now support User Access Token (UAT) in addition to Tenant Access Token (TAT). Previously the upload helpers forced bot identity unconditionally; this PR aligns them with the surrounding shortcut's --as flag so uploads and sends share the same identity.
Change-Id: I3d7fd528dd30fef9aea2d88100ceb03db4c7c3ac
* feat(mail): add signature foundation, draft exports, and +signature shortcut
- Add signature data model, API provider, and template variable
interpolation with tests (shortcuts/mail/signature/)
- Export signature-related symbols from draft package (SignatureWrapperClass,
BuildSignatureHTML, FindMatchingCloseDiv, SplitAtQuote, RemoveSignatureHTML,
SignatureSpacing, SignatureImage) for use by compose shortcuts
- Add +signature shortcut for listing and viewing email signatures
- Add signature reference documentation
Change-Id: I62525e7b475692ada9ec8590b6d0252cf5afcdbc
Co-Authored-By: AI
* feat(mail): add --signature-id to all compose shortcuts
- Add --signature-id flag to +draft-create, +send, +reply, +reply-all,
+forward for inserting a signature into the email body
- Add signature image download with SSRF protection (https enforcement,
no token leak, context timeout, size limit)
- Add signature HTML insertion with quote-aware placement
- Update compose shortcut reference docs
Change-Id: Ic5606bab7826a20364084898ad1714778e5a8bd0
Co-Authored-By: AI
* feat(mail): add signature insert/remove ops for +draft-edit
- Add insert_signature and remove_signature patch operations with
old-signature MIME cleanup and case-insensitive CID matching
- Expose signature ops in supported_ops flat list
- Update SKILL.md and draft-edit reference docs
Change-Id: I74affbf555e32351520f610ef42195f399a265d9
Co-Authored-By: AI
* test(mail): add unit tests for signature patch operations
Test insert_signature and remove_signature ops:
- Insert into basic HTML body
- Insert before quote block (reply/forward)
- Replace existing signature
- Error on plain-text-only draft
- Remove existing signature
- Error when no signature present
Change-Id: Icd713552b130d6eb461ef1cabca61e82327f4f0b
Co-Authored-By: AI
* fix(mail): address reviewer findings on signature PR
- Remove --device flag and device field from docs (not exposed in CLI)
- Fix signature interpolation to match --from alias address in send_as
list, instead of always using the primary mailbox address
- Update lark-mail-signature.md reference doc
Change-Id: I65f41a029cd33b17785e2355a99d042063962d23
Co-Authored-By: AI
* fix(mail): resolve lint issues — remove unused code, fix gofmt
- Remove unused cidSrcRe, collectSignatureCIDs, isCIDReferencedInHTML
from signature_html.go (CID logic lives in draft/patch.go)
- Remove unused strings import
- Run gofmt on all affected files
Change-Id: Ie142744a7ab17acf440dc69a5a78cefb3ce6c341
Co-Authored-By: AI
* fix(mail): use draft From address for signature interpolation in +draft-edit
Moved signature resolution after draft fetch+parse so insert_signature
reads the From header from the existing draft. This ensures alias and
shared-mailbox senders get correct template variable values (B-NAME,
B-ENTERPRISE-EMAIL) instead of falling back to the primary address.
Change-Id: I917016b17176090124814f30e8e15c67f1604de0
Co-Authored-By: AI
- Add buildSendResult helper that includes recall_available/recall_tip
when backend returns recall_status in send response
- Update +send, +reply, +reply-all, +forward to use buildSendResult
- Add "Recall Email" section to mail skill template with recall and
get_recall_detail command examples
- Regenerate SKILL.md
Change-Id: I44317ead8f8a65db81e874cfc3529ffeb21e1384
Co-Authored-By: AI
- New `slides +media-upload` shortcut: upload a local image to a slides
presentation and return the file_token for use in <img src="...">.
- `slides +create --slides` now supports `@./path.png` placeholders that
are auto-uploaded and replaced with file_tokens.
- Reject images >20 MB (multipart upload not supported for slide_file).
- Support wiki URL resolution for --presentation flag.
Implement +set-dropdown, +update-dropdown, +get-dropdown, and
+delete-dropdown shortcuts wrapping the v2 dataValidation API.
This resolves the issue where multipleValue writes silently
became plain text because the prerequisite dropdown configuration
step was not exposed as a CLI command.
Also add lark-sheets-formula.md reference for Lark-specific formula
rules (ARRAYFORMULA, native array functions, date diff, etc.) and
update the dropdown limitation note in SKILL.md to link to the new
+set-dropdown shortcut.
Add range download support for IM OAPI resources so lark-cli can reliably download large files. This improves stability for large payloads and network interruptions.
Change-Id: I38e6f6f9cf8b8711dc40650d19c77503f4e44989
The chat_p2p/batch_query endpoint that resolves a user's p2p chat_id
requires user identity. Calling +chat-messages-list with --user-id
under bot identity previously failed silently or returned wrong
results.
- Validate: reject --user-id when runtime.IsBot(), with a hint to
pass --as user or use --chat-id instead
- resolveP2PChatID: add defensive guard for the same condition in
case the helper is reached via another path
- Update --user-id flag description and the lark-im skill reference
to note the user-identity requirement
- Tests: add bot-rejection cases for Validate and resolveP2PChatID,
switch p2p happy-path tests to a user-identity runtime helper
* fix(mail): add missing event scope for mail watch
The mail +watch shortcut requires scope
mail:user_mailbox.event.mail_address:read to receive the mail_address
field in WebSocket event payloads, but this scope was neither declared
in the shortcut's Scopes list nor included in the auto-approve
(recommend.allow) set.
Without this scope, +watch events arrive without the mail_address field,
which breaks mailbox filtering and fetch-mailbox resolution.
- Add scope to mail +watch Scopes declaration
- Add scope to scope_overrides.json recommend.allow list so that
auth login --recommend requests it automatically
* fix(mail): add missing mailbox profile scope for mail watch
The +watch shortcut calls fetchMailboxPrimaryEmail (GET
user_mailboxes/me/profile) to resolve the mailbox address for event
filtering, which requires scope mail:user_mailbox:readonly. All other
mail shortcuts that call this API (send, reply, forward, draft-create,
draft-edit) already declare this scope, but +watch did not.
* fix(mail): remove event scope from scope_overrides.json
The mail:user_mailbox.event.mail_address:read scope only needs to be
declared in the +watch shortcut's Scopes list, not in the global
recommend.allow set.
* fix(mail): restrict --output-dir to current working directory
Previously, mail +watch --output-dir accepted absolute paths (e.g.
/etc, /tmp) and home directory paths (~/), allowing writes to arbitrary
locations. Since mail content is sender-controlled, this posed a risk
of writing attacker-influenced data to sensitive system directories.
Now all --output-dir values go through validate.SafeOutputPath which:
- Rejects absolute paths and ~ expansion
- Resolves .. and symlinks
- Enforces the result stays under CWD
* fix(mail): reject tilde paths in --output-dir explicitly
SafeOutputPath treats ~/x as a literal relative path, silently creating
a directory named "~" under CWD. Reject ~ prefixed paths with a clear
error message instead.
* fix(mail): reject all tilde-prefixed paths and use ErrValidation
- Broaden ~ check from "~ || ~/" to "~" prefix, covering ~user/path forms
- Use output.ErrValidation for consistent error type (exit code 2)
* fix(mail): add post-mkdir EvalSymlinks + CWD re-verification (TOCTOU)
SafeOutputPath validates before MkdirAll, but an attacker could replace
the newly created directory with a symlink between mkdir and the first
write. Add EvalSymlinks after MkdirAll and re-verify the resolved path
is still under CWD.
Also broaden ~ rejection to all tilde-prefixed paths (~user/path) and
use output.ErrValidation for consistent error types.
* fix(mail): use validate.SafeOutputPath for post-mkdir TOCTOU check
Replace direct os.Getwd and filepath.EvalSymlinks calls with a second
SafeOutputPath call after MkdirAll. This satisfies the forbidigo lint
rule (no direct os/filepath calls in shortcuts/) while maintaining the
same TOCTOU protection.
* fix(mail): use original relative path for post-mkdir re-validation
SafeOutputPath rejects absolute paths, but after the first call
outputDir was already resolved to an absolute path. Pass the original
relative path to the second SafeOutputPath call so it can properly
re-validate after MkdirAll.
* fix(mail): remove redundant post-mkdir SafeOutputPath call
The second SafeOutputPath call after MkdirAll provided no real TOCTOU
protection: mail +watch is long-running, so the directory could be
replaced at any point during the session, not just between mkdir and
the check. The first SafeOutputPath already validates and resolves
the path; one call is sufficient.
* docs(task): document sections API resources and add URL parsing reminder
* feat(task): support --section-guid flag in tasklist-task-add shortcut
* docs(task): document sections API resources, permissions, and URL parsing
After creating the presentation, call drive batch_query (with_url=true)
to fetch the document URL and include it in the output. The fetch is
best-effort so it won't break creation if the API call fails.
Also update the skill reference doc to document the new optional url
return field.
Add 5 new sheet shortcuts for row/column management:
- +add-dimension: append rows/columns at the end
- +insert-dimension: insert rows/columns at a position
- +update-dimension: update visibility and size
- +move-dimension: move rows/columns to a new position
- +delete-dimension: delete rows/columns
Includes unit tests (89-100% coverage) and skill reference docs.
Add BotInfo() method on RuntimeContext that lazily fetches the current
app's bot open_id and display name from /bot/v3/info on first call,
cached via sync.OnceValues for the lifetime of the process.
- BotInfo struct (OpenID, AppName) in Identity section of runner.go
- fetchBotInfo() uses DoAPIAsBot for consistent header injection
- CanBot() on CliConfig gates the call when bot identity is unavailable
- Nil guard prevents panic in test contexts
- Full test coverage via httpmock.Registry + mounted shortcuts
Change-Id: I40ac710fb52d13939853f71827a5cbdbddd4f80f
- Add `+dashboard-arrange` command that triggers server-side smart layout optimization via POST /open-apis/base/v3/bases/{token}/dashboards/{id}/arrange
- Add `text` block type support for dashboard blocks with Markdown syntax (headers, bold, italic, strikethrough, lists)
- Update `validateBlockDataConfig()` to handle text-specific validation rules
- Update documentation (SKILL.md, lark-base-dashboard.md, dashboard-block-data-config.md, lark-base-dashboard-arrange.md)
- Add comprehensive unit tests for new commands and block type
- [x] Unit tests pass (`go test ./shortcuts/base/...`)
- [x] All dashboard-related tests pass including new `TestBaseDashboardExecuteArrange`
- [x] Text block type validation tests pass
- None
* feat(base): add record batch add/set shortcuts
* docs: clarify record batch add/set input guidance
* docs: mark base shortcut references as required before calling
* fix(base): remove stale token stub calls in batch record tests
* feat(base): rename record batch add/set to create/update
* refactor(base): remove noop record json validators
* test(base): align record validate test with nil hooks
* fix: align base record batch shortcuts with openapi routes
* fix(base): pass parse context for record batch JSON parsing
* docs: move base record batch JSON guidance to tips
* refactor: remove noop record validate
* docs: remove has_more from batch update guide
---------
Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
* feat(base): add +record-search json passthrough shortcut
* docs(base): refine record-search wording and field constraints
* docs(base): prefer record-list unless keyword is explicit
* refactor(base): inline record-search parsing and align tests
* refactor(base): remove noop record validate hook
* docs(base): unify record example token placeholders
* fix: align record search JSON parsing with parse context
* feat: add help tips for base record search
* docs: refine base record search reference
---------
Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
* feat(base): add record field filters
* fix(base): align record field filter flags with OpenAPI params
* fix: scope record dry-run field filters and align docs
* docs(base): clarify record-list field_scope priority
* refactor(base): remove field-id from record-get
---------
Co-authored-by: zgz2048 <zhonggangzhi.tim@bytedance.com>
Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
- Add `+dashboard-arrange` command that triggers server-side smart layout optimization via POST /open-apis/base/v3/bases/{token}/dashboards/{id}/arrange
- Add `text` block type support for dashboard blocks with Markdown syntax (headers, bold, italic, strikethrough, lists)
- Update `validateBlockDataConfig()` to handle text-specific validation rules
- Update documentation (SKILL.md, lark-base-dashboard.md, dashboard-block-data-config.md, lark-base-dashboard-arrange.md)
- Add comprehensive unit tests for new commands and block type
- [x] Unit tests pass (`go test ./shortcuts/base/...`)
- [x] All dashboard-related tests pass including new `TestBaseDashboardExecuteArrange`
- [x] Text block type validation tests pass
- None
* fix(mail): replace os.Exit with graceful shutdown in mail watch
The signal handler in mail +watch called os.Exit(0), which bypassed all
deferred cleanup functions, made the code path untestable, and did not
follow Go's idiomatic context cancellation pattern.
Key changes:
- Remove os.Exit(0) and use context.WithCancel to propagate shutdown
- Run cli.Start in a separate goroutine so the main goroutine can return
immediately on signal receipt (the Lark WebSocket SDK does not return
promptly after context cancellation)
- Extract handleMailWatchSignal as a testable standalone function
- Use sync.Once + defer for idempotent cleanup on all exit paths
- Fix eventCount data race with atomic.Int64
- Add signal.Reset to support forced termination via a second Ctrl+C
Closes#268
* docs: add docstrings to handleMailWatchSignal test functions
* fix(mail): cancel watch context on signal handler panic
If handleMailWatchSignal panics, the recover block now calls
cancelWatch() to unblock the main select. Without this, a panic
would leave shutdownBySignal unclosed and watchCtx uncancelled,
causing the process to hang.
* fix(mail): use triggerShutdown to unblock main select on signal handler panic
The previous panic recovery only called cancelWatch(), but since the
WebSocket SDK does not return promptly after context cancellation,
the main select could still hang waiting on startErrCh.
Introduce triggerShutdown() that closes shutdownBySignal (via
sync.Once) and cancels the watch context, used by both the normal
signal path and the panic recovery path. This ensures the main
select unblocks immediately regardless of how the signal goroutine
exits.
Add regression test that forces a panic and asserts shutdownBySignal
is closed promptly.
* feat(mail): add --page-token and --page-size pagination support to mail +triage
Support external pagination for mail +triage with two new flags:
- --page-token: resume from a previous response's page token
- --page-size: alias for --max
Token carries a "search:" or "list:" prefix to identify the API path,
with strict validation: conflicting parameters (e.g. list: token with
--query) fail fast, and bare tokens without prefix are rejected.
JSON/data output now returns an object with messages, total, has_more,
and page_token fields. Table output shows next-page hint on stderr.
* fix(mail): address PR review — keep data format as array, fix whitespace query edge case
- --format data preserves backward-compatible flat array output
- --format json returns the new envelope object with pagination fields
- Align search: prefix guard with TrimSpace(query) to match usesTriageSearchPath
* fix(mail): simplify page-token format and fix page-size change data loss
- Remove page_size encoding from token (search:abc → not search:5:abc)
The search API token is a session cursor; page_size only controls how
many items to return, not the cursor position. Encoding page_size
caused data loss when users changed --page-size between requests.
- Token format is now simply "search:<raw>" / "list:<raw>"
- Add parseTriagePageToken/encodeTriagePageToken helpers for clean
token handling with proper validation
- next page hint in table output now includes --query and --filter
for easy copy-paste continuation
* docs(mail): update triage skill doc for json/data format split and search pagination note
- Separate --format json (object with pagination) and --format data (array) examples
- Update table next-page hint example to show --query/--filter inclusion
- Add search pagination caveat about cross-session result ordering
* fix(mail): make --format data include pagination fields same as json
* fix(mail): address remaining PR review comments
- Reject empty prefixed tokens (search: / list:) in parseTriagePageToken
- Shell-escape query/filter in next-page hint to handle single quotes
- Fix doc caption mismatch (data → json/data) and add language tag to code block
- Fix test comment for TestResolveTriagePageSizeDefaultMax
* fix(mail): rename total to count in triage pagination output
total was misleading — it represented the current page count, not the
global total. Renamed to count to match len(messages) semantics.
* fix(mail): improve dry-run desc when using --page-token