236 Commits

Author SHA1 Message Date
liangshuo-1
3e5dc3262f chore(release): v1.0.16 (#593)
Change-Id: I2ba82ed0b3cb21cecca0ac09b058b10ea656a98a
v1.0.16
2026-04-21 21:44:45 +08:00
chanthuang
c13644a247 feat(mail): support large email attachments (#537)
* feat(mail): add large attachment support via medias/upload API

When attachments would cause the EML to exceed the 25MB limit, they are
automatically uploaded to the mail attachment storage (medias/upload_all
with parent_type="email") and a download-link card is injected into the
HTML body, matching the desktop client's exportLargeFileArea style.

Key changes:
- Add classifyAttachments: EML-size-based splitting of normal vs oversized
- Add uploadLargeAttachments: upload via medias API with email MountPoint
- Add buildLargeAttachmentHTML: desktop-aligned card with CDN icons
- Add processLargeAttachments: unified entry point for all compose shortcuts
- Add LargeAttachmentHTML to emlbuilder.Builder for HTML block injection
- Fix 7bit line folding: use RFC 5322 limit (998) instead of incorrect 76
- Integrate into +draft-create, +forward, +reply, +reply-all

Known limitation: recipient access to large attachment links requires
backend support to register tokens with the draft (see progress doc).

Change-Id: If8d5938015cac8bc82de3ea3ff41022950f2571e
Co-Authored-By: AI

* refactor(mail): remove legacy size check, add 3GB limit, integrate +send

- Remove checkAttachmentSizeLimit (replaced by processLargeAttachments)
- Remove 25MB pre-check from validateComposeInlineAndAttachments so that
  large files reach Execute where they are uploaded as large attachments
- Integrate processLargeAttachments into +send shortcut
- Add 3GB single file limit aligned with desktop client
- Clean up unused imports from helpers.go and helpers_test.go

Change-Id: Ie590ad2b58263c075f48b338959b8f5b3f912f85
Co-Authored-By: AI

* feat(mail): quote-aware HTML insertion, +draft-edit support, cleanup emlbuilder

- Add insertBeforeQuoteOrAppend: insert large attachment HTML before the
  quote block (lark-mail-quote) instead of appending to body end, matching
  desktop's exportLargeFileArea placement logic
- Add preprocessLargeAttachmentsForDraftEdit: intercept add_attachment
  patch ops before draft.Apply, upload oversized files, inject HTML into
  snapshot's HTML body Part directly. No changes to draft sub-package.
- Remove LargeAttachmentHTML field/setter/logic from emlbuilder — it was
  business logic (quote-aware insertion) that doesn't belong in a generic
  EML builder. processLargeAttachments now sets the full HTML body via
  bld.HTMLBody() after merging the large attachment card at the right position.
- All compose shortcuts pass htmlBody to processLargeAttachments for
  quote-aware insertion (composedHTMLBody for reply/forward, body for others).

Change-Id: If6e7ed7e77989ab9a8a41a93758f686d72ccf497
Co-Authored-By: AI

* fix(mail): align large attachment HTML IDs with desktop client

- Container ID: lark-mail-large-file-container → large-file-area (matching
  desktop's MAIL_LARGE_FILE_CONTAINER constant)
- Item ID: lark-mail-large-file-item → large-file-item (matching
  desktop's MAIL_LARGE_FILE_ITEM constant)
- Timestamp: truncate to 9 digits (matching TIMESTAMP_CUT_OUT_ID = 9)
- Refactor HTML generation to use template constants for readability

These IDs are used by the desktop client's BigAttachmentPlugin
([id^=large-file-area]) and the server's LargeFileRule to identify and
remove the HTML block when rendering the attachment card UI.

Change-Id: Ib5a77a1a3d60eeb3a05c585f2af0a5ddaacf887b
Co-Authored-By: AI

* docs(mail): document large attachment behavior in skill references

Update --attach parameter descriptions across all compose shortcuts
(+send, +reply, +reply-all, +forward, +draft-create, +draft-edit) to
describe automatic large attachment handling when EML exceeds 25 MB.

Change-Id: I8c30e390c127ea1119cb8c4b83ec636e41fbaf66
Co-Authored-By: AI

* fix(mail): pass signature-injected HTML to processLargeAttachments

When both --signature-id and large attachments are used, the htmlBody
passed to processLargeAttachments must include the already-injected
signature. Previously mail_send and mail_draft_create passed the
original body, causing processLargeAttachments to overwrite the
signature-injected HTML body when inserting the large attachment card.

Use composedHTMLBody variable (same pattern as reply/forward) to
capture the full processed HTML including signature.

Change-Id: I6be330776abca704b10cc3b8bfd5e20838e6e538
Co-Authored-By: AI

* fix(mail): skip draft.Apply when all ops consumed by large attachment preprocessing

When all patch ops are add_attachment targeting oversized files,
preprocessLargeAttachmentsForDraftEdit uploads them and removes the
ops from the patch. The resulting empty patch caused draft.Apply to
fail with "patch ops is required". Now skip Apply when no ops remain.

Change-Id: I8067a54b5f849fa519e8344a7eb10c48f58e54b8
Co-Authored-By: AI

* fix(mail): add X-Lms-Large-Attachment-Ids header in draft-edit large attachment flow

draft-edit's preprocessLargeAttachmentsForDraftEdit uploaded oversized files
and injected HTML cards but never wrote the X-Lms-Large-Attachment-Ids header
into the snapshot, so the mail server could not associate the attachments with
the draft. Merge new token IDs with any existing ones already in the snapshot.

Also extract the duplicated largeAttID struct and header name string into
package-level declarations.

Change-Id: Id256d948ec07e86296157436feefa3c2052af721
Co-Authored-By: AI

* fix(mail): i18n large attachment HTML text aligned with desktop client

Parameterize title and download text in large attachment HTML templates.
Chinese lang uses "来自Lark邮箱的超大附件"/"下载", others use
"Large file from Lark Mail"/"Download", matching desktop's i18n keys
Mail_Attachment_AttachmentFromFeishuMail and Mail_Attachment_Download.

Change-Id: I2aada8d52af41ae77dd7001d24d14e333f12066e
Co-Authored-By: AI

* fix(mail): insert large attachment card before quote wrapper, not inside nested quote

insertBeforeQuoteOrAppend matched id="lark-mail-quote" which can appear
deeply nested inside quoted content from previous replies in a thread.
This caused the card to be placed inside the quote area instead of before
it. Switch to matching the "history-quote-wrapper" class which is the
outermost quote container generated by the CLI.

Change-Id: I720b6d62d719613b411b7ed4b7820a1535bf14bd
Co-Authored-By: AI

* feat(mail): unify large attachment handling in +draft-edit with normal attachments

Extend +draft-edit so that large attachments behave like normal attachments
from the user's perspective: survive body edits, are listed in inspect
output, and are removed via the same remove_attachment op.

Code-wise:
- remove_attachment target now accepts token (for large attachments) in
  addition to part_id / cid; priority part_id > cid > token.
- setBody / setReplyBody auto-preserve the large attachment card in the
  HTML body, mirroring how normal attachments (MIME parts) survive body
  edits. Detection checks only the user-authored region of the value so
  cards inside an appended quote block (from the original quoted message)
  are not mistaken for user-supplied cards.
- --inspect returns large_attachments_summary (token, filename, size) by
  parsing the X-Lms-Large-Attachment-Ids header and the HTML card DOM.
- Well-known Lark HTML/header constants (LargeAttachmentIDsHeader,
  LargeFileContainerIDPrefix, LargeFileItemID, LargeAttachmentTokenAttr)
  moved to the draft package alongside QuoteWrapperClass; the mail package
  consumes them.
- Shared helpers FindHTMLBodyPart and InsertBeforeQuoteOrAppend exported
  from the draft package; mail package switched to consume them, removing
  local duplicates.

Skill reference (lark-mail-draft-edit.md) updated: three locator fields by
attachment type, unified remove_attachment examples, set_body behavior.

Change-Id: Ic064d1a8df0edf1cef6069cd44ec2a7534cd2182
Co-Authored-By: AI

* fix(mail): place signature before large attachment card consistently

When inserting a signature into a draft that already has a large
attachment card, the signature was placed after the card, diverging from
the compose-time layout where the order is [user][sig][card][quote].

Root cause: insertSignatureOp split only at the quote block, so the
"user region" side inadvertently included the card.

Centralize signature placement in draft.PlaceSignatureBeforeSystemTail,
which splits at the earliest system-managed element (card or quote,
whichever comes first). Both edit-time insertSignatureOp and compose-time
injectSignatureIntoBody now share this single source of truth, removing
the duplicated HTML splicing logic.

Change-Id: I234bfebaaa31a32731ebbaa78c6596a72618b7c5
Co-Authored-By: AI

* fix(mail): auto-preserve signature in set_body and set_reply_body

Previously set_body / set_reply_body replaced the entire HTML body,
silently dropping the signature block. The "replace whole body" semantic
treated signature as user-authored content, which is inconsistent with
how attachments (normal + large) and quote blocks survive body edits —
signature is a system-managed element managed via insert_signature /
remove_signature ops.

Unify the mental model: body-edit ops replace user-authored content
only; signature, large attachment card, normal attachments, and (for
set_reply_body) quote block are all auto-preserved. Users can override
by including equivalents in value, or explicitly delete via dedicated
ops (remove_signature, remove_attachment).

- Add ExtractSignatureBlock helper (symmetric to RemoveSignatureHTML).
- Rename autoPreserveLargeAttachmentCard to
  autoPreserveSystemManagedRegions; extract and inject both sig and card
  from old body, respecting user-supplied equivalents in value's
  user-authored region.
- Update skill doc and patch template notes to reflect the new
  semantics consistently.

Change-Id: I96660d2ff06a6c9cdf1b86793c2d89cf9cb09ffe
Co-Authored-By: AI

* fix(mail): use brand-aware display name in large attachment card title

The title "Large file from Lark Mail" / "来自Lark邮箱的超大附件" hard-coded
"Lark" regardless of brand. The desktop client switches between
"Feishu"/"飞书" and "Lark" based on the APP_DISPLAY_NAME i18n
substitution.

Add brandDisplayName(brand, lang) helper:
  - BrandLark    → "Lark"
  - BrandFeishu  → "飞书" (zh) / "Feishu" (en)

Applied to title in buildLargeAttachmentHTML, aligning with the icon CDN
and download URL, which already branch on brand.

Change-Id: I06258b9982b6280a2230193d90a6a88884e10aa3
Co-Authored-By: AI

* style(mail): apply gofmt

CI fast-gate check flagged gofmt-unformatted files. Run gofmt -w on
touched mail files only.

Change-Id: Iec690dc63adfaa54b8f7c85ab5b3ca035476ddbd

* fix(mail): address review feedback on large attachment PR

- Strip <html><head><body> wrapper from xhtml.Render output in
  removeLargeFileItemFromHTML to avoid polluting the HTML body
- Reject plain-text messages with oversized attachments instead of
  silently losing the body content
- Fix attachment count limit in skill doc (100 → 250)
- Remove unused fio/attachFlag params from validateComposeInlineAndAttachments
- Add token escaping test for large attachment HTML builder

Change-Id: Ie589a1f1d204b0aeebc4486b16bb435041793ceb
Co-Authored-By: AI

* fix(mail): recognize server-format X-Lark-Large-Attachment header in draft-edit

When a draft with large attachments is created by the desktop client,
the server returns X-Lark-Large-Attachment (with file_key/file_name/
file_size fields) instead of the CLI-written X-Lms-Large-Attachment-Ids.
Previously CLI only recognized its own header, causing existing large
attachments to be silently dropped when the draft was edited.

- Parse both header formats via IsLargeAttachmentHeader and unified
  largeAttHeaderEntry struct
- Convert server-format entries to CLI-format on save so the server
  can process the update
- Fix inline attachment classification: require non-empty CID to
  classify as inline image (large attachments may have is_inline=true
  but no CID)

Change-Id: Ie7def4fc5923d2cf3446eedfbca4fd8cae44bfac
Co-Authored-By: AI

* fix(mail): skip large attachments in forward URL validation

Large attachments do not have download URLs since they are referenced
by token, not embedded in the EML. Validate only normal attachments
to avoid false "missing download URL" errors when forwarding messages
that contain expired or token-based large attachments.

Change-Id: Ibe3f45390cd3b3cbe6ddd15961dcda4f17aefe4f
Co-Authored-By: AI

* fix(mail): classify forwarded original attachments for large attachment upload

Previously, all original attachments were unconditionally embedded in
the EML before user attachments were processed for large attachment
upload. When original + user attachments together exceeded the 25 MB
EML limit, the build would fail.

Now all attachments (original + user-added) are classified together
via classifyAttachments. Original attachments that push the EML over
the limit are re-uploaded as large attachments with download cards,
matching the compose/reply flow behavior.

Also refactors uploadLargeAttachmentBytes to reuse the shared
common.UploadDriveMediaAll utility (via new Reader field on the
config struct) instead of duplicating the upload logic, and replaces
bare fmt.Errorf with output.ErrValidation for user input errors.

Change-Id: I98d4ad8960cd68e38765b05c94f7786d6a8444c8
Co-Authored-By: AI

* fix(mail): normalize large attachment header on draft edit to prevent loss

Server returns X-Lark-Large-Attachment header on draft readback, but only
recognizes X-Lms-Large-Attachment-Ids on write. Without normalization,
editing a draft with existing large attachments (e.g. adding a small
attachment) would send back the server-format header unchanged, causing
the server to drop the large attachment association.

Add normalizeLargeAttachmentHeader() at the entry of
preprocessLargeAttachmentsForDraftEdit to convert server-format headers
to CLI format before any processing or early return.

Change-Id: Id99a46f29015a32921bfb72a003f766c397787e1
Co-Authored-By: AI

* fix(mail): extract large attachment card from quote on forward

When forwarding a message that contains large attachments, the original
message's download card (large-file-area div) was left inside the
forward quote block. Extract it and place it in the main body area
(after signature, before quote), matching the desktop client behavior.

Change-Id: Iebede35cdf4ed0f65b72bce28ffb18af21ddf668
Co-Authored-By: AI

* fix(mail): use octet-stream for re-embedded attachments and file-based large upload on forward

- Use application/octet-stream instead of original content type when
  re-embedding downloaded attachments in forward EML. Prevents the mail
  server from treating image/* attachments as inline parts.
- Replace in-memory uploadLargeAttachmentBytes with temp-file-based
  uploadLargeAttachments for oversized original attachments. This
  enables multipart upload for files >20MB which the single-part API
  does not support.

Change-Id: Ib02add5710e8b052e47b513ed3d9a688e0f98212
Co-Authored-By: AI

* fix(mail): address PR review — blocked extension bypass, index-based op filtering, plain-text draft guard

1. Move CheckBlockedExtension into statAttachmentFiles so oversized
   attachments are validated before classification, covering compose,
   draft-edit, and forward paths.

2. Replace path-based oversized op filtering with SourceIndex-based
   filtering in preprocessLargeAttachmentsForDraftEdit to avoid
   incorrectly removing duplicate-path normal ops.

3. Add HTML body preflight in preprocessLargeAttachmentsForDraftEdit
   before uploading, so plain-text-only drafts fail early instead of
   silently producing a draft with tokens but no download card.

Change-Id: Ib8771812f50a18f00a40e50149b028b8aaa101fe
Co-Authored-By: AI

* fix(mail): preserve original content type for normal forwarded attachments

The octet-stream override was only needed for the large attachment
upload path (to prevent image/* from being treated as inline by the
drive API). Normal attachments embedded in the EML should retain their
original MIME type so recipients can preview/open them correctly.

Change-Id: Ie40b7c362524a3b82255b58e9bcfd770eacfe911
Co-Authored-By: AI

* fix(mail): reconstruct missing large attachment HTML cards on draft edit

The server strips HTML download cards from the EML body when storing
drafts, so every draft read-back (regardless of creator) lacks them.
Add ensureLargeAttachmentCards which runs before header normalization,
compares server-format header tokens against existing HTML cards via
data-mail-token, and rebuilds only the missing ones. This ensures
external recipients see download links after draft-edit → send.

Also exports ParseLargeAttachmentSummariesFromHeader and
ParseLargeAttachmentItemsFromHTML from the draft package for
cross-package use.

Change-Id: I9cb0f47a9f4582909de24984d9a9f6e366521e62
Co-Authored-By: AI

* feat(mail): support large attachments in plain-text emails

Previously large attachments required an HTML body for the download card.
Now plain-text emails (--plain-text or text/plain-only drafts) get download
info appended as structured text (title + filename + size + URL), with
i18n and brand awareness matching the HTML card.

Changes:
- Add buildLargeAttachmentPlainText and injectLargeAttachmentTextIntoSnapshot
- Add FindTextBodyPart in draft/projection.go
- Update processLargeAttachments to accept textBody parameter
- Update ensureLargeAttachmentCards to handle text/plain body reconstruction
- Update preprocessLargeAttachmentsForDraftEdit to allow text/plain drafts
- Update all callers (send, draft-create, reply, reply-all, forward)

Change-Id: I3b375e2ff34697eeb73a3768ace6d577d1bead3e
Co-Authored-By: AI

* fix(mail): FindBodyPart skips attachment-disposition parts; update skill docs

FindHTMLBodyPart and FindTextBodyPart now skip parts with
Content-Disposition: attachment, preventing .txt/.html file attachments
from being mistakenly treated as the email body.

Also update all lark-mail skill reference docs to reflect that large
attachments now work in both HTML (download card) and plain-text
(download link text) modes.

Change-Id: I1e6da4fd614217dff61304212304b5fd80c8246c
Co-Authored-By: AI

* fix(mail): fix origIdx mismatch, predictable temp files, and attachment count on forward

- Use SourceIndex instead of linear origIdx counter so classifyAttachments
  reordering does not cause content mismatch between normal/oversized loops
- Use os.CreateTemp for temp files instead of predictable names in CWD
- Include original large attachment count in totalCount limit check

Change-Id: Ide5dce14b1efc672687800d77c3853f15dfc191b
Co-Authored-By: AI

* fix(mail): use composed body size and source inline bytes in EML size estimation

estimateEMLBaseSize was using len(body) (raw --body flag) instead of the
actual composed body (which includes quotes, signatures, forward headers).
Source inline images downloaded from the original message were also not
counted. This could cause borderline attachments to be misclassified.

- Use len(composedHTMLBody) + len(composedTextBody) for body size
- Return total downloaded bytes from addInlineImagesToBuilder and pass
  as extraBytes to estimateEMLBaseSize
- Fix applied to all compose shortcuts: send, draft-create, reply,
  reply-all, forward

Change-Id: Ibe6c44e22d40ac51f0a4652d279e66bd92330723
Co-Authored-By: AI

* fix(mail): merge large attachment items into single container on draft edit

When draft-edit had both set_body and add_attachment (oversized), the
ensureLargeAttachmentCards and preprocessLargeAttachmentsForDraftEdit
each created independent large-file-area containers. The subsequent
set_body's autoPreserveSystemManagedRegions only captured the first
container via SplitAtLargeAttachment, discarding the second one.

Fix: injectLargeAttachmentHTMLIntoSnapshot now detects an existing
large-file-area container and appends new items inside it instead of
creating a new container, matching the desktop client's single-container
behavior.

Change-Id: I3d701683053842f1d7bdad34fc4b2ef26ede784e
Co-Authored-By: AI

* fix(mail): strip large attachment card from reply/reply-all quote

Reply and reply-all should not carry over the original email's large
attachment HTML card into the quoted block. Extract the shared
stripLargeAttachmentCard helper (also used by forward) that removes
the card from orig.bodyRaw before quote construction.

- Reply/reply-all: card is discarded (not re-inserted)
- Forward: card is moved to body area before the quote (unchanged)

Change-Id: I5399bb901c120206c7c045bed107f7d68be23bb1
Co-Authored-By: AI

* fix(mail): skip invalid attachments on forward instead of blocking

When forwarding a message with deleted/expired attachments, the forward
flow now automatically removes them instead of either blocking (normal
attachments) or silently including dead references (large attachments).

- Propagate failed_ids from fetchAttachmentURLs into composeSourceMessage
- Skip failed attachments in the forward download loop with a warning
- Remove corresponding large attachment HTML card items from the body
- Extend itemContainsToken to match server-generated href?token= format

Change-Id: I9c0096dcbe96f1d61caa0f6f0b2f8b738fdfa66b
Co-Authored-By: AI

* fix(mail): restore dry-run file preflight and reserve card overhead in classifier

1. Restore file existence and blocked-extension checks in
   validateComposeInlineAndAttachments so --dry-run surfaces local
   path errors before Execute.
2. Reserve 3KB per oversized file in classifyAttachments to account
   for the HTML card / plain-text block injected after classification.

Change-Id: Ib48a75f86a50298413c1f9ab8226e583c0161a8c
Co-Authored-By: AI

* fix(mail): revert classifier overhead reserve for simplicity

The 3KB-per-oversized-file reserve in classifyAttachments addressed
a boundary case that is practically impossible to trigger (requires
Normal attachments to fill to within a few KB of 25MB). Remove it
to keep the classifier simple.

Change-Id: I5148f14ecca1a0dee677a1a2c60ec4efab160ea8
Co-Authored-By: AI

* style(mail): fix gofmt indentation in draft create tests

Change-Id: Ib41aa22f94144f2d47b12675d444aa43cb333a88
Co-Authored-By: AI

* fix(mail): remove temp files in forward, use in-memory upload instead

Replace os.CreateTemp/os.WriteFile/os.Remove with in-memory Data field
on attachmentFile, conforming to the project's forbidigo rule against
temp files in shortcuts. Also remove dead uploadLargeAttachmentBytes.

Change-Id: Ic26e4025eebfa1bac3948438ef185ff3e2f15abb
Co-Authored-By: AI

* test(mail): add tests for validateComposeInlineAndAttachments and fileTypeIcon

Covers all branches: inline+plain-text conflict, inline+non-HTML body,
missing file, blocked extension, valid pass-through, and all file type
icon mappings.

Change-Id: I8b81c1b34010a9ecb7153462a5524e3d7b171de2
Co-Authored-By: AI

* test(mail): improve coverage for large attachment and draft edit functions

Add tests for snapshotEMLBaseSize, flattenSnapshotParts, estimateEMLBaseSize,
normalizeLargeAttachmentHeader, processLargeAttachments error paths,
preprocessLargeAttachmentsForDraftEdit early-return paths, inject edge cases,
buildLargeAttachmentItems, statAttachmentFiles edge cases, and
prettyDraftAddresses.

Change-Id: Ie661e6ebea63512864d97e20135dd89cb9e9304e
Co-Authored-By: AI
2026-04-21 20:56:37 +08:00
qioqio
cb301a3d1a feat(mail): add draft preview URL to draft operations (#438)
* feat(mail): add draft preview URL to draft operations

- Add draftPreviewURL helpers for send-preview link generation
- Integrate preview_url output in +draft-create, +draft-edit, +reply,
  +forward, +reply-all shortcuts
- Add unit tests (7 test cases, all passing)

Change-Id: Ie3cbb8f96b308aae225bc69f4c3fc2226af0c230

* fix(mail): derive draft preview url from meta service

Change-Id: Ibd10767bf4e4de7f453fff72487fe25090e14605

* fix: streamline mail draft and send outputs

Change-Id: I75a969af29fa862bdf94947a3aa775d6eebee812

* fix(mail): keep draft reference on create and update

Change-Id: Ie5787cf255ec2347c49f0a271209c1a2e4008fe3

* docs: refine mail draft link guidance for skills

Change-Id: Ieaa5afef310edd5253f07eef06678b7a5db38fc0

* fix(mail): return draft reference for save flows

Change-Id: Ied6031a05bdefecdcf60b09f66c5d3947d849f83

* refactor(mail): unify draft save output handling

Change-Id: I400b8f9df97d614b33da3cbdde410ef615444741

* fix(mail): surface automation disable reason

Change-Id: I23293fe6c2febf248c58ea14c87c05dde49872a1

* feat: flatten mail automation send disable output

Change-Id: I747bf54bc3251387b05d94f87fe61da958d78104

* fix(mail): address review feedback for draft docs and tests

Change-Id: I690df5612f36681c1690645d375c5c5e3ef9ca60

* test(mail): reuse upstream send-scope test factory

Change-Id: I7f73956696c5405d8eb81fcd2128f0e9898ea539

* refactor(mail): merge recall fields into send output helper

Change-Id: I5af612d70b05a3c0d8abbc9561fe76bb83b5b359

* fix(mail): omit raw recall status from send output

Change-Id: I2918226a0eb68a45f6cc4ea997e1c941d8c16d52

* style(mail): format send output tests

Change-Id: I8e0ec37aac48bcda6b5ad948f397d184a2a4d81d

* test(mail): cover draft reference output flows

Change-Id: Idd8abdb84613727a24e3fccb7b329e69566bc890
2026-04-21 20:55:41 +08:00
MK
04e3a28529 fix(docs): validate --selection-by-title format early (#256)
* fix(docs): validate --selection-by-title format early

* fix(docs): reject multiline selection-by-title before prefix check

* chore: refresh CI against current main (no code change)

* test(doc): cover DocsUpdate.Validate integration for selection-by-title

codecov/patch was at 27.27% because the PR added three lines to the
Validate closure (the `if err := validateSelectionByTitle(selTitle); err
!= nil { return err }` block) but nothing in the test file exercised
that closure — only the helper function was tested directly.

TestDocsUpdateValidate now builds a bare RuntimeContext via
common.TestNewRuntimeContext, sets the relevant flags on a cobra
command, and calls DocsUpdate.Validate(ctx, rt) across five cases:

  1. Heading-style selection-by-title passes — covers the happy path
     through the new call site and the final `return nil`.
  2. Plain-text title is rejected with heading-prefix guidance —
     covers the new error branch.
  3. Multi-line title is rejected as not a single heading line —
     covers the other error branch inside the helper.
  4. Invalid --mode is still rejected first — proves the new check
     doesn't swallow pre-existing validation.
  5. Conflicting --selection-with-ellipsis + --selection-by-title is
     rejected at the mutual-exclusion check — same ordering contract.

Coverage profile confirms the three added production lines
(docs_update.go L65-67) are now hit: condition 3x, error branch 2x,
happy path via the closure's return nil 1x.
2026-04-21 18:25:26 +08:00
hugang-lark
e02c442aea feat: support event share link and error details (#583)
* feat: support event share info

Change-Id: I4876df38effe44de04e587ac18ace7e230c9fa3a

* fix: return detail err info for calendar
2026-04-21 17:51:56 +08:00
tuxedomm
fbed6beac3 refactor: split Execute into Build + Execute with explicit IO and keychain injection (#371)
* refactor(cmd): split Execute into Build with IO/Keychain injection

Introduce a public cmd.Build entry point so external consumers (cli-server,
MCP server, other embedders) can assemble the full CLI command tree without
going through os.Args or the platform keychain. Build takes an
InvocationContext plus functional BuildOptions:

  * WithIO(in, out, errOut) — inject custom streams; terminal detection
    is derived from the input's underlying *os.File when present.
  * WithKeychain(kc)        — swap the credential store.
  * HideProfile(bool)       — registered later in cmd.HideProfile.

The existing Execute() keeps using the internal buildInternal (which
still returns the Factory so error handling can attribute exit codes),
and SetDefaultFS replaces the global VFS implementation at startup.

Hardening applied up front:

  * cmdutil.NewIOStreams(in, out, errOut) centralizes terminal detection
    so SystemIO() and WithIO share one path.
  * cmdutil.NewDefault normalizes partial IOStreams — callers may pass
    &IOStreams{Out: buf} without tripping nil-writer panics in the
    RoundTripper warnings, Cobra, or the credential provider.
  * Build guards against nil functional options.
  * An API contract test (cmd/build_api_test.go) exercises Build +
    WithIO + WithKeychain + HideProfile + SetDefaultFS so the public
    surface is reachable by deadcode analysis.

Change-Id: I7c895e6019817401accbde2db3ef800da40ad319

* feat(schema): filter methods by strict mode in schema output

When strict mode is active, schema output now excludes methods that
are incompatible with the forced identity. This applies to both
pretty and JSON output formats at the resource and method levels.

Change-Id: I39647d5578466c3e23dc545bfb917ae075203ad7

* refactor: centralize strict-mode as flag registration

Change-Id: Iec11151c5002c2f58a8aa067d08747db2e4d2d8c

* fix(cmd): align strict-mode completion and build context; drop dead register shims

Thread a context.Context through RegisterShortcuts, RegisterServiceCommands,
and service.registerService/Resource/Method by introducing explicit
*WithContext variants. Pass that context into NewCmdServiceMethodWithContext
so shortcut and service command construction can honor cancellation and
strict-mode pruning consistently.

Also drop the context-less registerMethod and registerResource shims —
they became unreachable once the WithContext variants took over, and
were the source of new deadcode warnings. registerService is retained
because service_test.go still calls it directly.

Change-Id: I3fe5673aed663c7383bbbc5b0ae94d1f3491f22d

* refactor(cmd): hide --profile in single-app mode via build option

- GlobalOptions gains HideProfile; RegisterGlobalFlags stays pure and reads
  the policy off the struct. No boolean-trap parameter, one call per site.
- buildConfig holds GlobalOptions inline so HideProfile(bool) BuildOption
  mutates it directly. buildInternal stays a pure assembly function and
  requires callers to supply WithIO — no implicit os.Std* fallback.
- Add WithIO BuildOption (wrapping raw io.Reader/Writer with automatic
  *os.File TTY detection); Execute injects streams explicitly and decides
  profile visibility via HideProfile(isSingleAppMode()).
- installTipsHelpFunc force-shows hidden root flags while rendering the
  root command's own help, so single-app users still discover --profile
  via lark-cli --help without it polluting subcommand helps.

Change-Id: I7755387e993992ca969e0a4a6f54441cc1993eef

* feat(transport): extension abort hook and shared base transport

Two transport-layer changes bundled because both reshape the base
round-tripper contract used by the HTTP client, the Lark SDK client,
and the in-process updater.

1. Extension abort hook (PreRoundTripE).

   Extensions implementing exttransport.AbortableInterceptor can now
   return an error from PreRoundTripE to skip the built-in chain. The
   post hook still fires with (nil, reason) so extensions can unwind
   resources. extensionMiddleware captures the provider name so the
   returned *AbortError carries attribution.

2. Shared base transport to stop RPC leak.

   util.NewBaseTransport cloned http.DefaultTransport on every call, so
   each cmdutil.Factory produced a fresh *http.Transport whose
   persistConn readLoop/writeLoop goroutines lingered until
   IdleConnTimeout (~90s). Invisible in a single-process CLI, but the
   fork is consumed by cli-server where each RPC request constructs a
   new Factory, causing linear memory + goroutine growth under load.

   Replace NewBaseTransport with SharedTransport — returns
   http.DefaultTransport (the stdlib-wide singleton) by default, and
   a cached proxy-disabled clone only when LARK_CLI_NO_PROXY is set.
   Return type is http.RoundTripper to discourage in-place mutation of
   the shared instance. FallbackTransport is kept as a thin
   *http.Transport wrapper so existing callers in internal/auth and
   internal/cmdutil transport decorators (which were already on the
   singleton path) do not have to migrate.

   Leak-site migrations: factory_default.go (HTTP + SDK base) and
   update.go now call SharedTransport directly.

Change-Id: Ia82462134c5c5ee838be878b887860f41446a235

* fix: unblock Build() zero-opts path and sidecar demo build

Two regressions surfaced on refactor/build-execute-split:

1. cmd.Build(ctx, inv) without WithIO panicked at rootCmd.SetIn/Out/Err
   because cfg.streams stayed nil — NewDefault normalized internally
   but cmd/build.go never saw the normalized value. Default cfg.streams
   to cmdutil.SystemIO() before the root command wires them, and add a
   TestBuild_NoOptions regression guard.

2. sidecar/server-demo/main.go still called cmdutil.NewDefault(inv),
   so `go build -tags authsidecar_demo ./sidecar/server-demo` failed
   with "not enough arguments". Pass nil for the new streams parameter
   to preserve the prior behavior (NewDefault substitutes SystemIO).

Change-Id: I20227b2355cde7d19e22eba3eb841c6d8611e8a7
2026-04-21 14:48:40 +08:00
JackZhao10086
e15aef922e refactor(auth): simplify scope reporting in login flow (#582) 2026-04-21 14:07:51 +08:00
河伯
ccc27ce417 feat(doc): add pre-write semantic warnings to docs +update (#569)
* feat(doc): add pre-write semantic warnings to docs +update

Two static checks run before the MCP update-doc call:

1. replace_* + blank-line markdown: replace_range / replace_all only
   swap text inside an existing block — a \n\n in the payload will
   render as literal text, not a paragraph break. Hint to use
   delete_range + insert_before instead.

2. Combined bold+italic emphases (***text***, **_text_**, _**text**_)
   cannot round-trip through Lark and are silently downgraded to a
   single emphasis. Hint to split into two separate emphases.

Both warnings go to stderr and never block the update — they inform,
not gate. Adds table-driven tests for each check plus an aggregation
test, and wires the checks into Execute right before CallMCPTool.

Closes the first batch of items from the docs +update pitfalls
review (Cases 1 and 5).

* fix(doc): exclude code regions and escaped markers from docs +update checks (#578)

* fix(doc): exclude code regions and escaped markers from docs +update checks

Addresses the three review comments on #569: the blank-line paragraph
check and the bold+italic emphasis check both operate on the raw
markdown string, so fenced code blocks / inline code spans / literal
escaped markers produce false-positive warnings on content users
expect to pass through verbatim.

Changes:

- Add proseHasBlankLine(): fence-aware detector that returns true only
  when a blank line sits outside of ```...``` or ~~~...~~~ regions.
  Replaces the raw strings.Contains("\n\n") check in
  checkDocsUpdateReplaceMultilineMarkdown.

- Add stripMarkdownCodeRegions(): blanks out fenced code lines and
  masks inline code spans (via scanInlineCodeSpans from markdown_fix.go)
  with equal-length whitespace so byte offsets outside the stripped
  regions are preserved.

- Add stripEscapedEmphasisMarkers(): removes "\*" and "\_" so literal
  sequences like "\***text***" — which CommonMark renders as a literal
  asterisk plus bold — don't match the combined bold+italic regex.

- Wire both helpers into checkDocsUpdateBoldItalic(): the regex now runs
  on stripEscapedEmphasisMarkers(stripMarkdownCodeRegions(markdown)),
  so code samples and escaped markers are sanitized away before
  detection.

Shared fence-parsing helpers (codeFenceOpenMarker, isCodeFenceClose,
leadingRun) are kept local to this file to avoid touching files outside
the scope of the reviewed PR. If a future change wants to reuse them
across the doc package, they can be promoted then.

Tests:

- TestCheckDocsUpdateReplaceMultilineMarkdown: add 4 negative/positive
  cases — blank line inside backtick and tilde fences (no flag), blank
  line in prose while fence also has blanks (flag wins), fenced code
  with no blank lines (no flag).

- TestCheckDocsUpdateBoldItalic: add 9 cases — ***text*** / **_text_** /
  _**text**_ inside fenced code (backtick and tilde), inside inline
  code spans, and escaped \***text*** / \*\*_text_\*\* (none flagged);
  plus two positive cases to verify the strip doesn't over-sanitize
  (real emphasis in prose still fires when inline/fenced code is nearby).

* fix(doc): close CommonMark gaps and add three more combined-emphasis shapes

Self-review of the first commit turned up three issues:

- isCodeFenceClose was strict on exact marker length. Per CommonMark
  §4.5, a closing fence must be at least as long as the opener, not
  exactly the same length. A 3-backtick open legitimately closed by a
  4-backtick closer (used to embed triple-backticks inside the code
  sample) was left open-ended, causing the rest of the document to be
  treated as code and both checks to silently skip it.

- Both fence helpers accepted any amount of leading whitespace because
  they ran on strings.TrimSpace(line). CommonMark allows 0..3 leading
  spaces before a fence marker; 4+ spaces (or any tab in leading
  position, which expands to 4 columns) makes the line indented code
  block content, not a fence open/close. Indented fence-like lines now
  correctly remain prose and blank lines around them are detected.

- The bold/italic check only covered three of the six documented
  combined-emphasis shapes. Added ___text___, __*text*__, and
  *__text__* so parity with the asterisk variants is complete. The
  regex set is now table-driven (combinedEmphasisPatterns) to make
  adding future shapes a one-line change.

Implementation changes:

- New fenceIndentOK(line) helper: returns (body, true) for 0..3 leading
  spaces with no tabs, else (_, false). Used by both codeFenceOpenMarker
  and isCodeFenceClose.
- isCodeFenceClose now counts the fence-char run and accepts any run
  length >= len(marker), with trailing whitespace only.
- checkDocsUpdateBoldItalic replaced three named var regexes with a
  table of six {shape, re} entries and a single early-exit loop.
- Updated docsUpdateWarnings top docstring to list all six shapes.
- Noted the known limitation of stripEscapedEmphasisMarkers around
  doubled backslash escapes ("\\***text***"), which is a false negative
  we accept in exchange for keeping this a simple string replace.

Test additions (docs_update_check_test.go):

- Fence close: longer-marker close correctly ends fence; real prose
  blank after a longer-close fence is still detected.
- Indentation: 4-space indented fence-like line is not a fence open,
  so a surrounding blank line still flags; tab-indented variant same;
  3-space indented fence is still a real fence.
- New shapes: ___text___ positive + all three negative-guards (fenced
  code, inline code, escaped); __*text*__ and *__text__* positive +
  fenced/inline negative-guards; plus two composition tests to ensure
  the strip does not over-sanitize across the six-regex alternative set.

All 53 sub-tests in this file pass; go vet and gofmt are clean.

---------

Co-authored-by: fangshuyu-768 <shuyufang768@outlook.com>

* fix(doc): address CodeRabbit review on docs +update warnings (#581)

Two CodeRabbit nits from #569:

1. Unit test hint assertion only checked for `delete_range` in the
   remediation message; the companion `insert_before` half of the
   guidance could regress undetected. Broaden the assertion to require
   both tokens so a future edit that drops half the remediation
   produces an immediate test failure.

2. No E2E coverage proved the dry-run contract in the PR description
   ("Not emitted in dry-run mode — kept quiet during planning"). The
   helper itself is unit-tested, but nothing caught a regression where
   a later refactor wired docsUpdateWarnings into the DryRun path.

   Add tests/cli_e2e/docs/docs_update_dryrun_test.go:
   TestDocs_UpdateDryRunSuppressesSemanticWarnings invokes
   `docs +update --dry-run --mode=replace_range --markdown "***x***\n\nb"`
   — an input crafted to trip BOTH pre-write warnings — and asserts
   neither the "warning:" prefix, the blank-line message, nor the
   combined-emphasis message appears on stdout or stderr.

   Note: the file needs -f to add because .gitignore has a bare
   `docs/` rule that accidentally matches tests/cli_e2e/docs/. The
   existing tracked files under that directory predate the rule; new
   additions have to be force-added until the ignore pattern is
   narrowed. Not worth rewriting .gitignore for one file.

Verified manually that the new E2E fails cleanly when warnings are
injected into DryRun and passes again after reverting — the test has
real regression-detection power, not just a sticker.

Co-authored-by: fangshuyu-768 <shuyufang768@outlook.com>
2026-04-21 12:38:48 +08:00
zhoule.hhh
24e0bb38eb fix(whiteboard): register +media-upload shortcut and add whiteboard parent type
- Register DocMediaUpload in doc/shortcuts.go (was defined but never
  registered, so lark-cli docs +media-upload was unavailable)
- Rename MediaUpload to DocMediaUpload for consistency with
  DocMediaInsert/DocMediaPreview/DocMediaDownload
- Add whiteboard to --parent-type flag description
- Update --parent-node description to mention board_token for whiteboard

Drive +upload (parent_type=explorer) produces file tokens that the
whiteboard API does not recognize (500 error). The correct approach
is docs +media-upload with parent_type=whiteboard.
2026-04-21 11:31:10 +08:00
河伯
9057299430 feat(doc): add --selection-with-ellipsis position flag to +media-insert (#335)
* feat(doc): add --after-keyword/--before-keyword flags to +media-insert

Allows inserting images/files at a position relative to the first block
whose plain text matches a keyword (case-insensitive substring match).

- Add --after-keyword: insert after the matched root-level block
- Add --before-keyword: insert before the matched root-level block
- Flags are mutually exclusive; default behavior (append to end) unchanged
- fetchAllBlocks: paginated block listing (up to 50 pages × 200 blocks)
- extractBlockPlainText: covers text, heading1-9, bullet, ordered, todo, code, quote
- findInsertIndexByKeyword: walks parent_id chain to resolve nested blocks to their root-level ancestor
- DryRun updated to show block-listing step when keyword flag is set

* test(doc): add fetchAllBlocks pagination and keyword dry-run coverage

- TestFetchAllBlocksPaginationViaExecute: exercises fetchAllBlocks via a
  full Execute flow with --after-keyword, covering multi-page block listing
  (fetchAllBlocks was previously at 0% coverage)
- TestDocMediaInsertDryRunWithAfterKeyword: verifies that the dry-run output
  includes a block-listing step and mentions "search blocks" in the
  description when --after-keyword is provided

fetchAllBlocks coverage: 0% → 76.2%

* refactor(doc): use MCP locate-doc for keyword-based block positioning

Replace fetchAllBlocks + keyword scan with MCP locate-doc tool,
consistent with DriveAddComment. Flags changed from --after-keyword /
--before-keyword to --selection-with-ellipsis + --before.

* fix(doc): show <locate_index> in dry-run create-block when selection is set

When --selection-with-ellipsis is provided, the create-block step in dry-run
now shows index: "<locate_index>" instead of "<children_len>" to accurately
reflect that the insertion position is computed from MCP locate-doc, not
appended to end.

* fix(doc): address CodeRabbit review on +media-insert selection feature

- Validate: reject blank/whitespace --selection-with-ellipsis unconditionally
  so a mis-typed empty value cannot silently fall back to append-mode.
- Redact the raw selection string when logging to stderr and when emitting
  error messages. --selection-with-ellipsis is copied verbatim from document
  content and may contain confidential text; the new redactSelection helper
  keeps a short prefix and rune count so operators can still identify the
  failing selection.
- Harden the after/before mode tests: root children now have three entries
  so the two modes land on different indices, and the tests decode the
  create-block request body to assert the computed `index` actually reaches
  the /children API. A regression that ignored --before would now fail.
- Harden the nested-block test so it exercises the fallback parent-walk:
  the anchor is now two levels deep (blk_grandchild under blk_section_child
  under blk_section), which forces the walk to fetch the intermediate block
  via GET /blocks/{id} to discover the root-level ancestor.

* fix(doc): harden +media-insert selection UX on top of #335 (#577)

Follow-up to #335 review: closes a handful of UX and robustness gaps in
the new --selection-with-ellipsis flow.

- Flag description rewritten to make the "insert at the top-level
  ancestor" semantics explicit — when the selection is inside a callout,
  table cell, or nested list, media lands outside that container, not
  inside. Also calls out the 'start...end' disambiguator.

- locate-doc is now called with limit=2 so an ambiguous selection
  (same phrase in more than one block) surfaces a stderr warning
  pointing at 'start...end', instead of silently picking the first
  match. The first-match return behaviour is unchanged.

- When the anchor is nested below the root, locateInsertIndex now
  logs a note to stderr naming the walk depth and the root-level
  ancestor's insert index. Users don't have to guess why the image
  landed outside the callout they were editing.

- maxDepth bumped 8 → 32 with a comment explaining the invariants:
  `visited` is the real cycle guard, `maxDepth` is belt-and-suspenders.
  32 comfortably exceeds real docx nesting depth so a deeply-nested
  but well-formed anchor is no longer silently rejected.

- Comment added before the parent-walk loop noting why the API calls
  are serial (each level's parent_id is only known after the previous
  GET returns; can't be batched or parallelised).

Tests:

- TestLocateInsertIndexWarnsOnMultipleMatches: stubs two matches,
  asserts the stderr warning names the ambiguity and mentions
  'start...end', and that the first-match insert index is unchanged.
- TestLocateInsertIndexLogsNestedAnchor: anchor two levels below root,
  asserts stderr carries the "nested … top-level ancestor" note.
- TestLocateInsertIndexCycleDetection: malformed parent chain with
  blk_x.parent = blk_y and blk_y.parent = blk_x, neither reachable
  from root. Registering a single GET /blocks/blk_y stub also bounds
  the call count — a regression that broke `visited` tracking would
  either hang or fail via httpmock's extra-call guard.

Co-authored-by: fangshuyu-768 <shuyufang768@outlook.com>
2026-04-20 23:24:11 +08:00
fangshuyu-768
9e891b758e test(doc): harden markdown_fix pipeline with invariant tests (#576)
Adds 5 invariant-level tests on top of #469's transforms:

- TestFixExportedMarkdownIdempotent — f(f(x)) == f(x) across rich
  fixtures (kitchen sink, CJK, nested containers). Protects the core
  round-trip promise from future transform interactions that rewrite
  their own output.
- TestFixExportedMarkdownPreservesFencedCodeByteForByte — packs every
  pipeline-touching shape into a fence and asserts byte-identical output.
  Code samples must never be silently rewritten by a formatting pass.
- TestFixExportedMarkdownPreservesCRLF — CRLF input preserves line
  endings AND still triggers transforms. Windows-authored markdown
  should not be silently LF-normalized.
- TestFixExportedMarkdownTransformInteractions — composition regressions:
  nested-list + trailing-space bold, text→list transition, callout
  containing list with emphasis, heading vs paragraph bold.
- TestNormalizeNestedListIndentationDocumentedSkips — locks in the
  deliberate no-op branches (odd-space indent, blank-line loose-list
  sibling, 4-space indented code block, parentless two-space) as an
  explicit spec so future heuristic tweaks surface in the test diff.

All transforms, fixtures, and expectations are derived from the head of
PR #469. No production code changes.

Co-authored-by: fangshuyu-768 <shuyufang768@outlook.com>
2026-04-20 22:54:51 +08:00
高春晖
293a9f896f fix(doc): preserve round-trip formatting in fetch output (#469)
* fix(doc): preserve round-trip formatting in fetch output

- trim leading spaces inside bold and italic emphasis exported by docs +fetch

- normalize nested list indentation to avoid flattening and literal text on re-import

- add regression tests for emphasis spacing and nested list indentation

* fix(doc): avoid false positives in markdown spacing fixes

- keep literal * x * and ** x ** text unchanged

- only normalize indented nested list markers when a parent list item exists

- add regression coverage for both CodeRabbit findings

* fix(doc): 修正嵌套列表缩进的空行误判

- 遇到空行时停止向上查找父级列表项,避免把 loose list sibling 误改成嵌套列表
- 避免把列表项中的四空格缩进代码块误改成 tab 缩进列表项
- 补充两个回归测试,并更新 fixBoldSpacing 注释使其与当前实现一致

* fix(doc): 修复 Markdown emphasis 空格回写

- 将 fixBoldSpacingLine 改为按星号 run 扫描,修复 ** hello **、* hello * 和同一行多个 italic span 的空格清理
- 保留 inline code、heading 和 *** hello** 这类近邻字面量,避免误改 emphasis nesting
2026-04-20 22:40:56 +08:00
liangshuo-1
0a0cdc8879 chore(release): v1.0.15 (#575) v1.0.15 2026-04-20 22:03:08 +08:00
zhengquanbin
67e51ec8d7 fix: base role view & record default perm on edit(#530)
fix: address coderabbit review comments on role-config docs

- Update `allow_edit` field description to reflect conditional default:
  `true` when table perm is `edit`, `false` for `read_only` or explicit restriction
- Move `record_operations.delete` out of "默认关闭项" into new "默认开启项(条件性)"
  section to accurately reflect it is default-included when `perm = edit`
- Add `view_rule.allow_edit` to "默认开启项(条件性)" section with same logic

Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-20 21:08:04 +08:00
sang-neo03
5943a20e2b Feat/auth sidecar proxy (#532)
* feat(sidecar): add sidecar proxy for sandbox credential isolation

Keep real secrets (app_secret, access_token) out of sandbox environments.
CLI instances inside sandboxes connect to a trusted sidecar process via
HTTP; the sidecar verifies HMAC-signed requests and injects real tokens
before forwarding to the Lark API.

Key components:

- `auth proxy` subcommand to start the sidecar server (build tag: authsidecar)
- Noop credential provider returns sentinel tokens in sidecar mode
- Transport interceptor rewrites requests to sidecar with HMAC signature
- Env provider yields to sidecar provider when AUTH_PROXY is set
- Supports both feishu and lark brand endpoints

* feat(sidecar): implement priority ordering for credential providers

* feat(sidecar): strip client-supplied auth headers and improve shutdown logging

* feat(sidecar): buffer request body to prevent HMAC mismatches on read errors

* feat(sidecar): fix CI

* refactor(sidecar): publish protocol package and move server to reference demo

  The sidecar server is no longer shipped as a `lark-cli auth proxy`
  subcommand. Instead, the CLI provides only the standard sidecar *client*
  (via `-tags authsidecar`), while the wire-protocol utilities are exposed
  as a public package for integrators to implement their own server.

  Changes:
  - Move `internal/sidecar/` → `sidecar/` so external integrators can
    import HMAC signing, headers, sentinels and address validators.
  - Remove `cmd/auth/proxy.go`, `proxy_stub.go`, `proxy_test.go` and the
    conditional registration in `cmd/auth/auth.go`.
  - Add `sidecar/server-demo/` — a reference server implementation behind
    the `authsidecar_demo` build tag. It reuses the lark-cli credential
    pipeline for local development; production integrators are expected
    to replace the credential layer with their own secrets source.
  - Update all internal imports from `internal/sidecar` to `sidecar`.

  Rationale:
  - Each integrator has different secrets management / HA / multi-tenant
    requirements, so a one-size-fits-all server doesn't belong in the
    shipped CLI.
  - Keeping the client in-tree guarantees all sandbox-side code stays
    protocol-compatible without a second repo to sync.
  - The public `sidecar/` package pins the wire protocol as a stable
    contract third-party servers must conform to.

  Build matrix after this change:
  - `go build`                         → standard CLI, no sidecar code
  - `go build -tags authsidecar`       → CLI + sidecar client
  - `go build -tags authsidecar_demo \
      ./sidecar/server-demo/`          → reference server binary

  No production users are affected today because the server was not yet
  released; existing sidecar-client users are unchanged.

* feat(sidecar): close 5 pre-release security gaps
  - Server: enforce https-only target (no path/query/userinfo), pin
    forwardURL to https:// — blocks cleartext token leak
  - Protocol v1: canonical now covers version/identity/auth-header,
    blocks identity-flip replay within drift window
  - Client: ValidateProxyAddr requires loopback or same-host alias,
    rejects userinfo and https (interceptor is http-only); cross-machine
    is out of scope
  - Build: non-authsidecar builds exit(2) when AUTH_PROXY is set,
    preventing silent fallback to env credentials
  - Demo: whitelist auth-header to Authorization / X-Lark-MCP-{UAT,TAT},
    blocks token injection into Cookie / UA / X-Forwarded-For exfil paths
2026-04-20 20:24:51 +08:00
kongenpei
cd666422ac fix(base): preserve attachment metadata on base uploads (#563)
* fix: preserve attachment metadata on base uploads

* test: cover attachment mime detection

* fix: address attachment upload review feedback

* fix: preserve source extension for attachment mime detection

* fix: avoid registry test refresh data race

* Revert "fix: avoid registry test refresh data race"

This reverts commit c1d12d0cf1.

---------

Co-authored-by: kongenpei <kongenpei@users.noreply.github.com>
2026-04-20 19:14:45 +08:00
mazhe-nerd
9acd121259 fix: update install message (#529) 2026-04-20 12:03:16 +08:00
caojie0621
1262aac480 fix(sheets): normalize single-cell range in +set-style and +batch-set-style (#548)
/style and /styles_batch_update require full "A1:A1" form and reject
single-cell shorthand "A1". +set-style was using normalizeSheetRange
(prefix-only) and +batch-set-style passed --data through unchanged,
so both failed with `wrong range` when callers supplied a single cell.

Switch +set-style to normalizePointRange, and walk each ranges[]
entry in +batch-set-style through normalizePointRange before sending.
Multi-cell spans pass through unchanged.
2026-04-18 23:29:14 +08:00
caojie0621
abb02cd46c feat(sheets): add float image shortcuts (#494)
Implement +create-float-image, +update-float-image, +get-float-image,
+list-float-images, and +delete-float-image shortcuts wrapping the v3
spreadsheet float_image API. The create reference doc includes the
prerequisite media upload step with the correct parent_type
(sheet_image) to avoid common token mismatch errors.
2026-04-18 23:27:11 +08:00
haozhenghua-code
db7d3cb64d fix(im): cap basic_batch user_ids at 10 per API limit (#551)
The POST /contact/v3/users/basic_batch endpoint caps user_ids at 1~10
per request, but batchResolveByBasicContact was chunking by 50. When
user identity needed to resolve >10 unresolved sender names, the
single oversized request was rejected, causing the batch resolver to
bail out and leave sender names empty for the rest.

Lower batchSize to 10 and add a unit test that exercises 25 missing
IDs and asserts they are sent as 10 / 10 / 5.
2026-04-18 18:41:30 +08:00
Paulazaaza-dev
5134719da9 feat: add remind/initiated method (#554)
Change-Id: I27c00d96a9478efbf39fbc1118bb6bcb75fe6b14
2026-04-18 17:46:23 +08:00
zkh-bytedance
5a0e1d3dd9 fix(whiteboard): Deprecate old lark-whiteboard-cli skill (#547) 2026-04-18 00:36:56 +08:00
syh-cpdsss
09e60eeaf4 fix: add OKR API restriction in SKILL.md (#546)
Change-Id: Ic2734f1da8525ec48f091ccd72c96921b9bb0fc1
v1.0.14
2026-04-17 22:36:50 +08:00
liangshuo-1
4f90fd3b77 chore: cut v1.0.14 (#544)
Change-Id: I601e893a048a155635ecd75d5c433b99c42e55fe
2026-04-17 21:46:09 +08:00
chanthuang
6212513c43 feat(mail): add email priority support for compose and read (#538)
* feat(mail): add email priority support for compose and read

Write: all compose shortcuts (+send, +reply, +reply-all, +forward,
+draft-create) accept --priority (high/normal/low) which sets the
X-Cli-Priority EML header. +draft-edit accepts --set-priority.

Read: normalizeMessage now infers priority from label_ids
(HIGH_PRIORITY/LOW_PRIORITY), with priority_type as fallback.

Change-Id: Ib5bc4e99331c6ce0d3850865825fcd1ff2183f0c
Co-Authored-By: AI

* docs(mail): add --priority and --set-priority to skill references

Update 6 skill reference docs: +send, +reply, +reply-all, +forward,
+draft-create add --priority param; +draft-edit adds --set-priority.

Change-Id: I75d13fbf6a5ca4dfbf76e84fe39e4ee55b689751
Co-Authored-By: AI

* test(mail): add unit and integration tests for --priority

- helpers_test.go: cover parsePriority (valid/invalid/case/whitespace)
  and applyPriority (empty vs non-empty) end-to-end via EML builder
- mail_draft_create_test.go: verify --priority propagates to X-Cli-Priority
  header in the built EML, and no header when priority is empty

Change-Id: I62ca96b3e296b5898798cfa681f5efd4f101cb40
Co-Authored-By: AI

* test(mail): cover buildDraftEditPatch --set-priority and label-based priority

- helpers_test.go: TestBuildMessageOutput_PriorityFromLabels verifies
  HIGH_PRIORITY/LOW_PRIORITY labels map to priority_type_text, and that
  label values override the priority_type fallback field
- mail_draft_edit_test.go (new): cover --set-priority high/low/normal
  (set_header vs remove_header), invalid value rejection, and absence
  of priority op when the flag is unused

Change-Id: Idd5ace2fb812cf3eb329c79eeab3c8b9808fcf0b
Co-Authored-By: AI

* fix(mail): write priority_type to output when inferred from label_ids

buildMessageOutput only wrote priority_type_text but not priority_type
when priority was inferred from HIGH_PRIORITY/LOW_PRIORITY labels.
Also covers the case where label overrides an explicit priority_type field.

Change-Id: I7879976d21235b8006b5c8ebe6a413e2815354e1

* fix(mail): validate --priority in Validate so invalid values fail before dry-run/Execute

Change-Id: Ic277ab683967c47f28c892d3512b0ab745bd86f6

* test(mail): add TestValidatePriorityFlag to cover invalid --priority rejected in Validate

Change-Id: I7f12c0a0b0d15c491c28fdcb8729f2f648ba0244
2026-04-17 20:49:32 +08:00
caojie0621
e8df0ea63e feat(drive): support sheet cell comments in +add-comment (#518)
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.
2026-04-17 20:05:08 +08:00
nickzhang
6d0d687be2 feat(doc): add --file-view flag to +media-insert (#419)
* 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>
2026-04-17 19:27:00 +08:00
syh-cpdsss
148a04a7f8 Feat: Add OKR business domain (#522)
* feat: okr domain

Change-Id: I1877c56e33e3620b696351ed9e4c8615dbe17c4b

* feat: okr skill update

Change-Id: I1877c56e33e3620b696351ed9e4c8615dbe17c4b
2026-04-17 18:04:15 +08:00
liujinkun2025
ba19bd9f93 feat(wiki): improve wiki skill docs and add wiki domain template (#512)
- Reorder sections, fix formatting and indentation in SKILL.md
- Add spaces.create method and its scope to API resources and permission table
- Add wiki domain template for skill-template

Change-Id: Ib03dacc02cf2b42f807615c2adedbf79694b5dc0
2026-04-17 18:03:21 +08:00
zkh-bytedance
830fb3bbe5 refactor(skills): introduce lark-doc-whiteboard.md and streamline whiteboard workflow (#502)
- add lark-doc/references/lark-doc-whiteboard.md: defines role boundaries
  between lark-doc and lark-whiteboard, step-by-step doc↔whiteboard
  coordination flow, and semantic-to-chart-type mapping table
- lark-doc-create.md: tighten post-create whiteboard flow (step 2 now
  directly references the "渲染 & 写入画板" section); strengthen 主动画板
  guideline with explicit placeholder syntax, prohibition on PNG/SVG
  substitution, and concrete routing examples
- lark-whiteboard/SKILL.md: upgrade to v0.2, rewrite with structured
  quick-decision table, creation/modification workflows, render routing
  table, and dry-run write guard
- extract rendering routes into routes/{dsl,mermaid,svg}.md; add
  per-chart scene guides under scenes/
- remove lark-whiteboard-cli/SKILL.md (absorbed into lark-whiteboard)
2026-04-17 17:51:50 +08:00
Yuxuan Zhao
1ad7cfab5b test: inject user env only for cli e2e user commands (#541) 2026-04-17 17:33:37 +08:00
Yuxuan Zhao
5280517d4b Feat/cli e2e tests with UAT (#528)
* test: expand and stabilize cli e2e workflows

* ci: run deadcode with test entrypoints
2026-04-17 16:57:17 +08:00
JackZhao10086
3ad6f2fac4 Revert "Add client_secret to device authorization request (#517)" (#539)
This reverts commit 663c24aadf.
2026-04-17 16:29:04 +08:00
feng zhi hao
be79485fe3 feat: mail support scheduled send (#534)
feat: mail support scheduled send (#534)
2026-04-17 15:41:42 +08:00
zgz2048
94bba91224 feat(base): auto grant current user for bot create and copy (#497)
* 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>
2026-04-17 14:30:47 +08:00
wangqiucheng2bd
0d50616e77 feat(base): add identity priority strategy and error handling (#505)
* feat(base): add identity priority strategy and 91403 error handling

Establish user-first identity selection with graceful degradation to bot,
and add no-retry rule for error code 91403 (permission denied on Base).

* fix(base): add 91403 early-exit before identity fallback logic

Move non-retryable error code check (e.g. 91403) to a dedicated step
before the user/bot fallback decision, resolving conflicting instructions
between the error table and the execution rules.


* Update SKILL.md

* Update SKILL.md

---------
2026-04-17 13:46:05 +08:00
JackZhao10086
d5784eac28 feat(auth): improve login scope handling and messages (#523)
* feat(auth): improve login scope handling and messages

- Add AuthorizedUser message to display current authorized account
- Update scope mismatch message wording to be more accurate
- Reorganize login success output to show scope issues first
- Remove redundant success message when scope issues exist

* fix(auth): update login success message wording from "login" to "authorization"

Update both Chinese and English login success messages to use "authorization" instead of "login" for consistency with the authentication flow. Also update corresponding test cases to match the new wording.

* test(auth): update login test for missing scope case

Update test assertions to verify correct error messages when requested scopes are not granted. Remove checks for success message in this scenario.
2026-04-17 12:16:29 +08:00
Uğur Tafralı
663c24aadf Add client_secret to device authorization request (#517) 2026-04-17 11:39:23 +08:00
zero-my
6ad25cd452 docs(task): document custom_fields and custom_field_options API resources and permissions (#524) 2026-04-16 23:40:55 +08:00
liangshuo-1
c442fa27d1 chore: cut v1.0.13 with reviewed release notes (#519)
Change-Id: If9a08002588cc9ae96280bdd8bbc4a05ba0f92a1
v1.0.13
2026-04-16 21:09:55 +08:00
ILUO
35a8288baf feat: default skip_task_detail in docs +fetch (#471) 2026-04-16 19:15:57 +08:00
tuxedomm
79379fbc6f fix(im): preserve original URL filename for uploaded file messages (#514)
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
2026-04-16 19:01:21 +08:00
liangshuo-1
d0ab8ee7dc ci: consolidate workflows into layered CI pyramid with results gate (#510)
* ci: consolidate 6 workflows into layered CI pyramid with results gate

Merge tests.yml, lint.yml, coverage.yml, cli-e2e.yml, gitleaks.yml,
and license-header.yml into a single ci.yml with fail-fast layering:

- L1 fast-gate: build, vet, gofmt, go mod tidy
- L2 quality: unit-test, lint, coverage (40% threshold + Codecov), deadcode (incremental)
- L3 e2e: dry-run (no secrets) + live (with secrets, fork-skip)
- L4 security: gitleaks, govulncheck, go-licenses, license-header

Results gate aggregates all jobs as the single required check for
branch protection.

Also adds:
- arch-audit.yml: weekly cron for dead code, complexity, deps, E2E gaps
- .golangci.yml: depguard shortcuts-no-raw-http, forbidigo fmt.Print/log.Fatal
- AGENTS.md: E2E testing conventions, updated pre-PR checks

Change-Id: I2e21067a9e9e12d366d1b1a092227e9f7d60fe41
2026-04-16 18:16:31 +08:00
zhouyue-bytedance
1608f95632 refactor: 删除第6、7章(参考文档和命令分组) (#500)
- 删除第6章:参考文档列表(重复了第2章的 reference 信息)
- 删除第7章:命令分组表(重复了第2章的模块导航)

这两章是静态索引,维护成本高且容易过时。第2章已提供完整的导航和 reference 链接。
2026-04-16 17:20:34 +08:00
zhouyue-bytedance
e10bf8eca2 fix: 统一 record 批量写入限制为 200 条,移除'建议'改为强制要求 (#499)
- 第106行:批量单次改为 200 条(与 lark-base-record-batch-create.md 对齐)
- 第290行:批量写入改为 200 条(与 API 限制对齐)
- 第291行:连续写入改为'必须串行'(强制要求,非建议)
- 第314行:错误代码 1254104 改为 200 条限制

修复 SKILL.md 与 reference 文档的数字不一致问题
2026-04-16 15:41:26 +08:00
syh-cpdsss
c1d6042552 fix: add atomic overwrite for whiteboard +update (#483) 2026-04-16 14:06:36 +08:00
chenxingtong-bytedance
656c16a47f feat(im): support user access token upload file/media/audio/image and send the resource message (#474)
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
2026-04-16 14:04:25 +08:00
wittam-01
9dfaff4664 feat: add drive create-folder shortcut and wiki node auto-grant (#470)
Change-Id: I1acd001a1d4616bc5a957cad437e5aa4f1afeb51
2026-04-16 11:19:19 +08:00
liangshuo-1
f0e724cbd4 chore: cut v1.0.12 with reviewed release notes (#493)
This release prep captures the version bump and changelog entry for v1.0.12 without pulling unrelated workspace edits into the release branch.

Change-Id: Ib343337c4851b7cc15a52dd0068795a92092b781
Constraint: Keep the release PR scoped to package version and changelog only
Rejected: Include .gitignore and local workspace files | unrelated to this release PR
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep release notes aligned with shipped changes only; exclude reverted work from summaries
Tested: make unit-test
Tested: go mod tidy
Tested: go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev=origin/main
Not-tested: Manual tag/release publishing flow
v1.0.12
2026-04-15 21:29:44 +08:00
chanthuang
03ba542a60 Revert "feat: mail support scheduled send (#449)" (#492)
This reverts commit 44e7b5b477.

Change-Id: I0b0c6454cf5ea4c15169a3c683b91795ef880478
2026-04-15 21:04:27 +08:00