Extend +download and +preview to accept --url and --wiki-token in
addition to --file-token (mutually exclusive). Wiki nodes are resolved
to their underlying object; only file-backed nodes are supported, and
non-file documents (docx/sheet/bitable/slides) return a typed
validation error hinting to use +export.
Check Drive export permission before file and document media downloads, while preserving whiteboard behavior and adding actionable preview and rate-limit recovery hints.
Spec source: active@ddbecbafcf0d68cc115faf3fcf0214fb6edc30b284147d33e5c9934e7bad82b4
Document the --raw, --slide-id, and --slide-number flags for slides +xml-get, including their mutual-exclusion rules and usage examples.
Clarify single-slide versus full-presentation output behavior, and remove the shallow documentation-only regression test.
Assembling the --slides JSON array by hand is what callers keep getting
wrong. A page of SML is multi-line and quote-heavy, and shell has no
built-in way to JSON-escape it, so callers reached for `jq -n --rawfile`
to build the array. In environments without jq the substitution silently
became an empty string and the command ran on to create an empty deck,
or the half-escaped XML reached the backend and came back as an opaque
3350001 after the presentation already existed.
Two input forms remove the escaping step:
--slides now declares Input{file, stdin}, so a finished array can be
read with `--slides @deck.json` or piped in with `--slides -`.
--slide is repeatable, takes one complete <slide> document (or @path),
and the CLI assembles the array. Repetition order is page order.
The forms are mutually exclusive: merging them would make page order
depend on flag-parsing rules nobody wants to reason about.
Notes on the repeatable flag: the framework only resolves Flag.Input for
single-valued string flags, so --slide resolves @path itself, through
the same cmdutil.ReadInputFile the framework uses, keeping the
"relative path under the current directory" rule identical. It rejects
"-" outright, because a process has one stdin and that cannot mean "this
occurrence" on a repeatable flag; the error names both forms that work.
Structural validation now runs on the assembled array, so both forms
fail the same way, and it runs before the create call so a malformed
page can no longer leave an orphaned empty presentation behind.
Three inputs the first round of review found still slipping through are
now rejected or normalized before the create call. `--slides null` is
valid JSON for a slice, so it parsed without error and left the array
nil, which read as "no pages given" and produced the blank deck
reported as success that the empty-value check exists to prevent. An
`<?xml ...?>` prolog is well-formed XML, so the parser accepted it and
only the backend rejected it, with 4001000 buildSnNode, after the
presentation already existed; it is now caught in the shared slide
validator, which covers +add-slide and +replace-pages too. And a
leading UTF-8 BOM made `--slide @page.xml` reject a file that
`--slides @deck.json` accepted, because the framework strips it for
Input flags and the repeatable flag resolved @path itself;
StripUTF8BOM is exported so both paths normalize the same way.
Also threads the source flag name through uploadSlidesPlaceholders,
which previously reported +add-slide upload failures as --slides.
Docs: the create/troubleshooting references now teach the file inputs
instead of the jq array-building template, and the follow-up snippet
uses the CLI's own --jq instead of piping to an external jq.
The lint gate in SKILL.md now names `slides +create` as a whole rather
than only its --slide form.
* fix(apps): detect the no-database failure by code or message
The recovery flow for "db command against an app that has no database"
keyed on one business code (500002759). The server has since renumbered
that case to 400002465, which silently disabled the flow: users now see
the raw internal message about workspace / app-id mapping and lose the
cloud-development recovery steps entirely.
Nothing catches the regression. There is no compile error, the unit tests
compare against the same constant they set, and the dry-run E2E does not
exercise a real response — the failure only shows up against a server that
has already renumbered.
Detect on code OR message instead. Both known codes are kept, plus narrow
lowercase markers of the server's internal wording. The two channels have
opposite failure modes: a code is precise but gets renumbered, a message
survives renumbering but breaks on rewording or localization. Requiring
either to match means one channel changing degrades nothing, and only a
simultaneous change of both regresses.
Markers stay deliberately narrow. "no db branch" in particular must not
also swallow env-pull's "invalid db branch" case, which needs its own
hint; a comment records that widening them requires a test proving the
neighbours still pass through.
Classification and the cause chain are untouched: the helper still mutates
the problem in place and returns the same error value.
* test(apps): assert the full typed-error contract in no-database cases
Review feedback: the new subtests checked only Message and Hint, so a
change that reclassified the failure — or replaced the error value and
dropped the cause chain — would still have passed.
Each case now asserts Category, Subtype and Code are untouched by the
rewrite, and that the helper returns the same error value. Inputs use a
concrete subtype rather than Unknown, so a clobbered classification is
actually observable. One new case wraps a cause and asserts errors.Is
still finds it through the rewrite.
Also covers the predicate's defensive nil guard, which withAppsHint cannot
reach on its own (ProblemOf returns ok=false for untyped errors), closing
the two uncovered lines the coverage report flagged. Both withAppsHint and
isAppNoDatabaseError are now at 100%.
* fix(errclass): classify the db-domain business codes
Three codes reaching the Apps db commands were absent from the Spark
table, so BuildAPIError fell through to the CategoryAPI + SubtypeUnknown
catch-all and the envelope carried no usable classification.
"App has no database yet" registers as Validation / FailedPrecondition:
the app resolves fine and the request is well-formed, but a prerequisite
the caller must create first is missing, so retrying unchanged can never
succeed. This moves its exit code from 1 to 2 — "fix the state" rather
than "the call failed" — and a test pins that so a future
reclassification has to be deliberate. Two codes cover it because the
server renumbered the case into the 4xx band; the legacy one stays for
older servers.
"Table does not exist" registers as API / NotFound, an ordinary
missing-resource lookup with no exit-code change. SubtypeNotFound has no
APIHint default, which matters here: the Apps layer fills its
command-scoped hint only when the classifier left Hint empty, so a
context-free default would displace the more actionable one. A test
guards that too.
* feat: accept frontend app-type in apps +create
* feat: accept frontend app-type filter in apps +list
* docs: clarify frontend app-type handling in apps +init
* docs: note FRONTEND in queryAppType comment
* docs: add frontend app-type guidance to lark-apps skill
* docs: add frontend app-type to lark-apps command references
SKILL.md's routing table already covered frontend, but the per-command
reference docs still enumerated only html/full_stack. Update create/list/get
enum values, add a frontend local-dev section, and note frontend in the
release-create entry so agents document the third app type consistently.
* docs: address CodeRabbit feedback on frontend app-type refs
- create.md: state the app-type enum is matched exactly (lowercase), drop
the incorrect claim that the CLI normalizes case
- local-dev.md: scope database debugging to full_stack (frontend/html have
no DB) and add the +release-get finished-status poll to the frontend flow
* docs: align app-type enum in apps E2E coverage and test comments
The E2E coverage table and two test comments still described the --app-type
enum as html/full_stack after frontend was added. Update them to
html/frontend/full_stack for consistency; assertions are unaffected (they
match on substrings, not the full enum set).
* docs(lark-apps): cloud-dev honors routed app_type instead of hardcoding full_stack
The main SKILL.md router declares app_type and dev-method orthogonal and
routes no-database interactive tools to frontend, but cloud-dev/create
references still hardcoded `+create --app-type full_stack` for cloud
generation. This forced a frontend-routed request into a full_stack app
(unrecoverable since apps have no +delete).
Align with the 2026-07-24 design decision: cloud generation also splits by
database need — full_stack when persistence is required, frontend by default
when unstated. Verified on BOE that a frontend app runs the full cloud
session+chat pipeline to completed.
Both are registered, reachable business domains — `lark-cli attendance
user_tasks query` and `lark-cli mindnotes nodes create` run, and both appear in
`auth login --domain` — but neither had an entry in
service_descriptions.json. GetServiceTitle/GetServiceDescription returned "",
so buildDomainMeta fell back to the typed service spec, which carries one
string for both languages. The result was visible in `--help`: mindnotes
rendered its Chinese description in the English domain list, and attendance
rendered "attendance record query", a lowercase fragment shown in both locales.
Both keep their own scope namespaces (attendance:task:readonly,
mindnote:node:create/read) and stay independent auth domains, so no
auth_domain is set. whiteboard remains the only domain folded into docs.
TestGetDomainMetadata_HasTitleAndDescription could not catch this: it asserts
on buildDomainMeta's output, which the fallback had already made non-empty.
The new reconciliation test walks EmbeddedServicesTyped plus AllShortcuts and
asserts both languages resolve from the config itself, before any fallback.
EmbeddedServicesTyped is the overlay-free parse, so the test does not depend on
what remote_meta.json happens to hold on the machine — a domain that only ever
arrives via remote overlay stays out of its reach.
Wrap the Drive file-copy endpoint as drive +copy. Accept a document URL
(recommended) or bare token + --type for the source; the target takes a
folder token, a folder URL, or the my_space constant, which resolves the
caller's My Space root folder via the root-folder-meta endpoint (absent
from platform metadata, works for both user and bot). Repeatable --extra
key=value pairs are forwarded verbatim for special copy semantics (e.g.
target_type=docx to convert a legacy doc during copy). Source and folder
tokens are validated with validate.ResourceName before path
interpolation. Reject wiki URLs/tokens with a typed validation error
whose hint carries a wiki +node-copy command template using a fixed
<node-token> placeholder, because a Drive copy of a wiki-backed document
would land in Drive space instead of the wiki tree. In bot mode the CLI
auto-grants the current CLI user full_access on the new copy (same
behavior as +upload/+import), reporting the outcome in the
permission_grant output field without failing the copy.
Declare docs:document:copy (the narrowest scope in the endpoint's any-of
set) plus a conditional drive:drive.metadata:readonly for my_space
resolution. Cover the shortcut with unit tests, dry-run e2e and a
self-contained live workflow (upload -> copy -> download-verify ->
my_space copy -> cleanup), and register it in
tests/cli_e2e/drive/coverage.md. Route copy intents in the lark-drive
skill to the shortcut instead of the raw files copy service command.
* feat(sheets): add --ai-only to +formula-verify for AI formula status polling
BE-2: +formula-verify gains --ai-only, mapping to verify_formula tool
input ai_only=true. AI formulas (AI_WRITE / AI_CLASSIFY / …) compute
asynchronously; --ai-only is a single-shot polling probe (no built-in
wait/timeout) that skips the ordinary 7-Excel-error scan and returns
current AI-formula compute status. Coexists with --sheet-id/--range and
honors --exit-on-error.
BE-4: mirror sheet-skill-spec SoT docs (AI formula list in
lark-sheets-formula-translation, --ai-only + async polling section in
lark-sheets-formula-verify) and regenerate flag_defs_gen.go.
Spec source: active@6fe4ea7389c6d0631dc8279ee7506c357d0600325e28ffabdc97d9a66339e762
* docs(sheets): mirror enriched AI formula params + --ai-only verify wording
Mirror from sheet-skill-spec SoT (ee/sheet-skill-spec MR!55):
- formula-translation: full AI function param details (range rules,
AI_TRANSLATE language keys, AI_EXTRACT type, AI_CLASSIFY modes,
AI_INFER/AI_IMPORTDATA array semantics)
- formula-verify: clarify --ai-only convergence expectation, drop
implementation-leaking phrasing
* docs(sheets): mirror unified AI() formula docs from spec
Sync the lark-sheets AI formula skill from sheet-skill-spec (SoT) after
the product change that merges all AI formulas into one AI() function:
- formula-translation reference: single =AI(prompt, [range]) function with
syntax (incl. variadic =AI(part1, part2, ...) concatenation), per-case
usage table (translate / sentiment / classify / extract / summarize /
rewrite / generate / cleanup / keywords / multi-cell prompt), and
prompt best practices
- formula-verify reference + --ai-only flag def: unified AI() wording
- flag_defs_gen.go regenerated from flag-defs.json
- lark_sheet_formula_verify.go: drop stale AI_WRITE / AI_CLASSIFY names
in the ai_only comment, reference the unified AI() function
* docs(sheets): sync lark-sheets skill from spec
Mirror the lark-sheets generated artifacts from sheet-skill-spec dwc/lark-sheet-ai:
- update AI formula verification delivery guidance
- add read-data inspection/profile helper scripts
- refresh generated flag definitions for +formula-verify --ai-only
* docs(sheets): sync updated AI formula guidance
Mirror the latest lark-sheets generated docs from sheet-skill-spec dwc/lark-sheet-ai after c292420:
- clarify that #ERROR/readback issues can be formula-write escaping failures rather than AI compute failures
- recommend +cells-set JSON formula writes for AI() formulas containing commas and quotes
- document <=1000-row serial batching for AI formula copy-to-range workflows
* docs(sheets): add license headers to helper scripts
Add the repository-standard copyright and SPDX headers to the generated lark-sheets helper scripts so the license-header check passes.
* docs(sheets): regenerate sheet flag defs
Regenerate shortcuts/sheets/flag_defs_gen.go after the latest sheet-skill-spec sync updated flag-defs.json.
* docs(sheets): revert lark-sheets skill sync
#2194 extended `im +messages-search` to `AuthTypes: {user, bot}` but left the
affordance example and the skill reference asserting user-only, so the
dual-identity guard added by #2199 fails on main.
Scoped repos created by Miaoda apps failed day-2 `git pull/push`
authentication because a stale, higher-priority credential helper (e.g.
macOS osxkeychain) served cached credentials instead of the lark-cli
helper. This isolates the URL-scoped credential helper chain and closes
correctness gaps found in review:
1. Helper-order isolation. Git's empty-helper ("") reset only clears
helpers that parse BEFORE the lark-cli section; a generic
credential.helper (or one from a later [include]) that parses AFTER it
still participated in get/store/erase. SetHelper now verifies, via the
faithful parse-order oracle (`git config --includes --show-origin -z
--list`), that the lark-cli helper is last in fill order. If not, it
repositions the section to the end of the writable file; if a later
helper lives in a file we must not edit, it fails closed with a
FailedPrecondition error and restores the prior state.
2. Concurrent read-modify-write. The read-modify-write of the writable
global config file is serialized across lark-cli processes by a new
cross-process lockGlobalConfig, ordered after lockApp and before
lockURL. That lock cannot stop an unrelated (non-lark-cli) process, so
the helper rewrite also no longer clears the whole key: it deletes only
the values observed in the ownership snapshot by exact match, leaving a
helper a third party inserted during the write window in place. The
readback then diverges from the expected state and SetHelper fails
closed with the foreign value preserved, instead of a whole-key
--unset-all silently deleting it.
3. Recoverable teardown. UnsetHelper now deletes useHttpPath before the
helper list so a mid-teardown failure leaves a lark-cli-recoverable
residue (never a useHttpPath-only orphan that blocks re-init), and
removes only lark-cli values from a mixed list, reporting that a
third-party helper remains rather than silently no-op'ing. The state
taxonomy gains Foreign/Partial/Mixed to drive these paths.
Reword the note under the cross-domain search commands so agents ask
the user to clarify an attendee's type when a name cannot be resolved
or is ambiguous, instead of guessing the type from the name shape
(e.g. treating an unmatched room name as a bot).
Co-authored-by: TRAE CLI <noreply@bytedance.com>
Bind Slides lint diagnostics to exact source XML nodes through stable xml_path references.
Preserve correct paths for filtered, anonymous, nested, and duplicate-ID elements. Detect duplicate source element IDs across slides and block release with actionable guidance.
Document the new locator contract and add regression coverage for path accuracy, metadata compatibility, and duplicate-ID handling.
Adds AI-friendly output path handling to `slides +screenshot`.
- Supports `--output` for a single screenshot in both existing-slide and XML render modes.
- Validates selector count, conflicting output flags, unsafe paths, directories, whitespace, and unsupported extensions with structured errors.
- Reconciles the requested filename with the server’s actual PNG/JPEG format and reports the final path through `output`, `requested_output`, and `output_adjusted`.
- Avoids replacing existing screenshots by appending `_2`, `_3`, and subsequent suffixes.
- Keeps `--output-dir` for multi-page screenshots and preserves `--output-name` for render mode.
- Updates the Slides Skill with explicit `--slide-number` / `--slide-id` guidance and task-scoped screenshot directories.
- Adds unit, dry-run E2E, and live workflow coverage for validation, path handling, format adjustment, and collision behavior.
* docs(slides): route skill guidance back to +replace-pages until whole-page rollout completes
Partially reverts the skill portion of #2143 (56fd29e6). lark-cli now ships
+update-slide, but the backend capability it depends on is still rolling out;
on environments without it every call answers an invalid-param error. The skill
was routing all whole-page and multi-page edits there, which turns a staged
backend rollout into a hard failure for every agent that follows the guidance.
Only the guidance moves: whole-page work routes to +replace-pages again, its
reference page is restored (with the SML namespace examples migrated to https,
a tree-wide change that landed while the page was deleted), and
lark-slides-update-slide.md is removed so nothing steers a caller to a command
the backend may refuse.
The binary is untouched. +update-slide stays available for environments that
already have the capability, and +replace-pages keeps working everywhere.
Re-apply the skill routing by reverting this commit once the rollout completes.
* docs(slides): override premature update-slide guidance
* docs(slides): route single-page rebuilds to replace-pages
XML written into a field name this shortcut does not accept — most often
"content", because <shape> nests a <content> child — was silently dropped,
so the part failed the required-field check and reported "requires
non-empty replacement". That reads as "the value is empty", which sends
callers rewriting the value instead of the key.
Reject fields outside the action's own set and name the field the caller
most likely meant, with a correct one-liner attached as a hint. Matching
folds case and separators so "Content", "newXml" and "block-id" resolve
too, while the whitelist itself stays exact: the API accepts only
snake_case, so "Replacement" must be rejected rather than slip through.
Only block_replace and block_insert parts are checked, so missing /
str_replace / unknown actions keep their existing errors, and an
actually-empty payload still reports the non-empty wording.
The alias list covers only names that plausibly carry a fragment. A shape
attribute like "fill" is deliberately absent: whoever writes it means
"recolor this block", not "here is my XML", so answering did-you-mean
"replacement" would be guessing. The unknown-field error already names the
valid set, which is true under either reading.
Docs carry the same constraint at the three points a caller can hit first:
SKILL.md, the +replace-slide reference (warning + counter-examples + error
table), and the read-modify-write workflow. The --parts flag description
now spells the field names out instead of eliding them behind "...".
Note: this tightens parsing. Extra keys inside a part used to be ignored;
they are now rejected.
Register <embed> in the bundled Slides SML 2.0 schema, document its SVG
contract, and teach the lint/schema validator to handle XSD wildcards and
embed geometry. Keep embedded SVG payloads out of IconPark validation so
foreign-namespaced <icon> elements inside a payload are not misreported.