mirror of
https://github.com/larksuite/cli.git
synced 2026-07-05 07:31:22 +08:00
feat/sidecar-remote-https
48 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
57ba4fae61 |
feat: unconditionally inject --format flag for all shortcuts (#1156)
* feat: unconditionally inject --format flag for all shortcuts Removes three HasFormat guards in runner.go so every shortcut gets --format regardless of the Shortcut.HasFormat field value. Shortcuts that already define a custom 'format' flag in Flags[] are skipped to avoid redefinition panics (e.g. mail +triage, +watch). HasFormat is retained in the struct but marked deprecated. Change-Id: I5e8fe07e839d5aed4cefaf7d753dabbaee68fb6e * test: isolate config dir in format-universal test Change-Id: I3a59942aa8a6753cd949ca42f2a19a72f032ff55 * test: revert unnecessary config-dir isolation (mount-only test) Change-Id: I0146e5a2f57f5419863bdeeaa1a662fd8f70bddf |
||
|
|
99e314fe0b |
feat(errs): typed envelope contract for auth-domain errors (#1135)
Every failure on the authentication, authorization, and configuration
path now surfaces as a typed structured error instead of an ad-hoc
envelope. Users and scripts that consume CLI output get:
- a fixed nine-category taxonomy on the wire, each mapped to a
stable shell exit code (authentication/authorization/config = 3,
network = 4, internal = 5, policy = 6, confirmation = 10)
- identity-aware detail fields (missing_scopes, requested_scopes,
granted_scopes, console_url, log_id, retryable, hint) carried
uniformly on the envelope
- a single canonical policy envelope at exit 6; the legacy
auth_error carve-out is retired
- per-subtype canonical message + hint that preserves Lark's
diagnostic phrasing and routes recovery to the right actor:
app developer (app_scope_not_applied), user (missing_scope,
token_scope_insufficient, user_unauthorized), or tenant admin
(app_unavailable, app_disabled)
- wrong app credentials classify as config/invalid_client whether
surfaced by the Open API endpoint (99991543) or the tenant
access-token mint endpoint (10003 / 10014), instead of
collapsing to a transport error or api/unknown
- local shortcut scope preflight emits the same
authorization/missing_scope envelope (identity + deterministic
missing-scope set) used by the post-call permission path, so AI
consumers read the same structured shape from precheck and from
server-returned permission denial
- streaming download/upload failures keep the same network subtype
split (timeout / TLS / DNS / transport) as the non-stream path
instead of collapsing every cause to a generic transport failure
- console_url is carried only on the bot-perspective
app_scope_not_applied envelope (where the recovery action is
"developer applies the scope at the developer console"); the
user-perspective missing_scope envelope drops the field, since
the only actionable user recovery is `lark-cli auth login --scope`
and pointing an end user at a console they cannot modify is
misleading
- bind workflows (Hermes / OpenClaw / lark-channel) flatten dynamic
Type tags to wire 'config' with the original module name kept
as a metric label
All 10 typed errors are cause-bearing, nil-safe on .Error() and
.Unwrap(), and defensively clone slice setter inputs. Four lint
rules (CheckNilSafeError / CheckBuilderImmutable / CheckUnwrapSymmetry
/ CheckBuildAPIErrorArms) lock these invariants on migrated paths.
|
||
|
|
3b770558e5 | feat: decouple --lang preference from TUI display language (#1132) | ||
|
|
b91f6a23f3 | fix: include log_id in base attachment media errors (#1133) | ||
|
|
f00261da9f | fix(drive): support doubao drive inspect URL variants (#1106) | ||
|
|
fe72e41fb2 |
feat(errs): add structured CLI error contract (#984)
Introduce a typed error contract framework for lark-cli so in-process
Go callers can branch via errors.As(&errs.XxxError{}) and shell scripts,
AI agents, and protocol adapters can branch on stable JSON type/subtype
fields instead of regex-parsing free-form messages.
Adds:
- Canonical taxonomy under errs/ (9 categories + typed Error structs
embedding a shared Problem, RFC 7807-aligned)
- Centralized Lark code metadata + identity-aware BuildAPIError dispatch
- Typed JSON envelope writer alongside the legacy envelope writer
- MCP / OAuth (RFC 6750 Bearer) projection adapters
- Five CI lint guards preventing ad-hoc taxonomy drift
Backward compatibility: legacy *output.ExitError producers (ErrAPI,
ErrWithHint, Errorf, ErrBare) and business shortcuts that use them
continue to render the legacy envelope unchanged. SecurityPolicyError
wire format and exit code are preserved via a carve-out; taxonomy
migration is deferred to PR 2. Domain-specific business migration is
staged across PR 3+.
Framework-direct paths now return typed *errs.*Error: ErrAuth /
ErrValidation / ErrNetwork emit category literals on the wire
(authentication / validation / network), *core.ConfigError is promoted
at the cmd/root boundary with exit code aligned from 2 to 3, and Lark
API permission denials classified by BuildAPIError exit 3.
At the SDK boundary, WrapDoAPIError preserves any already-classified
error (legacy *output.ExitError or typed *errs.*) so output.ErrAuth
from missing credentials surfaces with the auth category and exit 3
intact instead of being downgraded to a network error. Policy responses
classified by BuildAPIError (codes 21000 / 21001) extract challenge_url
and the canonical hint from the response body, matching what the
auth transport already surfaces at the HTTP layer; non-https
challenge URLs are dropped.
First PR in the feat/error-contract-* series.
|
||
|
|
8bc4ec3fff |
fix(common): escape special chars in multipart form filenames (#1037)
* fix(common): escape special chars in multipart form filenames
MultipartWriter.CreateFormFile concatenated the fieldname and filename
into the Content-Disposition header without escaping, so a filename
containing a double-quote, backslash, CR, or LF produced a malformed
header. For example, uploading `report "draft" v2.pdf` via
`task +upload-attachment` made the server see `filename="report "`
(truncated at the first internal quote) and drop the rest.
Drop the custom override and let CreateFormFile be promoted from the
embedded *multipart.Writer, which applies the stdlib's quoteEscaper
(backslash and double-quote get a backslash prefix; CR and LF get
percent-encoded). The Content-Type ("application/octet-stream") and
the wrapper API are unchanged, so the existing `task +upload-attachment`
call site is unaffected -- filenames with special characters just now
round-trip correctly.
Add helpers_test.go covering plain, quoted, backslashed, mixed, and
unicode filenames. The test asserts both the on-wire encoding and a
round-trip through mime.ParseMediaType (bypassing Part.FileName, whose
filepath.Base is platform-dependent for backslash on Windows).
* test(common): cover CR/LF/CRLF in multipart filename escaping
Per code-review feedback, extend the helpers_test.go cases table with
CR, LF, and CRLF filenames so the test exercises both legs of the
stdlib's quoteEscaper:
- backslash and double-quote use backslash escaping (quoted-pair);
these round-trip exactly through mime.ParseMediaType.
- CR and LF use percent encoding to prevent header injection; the
MIME parser does not decode percent escapes, so the read-side
filename param contains literal "%0D"/"%0A".
The cases table grows a wantParsed column so each case can declare its
expected post-parse value (same as filename for backslash-escaped chars,
percent-encoded for CR/LF).
* refactor(common): polish doc comments and regroup test cases
Two follow-up tweaks suggested by a re-read of the PR:
- helpers.go: stop naming the stdlib's internal `quoteEscaper` in the
doc comment. Describe the observable behaviour ("escapes special
characters") instead, so the comment stays valid if the stdlib ever
renames or reimplements its escaping.
- helpers_test.go: rename the vague `with both` case to
`backslash and quote`; split the table-driven cases into three
visually-separated groups (happy path / backslash escaping /
percent encoding) so it is obvious why two cases have a different
wantParsed than filename.
No behaviour change; tests still pass 8/8.
* test(common): drop CR/LF filename cases that depend on Go 1.24+ stdlib
CI runs against the toolchain pinned in go.mod (1.23.0), whose
multipart/Writer.quoteEscaper escapes only backslash and double-quote.
Percent-encoding of CR and LF was added to the stdlib later, so the
three CR / LF / CRLF cases I added on review feedback fail on CI: the
literal CR/LF lands in the Content-Disposition header and the parser
reports `malformed MIME header: missing colon`.
Drop those three cases. The fix in the prior commits still covers the
real-world bug — backslash and double-quote in filenames — which is
what the original `report "draft".pdf` example demonstrates. CR or LF
in a filename is essentially never legal on any supported OS, so
leaving that edge case to a future stdlib upgrade keeps the test
stable across toolchains.
Also dropped the now-unused wantParsed column from the cases table:
with only round-trippable characters left, mime.ParseMediaType returns
the original filename byte-for-byte, so a single tc.filename comparison
suffices.
---------
Co-authored-by: Wang-Yeah623 <Wang-Yeah623@users.noreply.github.com>
|
||
|
|
6d1f9980fa |
fix: annotate auto-grant permission failures with required_scope and console_url (#1045)
When AutoGrantCurrentUserDrivePermission encounters lark code 99991672/99991679, extract permission_violations from the underlying ExitError and surface lark_code, required_scope, and console_url on the result map. Override the generic fallback hint with one pointing at the developer console — the concrete next step a user can take. Refactor extractRequiredScopes / SelectRecommendedScope wrapping / console URL construction out of cmd/root.go into internal/registry/scope_hint.go so both the top-level enrichPermissionError path and the best-effort sub-call path in shortcuts/common share one implementation. Change-Id: Ida63ed160d1167b7961b6faac5c2cf9b7f971c65 |
||
|
|
816927f8b8 |
fix: surface auto-grant failures via stderr and JSON hint (#1015)
When a resource is created with bot identity, the CLI attempts to auto-grant full_access to the current user. If the user open_id is missing or the grant API call fails, the result was only written to the JSON permission_grant field and easily overlooked. Changes: - Add stderr warnings when auto-grant is skipped or fails - Add 'hint' field to permission_grant JSON output with failure reason and actionable next step (e.g. auth login, check scope, retry) - Add end-to-end skipped/failed tests across all affected shortcuts (doc, drive, sheets, slides, wiki, markdown, base) Closes #963 |
||
|
|
e19e09019c |
feat: return real tenant URLs for drive +upload and markdown +create (#992)
Change-Id: I6b513eef57a3479c8971b3bb6cbf005cad3f8040 |
||
|
|
e6bc292575 |
fix(identitydiag): harden verify path and tighten status semantics (#961)
* fix(identitydiag): harden verify path and tighten status semantics Follow-ups to #957: - bound bot/user verify calls with a 10s timeout (mirrors the doctor endpoint probe) so a hanging server cannot wedge `auth status --verify` or `doctor` - return StatusNotConfigured (not StatusMissing) when the user-identity path is blocked by missing app config, matching the bot side - surface the `{code, msg}` envelope on bot-info HTTP 4xx responses so callers see why bot auth was rejected, not just the bare HTTP code - introduce identity{User,Bot,None} constants in cmd/auth/status.go and use the exported StatusMessage() in the human-readable note instead of raw status codes like "not_configured" - collapse the duplicated verify-failed identity construction in the user path into a local helper - cover the new failure paths with unit tests (HTTP 4xx with envelope, business error code, user server-rejected, expired user token, strict-mode user-only, missing app config for user) Change-Id: I581348a65f15b1452a6f48a3e3245d09257314ac * fix(identitydiag): decode bot/v3/info from "bot" field, not "data" `/open-apis/bot/v3/info` returns `{code, msg, bot: {...}}` — the bot payload is under `bot`, not `data` as the newer Lark API convention would suggest. The decoder was reading from a non-existent `data` field, so `envelope.Data.OpenID` was always empty and every successful verify was reported as `Bot identity: verify failed: open_id is empty`. The pre-existing test mocks used `{"data": {...}}` matching the buggy decoder, so unit tests passed while production reads of every Lark account failed verification. Fix: - change the JSON tag on the envelope from `json:"data"` to `json:"bot"` - update mocks in identitydiag and cmd/auth/status tests to emit `bot` Verified locally: `lark-cli doctor` now reports `bot_identity: pass` for both a normal account and a bot-only profile, restoring the behavior that #957 set out to deliver. Change-Id: Ib26dfdd5a0cc37d2d62537ae2bf5e854e67cb83c * fix(shortcuts/common): decode bot/v3/info from "bot" field, not "data" Same schema bug as the one fixed in identitydiag — `RuntimeContext. fetchBotInfo` reads from a non-existent "data" key, so every successful call would report "open_id is empty" once a caller starts depending on it. There are no production callers of `RuntimeContext.BotInfo()` yet (only tests + the `TestNewRuntimeContextWithBotInfo` helper), so this bug is dormant — but the pre-existing tests pass with the same wrong schema in their mocks, so the first real consumer would silently break. Fix: tag `json:"data"` → `json:"bot"` plus aligning the four mock fixtures in runner_botinfo_test.go. The Go field name `Data` is kept to minimize the diff; only the JSON contract is corrected. Change-Id: I11e1e871603e5349f8df29b1d58e35d07b628dfd |
||
|
|
4aa61db8b2 |
feat(drive): add +inspect shortcut for document URL inspection with wiki unwrapping (#947)
* feat(drive): add +inspect shortcut for document URL inspection with wiki unwrapping Implements #662: `lark-cli drive +inspect --url <url>` inspects any Lark/Feishu document URL to get its type, title, and canonical token, with automatic wiki URL unwrapping via get_node API. - Add ParseResourceURL (inverse of BuildResourceURL) in common - Extract FetchDriveMetaTitle as public shared helper - Add drive +inspect shortcut with wiki unwrapping support - Add skill reference docs and update SKILL.md - Dry-run E2E tests for docx URL, wiki URL, and bare token * refactor: move host validation from ParseResourceURL to +inspect ParseResourceURL is a general-purpose URL parser that should not hardcode domain lists — future Lark domains would silently break. Move isLarkHost/larkHostSuffixes to drive_inspect.go where host validation is a business decision of the +inspect command. Add E2E test for non-Lark host with Lark-like path. * refactor: remove host validation from +inspect Lark supports custom enterprise domains, so a hardcoded suffix list can never be exhaustive and would falsely reject valid URLs. Path-based matching in ParseResourceURL is sufficient; invalid URLs will fail naturally at the API call stage. |
||
|
|
de00343063 |
feat: add markdown +patch shortcut (#857)
* feat: add markdown +patch shortcut Change-Id: I8159941ff9dec4e5cbf0c757ec19ee172b302224 * fix: align markdown patch validation and dry-run Change-Id: I98079901e980b74998938afc4917b91a79689948 |
||
|
|
241952459d |
feat: add drive version shortcut (#841)
Change-Id: I87bb32c86e3c3362f541ccc6320c656eb795ec9b |
||
|
|
52e0129078 | feat(drive): add quick mode to status diff (#870) | ||
|
|
b4c9c09de0 |
feat(base): support batch record get and delete (#630)
* feat(base): support batch record get and delete * fix(base): address batch record PR feedback * docs(base): refine record skill routing * refactor(base): use batch record get and delete only * refactor(base): share record selection normalization * docs(base): clarify record get field projection help |
||
|
|
c100ca049e |
feat(cmdutil): support @file for params and data (#724)
* feat(cmdutil): support @file for --params/--data (issue #705) Inline JSON values for --params/--data are mangled by Windows PowerShell 5's CommandLineToArgvW. Stdin (-) was the only escape hatch but supports just one flag at a time. Extend ResolveInput to accept @<path> (read JSON from a file) and @@... (escape for a literal @-prefixed value), mirroring the shortcuts framework's resolveInputFlags semantics. With this, both --params and --data can be sourced from files in the same call, sidestepping shell quoting on every platform. - internal/cmdutil/resolve.go: add @path / @@ handling, trim file content like stdin does, error on empty path or empty file - internal/cmdutil/resolve_test.go: cover file read, whitespace trim, missing file, empty path, empty content, @@ escape, plus ParseJSONMap / ParseOptionalBody integration through @file - cmd/api/api.go, cmd/service/service.go: update --params/--data help text to mention @file Change-Id: I366aa0f5783fbec6f05403f7f542505098a98c82 * refactor(cmdutil): route @file through fileio.FileIO abstraction The first cut of @file support called os.ReadFile directly inside ResolveInput, bypassing the codebase's fileio.FileIO abstraction (SafeInputPath validation, pluggable provider). That diverged from how every other file-reading path works: BuildFormdata for --file uploads and the shortcuts framework's resolveInputFlags both go through fileio.FileIO.Open with explicit fileio.ErrPathValidation handling. Re-route @file through the same path: - ResolveInput, ParseJSONMap, ParseOptionalBody now take a fileio.FileIO; @path uses fileIO.Open which goes through SafeInputPath (control-char rejection, abs-path rejection, symlink-escape check) — same security posture as --file - cmd/api and cmd/service callsites pass Factory.ResolveFileIO(ctx); the upload path now reuses the resolved fileIO instead of resolving twice - Path-validation errors surface as `--params: invalid file path "...": ...` distinct from `--params: cannot read file "...": ...` for genuine I/O errors - Nil fileIO with an @path returns a clear "file input (@path) is not available" error - Tests use localfileio.LocalFileIO with TestChdir(t, dir), matching the existing fileupload_test.go pattern; absolute-path rejection and nil-fileIO are covered This makes the feature behave identically under any FileIO provider (including server mode) instead of being silently bound to the local filesystem. Change-Id: I878c4e8fb03f43f1f19afad75ec3af9cdab7a7f9 * refactor(cmdutil): share at-file input handling Change-Id: I92a6eb6ea8fd02054bf8f4925cd81807449d5e51 |
||
|
|
4d68e09537 |
feat(drive): add +push shortcut for one-way local → Drive mirror (#709)
* feat(drive): add +push shortcut for one-way local → Drive mirror
Mirrors a local directory onto a Drive folder: walks --local-dir,
recursively lists --folder-token, mirrors local subdirectory structure
(including empty dirs) onto Drive via create_folder, and for each
rel_path uploads new files, overwrites already-present files, or skips
them per --if-exists. With --delete-remote --yes, any Drive type=file
entry absent locally is removed; Lark native cloud docs (docx/sheet/
bitable/mindnote/slides) and shortcuts are never overwritten or deleted.
Overwrite hits POST /open-apis/drive/v1/files/upload_all with the
existing file_token in the form body and the response's `version` is
propagated to items[].version, mirroring the markdown +overwrite
contract. Files >20MB fall back to the 3-step
upload_prepare/upload_part/upload_finish path with a single shared fd
reused via io.NewSectionReader per block.
Output is a {summary, items[]} envelope; items[].action is one of
uploaded / overwritten / skipped / folder_created / deleted_remote /
failed / delete_failed.
--delete-remote is bound to --yes upfront in Validate, same pattern as
+pull's --delete-local: a stray flag never silently deletes anything.
Path safety reuses the canonical-root walk + SafeInputPath mechanics
from the sibling +status / +pull commands.
Scopes: drive:drive.metadata:readonly + drive:file:upload +
space:folder:create. space:document:delete is intentionally NOT in the
default set — the framework's pre-flight scope check would otherwise
block plain pushes and dry-runs for callers that haven't granted delete;
--delete-remote --yes relies on the runtime DELETE call to surface
missing_scope. The skill ref calls out the scope so users running
mirror sync can grant it upfront.
13 unit tests cover the upload/overwrite/skip/delete matrix, online-doc
protection, same-name conflict between local file and native cloud doc,
empty-directory mirroring, multipart, scope/path validation, and helper
correctness. 4 dry-run e2e tests pin the request shape.
* fix(drive +push): address review — failure semantics, default skip, scope pre-check, mirror wording
- Item-level failures now bump the exit code via output.ErrBare(ExitAPI)
while keeping the structured items[] envelope on stdout. The
--delete-remote phase is skipped entirely when any upload / overwrite /
folder step fails, so a partial upload never proceeds to delete remote
orphans (a half-synced state).
- Default --if-exists flipped from "overwrite" to the safer "skip": the
upload_all overwrite-version protocol field is still rolling out, so
the default no longer fails a first push against a pre-populated
folder. Callers must opt into "overwrite" explicitly.
- --delete-remote --yes now triggers a conditional space:document:delete
scope pre-check in Validate via the new RuntimeContext.EnsureScopes
helper, so a missing grant fails the run before any upload — instead
of after the upload phase, which would leave orphans uncleaned.
- Description, Tips and skill doc rewritten to call this a file-level
mirror (not a directory mirror): the command does not remove
remote-only directories or close gaps in directory structure that
exists only on Drive.
Tests:
- new TestDrivePushDefaultsToSkipForExistingRemote pins the new default
- new TestDrivePushSkipsDeleteAfterUploadFailure pins the half-sync
guard and the non-zero exit on item-level failure
- new TestDrivePushExitsZeroOnCleanRun pins the inverse
- existing tests that relied on the old overwrite default now opt in
explicitly with --if-exists=overwrite
- TestDrivePushOverwriteWithoutVersionFails updated to assert
*output.ExitError with Code=ExitAPI
- new TestDrive_PushDryRunAcceptsDeleteRemoteWithYes (e2e) symmetric to
the existing reject-without-yes test, pinning that EnsureScopes is a
silent no-op when the resolver has no scope metadata
* fix(drive +push): close remaining CodeRabbit comments
Three small follow-ups on the +push review thread that were still
open after the earlier failure-semantics / default-skip / scope
pre-check fix:
- drivePushUploadAll now extracts data.file_token before checking
larkCode, and surfaces the returned token on the partial-success
path (non-zero code + non-empty file_token). Without this, a backend
response where bytes already landed but code != 0 would force the
caller to fall back to entry.FileToken and silently lose the actual
Drive token, defeating the overwrite-error token-stability handling
in Execute.
- TestDrivePushOverwriteWithoutVersionFails switched from "tok_keep"
to "tok_keep_new" in the upload_all stub and now asserts that the
returned token (not entry.FileToken) lands in items[].file_token —
pins the contract that a regression to the fallback branch would
otherwise pass silently.
- New TestDrivePushOverwritePartialSuccessSurfacesReturnedToken pins
the new partial-success branch end-to-end.
- drive_push_dryrun_test.go: tightened the three Validate / cobra
rejections from `exit != 0` to exact codes — `exit == 2` for the
two Validate-stage rejections (--local-dir absolute,
--delete-remote without --yes), `exit == 1` for the cobra
required-flag check (--folder-token missing). Locks in failure
classification so a regression that misroutes the error layer
doesn't slip through.
|
||
|
|
fc9f9c1f26 |
feat(contact +search-user): add search filters and richer profile fields (#648)
* feat(contact +search-user): add search filters and richer profile fields
- Filter results by chat history, employment status, tenant boundary,
or enterprise email presence; keyword is now optional so filter-only
queries ("list all my external contacts") work end-to-end.
- Each result now carries multilingual names, contact email, activation
state, whether you've chatted with them, tenant context, user
signature, and a hit-highlight line that surfaces the matched segment
and the user's department path.
- Always-empty legacy columns and fields the new backend no longer
returns are dropped.
- Also fixes the contact +get-user skill doc, which previously
instructed callers to pass --table (a flag that never existed); now
correctly documents --format table and the full --format enum.
* refactor(lark-contact): clean up search-user code, tighten skill docs
- contact_search_user.go / _test.go: simplify and clarify
- SKILL.md: focus description on user-facing trigger scenarios;
rework decision table; trim notes to load-bearing constraints
- references/lark-contact-search-user.md: add flag table covering
all four bool filters; add multi-filter examples; clean up
output field contract (drop server <h> tag implementation detail)
- references/lark-contact-get-user.md: trim to two real use cases
(self via user identity; full profile of others via bot identity);
point user-mode-by-id users to +search-user instead
- .golangci.yml: replace package-level deny on net/http with a
symbol-level forbidigo rule. Constants (http.MethodPost,
http.StatusOK) and helpers (http.StatusText) were never the
intent; only Client / NewRequest / Get / Post / Do etc. are now
blocked in shortcuts/, matching the rule's actual purpose
Change-Id: Ic42043d3f4c1b675800e48229c7ba2e970da26fe
* fix(contact +search-user): align query limit and reject empty user-ids
API rejects queries longer than 50 characters; local cap was 64 runes,
producing confusing "passed local validation but server-rejected"
behaviour. Lower the cap to 50 and rename the constant accordingly.
Also reject --user-ids inputs that parse to zero entries (",,,",
" , , ", ","): SplitCSV silently dropped empty segments, so the
shortcut sent an empty body to the API and returned indeterminate
results.
Change-Id: Ib34fe897023e175bf4c657273bdb49a33d2f083b
---------
Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
|
||
|
|
fc22e9a04b |
feat(common): backfill resource URL when create APIs omit it (#680)
Add BuildResourceURL helper and wire it into doc/sheets/drive/base/wiki create paths so callers always receive a clickable link, even when the backend response (MCP degraded path or upstream OpenAPI) returns an empty URL field. The fallback uses the brand-standard host (www.feishu.cn / www.larksuite.com), which redirects to the tenant domain. Affected entries: - docs +create v1 / v2 - sheets +create - drive +create-folder / +import / +upload (newly exposes url) - wiki +node-create (newly exposes url) drive +create-shortcut is intentionally skipped because the URL form depends on the underlying file kind, which the shortcut payload does not carry. |
||
|
|
9ba0d15161 |
Feat/risk tiering (#633)
* feat(risk): implement confirmation for high-risk write operations * feat(risk): streamline confirmation for high-risk write operations * feat(risk): document approval protocol for high-risk write operations * feat(risk): refine confirmation protocol for high-risk write operations * feat(risk): remove redundant variable declaration in risk test * feat(risk): add 'Yes' flag to various test cases for confirmation |
||
|
|
c09b03f854 |
fix(cmdutil): default flag completions to disabled (#688)
The previous default (atomic.Bool zero-value = enabled) meant any *cobra.Command built without first calling configureFlagCompletions leaked into cobra's package-global flagCompletionFunctions map. Bench runs (scripts/bench_build) showed hundreds of KB and thousands of objects retained per Build call. Flip the semantics so the zero-value matches the safe default: - Rename internal var to flagCompletionsEnabled (zero = disabled). - Rename public API to SetFlagCompletionsEnabled / FlagCompletionsEnabled. - Update call sites in cmd/root.go and scripts/bench_build/main.go. - Add cmd.TestBuild_DefaultNoCompletionLeak: asserts that, with no setter call at all, repeated cmd.Build invocations stay under 50 KB and 500 objects per build (observed: ~0.7 KB, 3 objs/build). This closes the gap that let the wrong default ship — every previous test explicitly Set the switch before exercising it. Change-Id: Ifefb04af5fd45eea9676a344a64ad071b6a4cd1a |
||
|
|
4d4508dfd7 |
feat(event): add event subscription & consume system (#654)
* feat(event): add event subscription & consume system with orphan bus detection
Introduces end-to-end Feishu event consumption via a new `lark-cli event`
command family. Users can subscribe to and consume real-time events
(IM messages, chat/member lifecycle, reactions, ...) in a forked bus
daemon architecture with orphan detection, reflected + overrideable JSON
schemas, and AI-friendly `--json` / `--jq` output.
Commands
--------
- `event list [--json]` list subscribable EventKeys
- `event schema <key>` Parameters + Output Schema + auth info
- `event consume <key>` foreground blocking consume; SIGINT/SIGTERM
/stdin-EOF shutdown; `--max-events` /
`--timeout` bounded; `--jq` projection;
`--output-dir` spool; `--param` KV inputs
- `event status [--fail-on-orphan] [--json]` bus daemon health
- `event stop [--all] [--force] [--json]` stop bus daemon(s)
- `event _bus` (hidden) forked daemon entrypoint
Architecture
------------
- Bus daemon (internal/event/bus): per-AppID forked process that holds
the Feishu long-poll connection and fans events out to 1..N local
consumers over an IPC socket. Drop-oldest backpressure, TOCTOU-safe
cleanup via AcquireCleanupLock, idle-timeout self-shutdown, graceful
SIGTERM.
- Consume client (internal/event/consume): fork+dial the daemon,
handshake, remote preflight (HTTP /open-apis/event/v1/connection),
JQ projection, sequence-gap detection, health probe. Bounded
execution (`--max-events` / `--timeout`) for AI/script usage.
- Wire protocol (internal/event/protocol): newline-delimited JSON
frames with 1 MB size cap and 5 s write deadlines. Hello / HelloAck /
PreShutdownCheck / Shutdown / StatusQuery control messages.
- Orphan detection (internal/event/busdiscover): OS process-table scan
(ps on Unix, PowerShell on Windows) with two-gate cmdline filter
(lark-cli + event _bus) that naturally rejects pid-reused unrelated
processes.
- Transport (internal/event/transport): Unix socket on darwin/linux,
Windows named pipe on windows.
- Schema system (internal/event, internal/event/schemas): SchemaDef with
mutually-exclusive Native (framework wraps V2 envelope) or Custom
(zero-touch) specs. Reflection reads `desc` / `enum` / `kind` struct
tags, with array elements diving into `items`. FieldOverrides overlay
engine addresses paths via JSON Pointer (including `/*` array
wildcard) and runs post-reflect, post-envelope. Lint guards orphan
override paths.
- IM events (events/im): 11 keys — receive / read / recalled, chat and
member lifecycle, reactions — all with per-field open_id / union_id /
user_id / chat_id / message_id / timestamp_ms format annotations.
Robustness
----------
- Bus idle-timer race fix: re-check live conn count under lock before
honoring the tick; Stop+drain before Reset per timer contract.
- Protocol frame cap: replace `br.ReadBytes('\n')` with `ReadFrame` that
rejects frames > MaxFrameBytes (1 MB). Closes a DoS path where any
local peer could grow the reader's buffer unbounded.
- Control-message writes gated by WriteTimeout (5 s) so a wedged peer
kernel buffer can't stall writers indefinitely.
- Consume signal goroutine: `signal.Stop` + `ctx.Done` select, no leak
across repeated invocations in the same process.
- JQ pre-flight compile so bad expressions fail before the bus fork and
any server-side PreConsume side effects.
- `f.NewAPIClient`'s `*core.ConfigError` now passes through unwrapped
so the actionable "run lark-cli config init" hint reaches the user.
Subprocess / AI contract
------------------------
- `event consume` emits `[event] ready event_key=<key>` on stderr once
the bus handshake completes and events will flow. Parent processes
block-read stderr until this line before reading stdout — no `sleep`
fallback needed.
- All list-like commands have `--json` for structured consumption.
- Skill docs in `skills/lark-event/` (SKILL.md + references/) brief AI
agents on the command surface, JQ against Output Schema, bounded
execution, and subprocess lifecycle.
Testing
-------
Unit tests across bus/hub, consume loop, protocol codec, dedup,
registry, transport (Unix + Windows), schema reflection, field
overrides, pointer resolver. Integration tests cover fork startup,
shutdown, orphan detection, probe, stdin EOF, preflight, bounded
execution, and Windows busdiscover PowerShell compatibility.
Change-Id: Ib69d6d8409b33b99790081e273d4b5b01b7dbf80
* fix(event): address CodeRabbit findings + lift patch coverage above 60%
CodeRabbit comments (PR #654)
-----------------------------
1. bus/dedup: IsDuplicate dropped legitimate (post-TTL) events after
cleanupExpired fired. The run-every-1000-inserts cleanup removed
TTL-expired IDs from the `seen` map but left them in the ring;
IsDuplicate's ring-scan fallback then rediscovered them and falsely
reported "duplicate", and bus.Publish silently dropped the event.
Removed the ring-scan branch — `seen` is the sole authority, the ring
only bounds map size via overflow eviction. New regression test
TestDedupFilter_TTLExpiryAfterCleanupRunRespected exercises the 10-
insert + cleanup path and guards the fix.
2. consume/remote_preflight: the decoder only read `data.online_instance_
cnt`. A non-zero business code with no data payload decoded to 0 and
callers treated it as "verified zero", forking a local bus that would
duplicate events. Added Code / Msg fields and promoted code != 0 into
an error so the caller distinguishes verified-zero from check-failed.
3. cmd/event/stop: swapped os.ReadDir / os.Stat to vfs.ReadDir / vfs.Stat
in discoverAppIDs per project guideline (enables test mocking). New
TestDiscoverAppIDs_* lifts discoverAppIDs from 0% to 100%.
4. cmd/event/appmeta_err: narrowed authURLPattern from
feishu.cn|feishu.net|larksuite.com|larkoffice.com to the two hosts
consoleScopeGrantURL actually produces. Kept the allowlist pinned to
ResolveEndpoints' output with a comment flagging the synchrony.
5. cmd/event/list: moved "No EventKeys registered." and "Use 'event
schema <key>' for details." hints to stderr so `event list | jq`
style pipelines don't ingest them as data.
6. cmd/event/schema: runSchema is a RunE entry point; swapped the bare
fmt.Errorf on resolveSchemaJSON failure to output.Errorf so AI
agents parse a structured error envelope.
Coverage bumps (patch ~50% -> ~60%)
-----------------------------------
internal/event/consume/loop_test.go: loop.go was 0% at patch time.
New tests cover consumeLoop end-to-end via net.Pipe (events -> sink,
max-events -> ctx.Done -> PreShutdownCheck/Ack), seq-gap warning,
jq filtering + early compile failure, isTerminalSinkError classifier.
Takes consumeLoop from 0% to ~74%.
internal/event/protocol/messages_test.go: all NewXxx constructors,
Encode/Decode roundtrip per message type, EncodeWithDeadline deadline
enforcement, ReadFrame MaxFrameBytes rejection + EOF propagation.
Takes protocol from 28% to ~86%.
Also bundles small UX polish:
- cmd/event/consume: --output-dir flag doc flags path-traversal behavior;
jq-validation failures now re-wrap with an event-specific hint
pointing at `event schema` for payload shape.
- internal/event/consume.validateParams: error now names the EventKey
and lists valid param names inline so AI callers recover without a
second `event schema` round-trip.
- skills/lark-event: description expanded to mention
listener/subscribe/consume synonyms + the IM scope set explicitly;
lark-event-im reference polished; obsolete lark-event-subscribe
reference removed.
Verified with go test -race -timeout 120s across ./cmd/event/...,
./events/..., ./internal/event/...; gofmt clean; go vet clean.
Change-Id: I3837b8645ea1d7529c9a8fd4c2bbfa965ae1b519
* test(event): cover format helpers + cobra factories
Adds cmd/event/format_helpers_test.go covering the pure output helpers
and factory wire-ups that RunE-level tests would need a live bus to
exercise:
- writeStopJSON: shape assertions + nil → [] (scripts expecting
.results | length must not see null).
- writeStopText: stdout vs stderr routing — stopped / no-bus lines to
stdout, refused / errored lines to stderr.
- busState.String: all three discriminator values.
- humanizeDuration: each bucket boundary (seconds / minutes / hours / days).
- writeStatusText: covers stateNotRunning / stateRunning (with consumer
table) / stateOrphan (with kill hint).
- writeStatusJSON: orphan entry carries suggested_action + issue;
running entry must NOT carry those fields (hint-leak guard for
scripts that key on issue != "").
- exitForOrphan: flag-off never errors; flag-on errors iff any orphan
is present, with ExitValidation code.
- NewCmdConsume / NewCmdStatus / NewCmdStop / NewCmdList / NewCmdBus:
flag registration + RunE presence, so review catches flag-name drift.
NewCmdBus check also pins Hidden=true.
Lifts cmd/event coverage 51.7% → 61.1%; aggregate event-package
coverage crosses the 60% codecov patch threshold (62% locally).
Change-Id: I9ecf3d905a8f9607b9441ee8a61e746496e2be63
* fix(event): address lint + deadcode CI failures
4 golangci-lint findings + 1 deadcode finding flagged on PR #654.
lint
----
1. cmd/event/stop.go:86 (ineffassign): `targets := []string{}` is
overwritten by both branches of the `if o.all` below, so the empty-
slice initializer is dead. Switched to `var targets []string`.
2. cmd/event/consume.go nilerr: the user-identity scope preflight
swallows a non-nil ResolveToken error and returns nil. This is
intentional — a missing/expired user token must not block consume;
the bus handshake will surface the real auth error with actionable
hints. Added `//nolint:nilerr` with a 4-line comment pinning the
reasoning.
3. events/im/message_receive.go:62 nilerr: malformed JSON payload
returns the original bytes + nil so consumers still see the event
(the WARN breadcrumb lives in the outer loop). Added
`//nolint:nilerr` with a one-line comment.
4. internal/event/schemas/fromtype_test.go:26 unused: `unexportedStr`
is a reflection-test fixture — its presence (not value) exercises
the FromType skip-unexported path verified at the "unexported
field should not be in schema" assertion. Added `//nolint:unused`
and a 4-line comment pointing at the guarded assertion.
deadcode
--------
5. internal/event/testutil/testutil.go: NewTCPFake has no callers in
the repo. Removed the constructor plus the `inner == nil` TCP-mode
branches from Listen / Dial / Cleanup. FakeTransport now only
supports the wrapped-overlay mode (NewWrappedFake), which is the
one every existing test uses. Doc comment simplified accordingly.
Verified locally: go test -race -timeout 120s across ./cmd/event/...,
./events/..., ./internal/event/... all green; gofmt clean; go vet
clean.
Change-Id: Ie8a2270827a0bde6b8159ab70aaf5c1e9ca7d5b9
* fix(event): drop stale enum + simplify protocol test type helper
- events/im/message_receive.go: dropped the `enum` tag on
ImMessageReceiveOutput.MessageType. convertlib registers many more
message types than the old 11-item list (video / location /
calendar / todo / vote / hongbao / merge_forward / folder / ...),
so a partial enum would tell AI consumers that valid values like
"video" are invalid and produce false-negative JQ filters.
- internal/event/protocol/messages_test.go: collapsed the
typeOf → reflectTypeName → stringType chain in
TestEncode_DecodeRoundtripAllTypes to a single fmt.Sprintf("%T", v).
The hand-maintained type switch silently returned "<unknown>" for
any new message type, which would have let future Decode bugs slip
past the roundtrip assertion. Also removed a dead `cases` table at
the top of TestConstructors_PinTypeField left over from an earlier
refactor.
Change-Id: I831e96f8417e80637596030d652a559de0d33122
* docs(event): polish skill docs + rename root_path_hint to jq_root_path
- skills/lark-event/SKILL.md, lark-event-im.md: translated to English,
reorganized around a top-level "Core commands" table, scenario
recipes tightened.
- cmd/event/schema.go: renamed the writeSchemaJSON hint field
RootPathHint / "root_path_hint" -> JQRootPath / "jq_root_path" to
make its purpose (a jq path prefix) obvious at the call site; no
external consumer depends on the old name yet.
Change-Id: I00c14061ca33caedc0975bfeadc4b26d3dcd314d
* chore(event): strip excessive comments
Change-Id: I8f44f36f5dbdba3ef95dfc67069dc796232f91ec
* fix(event): dedup self-eviction race + protocol oversized-frame test
dedup: in IsDuplicate, the ring-slot eviction step deleted seen[id] even
when ring[pos] equalled the freshly-recorded id (post-TTL reinsertion
landing on its own historical slot). Net result: ring still held id but
seen did not, so the next IsDuplicate(id) returned false and the
duplicate was delivered. Skip the delete when old == eventID. New
TestDedupFilter_SelfEvictionPreservesFreshEntry pins the invariant by
pre-loading the ring slot and asserting the second call still reports
duplicate.
protocol: TestReadFrame_RejectsOversized used strings.Contains feeding
t.Logf, so any non-nil error passed — including a future regression
that returned io.ErrUnexpectedEOF while silently keeping the buffer
unbounded. Promoted MaxFrameBytes overflow to a sentinel
ErrFrameTooLarge and the test now asserts via errors.Is.
Change-Id: I50281dad392152b0ca083fd30c38eb0695e63bd3
* docs(event): clarify .content shape per message_type + add sender filter recipe
Change-Id: I619fd15c1a362e42e6602fd3e3316bbc75eddc5e
* fix(event): replace cmdline-regex bus discovery with PID file + close concurrent fork race
Bus discovery previously walked the OS process table and parsed `--profile cli_*` from
cmdline; the regex rejected any non-cli_ profile name (D-03a). Replace with per-AppID
bus.pid + bus.alive.lock under events/<AppID>/, probed via try-lock. AppID round-trips
through the directory name, so the profile-vs-AppID confusion is gone by construction.
Also fix B-07 (two consumers each fork an independent bus, halving event delivery):
- forkBus holds bus.fork.lock until child is dial-able, not just until cmd.Start
- bus daemon takes alive.lock before binding the socket; cleanup-TOCTOU race can no
longer leave two listeners on different inodes
status.go renders an orphan with PID=0 distinctly (live bus but pid file unreadable)
so we never print "Action: kill 0".
Change-Id: I3bf0a6cf1d91fb274ac5a6df83d66896aafb291f
* style(event): gofmt bus.go
Trailing blank line introduced when appending acquireAliveLock helper.
Change-Id: I4ae1b4a4363dc6c89dcbd6a170f4563117490ba3
* fix(event): swap os.Remove/Rename for vfs.* and silence forbidigo on internal diagnostics
golangci-lint forbidigo blocks os.* in internal/. Switch the pid-file write to vfs.Remove/vfs.Rename and add a nolint marker on the two stderr diagnostics in busdiscover, matching the existing pattern in consume/*.
Change-Id: Ia6768be62aefeb8ca40f991d3130a78ef2ec0ea5
* fix(event): cross-platform --all + clean SIGPIPE shutdown for consume
- stop --all: replace bus.sock-file probe with busdiscover lock-based
scan; previously skipped Windows entirely (named-pipe transport, no
socket on disk) and misidentified Unix stale sockets as live. Same
win for `event status` (shares discoverAppIDs).
- consume: ignore SIGPIPE so a closed stdout pipe (e.g. `... | head -n 1`)
surfaces as EPIPE error and reaches the existing isTerminalSinkError
cleanup path (log "output pipe closed", lastForKey query, hub
unregister), instead of being killed by Go's default fd 1/2 SIGPIPE
handler with exit 141 and zero deferred cleanup.
Build-tagged: real on unix, no-op on windows (no SIGPIPE there).
Change-Id: I453b19f05c489fd9d5c1a9ba3bdc35e127c15b83
* docs(event): translate IM EventKey descriptions and field tags to English
Aligns with the rest of the codebase (titles, struct names, README) which
are already in English. Surfaces in `event list` / `event schema` and is
also consumed by AI agents.
- events/im/message_receive.go: 11 desc tags on ImMessageReceiveOutput
- events/im/native.go: 10 description fields on Native EventKeys
- events/im/register.go: im.message.receive_v1 Description
Change-Id: I6f46950b4793f137e0129c1f06019a3419195443
* docs(event): drop misleading AuthTypes[0] auto-default claim
The KeyDefinition comment and SKILL.md flag table both stated that
`--as auto` resolves to `AuthTypes[0]`. It does not — ResolveAs goes
through global rules (config default_as / credential hint / `bot`
fallback) without consulting the EventKey. AuthTypes is only used by
CheckIdentity as a post-resolve whitelist.
Reword the field comment to plain whitelist semantics and have SKILL.md
defer `--as` documentation to lark-shared.
Change-Id: Ia5d3d3790aed05813a0fa72d6b43518224e2055b
* revert(comments): restore original comments on 3rd-party files
e61482a stripped comments across 105 files. Restore the four files
authored by others (cmd/build.go, shortcuts/common/{types,runner}.go,
shortcuts/event/subscribe.go) to their pre-strip state so unrelated
documentation isn't churned in this PR.
Change-Id: Ie2527b06bfaf5b3861b0b9dff1e19bbfe7dde456
|
||
|
|
e42033f5b5 |
feat(doc): add v2 API for docs +create / +fetch / +update (#638)
Adds an `--api-version v2` path to the docs shortcuts, backed by the
`docs_ai/v1/documents` OpenAPI. DocxXML is the default document format
and Markdown is available as an alternative. Content input is unified
across the three shortcuts via `--content` + `--doc-format`. The v1
(MCP) path is preserved for backward compatibility and now prints a
deprecation notice on use.
Shortcuts:
- `docs +create --api-version v2`: create a document from XML or
Markdown, with optional `--parent-token` or `--parent-position`.
Bot identity continues to auto-grant the current CLI user
full_access on the new document.
- `docs +fetch --api-version v2`: adds `--detail simple|with-ids|full`
for export granularity and `--scope full|outline|range|keyword|section`
for partial reads, along with `--context-before` / `--context-after`,
`--max-depth`, and `--revision-id`.
- `docs +update --api-version v2`: introduces structured operations
via `--command`: `str_replace`, `block_delete`, `block_insert_after`,
`block_copy_insert_after`, `block_replace`, `block_move_after`,
`overwrite`, `append`.
Framework support in `shortcuts/common`:
- `OutRaw` / `OutFormatRaw` emit the JSON envelope with HTML escaping
disabled so XML/HTML document bodies are preserved verbatim.
- New `Shortcut.PostMount` hook runs after a cobra.Command is fully
configured; used here to install a version-aware help function
that hides flags belonging to the inactive `--api-version`.
Also refreshes the lark-doc skill pack (SKILL.md, create/fetch/update
references, new lark-doc-xml and lark-doc-md references, style and
workflow guides), README examples, and downstream skill call sites
(lark-drive, lark-vc, lark-whiteboard, lark-workflow-meeting-summary,
lark-event).
Change-Id: Ide2d86b190a4e21095ae29096e7fb00031d80489
|
||
|
|
600fa50517 |
feat: add configurable content-safety scanning (#606)
* feat(contentsafety): add extension interface layer with Provider, Alert, and registry Change-Id: Ibeac6366c7201293057bc3b063f75ac34565bcd5 * feat(contentsafety): add normalize utility for JSON type conversion Change-Id: I7d4729a5ddcab2553abc110f8f6ecc88435ae921 * feat(contentsafety): add tree walker and regex scanner Change-Id: I215dad7cf3072711d05e45f7d384162e1f8752d4 * feat(contentsafety): add config loading with lazy creation, default rules, and allowlist matching Change-Id: I75e10df28f1f8d4f433cb2b469a0ff317af3bf70 * feat(contentsafety): add regex provider with config-driven scanning and allowlist Change-Id: I658889b3647cbbbde6881e0c5f7c13887a1eb1d4 * feat(contentsafety): add output core with mode parsing, path normalization, and scan orchestration Change-Id: I1cb9df75f1a4d176d660e2e7a9561314c3787191 * feat(contentsafety): add ScanForSafety entry point and Envelope alert field Change-Id: I5fdb311e1c8d983a35a58667970b9fd3ac729a5c * feat(contentsafety): integrate scanning into shortcut Out() and OutFormat() Change-Id: I33eef1dba14c8a9bd1998857311bdd611f33b916 * feat(contentsafety): integrate scanning into API/service output paths and register provider Change-Id: Ic3981db6c546a19eadea095d82175f92f4783bec * fix(contentsafety): emit stderr notice when lazy-creating default config Change-Id: Ia2491f7a17caceea3125ff9fb58d750dc196d7e7 * style: gofmt factory_default and exitcode Change-Id: I86c5afdfbbdb68d8137f0ca09ef3b5a1139f4b4e * fix(contentsafety): vfs for config I/O, mutex for lazy-create, sort matched rules, emit warn on --output path Change-Id: Ib4982cd54e1bfe0580a0eb03368e6ca818304e1b * fix(contentsafety): isolate scan goroutine errOut to prevent race on timeout Change-Id: Ia5a770d7387ba6d3b7fa318fc5f1384214ea10b7 * fix(contentsafety): deep-normalize typed slices so scanner can walk shortcut data Change-Id: I641e89113d1a2f2285ac6109bd3d7264f5845ea7 * fix(contentsafety): file perms 0600/0700, no result mutation, timeout test, scanTimeout comment Change-Id: Ie45a2e365ee7098e214e94f8871026cc12029d83 |
||
|
|
c7ced37959 |
feat: unify minute artifacts output to ./minutes/{minute_token}/ (#604)
* feat: unify minute artifacts output to ./minutes/{minute_token}/
* fix: tighten path validation and batch-mode --output rejection
* style: translate comments to english and trim historical context
* style: translate leftover chinese comments in vc_notes
* refactor: address review findings across validate ordering, error types, JSON, tests
* fix: sanitize server-provided filename to prevent escape from artifact dir
* style: tighten flag help text for minutes/vc output flags
* docs: update minutes/vc skill docs for unified artifact layout
|
||
|
|
bc6590abef |
feat(doc): add --from-clipboard flag to docs +media-insert (#508)
* feat(doc): add --from-clipboard flag to docs +media-insert
Allow users to upload the current clipboard image directly to a Lark
document without saving to a local file first.
- New --from-clipboard bool flag (mutually exclusive with --file)
- shortcuts/doc/clipboard.go: readClipboardToTempFile() with per-OS impl
macOS — osascript (built-in, no extra deps)
Windows — PowerShell + System.Windows.Forms (built-in)
Linux — tries xclip / wl-paste / xsel in order; clear install hint
on failure
- No new Go dependencies, no Cgo
- Temp file is created before upload and removed via defer cleanup()
- --file changed from Required:true to optional; Validate enforces
exactly-one of --file / --from-clipboard
* fix(doc): fix clipboard image read on macOS for screenshots and browser-copied images
- Add TIFF fallback (macOS screenshots default to TIFF, not PNG)
- Add HTML base64 fallback (images copied from Feishu/browser embed data URI)
- Use current directory for temp file so FileIO path validation passes
* fix(doc): scan HTML/RTF/text clipboard formats for base64 image data URIs
Extend attempt-3 fallback to iterate all text-based clipboard formats
(HTML, RTF, UTF-8, plain text) rather than only HTML. Any format that
contains a "data:<mime>;base64,<data>" pattern is accepted, covering
images copied from Feishu, Chrome, Safari, and other apps that embed
base64 in non-HTML clipboard slots. Also handle URL-safe base64.
* test(doc): add unit tests for clipboard helpers to meet 60% coverage threshold
Cover decodeHex, hexVal, decodeOsascriptData, reBase64DataURI, and
extractBase64ImageFromClipboard (via fake osascript on PATH).
Package coverage: 57% → 61.2%.
* fix(doc): address CodeRabbit review comments on clipboard feature
- Extend reBase64DataURI regex to cover URL-safe base64 chars (-_) so
URL-safe payloads are matched before decoding is attempted
- Fix readClipboardLinux to continue to next tool when a found tool
returns empty output instead of failing immediately
- Guard fake-osascript test with runtime.GOOS == "darwin" skip
- Use os.PathListSeparator instead of hardcoded ":" in test PATH setup
* fix(doc): replace os.* temp-file clipboard path with in-memory streaming
Fixes forbidigo lint violations in shortcuts/doc: os.CreateTemp, os.Remove,
os.Stat, os.WriteFile are banned in shortcuts/; replaced with vfs.* equivalents
for sips TIFF→PNG conversion, and eliminated temp files entirely elsewhere by
having platform clipboard readers return []byte directly.
- readClipboardDarwin: osascript outputs hex literals decoded in Go (no file I/O)
- readClipboardWindows: PowerShell outputs base64 to stdout, decoded in Go
- readClipboardLinux: tool stdout bytes returned directly
- convertTIFFToPNGViaSips: still needs temp files — uses vfs.CreateTemp/Remove
- DriveMediaUploadAllConfig/DriveMediaMultipartUploadConfig: add Content io.Reader
field so in-memory clipboard bytes skip FileIO.Open() path
- Fix ineffassign in clipboard_test.go (scriptBody double-assignment)
- Update TestReadClipboardLinux_NoToolsReturnsError for new signature
* fix(doc): address CodeRabbit review comments on Linux clipboard path
- Update --from-clipboard flag description to list xclip, xsel and wl-paste
- Preserve last backend-specific error in readClipboardLinux so users see
a meaningful message when a tool is found but fails
- Validate PNG magic bytes for xsel output (xsel cannot negotiate MIME types)
- Add URL-safe base64 regression test for reBase64DataURI
* fix(doc): strip whitespace from base64 payload before decoding clipboard data URI
HTML and RTF clipboard content often line-wraps base64 at 76 characters.
FindSubmatch returns the raw wrapped token so direct decode would fail.
Normalize whitespace with strings.Fields before passing to base64.Decode.
* fix(doc): drop TIFF fallback and internal/vfs import on macOS clipboard
depguard rule shortcuts-no-vfs forbids shortcuts/ from importing
internal/vfs directly. The only caller was the sips TIFF→PNG
conversion, which was already a fragile best-effort fallback that
required temp files.
Remove the TIFF fallback entirely; the remaining two attempts cover
the real-world cases:
1. osascript → PNG hex literal — native screenshots and most apps
2. scan text clipboard formats for base64 data URI — Feishu/browsers
* test(doc): cover readClipboardLinux xsel PNG validation and dispatcher path
Added tests:
- TestReadClipboardLinux_XselRejectsNonPNG: fake xsel that returns plain
text is rejected by the PNG-magic check, preventing text from being
uploaded as an "image".
- TestHasPNGMagic: table-driven coverage of the PNG signature check.
- TestReadClipboardImageBytes_UnsupportedPlatform: exercises the shared
dispatcher post-processing and asserts the (nil, nil) invariant.
Raises clipboard.go diff coverage and brings the package from 61.6% to
63.8% overall.
* test: cover in-memory Content upload paths for clipboard feature
Adds unit tests for the new Content io.Reader branches introduced by
the clipboard feature:
- UploadDriveMediaAll with in-memory Content (drive_media_upload.go 87.5%)
- UploadDriveMediaMultipart with in-memory Content (84.6%)
- uploadDocMediaFile single-part and multipart with clipboard bytes
(doc_media_upload.go 0% -> 88.9%)
Adds TestNewRuntimeContextForAPI helper that wires Factory, context,
and bot identity so package tests can invoke DoAPI without mounting
the full cobra command tree.
* test: cover clipboard Validate/DryRun branches and testing helper
Adds unit tests for the clipboard-related Validate/DryRun paths that
Codecov patch-coverage was flagging as uncovered:
- Validate error when neither --file nor --from-clipboard is supplied
- Validate error when both are supplied (mutual exclusion)
- DryRun output contains <clipboard image> placeholder
- Self-test for TestNewRuntimeContextForAPI so shortcuts/common
sees coverage for the new helper (not just shortcuts/doc)
* test: cover Execute clipboard branch via injectable readClipboardImage
Makes readClipboardImageBytes swappable in tests by routing the call
through a package-level variable readClipboardImage. Tests inject a
synthetic PNG payload so the full Execute clipboard flow
(resolve → create block → upload in-memory bytes → bind) runs under
unit test without a real pasteboard.
Covers:
- TestDocMediaInsertExecuteFromClipboard: end-to-end happy path
- TestDocMediaInsertExecuteClipboardReadError: early-return on
readClipboardImage() failure
* ci: re-trigger pull_request workflow for PR #508
Previous push to
|
||
|
|
11191df703 |
fix: skip flag-completion registration outside completion path (#598)
* fix: skip flag-completion registration outside completion path Cobra keeps completion callbacks in a package-global map keyed by *pflag.Flag with no removal path, so registrations made during Build() outlive the command itself. Route all seven call sites through cmdutil.RegisterFlagCompletion and enable registration only when the invocation actually serves a __complete request. Measured over 30 dropped Builds: ~202 KB / 2180 retained objects per Build before, ~0 after. Change-Id: I734d598a4c91a92c33b02e0f292f640cc0e224c6 |
||
|
|
f3699298aa |
feat: cli 支持记录分享 no-meego (#466)
Change-Id: Ie78da99096cc1fc8a4671d8178176f4c587466ba |
||
|
|
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 |
||
|
|
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
|
||
|
|
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 |
||
|
|
a9c07cebb6 |
feat(slides): add slides +create shortcut with --slides one-step creation (#389)
Co-authored-by: caichengjie.viper <caichengjie.viper@bytedance.com> |
||
|
|
9f81e7e567 |
feat: add RuntimeContext.BotInfo() for lazy bot identity retrieval (#409)
Add BotInfo() method on RuntimeContext that lazily fetches the current app's bot open_id and display name from /bot/v3/info on first call, cached via sync.OnceValues for the lifetime of the process. - BotInfo struct (OpenID, AppName) in Identity section of runner.go - fetchBotInfo() uses DoAPIAsBot for consistent header injection - CanBot() on CliConfig gates the call when bot identity is unavailable - Nil guard prevents panic in test contexts - Full test coverage via httpmock.Registry + mounted shortcuts Change-Id: I40ac710fb52d13939853f71827a5cbdbddd4f80f |
||
|
|
37747177fc |
feat: auto-grant current user access for bot-created docs, sheets, imports, and uploads (#360)
Change-Id: Idf5b35dbf77d72788895e0a3c34563281d658c88 |
||
|
|
0bf4f80ef4 |
refactor: migrate drive/doc/sheets shortcuts to FileIO (#339)
* refactor: migrate drive/doc/sheets shortcuts to FileIO - drive_download/upload/import/export: SafeInputPath/SafeOutputPath + vfs.Stat/Open/MkdirAll + AtomicWrite → FileIO.Stat/Open/Save - doc_media_download/insert/upload: same migration pattern - sheet_export: same migration pattern - Add Mode() fs.FileMode to fileio.FileInfo for IsRegular() checks - Add WrapInputStatError helper to preserve error message fidelity - Add WrapSaveErrorByCategory for standardized save error mapping |
||
|
|
63ea52b2e6 |
refactor: migrate vc/minutes shortcuts to FileIO (#336)
* refactor: migrate vc/minutes shortcuts to FileIO - vc_notes: replace vfs.Stat + validate.SafeOutputPath + validate.AtomicWrite with FileIO.Stat/Save for transcript download - minutes_download: replace validate.SafeOutputPath + validate.AtomicWriteFromReader with FileIO.Save, use FileIO.Stat for overwrite checks - Use WrapSaveError to preserve original error messages |
||
|
|
555722ac8e |
fix: resolve concurrency races in RuntimeContext (#330)
* fix: resolve concurrency races in RuntimeContext - getAPIClient: replace check-then-act with sync.OnceValues, matching the factory_default.go convention; use NewAPIClientWithConfig to avoid post-construction config override; fall back to direct construction for test contexts that bypass newRuntimeContext. - outputErr: guard first-error capture with sync.Once to prevent data races if Out() is ever called from concurrent goroutines. Change-Id: I99c94c3dcb7663fa61571c9720163e41a5fc0e36 * fix: use tenant token for auth scopes Change-Id: I83bb677e9a33e906e207679b2ba8d0364bc20fe3 |
||
|
|
f5a8fbf8f1 |
refactor: migrate common/client/im to FileIO and add localfileio tests (#322)
* refactor: migrate common/client/im to FileIO and add localfileio tests - runner resolveInputFlags: replace validate.SafeInputPath + vfs.ReadFile with FileIO.Open + io.ReadAll - SaveResponse: delegate to FileIO.Save + ResolvePath - cmd/api, cmd/service: pass FileIO to ResponseOptions - im: replace validate.SafeLocalFlagPath with RuntimeContext.ValidatePath, migrate download/upload to FileIO.Save/Open/Stat - Add path_test.go and atomicwrite_test.go for localfileio - Add validate_media_test.go for im media flag validation - Adapt test mocks to fileio.FileInfo interface |
||
|
|
6ac5b4d566 |
support multipart doc media uploads (#294)
Change-Id: I9d9fb00079dacfc96b5781e12e6ce79945baa2ed |
||
|
|
7158dc2f3c |
fix: reject positional arguments in shortcuts (#227)
* fix: reject positional arguments in shortcuts with clear error Shortcuts silently ignored positional arguments (e.g. `lark-cli docs +search "hello"`), causing empty results. Add Args validator to all declarative shortcuts so cobra prints usage and a clear error message telling users to pass values via flags instead. Change-Id: I7579f9c871138cf91dd5f5d8c1d51bda3f77a1db * fix: address PR review comments - Remove unused *Shortcut parameter from rejectPositionalArgs - Show all positional args in error message instead of only the first - Add test case for multiple positional arguments Change-Id: Ifea92d09ddabcd35fbf2db98d9888d18af59b894 |
||
|
|
900c12ce8d |
feat: add FileIO extension for file transfer abstraction (#314)
* feat: add FileIO extension for file transfer abstraction Introduce extension/fileio package with Provider/FileIO/File interfaces and a global registry, following the same pattern as extension/credential. - Add LocalFileIO default implementation with path validation and atomic writes - Wire FileIOProvider into Factory and resolve at runtime via RuntimeContext.FileIO() - Factory holds Provider (not resolved instance), deferring resolution to execution time |
||
|
|
8db4528269 |
feat: add strict mode identity filter, profile management and credential extension (#252)
* feat: add strict mode identity filter, profile management and credential extension Port changes from feat/strict-mode-identity-filter_3 branch: - Add strict mode for identity filtering and configuration - Add profile management commands (add/list/remove/rename/use) - Add credential extension framework (registry, env provider) - Add VFS abstraction layer - Refactor factory default and client options - Update shortcuts to use new credential and validation patterns Change-Id: I8c104c6b147e1901d94aefcefe35a174932c742b Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: go mod tidy Change-Id: I0f610ccea6bc874248e84c24770944a3071dcc57 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fix test failures from credential provider migration - Remove unused TAT stub registrations in api and service tests (CredentialProvider manages tokens, SDK no longer calls TAT endpoint) - Update strict mode integration test: +chat-create now supports user identity, so it should succeed under strict mode user Change-Id: Iab51c2e12a97995e0b95dcd71df212d2d1f76570 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: migrate remaining os calls to internal/vfs Replace direct os.Stat/Open/MkdirAll/OpenFile/Remove/ReadDir/UserHomeDir with vfs equivalents in shortcuts/minutes, shortcuts/drive, and internal/keychain. Add ReadDir to the vfs interface and OsFs implementation. Change-Id: I8f97e5fb3e1731b4684d276644fcb10fae823067 * fix: resolve gofmt and goimports formatting issues Change-Id: If61578631f5698f7ca2d9a946ca59753651463fb * feat: add Flag.Input support for @file and stdin input sources Add framework-level support for reading flag values from files (@path) or stdin (-), solving the fundamental problem of passing complex text (markdown, multi-line content) via CLI arguments where shell escaping breaks content. Closes #239, fixes #163. - Add File/Stdin constants and Input field to Flag struct - Add resolveInputFlags() in runner pipeline (pre-Validate) - Support @@ escape for literal @ prefix - Guard against multiple stdin consumers - Auto-append "(supports @file, - for stdin)" to help text - Apply to: docs +create/+update --markdown, im +messages-send/+reply --text/--markdown/--content, task +comment --content, drive +add-comment --content Change-Id: I305a326d972417542aeadd70f37b74ea456461ef Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: fix pre-existing test failures in task, minutes, and registry - task/minutes: remove unused tenant_access_token httpmock stubs (TestFactory's testDefaultToken provides tokens directly, so the HTTP stub was never consumed and failed verification) - registry: fix hasEmbeddedData() to check for actual services instead of just byte length (meta_data_default.json has empty services array) Change-Id: Ic7b5fc7f9de09137a7254fe1ddf47d24ade40587 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: suppress nilerr lint for intentional nil returns Both cases intentionally return nil on error for graceful degradation: - profile list: show friendly message when config is not initialized - service: skip scope check when token resolution fails Change-Id: I7285c37277c9b0361a421ab00359244c2cd150b3 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address CodeRabbit review feedback - runner.go: fail fast when Input is used on non-string flags - remote_test.go: rename hasEmbeddedData → hasEmbeddedServices - profile/list.go: add omitempty to optional JSON fields - service.go: surface context cancellation errors in scope check Change-Id: I7072d41f8c711b4b37c542e32dfd8150f42b13c0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: tighten credential resolution and profile flows Change-Id: I83f6d424540eab9b1708944b9b6e26e8477cc60d * refactor: centralize identity hint resolution Change-Id: I38d5f98160b92adb62dc929ae73697ae5b3d64f8 * fix: surface unverified extension identities Change-Id: Ia86d9bd19add9010176339ec4cc89deb033f5b4f * fix: honor runtime credential sources in config views Change-Id: I40b2ffedc5c1db5e08e86b9472ea2b84fa02bb29 * fix: prefer runtime values in config show commands Change-Id: I5663a53e147577f0f1f533f67d12bea504e6b839 * Revert "fix: prefer runtime values in config show commands" This reverts commit |
||
|
|
a641fdd5e6 |
feat: support user identity for im +chat-create (#242)
- Add --as user support to +chat-create - Add UserScopes (im:chat:create_by_user) / BotScopes (im:chat:create) - Update skill docs and reference files to reflect user/bot support - Default identity remains bot (first element of AuthTypes) Change-Id: I6be0a160567a0d87a92f176ae12297a11d06dcb1 |
||
|
|
7baba213bc |
feat: add --jq flag for filtering JSON output (#211)
* feat: add --jq flag for filtering JSON output across all command types Add jq expression filtering (--jq / -q) to api, service, and shortcut commands using gojq. Includes early expression validation, mutual exclusion checks with --output and non-json --format, pagination+jq aggregation path, and comprehensive test coverage. * fix: correct gofmt alignment in jq_test.go struct literal * fix: downgrade gojq to v0.12.17 to keep Go 1.23 compatibility gojq v0.12.18 requires Go 1.24, which unnecessarily bumped the project minimum version. v0.12.17 requires only Go 1.21 and provides the same jq functionality needed. * refactor: consolidate jq validation and pagination logic Extract ValidateJqFlags() and PaginateWithJq() shared functions to eliminate duplicated jq logic across api, service, and shortcut commands. * fix: reject --jq for non-JSON responses and propagate shortcut jq errors - HandleResponse now returns a validation error when --jq is used with a non-JSON Content-Type instead of silently falling through to binary save. - Shortcut runtime jq errors are captured in RuntimeContext.outputErr and propagated as the command exit code, matching api/service behavior. |
||
|
|
f231031041 |
feat: support im message send/reply with uat (#180)
- Add --as user support to +messages-send and +messages-reply - Add UserScopes (im:message.send_as_user) / BotScopes (im:message:send_as_bot) - Add DoAPIAsBot to RuntimeContext so file/image uploads always use bot identity even when the surrounding command runs as user - Update skill docs and reference files to reflect user/bot support - Default identity remains bot (first element of AuthTypes) |
||
|
|
27139a0919 |
feat: add automatic CLI update detection and notification (#144)
Add non-blocking update check that queries the npm registry for the latest @larksuite/cli version. Results are cached locally (24h TTL) to avoid repeated network requests. When a newer version is detected, a `_notice.update` field is injected into all JSON output envelopes (success, error, and shortcut responses), enabling AI agents and scripts to surface upgrade prompts. Key changes: - New `internal/update` package: registry fetch, semver compare, cache - Async check in root command (cache-first, then background refresh) - `_notice` field added to Envelope/ErrorEnvelope structs - `PrintJson` injects notice into map-based envelopes with "ok" key - `doctor` command gains cli_version and cli_update checks - Suppressed for CI, DEV builds, shell completion, and git-describe versions |
||
|
|
83dfb068ad |
feat: open-source lark-cli — the official CLI for Lark/Feishu
Change-Id: I113d9cdb5403cec347efe4595415e34a18b7decf |