mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
cb8e2c58df68a1fa9fd53efae6eb8d34dbe626b0
126 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
cb8e2c58df |
fix: reject oversized artifacts and split multi_turn into per-verb caps
Two review follow-ups on the agent command tree. Artifact download (data integrity): fetchArtifactURL now reads one byte past maxArtifactBytes and refuses a body over the cap with a typed error, instead of letting io.LimitReader silently truncate a >256 MiB artifact to a corrupt, partial file that reported success (exit 0). The LimitEnforced test is inverted to assert the rejection. Capabilities: the single multi_turn card bit is replaced by three independent capabilities — context_list / context_get / context_delete — each derived from its own wired hook (ListContexts / GetContext / DeleteContext). One bit could not honestly represent three separately-deliverable verbs: a provider wiring ListContexts but not DeleteContext advertised multi_turn=true while `context delete` failed claiming multi_turn=false. Each context verb now gates on and reports its own capability. Card schema, capability matrix, the three context gates, tests, and the lark-agent skill docs are updated in lockstep. |
||
|
|
75bdefa513 |
fix: harden agent command layer (render sanitize, nil-safety, arrays)
Review follow-up on the agent command tree. A batch of low-risk hardening fixes; no behavior change for the shipped example provider. - Terminal-injection: pretty/TSV renderers now sanitize the agent-controlled State / UpdatedAt / CreatedAt fields (kvValue on pretty rows, stripANSI on TSV), matching the id/summary/title fields — a malicious provider can no longer inject CSI/OSC escapes via a forged state or timestamp. - Nil-safety: `task get --watch` and artifact download return a typed invalid_response error when a provider hook yields (nil, nil) (a legitimate Call[*T] result on an empty "data") instead of panicking; an artifact with neither inline bytes nor a URL no longer writes a 0-byte file. - Array convention: task/context/agent list normalize a nil slice to [] so an empty list serializes as [] not null, matching Card.Parameters. - Error hint: unknown-agent errors keep LookupSpec's scheme-scoped `agent list <scheme>` hint instead of being flattened to the generic one. - agent list <scheme> (online path) sets the resolved identity on its envelope, consistent with the other leaves. - Comment/doc drift: drop references to the removed Deps probe / Discoverer / ProviderInfo / resolveProvider symbols; rename Supports(cap) -> capKey. - Tests: cross-agent isolation in the example store, empty-list [] contract, State/timestamp sanitization regression, and a real stdout assertion for context list --jq. |
||
|
|
118ab4bf1e |
refactor: typed Runtime.Call[T]/CallUpload[T] over raw data JSON
Runtime.CallAPI/CallMultipart now return the response "data" object as json.RawMessage instead of map[string]any, and provider hooks decode it through the generic Call[T] / CallUpload[T] helpers: a hook declares the response struct it expects and the framework unmarshals + classifies errors, instead of poking at a map. Call[map[string]any] remains available for genuinely dynamic shapes; a response with no "data" (e.g. a pure write) yields T's zero value and a nil error. decodeData centralizes the unmarshal + invalid_response classification. cmdRuntime re-encodes the unwrapped "data" sub-object to raw JSON after CheckResponse. Test doubles (cmd/agent + example fakeRuntime, card_test fakeRT) updated to the raw signature; the runtime tests keep the raw-data assertion and add a Call[T] decode assertion. |
||
|
|
a1c3530ec9 |
refactor: align agent scope preflight with event (bot + user)
The agent scope preflight now mirrors cmd/event's scopeRemediationHint for both identities, replacing the bespoke replacement-era hint: - user: the re-auth hint lists ONLY the missing scopes (the open platform authorizes incrementally, so re-login with just the missing keeps existing grants — no merge needed). Uses the canonical repo-wide `auth login --scope` phrasing instead of a one-off Chinese string. - bot: previously skipped entirely; now checks the app's published TenantScopes (fetched best-effort via appmeta.FetchCurrentPublished behind a swappable seam; a fetch failure downgrades to a no-op). A missing scope reports the developer-console re-publish remediation (the event-style scan-to-enable deep link lives in cmd/event and is not duplicated here). preflightScopesForRef keeps its signature (no call-site changes); bot fetch uses a bounded background context so no ctx param is threaded. Tests cover the incremental user hint, the bot missing/present/no-scopes branches, and the bot seam wiring. |
||
|
|
be8356b900 |
feat: enrich task/context summaries for triage (updated_at, summary, active_task)
`task list` / `context get` carried only {task_id, context_id, state,
is_terminal} — too thin for a caller (especially an AI) to tell which task
to resume without a `task get` per item. Enrich the summary surface so the
list is self-sufficient for triage, aligning with A2A's Task
(status.timestamp + last message).
- TaskSummary: add updated_at + summary (last agent message, or the pending
prompt for input_required; rune-truncated)
- AgentTask: add created_at + updated_at
- ContextSummary: add updated_at + task_count + awaiting_input
- ContextDetail: drop the embedded tasks[]; add updated_at, task_count,
awaiting_input, and active_task (the latest-updated task). Full task
enumeration stays in `agent task list --context-id`.
- task list / context list sort by updated_at desc
- route task list / context list / context get through the content-safety
scan (they now carry untrusted agent text); ANSI-strip + flatten summary
in pretty/TSV
- example provider fills the new fields; tests + lark-agent skill docs updated
|
||
|
|
5d9370ad13 |
refactor: inject a Runtime into provider hooks; declarative Provider/AgentSpec
Supersede the struct-of-func-fields Provider with a runtime-injection model that
stops leaking framework plumbing to integrators (the Deps{Client, As} an
onboarding author previously had to receive and destructure — the mock didn't
even use it).
Framework (internal/agent):
- Provider is now one declarative value per business domain (scheme): metadata +
a Catalog []AgentSpec (offline-enumerable) XOR an Instance *AgentSpec template,
plus an optional online ListAgents hook. AgentSpec carries per-agent card
metadata, the FileInput/InputRequired flags, and the verb hooks.
- Every hook receives an identity-opaque agent.Runtime (AgentID/IsBot/CallAPI/
CallMultipart) instead of a raw client; the concrete cmdRuntime lives in
cmd/agent (like event's consumeRuntime), so internal/agent no longer depends on
internal/client and the Deps struct is gone. CallMultipart is the centralized,
SafeInputPath-validated file-upload seam that makes file_input deliverable.
- Register takes a Provider (pure-struct validation, fail-fast). LookupSpec
resolves ref→spec fully offline. Capability = wired-hook presence
(DeriveCapabilities), card synthesized by BuildCard (rt=nil ⇒ offline caps +
static metadata; rt!=nil ⇒ best-effort Describe enrichment). Deleted catalog.go,
Deps, Factory, Resolve, NewCard, the zero-Deps probe, and the Discoverer interface.
Command layer (cmd/agent):
- resolveSpec (offline: identity + LookupSpec) then capability nil-gate BEFORE
runtimeFor, so an unsupported verb returns unsupported_capability (exit 2)
before any client is built — uniformly across list/context/artifact (previously
only cancel gated offline). Then runtimeFor + scope preflight + spec.<hook>(rt).
- agent list: catalog enumerates offline (ListCatalog); instance enumerates via
the online ListAgents hook, else reports not-enumerable.
Providers: example is now a declarative Provider() value + plain hooks reading
rt.AgentID() (echo minimal / reporter full differ only by wired fields). Explicit
aggregation in agent/register.go.
Adds cmd/agent runtime tests (CallAPI unwrap/error/transport, IsBot, CallMultipart
SafeInputPath), negative capability-gate tests (send --file / task list / context
get / artifact download), the https-only artifact check, and BuildCard's dynamic
Describe path.
|
||
|
|
a821bd0e9a |
refactor: capability-registration provider model (func fields, framework-gated)
Replace the fat 9-method Provider interface with a struct of function fields, mirroring the events KeyDefinition / shortcuts Shortcut convention: a provider wires only the capabilities it supports and leaves the rest nil. This removes the two things every integrator previously had to keep in sync by hand — the Capabilities bool matrix and the per-method ErrUnsupported returns. - Provider is now a struct: core Send/GetTask (mandatory, asserted at Register) plus optional func fields (ListTasks/CancelTask/context trio/DownloadArtifact/ ListAgents) whose presence == support, plus FileInput/InputRequired flags and an optional Describe for per-agent card metadata. - The card capability matrix is DERIVED from which fields are wired (DeriveCapabilities / BuildCard), so declaration and behavior are single- sourced and cannot drift. CatalogEntry drops its Capabilities field. - The command layer gates every optional verb on the nil field and returns a unified unsupported_capability (exit 2) before any network access; the ErrUnsupported sentinel and convertUnsupported are deleted. --file is now capability-gated too (file_input=false ⇒ unsupported before the upload prompt). - The Discoverer interface becomes the ListAgents field; catalog providers must wire it (asserted at Register). example expresses echo's minimal set vs reporter's full set purely by which fields its Factory wires per agent — no bool matrix, no refusal code. Conformance + tests updated to the new shape. |
||
|
|
7a65b43949 |
refactor: move agent providers to top-level agent/ package
Mirror the events layering: the framework/SPI stays in internal/agent, the concrete business providers move to a top-level agent/ package (agent/example/), and agent/register.go blank-imports each so their init() self-registration runs. cmd/build.go blank-imports the top-level agent package (alongside events); the command layer (cmd/agent) no longer wires providers directly. A test-only blank import keeps the example scheme registered for cmd/agent tests, which exercise example:echo / example:reporter offline. |
||
|
|
9b3b07398d |
feat: add provider-neutral agent command tree
Add the `lark-cli agent` command tree: a provider-neutral surface over remote A2A agents. One constant verb set (list / card / send / task / context) routes by agent_ref (<scheme>:<agent_id>) to registered providers; remote agents never grow new top-level commands and their capabilities are declared in a machine-readable card. - SPI (internal/agent): Provider interface, registry with fail-fast registration checks, typed ProviderKind / IdentityType, a closed Capabilities struct, NewCard single-source card synthesis, and the 9-state task machine aligned with A2A. - Command surface (cmd/agent): list / card / send / task / context with default JSON envelopes, meta.next suggested commands, fire + bounded `--watch --timeout` polling, local all-or-nothing scope preflight, and capability gating. Two CLI-enforced high-risk-write confirmations: `send --file` (off-machine upload) needs --yes, and artifact download refuses to clobber an existing -o target without --force; both return confirmation_required (exit 10) before any network/write. Artifact download is SSRF-guarded, https-only and size-capped. - Typed error contract with stable exit codes and codemeta classification. - Ignore local-only proof artifacts (tests_e2e/, tests_skill_eval/, coverage.html). |
||
|
|
4847f06ca8 | fix: route brand-sensitive endpoints through the resolver (#1836) | ||
|
|
cdd9d3409b | feat(affordance): usage guidance for shortcuts and per-command skills (#1793) | ||
|
|
06f6b0b18c |
fix: preserve original filename in multipart file upload (#1767)
* chore: bump oapi-sdk-go/v3 to v3.7.2 for filename-aware multipart upload * fix: preserve original filename in multipart file upload BuildFormdata read local files into a bytes.Reader before handing them to the SDK, so the SDK's part-filename detection (which only reads *os.File) fell back to "unknown-file" for every local --file upload. Use AddFileWithName with the file's basename instead. |
||
|
|
6f95c5eb22 | e2e: harden CLI E2E retry, cleanup, and domain selection (#1709) | ||
|
|
f0b6f35fee |
fix: resolve schema against runtime metadata in plugin builds; gate cache overlay by version (#1764)
* fix(schema): fall back to runtime catalog when no embedded metadata Binaries built from the bare Go module (plugin builds) embed only the empty meta_data_default.json stub because meta_data.json is gitignored and fetched at build time. The schema command, its completion, and the affordance command-form resolver read the embedded-only catalog, so every schema lookup failed with "Unknown service" even though the runtime registry had already sync-fetched full metadata. Add registry.SchemaCatalog(): embedded when compiled in (official builds unchanged, still deterministic), otherwise the merged runtime catalog seeded from cache or remote fetch. When neither source has data (offline plugin build with a cold cache), schema now returns a failed_precondition error with an actionable hint instead of "Unknown service" with an empty candidate list. * fix(registry): gate cached meta overlay on version newer than embedded The cached remote meta was overlaid onto the embedded meta_data.json unconditionally, so after a CLI upgrade an equal- or older-version cache kept shadowing the freshly shipped embedded definitions until a later refresh happened to rewrite it. Only overlay when the cache version is strictly newer than the embedded baseline. The bare-module stub baseline is "0.0.0", so plugin builds without compiled metadata still take any real cached version (TestOverlayGate_StubEmbedded_OverlaysRealCache) and the schema runtime fallback keeps working offline from a warm cache. Ports #1376 onto the typed meta model. --------- Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com> |
||
|
|
e621c6e50f | fix: support pnpm global installs in self-update (#1705) | ||
|
|
a1506cdffb |
feat: add docs history shortcuts (#1612)
Add docs +history-list, +history-revert, and +history-revert-status backed by docs_ai history OpenAPI endpoints. Document the safe history workflow and extend dry-run/live E2E coverage for the new shortcuts. |
||
|
|
bdffffb368 | feat: interactive upgrade prompt for bare lark-cli (#1498) | ||
|
|
6f2cddfce1 | fix(identity): correct identity diagnosis under external credential providers (#1693) | ||
|
|
fb042758db | feat: add whoami command showing effective identity (#1666) | ||
|
|
ebb0b6fe73 | feat(affordance): per-command usage guidance system (markdown source) (#1565) | ||
|
|
297776ea66 | feat(event): support VC meeting lifecycle events (#1632) | ||
|
|
b46e60c156 |
feat: add task event consumer (#1510)
* feat: add task event consumer * fix: address task event review feedback * feat: remove legacy task event subscription shortcut * test: strengthen task preconsume error assertions |
||
|
|
c5b5aece33 |
refactor: retire legacy error envelopes and enforce typed contract (#1449)
* refactor: retire legacy error envelopes and enforce typed contract
Consolidate all command error reporting onto the typed errs.* contract, remove
the legacy error surface that predated it, and tighten the lint guards so the
contract holds across the whole repository going forward.
Every failure now reaches stderr as one envelope shape: a category, an
optional subtype, a human- and agent-readable message, and a recovery hint,
with invalid parameters listed under `params`. The legacy ExitError envelope,
its constructors, and the boundary bridge that promoted untyped config and
authorization errors are deleted, leaving a single path from error to wire.
Predicate commands keep their silent-exit behavior through a dedicated signal
that carries only an exit code.
Infrastructure paths that still emitted ad-hoc envelopes — flag parsing,
unknown commands and subcommands, plugin and policy guards, confirmation
prompts, and auth/config failures — now classify into the same taxonomy.
Business, API, auth, and config exit codes are preserved; the one behavioral
change is that Cobra usage failures (missing required flag, unknown command,
bad arguments) now emit the typed validation envelope and exit 2, matching the
explicit flag and subcommand guards, instead of Cobra's plain-text exit 1.
Enforcement is repo-wide rather than per-path:
- The errscontract guards run by default everywhere instead of through a
migration allowlist, so legacy envelopes cannot be reintroduced anywhere.
- errorlint runs across the whole repository: every error wrap must use %w and
every comparison must use errors.Is/errors.As, so interior wraps stay legal
but can no longer break the chain the typed boundary relies on.
- The errs-no-bare-wrap guard is keyed by structural prefix instead of an
explicit per-domain allowlist, so new shortcut domains are covered without
editing a list. It runs where forbidigo is enabled (the shortcut domains and
the auth/config/service command groups); repo-wide chain integrity for the
remaining command paths is carried by errorlint above.
* test: align cli_e2e success assertions to the ok envelope
The api and service success path now emits the {"ok":true} envelope, so the
cli_e2e workflow assertions that still expected the old {"code":0} shape via
AssertStdoutStatus(t, 0) fail once they run with live credentials. Switch those
workflow assertions to AssertStdoutStatus(t, true); the fake-payload helper test
in core_test.go keeps its code-shape assertion.
|
||
|
|
4a4c3344c8 | fix: align api success envelopes (#1489) | ||
|
|
c61acb5264 | feat: add ci quality gate | ||
|
|
ed7fdd1a27 |
feat: optimize event subscription precheck, links, and consumer guard (#1447)
* feat: add SubscriptionType and SingleConsumer to EventKey definition * feat: fetch subscribed callbacks from application/get * feat: build addons scan-to-enable deep link for event precheck * feat: route callback precheck to application/get and emit scan links * feat: add reject fields to hello_ack protocol message * feat: add exclusive registration to event bus hub * feat: reject duplicate consumer for SingleConsumer EventKey at bus handshake * feat: surface bus consumer rejection as failed_precondition error * fix: encode empty addons sides as [] not null per launcher contract * fix: report missing callbacks when console has none subscribed * feat: bound exclusive consumer cleanup wait with configurable timeout * refactor: drain exclusive-wait timer and document websocket-only callbacks * fix: use camelCase clientID param in event scan-to-enable link * test: cover null/omitted callbacks and assert typed error category * fix: keep auth login remediation for user-identity missing scopes * refactor: simplify SubscriptionType normalization to match validateAuth style |
||
|
|
deb0bd9dd6 | refactor: converge command pipelines onto a typed metadata model + catalog (#1191) | ||
|
|
d1a0926dd6 | feat/revoke token (#1434) | ||
|
|
842be3fdc5 | feat(token): mint TAT via unified OAuth v3 Token Endpoint (#1408) | ||
|
|
7c64e63b9d |
feat(note): clarify note ownership with dedicated detail and transcript flows (#1435)
* feat: split note domain * fix: address note transcript review comments * fix: stabilize empty note detail detection |
||
|
|
465c789f7c |
feat: add --json flag support to auth subcommands (#1431)
* feat: add --json flag support to auth subcommands * feat(auth/logout): add json output support for logout command * feat(auth/list): add json output support for auth list command |
||
|
|
510545f1e5 | refactor(vc): consolidate note handling back into the vc domain (#1417) | ||
|
|
c11cf3b716 |
feat: split note domain (#1345)
Add note shortcuts for note detail and unified transcript retrieval, route vc note detail parsing through the note domain, and update note/vc/minutes skill guidance for normal versus unified transcript handling. Includes dry-run E2E coverage for the new note shortcuts and documents the remaining live E2E fixture gap. |
||
|
|
3f77eded9d |
feat: per-resource subscription identity + Match hook (#1185)
Framework support for resource-scoped event subscriptions, so one EventKey can fan out into independent per-resource subscription scopes: - KeyDefinition gains SubscriptionKey / NormalizeParams / Match hooks - ComputeSubscriptionID derives a dedup identity from (EventKey, sub-key params); plumbed through bus Hub, consume loop, and the Hello / PreShutdownCheck / ConsumerInfo protocol messages - add a synchronous Match filter stage before Process - change PreConsume cleanup to func() error and surface cleanup (unsubscribe) failures as WARN with an idempotency note - adapt minutes/vc/whiteboard PreConsume to the new cleanup signature - render SubscriptionID / SubscriptionKey in event status & schema output No domain wires these hooks yet; covered by unit tests using bus/protocol doubles. (Mail, the original exerciser, is intentionally not included.) Change-Id: Ifc743f1aa0bc4dff0c8a1e35da24883694fe7699 |
||
|
|
2b4c6349a1 |
feat(event): emit typed error envelopes across the event domain (#1289)
Replace every command-facing error path in the event domain — the consume/schema command layer, the +subscribe shortcut, EventKey definitions, and the consume orchestration — with typed errs.* envelopes, so consumers get stable type, subtype, param, hint, and missing_scopes metadata for classification and recovery instead of free-form message text. - Input validation (--jq, --param, --output-dir, --filter, --route, unknown EventKey, EventKey params) reports validation / invalid_argument with the offending flag in param and an actionable hint. - Scope preflight reports authorization / missing_scope with the machine-readable missing_scopes list; console-subscription and single-bus preconditions report failed_precondition with recovery hints. - The consume API boundary passes already-typed errors through and classifies transport, non-JSON HTTP, and unparsable responses; the vc note-detail retry now matches the not-found code on typed errors (it silently never fired against the legacy envelope shape). - Previously-bare failures exited 1 with a plain-text "Error:" line and now exit with their category code (validation 2, auth 3, network 4, internal 5) alongside the typed stderr envelope. - forbidigo and errscontract guards now cover the event paths so regressions fail lint; AGENTS.md and the lark-event skill document the typed contract for agent consumers. Validation: make unit-test (race) green; event unit and e2e suites assert category/subtype/param/hint and cause preservation against the real binary; errscontract and golangci lint clean. |
||
|
|
00d45f8fa2 | feat: adjust agent timeout hint output conditions (#1328) | ||
|
|
ec2ffebf47 | fix: keep bounded event consume runs alive after stdin EOF (#1285) | ||
|
|
9d845442ce | feat: add skills command to read embedded skill content (#1318) | ||
|
|
7c50b3d9e3 |
feat: fetch official skills index (#1301)
lark-cli update currently discovers official skills by parsing unstable human-oriented `skills add --list` output. This prefers the stable official JSON index for skills discovery, while preserving the existing CLI-list fallback and full-install fallback for resilience. Changes: - Add official skills index JSON parsing in `internal/skillscheck/sync.go` - Prefer JSON index discovery before existing CLI list parsing in `internal/skillscheck/sync.go` - Add reason-chain details when both discovery layers fall back to `fallbackFullInstall` - Add bounded HTTPS fetch for `https://open.feishu.cn/.well-known/skills/index.json` in `internal/selfupdate/updater.go` - Add unit tests for parser behavior, discovery fallback order, and fallback detail reasons in `internal/skillscheck/sync_test.go` Co-authored-by: zhaoyukun.yk <zhaoyukun.yk@bytedance.com> |
||
|
|
5788a6c384 | feat(im): return typed error envelopes across the im domain (#1230) | ||
|
|
a4a4bd6ee0 |
feat: check shortcut example commands against the live CLI tree (#1244)
Validate the example commands embedded in shortcut definitions (the "Example: lark-cli ..." lines in each shortcut's Tips, shown in --help) against the real command tree built by cmd.Build. Implemented entirely as test-only code in cmd/ (package cmd_test), so it ships in no binary and is not importable by product code; the truth source is cmd.Build, the same tree the binary uses, so the check cannot drift. It runs in the standard unit-test CI job (go test ./cmd/...); a renamed command or unaccepted flag in an example fails that job. |
||
|
|
abc0553f21 |
fix: use json skills list during update (#1251)
* fix: use json skills list during update * fix: preserve versioned skill names |
||
|
|
b07a6003f9 |
feat(sheets): spec-driven shortcut refactor with backward-compatible package (#1220)
* refactor(sheets): rebuild lark-sheets on sheet-skill-spec canonical + One-OpenAPI
Restart lark-sheets as a spec-driven downstream. Skill content (SKILL.md
and 16 references covering 13 operations skills + 3 workflow skills,
including the standalone filter-view skill) is mirrored from the
sheet-skill-spec canonical-spec; do not hand-edit, change upstream and
rerun npm run sync:consumers.
Drop the 11 legacy shortcut sources (spreadsheet / sheet management,
cell ops, dropdown, filter-view, float image, etc.) and 10 associated
tests. Wire up the new sheet_ai/v2 One-OpenAPI single entry that
dispatches by tool_name with JSON-string input/output, and land the
first canonical shortcut +workbook-info as a template that exercises
the public token XOR pair, Risk tiering, and zero-side-effect DryRun.
sheet_ai_api.go provides callTool / invokeToolDryRun and bypasses
runtime.CallAPI's silent swallowing of non-envelope responses so
gateway and business errors from the new endpoint surface precisely.
The remaining 55 shortcuts will be designed and landed separately,
canonical skill by canonical skill.
* feat(sheets): implement lark_sheet_workbook shortcuts (B1)
Land the 8 modify_workbook_structure shortcuts that round out the
lark_sheet_workbook canonical skill alongside the existing +workbook-info:
+sheet-create / +sheet-delete / +sheet-rename / +sheet-move / +sheet-copy
/ +sheet-hide / +sheet-unhide / +sheet-set-tab-color. All eight call
modify_workbook_structure via the One-OpenAPI invoke_write endpoint,
dispatched by the `operation` enum.
Helpers in helpers.go grow publicSheetFlags() / resolveSheetSelector() /
sheetSelectorForToolInput() / sheetSelectorPlaceholder() so future
sheet-level shortcuts share the public --sheet-id / --sheet-name XOR
treatment. +sheet-create intentionally drops the sheet selector pair since
create has no existing-sheet anchor (matches the spec fix in
tool-shortcut-map.json).
+sheet-delete is the first high-risk-write shortcut in the canonical
package; the framework requires --yes (exit code 10 otherwise).
+sheet-move's tool requires source_index in addition to target_index. The
CLI accepts an optional --source-index override and falls back to a
single get_workbook_structure read to derive it (and to resolve sheet_id
from --sheet-name). DryRun stays network-free by rendering <resolve>
placeholders for any field that would need that read.
* feat(sheets): implement lark_sheet_sheet_structure shortcuts (B2)
Add 8 shortcuts under the lark_sheet_sheet_structure canonical skill:
+sheet-info (get_sheet_structure) plus +dim-insert / +dim-delete /
+dim-hide / +dim-unhide / +dim-freeze / +dim-group / +dim-ungroup
(modify_sheet_structure, dispatched by operation enum).
Two reusable conversion helpers cover the impedance mismatch between
the CLI surface and the tool input:
- dimRange / dimPosition translate the CLI's 0-based exclusive-end
range into the tool's 1-based A1 notation. row 5..8 becomes
position "6" + count 3 (insert) or range "6:8" (range ops); column
26..29 becomes "AA:AC".
- infoTypeFromInclude maps the fine-grained --include vocabulary
(row_heights / col_widths / merges / hidden_rows / hidden_cols /
groups / frozen) to the coarse info_type enum the tool accepts;
mixed categories collapse to "all".
+dim-delete is high-risk-write (irreversible row/column removal).
+dim-freeze --count 0 auto-dispatches to operation=unfreeze. +dim-group
accepts --depth for forward-compat with a future server-side nested
group endpoint but does not pass it through today.
* feat(sheets): implement read_data / search_replace / write_cells shortcuts (B3)
Land 11 shortcuts across three canonical skills:
- lark_sheet_read_data (3): +cells-get / +csv-get / +dropdown-get
- lark_sheet_search_replace (2): +cells-search / +cells-replace
- lark_sheet_write_cells (6): +cells-set / +cells-set-style / +csv-put
/ +dropdown-set / +dropdown-update / +dropdown-delete
+dropdown-get reads the data_validation field via get_cell_ranges with
the range carrying its own sheet prefix (no --sheet-id needed). The
fine-grained --include vocabulary (value / formula / style / comment /
data_validation) maps to the tool's coarse include_styles bool plus
value_render_option enum. +csv-get's --include-row-prefix=false strips
the [row=N] prefix client-side because the tool only emits the
annotated form.
+cells-search / +cells-replace flatten the tool's options sub-object
into four independent flags (--match-case / --match-entire-cell /
--regex / --include-formulas) per the flat-flag rule, then repack them on the way
in.
+cells-set takes a raw --data JSON body whose `cells` array must match
the --range dimensions. +cells-set-style fans a single --style block
out to every cell in the range via a new fillCellsMatrix helper; the
range parser (rangeDimensions / splitCellRef / letterToColumnIndex)
only accepts rectangular A1:B2 forms — whole-column / whole-row need
sheet totals and are deferred.
+dropdown-set fans the validation block out to one range; +dropdown-
update / +dropdown-delete iterate sheet-prefixed --ranges and call
set_cell_range sequentially (partial failure leaves earlier ranges
already mutated; the Tip calls this out). +dropdown-delete is
high-risk-write and requires --yes.
+cells-set-image stays deferred to the cli-only batch (needs the
shared local-file upload helper alongside +workbook-create / +dim-move
/ +workbook-export).
* refactor(sheets): move +dropdown-update / +dropdown-delete to lark_sheet_batch_update
Follow-up to B3 after the spec re-mapped these two shortcuts to the
batch_update tool (atomic multi-range CRUD) instead of fan-out via
set_cell_range. Drop their Go implementations + helper validateDropdownRanges
+ splitSheetPrefixedRange from lark_sheet_write_cells.go and remove the
registrations from Shortcuts(); the shortcuts will reappear under
lark_sheet_batch_update during B7.
Also pull in the re-rendered reference docs:
- skills/lark-sheets/references/lark-sheets-write-cells.md
- skills/lark-sheets/references/lark-sheets-batch-update.md
* feat(sheets): implement lark_sheet_range_operations shortcuts (B4)
Land 8 shortcuts across four canonical tools:
- clear_cell_range → +cells-clear (high-risk-write)
- merge_cells → +cells-merge / +cells-unmerge
- resize_range → +dim-resize
- transform_range → +range-move / +range-copy / +range-fill / +range-sort
Three CLI↔tool vocabulary bridges live in this file:
- +cells-clear: --scope content normalizes to the tool's clear_type
"contents" (singular/plural spec mismatch is absorbed in the CLI).
- +dim-resize: --size <px> wraps as resize_{height,width}:{value:N};
--reset wraps as {reset:true}. The two flags are mutually exclusive
and at least one is required.
- +range-fill: CLI's five-valued --series-type collapses to the tool's
binary fill_type — `copy` → "copyCells", anything else → "fillSeries"
(the actual series progression is inferred server-side from the
seed cells in --source-range).
- +range-copy: --paste-type {values, formulas, formats} maps to the
tool's {value_only, formula_only, format_only}; "all" omits the
field entirely so the server applies its default.
+cells-clear is the second high-risk-write shortcut in the package;
the framework enforces --yes with exit code 10 as usual.
* feat(sheets): implement object-list shortcuts (B5)
Land 7 read shortcuts, one per object skill — chart / pivot table /
conditional format / filter / filter view / sparkline / float image. All
share the same shape (public sheet selector + optional <obj>-id filter)
so they're declared via newObjectListShortcut + an objectListSpec.
Notes:
- +cond-format-list exposes --rule-id, which is renamed to
conditional_format_id on the wire (the tool's full field name).
- +sparkline-list exposes --group-id (the higher-level handle); the
tool also accepts sparkline_id, intentionally not surfaced.
- +filter-list takes no id filter — at most one sheet-level filter
per sheet, so the listing is already unique.
- +filter-view-list is `cli_status: cli-only` but get_filter_view_objects
is in mcp-tools.json and dispatches through the same One-OpenAPI
endpoint; no special path required.
* feat(sheets): implement object CRUD shortcuts (B6)
Land 21 shortcuts — three (create / update / delete) per object skill —
backed by the manage_<obj>_object tools dispatched on the operation
enum. Five standard objects (chart / cond-format / sparkline /
float-image / filter-view) share an objectCRUDSpec factory; pivot and
filter are special-cased.
Shared wire contract:
excel_id + sheet_id|sheet_name + operation + [<obj>_id] + [properties]
CLI --data is passed through as the tool's `properties` field as-is, so
callers shape it per each object's spec doc.
Special cases:
- pivot adds optional --target-sheet-id / --target-position on create
(siblings of properties, not inside it).
- cond-format exposes --rule-id (short CLI name) wired to the tool's
conditional_format_id on the wire.
- sparkline uses --group-id (higher-level object handle) instead of
sparkline_id.
- filter has no separate id flag — at most one filter per sheet, so
filter_id is implicit. +filter-create promotes --range to a first-
class flag (instead of burying it inside --data).
- filter-view CRUD are `cli_status: cli-only` but
manage_filter_view_object is in mcp-tools.json, so they go through
callTool / One-OpenAPI alongside everything else.
All delete shortcuts are high-risk-write and require --yes.
* feat(sheets): implement lark_sheet_batch_update shortcuts (B7)
Land 4 shortcuts that all funnel through the batch_update tool's atomic
operations array:
- +batch-update raw passthrough; --data carries the full
{ operations: [{tool, params}, ...] } payload
plus optional continue_on_error. high-risk-write
since the caller may stuff anything inside.
- +cells-batch-set-style --data is [{ranges, style}, ...]; CLI flattens
each (entry × range) pair into a set_cell_range
op with a fan-out cells matrix carrying
cell_styles + border_styles.
- +dropdown-update --ranges + --options (+ --colors / --multiple /
--highlight) — installs/replaces one dropdown
across many ranges, each becoming a separate
set_cell_range op with data_validation in cells.
- +dropdown-delete --ranges — clears data_validation across many
ranges (high-risk-write).
Default is strict transaction: if any sub-tool fails the whole batch rolls
back. +batch-update exposes --continue-on-error to flip the policy; the
three fan-out shortcuts leave it strict (they're meant to be all-or-nothing).
Reinstates validateDropdownRanges + splitSheetPrefixedRange that were
removed during B3 → B7 relocation.
* feat(sheets): implement cli-only shortcuts (B8) — 70/70 complete
Land the four cli-only shortcuts that can't route through the One-OpenAPI
dispatcher (their backing capabilities aren't in mcp-tools.json):
- +workbook-create POST /open-apis/sheets/v3/spreadsheets
+ optional set_cell_range follow-up that zips
--headers and --data into the first sheet starting
at A1.
- +workbook-export POST /open-apis/drive/v1/export_tasks (type=sheet)
→ poll /export_tasks/:ticket up to ~30s
→ optional GET /export_tasks/file/:file_token/download.
CSV mode requires --sheet-id (single sheet export).
- +dim-move POST /open-apis/sheets/v2/spreadsheets/:token
/dimension_range
CLI is 0-indexed inclusive (--start / --end); the v2
endpoint expects half-open [startIndex, endIndex)
so the body uses endIndex = --end + 1. --sheet-name
is resolved client-side to sheet_id via
lookupSheetIndex when needed.
- +cells-set-image common.UploadDriveMediaAll
(parent_type=sheet_image, parent_node=token)
then callTool set_cell_range with cells carrying
rich_text: [{type:"embed-image", attachment_token, attachment_name}].
--range must be exactly one cell.
All four use runtime.CallAPI / DoAPI directly; only +cells-set-image
combines a legacy upload with the new One-OpenAPI for the second step
(set_cell_range is in mcp-tools.json so callTool is the right path).
This closes the migration: 70 shortcuts × 17 canonical skills × matching
the sheet-skill-spec v0.5.0 tool-shortcut-map.
* test(sheets): cover all 70 shortcuts with dry-run + execute-path tests
Twelve _test.go files alongside the implementation, mirroring the legacy
package's coverage style:
- testhelpers_test.go shared rig: TestFactory + Mount + dry-run
capture + JSON-input decode + envelope helpers.
- lark_sheet_*_test.go one test file per implementation file (9
files), table-driven dry-run cases per shortcut
plus targeted validation guards.
- execute_paths_test.go end-to-end execute paths via httpmock stubs.
Covers callTool unwrap, JSON-string output
decoding, two-step lookup (+sheet-move),
batch_update fan-out, dropdown atomic writes,
and the legacy OAPI shortcuts (+workbook-create,
+dim-move) including CLI inclusive → API
half-open index conversion.
Test coverage on the sheets package is 60.5 % of statements with -race
clean, meeting the dev manual's ≥ 60 % patch-coverage gate.
* refactor(sheets): inline cli-only shortcuts into their canonical skill files
Two naming cleanups:
- lark_sheet_cli_only.go is gone. The four shortcuts it grouped
(+workbook-create / +workbook-export / +dim-move / +cells-set-image)
were bundled by their implementation pattern (legacy OAPI direct
calls) rather than by canonical skill. The whole sheets package IS
the CLI implementation, so "cli only" wasn't a meaningful grouping
at the Go layer. Each shortcut now lives next to its skill peers:
+workbook-create / +workbook-export → lark_sheet_workbook.go
+dim-move → lark_sheet_sheet_structure.go
+cells-set-image → lark_sheet_write_cells.go
Per-skill shortcut counts now match tool-shortcut-map.json exactly
(workbook: 11, sheet_structure: 9, write_cells: 5). Helpers
(buildInitialFillInput, pollExportTask, downloadExportFile,
dimMoveBody) move with their shortcuts; nothing else in the package
referenced them.
- testhelpers_test.go → helpers_test.go. The _test.go suffix already
conveys "test"; the leading "test" was redundant. Matches the
helpers.go naming convention.
Behavior unchanged. go test -race -cover stays at 60.5 %.
* refactor(sheets): sync shortcut flags with sheet-skill-spec v0.5.0
Upstream hoisted a batch of high-frequency scalar fields out of --data
into independent flags and renamed several composite-JSON flags to
match their semantic content. CLI catches up.
Renames (drop-in, same payload semantics):
- +cells-replace --replace → --replacement
- +cells-set --data → --cells
- +workbook-create --data → --values
- +batch-update --data → --operations (now a bare array;
still accepts the envelope form for
back-compat with continue_on_error)
Flat-flag hoists out of --style / --data:
- +cells-set-style / +cells-batch-set-style
--style JSON drops; replaced by 11 flat style flags
(--background-color / --font-color / --font-size / --font-style /
--font-weight / --font-line / --horizontal-alignment /
--vertical-alignment / --word-wrap / --number-format) plus
--border-styles for the one field that's still nested. Both
shortcuts share styleFlatFlags() + buildCellStyleFromFlags().
- +cells-batch-set-style also drops the [{ranges, style}] array shape
in favor of one --ranges + the same flat style flags applied to
all of them.
Object CRUD --data → --properties everywhere (chart / pivot / cond-format
/ filter / filter-view / sparkline / float-image). Per-skill scalar
hoists merged into properties via an enhanceCreate/UpdateInput callback:
- +pivot-create adds --source (required), --range
(and continues to expose --target-sheet-id /
--target-position at top level)
- +cond-format-{create,update}
adds --rule-type (enum) + --ranges (JSON array);
merged into properties.rule.type and
properties.ranges respectively
- +filter-view-{create,update}
adds --view-name and --range; both override
their properties.* counterparts
- +filter-update adds first-class --range (was buried in --data)
Float-image is fully hoisted — no --properties flag at all. Ten flat
flags (--image-name / --image-token | --image-uri / --position-row /
--position-col / --size-width / --size-height / --offset-row /
--offset-col / --z-index) compose the properties block. Implemented as
its own factory (newFloatImageWriteShortcut) since it diverges from the
shared CRUD spec.
Tests track every flag renamed and add explicit cases for the new flag
combos. go test -race -cover stays at 60.3 %.
* refactor(sheets): align batch_update + cells-set with synced reference docs
Sync to upstream reference doc updates for 9 skills:
- batch_update sub-ops: rewrite wire fields tool/params -> tool_name/input
in CellsBatchSetStyle and DropdownUpdate/Delete fan-out (the actual
server contract per Schemas section); update --operations flag desc
and tests.
- +cells-set --cells: accept bare 2D matrix [[{cell},...],...] instead
of envelope {"cells":[[...]]}; spec example shows bare-array form.
- sparkline createDataDesc enum: win_loss -> winLoss (camelCase).
All other doc changes (float-image flat flags, cond-format
--rule-type/--ranges, pivot create-only --source/--range, filter /
filter-view extra flags, chart --properties) were already aligned in
commit
|
||
|
|
24ce3ec151 |
feat: add --json flag as no-op alias for --format json (#1104)
* feat(api): add --json flag as no-op alias for --format json * feat(service): add --json flag as no-op alias for --format json * feat(shortcut): add --json flag as no-op alias for --format json Skip registration when a custom --json flag already exists on the command (e.g. base shortcuts use --json for body input). Change-Id: If66236cadeea7fa81811061cce775deff51b92ce |
||
|
|
2bbab4d851 |
feat: validate credentials after config init (#1151)
* refactor: extract FetchTAT sharing the TAT-rejection classifier doResolveTAT minted the tenant access token inline. Extract the HTTP call into FetchTAT(ctx, httpClient, brand, appID, appSecret) so callers that already hold plaintext credentials — notably the post-config-init probe — can validate them without a second keychain round-trip. FetchTAT routes a non-zero TAT body code through the same classifyTATResponseCode the credential layer already uses, so a rejection is the canonical CategoryConfig / SubtypeInvalidClient (10003 / 10014) typed error — identical to what every token-resolving command returns. Transport, HTTP-status and JSON-parse failures stay raw (untyped) so callers can use errs.IsTyped to separate a deterministic credential rejection from upstream noise. doResolveTAT now delegates to FetchTAT; observable behavior unchanged. * feat: validate credentials after config init After config init saves the App ID / App Secret, fire a best-effort probe: mint a tenant access token with the just-saved credentials, then POST the application probe endpoint. When the credentials are deterministically rejected, FetchTAT returns a typed errs.* error and runProbe propagates it, so config init exits non-zero with the canonical ConfigError / invalid_client envelope (the same one every other command shows for the same bad creds) instead of letting the user discover the mistake on a later request. Ambiguous failures (transport, HTTP non-200, JSON parse, timeout, http-client init) come back untyped and are swallowed (errs.IsTyped is the discriminator), so a valid configuration is never blocked by upstream noise. The probe is wired into all four init paths and skipped when the user reused an existing secret. The saved config is not rolled back on rejection: stdout still records what was saved, stderr carries the typed error envelope. |
||
|
|
98173ae5a9 |
feat(drive): emit typed error envelopes across the drive domain (#1205)
Drive-domain errors now leave the CLI as typed, machine-branchable envelopes — a stable `type` plus `subtype` and named fields (param, params, retryable, log_id, hint) — so scripts and AI agents can branch on structure and act on a recovery hint instead of parsing prose. Changes: - Every error produced in the drive domain — validation, file I/O, and the failures returned from its Lark API calls — is emitted as a typed errs.* error; the exit code is derived from the error category. Drive's API calls now go through a shared typed classifier, so failures carry subtype, troubleshooter, a recovery hint, and the request's log_id whether the server returns it in the response body or the x-tt-logid header; an already-typed network/auth error is never downgraded into a generic API error. - Known API conditions (resource conflict, cross-tenant, cross-brand, ...) carry a recovery hint keyed by their error class; a command can refine that hint with command-specific guidance. - Batch partial failures (+push / +pull / +sync, where some items succeed and some fail) now report an honest ok:false multi-status result on stdout — the summary and every per-item outcome stay machine-readable — and exit non-zero, instead of a misleading ok:true success envelope. - Duplicate rel_path conflicts report each colliding path as a structured params entry (RFC 7807 invalid-params style). - Static guards lock the drive path so legacy error construction — direct envelopes or the auto-classifying API helpers — cannot be reintroduced, making drive the template for the remaining domains. Output changes worth noting for consumers: - Error envelopes now carry typed type/subtype and named fields; exit codes follow the error category (malformed or incomplete API responses are reported as internal errors rather than generic API errors). - Batch partial failures (+push / +pull / +sync) emit an ok:false result envelope on stdout (summary + per-item items[]) and exit non-zero; the per-item results stay on stdout rather than in a stderr error envelope. Errors surfaced through shared cross-domain helpers (scope precheck, media import upload, metadata lookup, save-path resolution) are not yet typed; they migrate with the shared layer in a follow-up change. |
||
|
|
b216363e63 |
fix(cli): remove FLAGS section from root --help (#1226)
Follow-up to #1223. The hand-written FLAGS block in `lark-cli --help` restated leaf-command flags at the root level — flags that are not registered on the root command (they error "unknown flag" there). Even trimmed to an illustrative example list, it duplicated information Cobra's per-command `--help` already renders authoritatively, and any static list in root help drifts from the real per-command flag sets over time. Drop the section entirely: Cobra's per-command `Flags:` output is the single source of truth. `USAGE:`/`EXAMPLES:` still show flags in context, and the `Flags:` block at the bottom of root help lists the actual root flags. Also removes the now-obsolete TestRootLong_FlagsSectionPointsToCommandHelp. |
||
|
|
b0b163d0ef |
fix(cli): stop root --help listing per-command flags as global (#1223)
The hand-written FLAGS block in `lark-cli --help` listed --params, --data, --as, --format, --page-all, --page-size, --page-limit, --page-delay, -o, --jq, -q and --dry-run as if they were global flags. None are registered on the root command — they all error "unknown flag" at the top level and exist only on leaf commands (api, service). The block also contradicted the Cobra-generated "Flags:" section rendered directly below it, which shows only -h/--help, --profile, -v/--version. Replace it with a short illustrative example list (common flags first) and a pointer to `lark-cli <command> --help` for the full per-command set. Root help stays a discovery signpost without claiming the flags are global or restating defaults/descriptions that drift from the real flag sets. Change-Id: Ia1cab889dd70b6b49a61dac468dedfd7fe39043f |
||
|
|
4710a294f5 |
refactor(transport): own all HTTP transport in internal/transport, fix util layering inversion (#1213)
internal/util imported internal/proxyplugin (SharedTransport, FallbackTransport, NewHTTPClient, and WarnIfProxied via proxyPluginStatus), so a foundational util package depended up into a feature package, pulling binding/core/vfs into the transitive cone of every util importer. Move internal/proxyplugin -> internal/transport and make it the single owner of outbound transport: fold the two SharedTransport functions into one Shared() (proxy-plugin override -> LARK_CLI_NO_PROXY -> http.DefaultTransport), and move Fallback/NewHTTPClient/WarnIfProxied/DetectProxyEnv/noProxyTransport out of the now-deleted internal/util/proxy.go into the new package. The proxy-plugin probe is demoted to a private pluginTransport(); the duplicate redactProxyURL collapses to one. internal/util keeps no proxy code and is a leaf again. Re-point all consumers (registry, doctor, config, auth, cmdutil, update) to internal/transport. Behavior-preserving: package move + symbol rename + dedup. Two new tests lock the fail-closed contract (plugin overrides NO_PROXY; malformed config never falls through to direct egress). |
||
|
|
f65712cacf |
feat: add proxy plugin mode for CLI HTTP transport (#1181)
* feat: add security plugin for proxy * docs: remove outdated proxyplugin README files * refactor(proxyplugin): tighten proxy URL validation and add security checks * refactor(proxyplugin): cache blocked transport and clean up error handling * fix(proxyplugin): fix CR issues for Security hardening --------- Co-authored-by: AlbertSun <sunxingjian@bytedance.com> |