* 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.
* 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
* 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>
- 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.
* 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>
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>
* 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
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>
* 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
/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.
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.
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.
* 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
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>
- 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
* 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>
* 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
---------
* 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.
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
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
* 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