* fix(apps): friendly-ize "Container not exists" for observability commands
+metric-list and +analytics-list passed the upstream business code 400002655
("Container not exists") through verbatim. The message reads like an
infrastructure fault and misleads callers (including AI agents) into retrying a
non-retryable, expected business state: an app with no running container simply
has no metrics/analytics to query yet.
Rewrite it at a scoped observability helper (withObservabilityHint) into a
user-facing explanation plus a deploy-then-retry next step, mirroring the
existing isAppNoDatabaseError override. Detection is code-OR-message so a server
renumber alone does not silently drop the rewrite. Classification, code, and the
wrapped cause are preserved; unrelated failures still fall through to the shared
app-id recovery hint (and its own no-database override).
* test(apps): add execution-path regression tests for observability container hint
common_test.go proves withObservabilityHint in isolation but stays green if a
call site reverts to withAppsHint. Drive +metric-list and +analytics-list
Execute with a mocked 400002655 "Container not exists" envelope and assert the
container-specific message/hint/code, so a revert fails the build. Also closes
the two uncovered call-site lines flagged by coverage.
* fix(errclass): classify no-container code as validation/failed_precondition
Register 400002655 in sparkCodeMeta mirroring its no-database twin
(400002465) so both "expected precondition not met" business states expose
the same validation/failed_precondition classification to machine consumers,
instead of falling back to api/unknown. The shortcut-layer message rewrite
already keyed off the raw code, so this only aligns the typed envelope's
category/subtype; update the execution-path tests to pin the new
classification.
* fix(apps): gate the no-container hint's release behind user authorization
The no-container hint told a harness to deploy via +release-create, a "write"
that takes the whole app live and can affect existing production traffic —
without the user-confirmation gate its no-database twin deliberately carries.
Since the hint's audience is an AI agent that acts on it, a failed metrics read
could trigger an unconfirmed go-live. Lead with a read-only +release-list
status check and gate +release-create behind an explicit user confirmation,
mirroring appNoDatabaseHint.
* fix(apps): reject non-HTML apps in +html-publish with actionable error
+html-publish only supports html/modern_html apps, but the backend
rejected frontend/full_stack apps with an opaque business code
(400000059) partway through the publish, and a wrong --app-id surfaced
a bare "app not exist" code (400002577). Add an app_type whitelist
precheck at the top of runHTMLPublishTOS: it resolves the type via
queryAppType, rejects unsupported types with a failed-precondition error
that names the type and redirects to +release-create, and annotates a
lookup failure with the +list recovery hint. A defense-in-depth
translation covers the case where the type changes between precheck and
release-create.
* test(apps): cover release-create app_type fallback translation
Pin the defense-in-depth layer in runHTMLPublishTOS: when the app_type
precheck passes but release-create still returns 400000059 (type changed
mid-flight), the opaque business code is translated into a
+release-create hint instead of bubbling raw.
* test(apps): pin app_type precheck runs before packaging
Use a nonexistent --path in the full_stack/frontend rejection tests so
the assertion fails if the precheck is ever moved below the tarball
packaging step. A valid site only proved the precheck runs before the
network call, not before walkHTMLPublishCandidates.
* docs(apps): fix registerAppTypeStub comment on httpmock match direction
httpmock matches when the request URL contains stub.URL. The bare
/apps/{id} query cannot contain the longer pre_release/releases stub
URLs, so it only matches the app_type stub — the previous comment stated
the substring relationship backwards. Comment-only; no behavior change.
The embedding guide (extension mechanism: Credential / Transport /
Restrict / Observer / Wrap / On) lives on the Open Platform doc site,
but the repo itself never mentions it — enterprise IT scanning GitHub
cannot discover the centralized-integration path at all.
- README(.zh): add a 'Personal or Enterprise?' routing table right
after 'Why lark-cli?', linking the official embedding guide and
extension/; add 'Enterprise' to the nav bar; document the '.md'
raw-Markdown trick for AI Agents
- extension/README.md: thin index mapping packages to extension
points, pointing to platform/README.md and the official guide
* feat(apps): add +user-id-convert shortcut for Miaoda↔Feishu ID conversion
Wrap the platform id_convert OpenAPI as a read-only shortcut that maps
Miaoda user_id ↔ Feishu open platform IDs (open_id / union_id / Feishu
user_id). It does one thing — conversion — with no local mapping table,
caching, permission pre-check, or direction guessing.
- --convert-type enum → server id_convert_type (10/11/20/21/40)
- --ids: csv / @file / stdin, 1-100 per call, not de-duped, input order
- reconstructs data.missed by diffing input positions against returned
source_ids (server silently drops unresolved IDs), keyed by 0-based index
- meta counters (total/hit_count/missed_count) via pointer fields on
output.Meta so an explicit missed_count: 0 survives omitempty
* test(apps): address review feedback on +user-id-convert
- reject empty --ids CSV entries (e.g. "a,,b") with a typed validation
error instead of silently dropping them, since a dropped entry shifts
every later result's 0-based index and breaks the position-keyed
items/missed contract; add an interior-empty-element test
- reuse common.GetSlice / common.GetString for response projection
(house convention) instead of local asSlice/asString helpers
- requireConvertValidation now asserts CategoryValidation +
SubtypeInvalidArgument via errs.ProblemOf, keeping ValidationError.Param
- table-drive TestResolveConvertType over all five directions so every
--convert-type → id_convert_type mapping (10/11/20/21/40) is protected
* fix(apps): split newline-delimited --ids for +user-id-convert @file/stdin
@file and - (stdin) input arrives verbatim from the framework as
one-ID-per-line text, but parseConvertIDs only split on commas, so such a
block was sent as a single malformed request ID. Treat a newline as
equivalent to a comma, tolerating a file's trailing newline while still
rejecting interior empty entries so position-keyed result indices stay
aligned. Add @file and stdin tests asserting the request body's ids are
split into discrete IDs.
* fix(apps): stringify numeric JSON IDs in +user-id-convert results
Responses decode with json.Number (client.ParseJSONResponse uses
dec.UseNumber()), so a server that emits source_id/target_id as bare
numbers — plausible for the numeric Miaoda user_id form — was silently
coerced to "" by buildConvertResult's strict string assertion: the
source_id got dropped (false not_found) and the target_id blanked
(false success).
Add common.GetStringLoose, which stringifies string/json.Number/int64/
float64 via literal text (large integer IDs keep full precision, never
routed through a lossy float64), and use it for both id reads. Cover it
with a package-level table test plus an end-to-end regression asserting a
numeric-JSON response yields intact, non-blank ids and no false miss.
Also exercise resolveConvertType's non-empty "not a valid direction"
branch directly, since the runner's enum gate preempts it in normal flow.
* test(common): tighten GetStringLoose numeric coverage
Add an int-branch case (was only covering int64) and swap the float64
fixture from 42 — which no formatter would render in exponent form — to
1e-7, whose fixed-point rendering "0.0000001" fails under the 'g' verb.
This turns the "no scientific notation" case into a real guard for the
'f' verb choice, per CodeRabbit review on c64cca39.
* feat: add apps database sync shortcuts
Add Base-to-database sync shortcuts for preview, create, list, get, enable, disable, update, and delete flows.
Cover OpenAPI request contracts, typed sync error classification, dry-run E2E coverage, and lark-apps skill guidance.
Co-authored-by: TRAE CLI <noreply@bytedance.com>
* fix(apps): send db-sync task_id and config in request body
The enable/disable/delete/update sync commands placed task_id (and
update's config) in query params, but the OpenAPI contract binds these
fields via api.json (request body). BOE testing returned
"field validation failed" (99992402) because the body was empty.
Move task_id to the request body for enable/disable/delete, and move
both task_id and config to the body for update. Dry-run previews now
render these under body, and unit tests pin the body binding so a
regression to query params fails.
* fix(apps): use POST for db-sync-delete action endpoint
The delete command issued an HTTP DELETE to db/sync_del, but the
action-style endpoint is registered as POST (like sync_create and
sync_disable). The method mismatch made the gateway return a plaintext
404, surfacing as "API returned a non-object JSON response".
Switch the request and dry-run preview to POST, and pin the method in
the delete unit tests so a regression to DELETE fails.
* test: pin db-sync update base_url as optional contract
* test: pin db-sync update omits base_url without silent default
* docs(skills): clarify db-sync source.base_url create-required update-optional contract
* fix(apps): send db-sync env in request body not query params
The +db-sync-create and +db-sync-update endpoints read env from the
request body (peer of config/preview/task_id), not the query string.
Placing env in query params left the body env empty, so the server
treated every request as online and rejected DDL operations
(code 500002776: forbid ddl/dcl operation in online env), making it
impossible to create/update sync tasks against a dev environment.
Move env into the request body via a new dbEnvBody helper that mirrors
dbEnvParams' omit-empty contract, so unset env still lets the server
auto-select the branch. Pin the contract in unit and e2e dry-run tests
by asserting body.env and that env is absent from query params.
* test: align db-sync operate/delete e2e with request-body contract
The enable/disable/delete dry-run e2e still asserted the pre-migration
wire shape: delete on DELETE and task_id in query params. The shortcuts
now POST these actions with task_id in the request body (commits moving
task_id and the delete verb), so the stale assertions failed against a
current binary.
Assert POST + body.task_id and that task_id is absent from query params,
pinning the same body-over-query contract the env fix established.
* fix(apps): improve db-sync create ergonomics and error guidance
Refine +db-sync-create/update validation, error hints, and docs so AI
agents recover from common Base-to-database sync failures without guessing:
- source.table.name: document that a user-named table must be set, name
takes precedence over the base_url ?table= token; fix test fixtures that
used a fictional source.table.url instead of source.base_url.
- Preflight source table locate: reject create locally when base_url has no
?table= and source.table.name is empty, pointing at base +table-list.
- Online DDL ban: attach a precise hint for code 500002776 + subcode
k_dl_4000001 telling multi-env apps to create tables on --environment dev.
- Missing record-id column: extend the 500002783 hint to add a unique text
column via +db-execute before retrying.
- Optional field_maps on create: allow omitted or empty field_maps so the
server auto-matches and creates the task; keep update requiring an enabled
mapping and still reject an all-disabled array.
- Environment default: db-sync commands use online when --environment is
omitted; align help text, comments, and skill docs.
* fix(apps): migrate db-sync error codes to the 4xx client-error range
The backend moved the seven db-sync error codes from the 5000027xx
server-error range to the 4000024xx client-input range to reflect that
they are client-input errors. Mirror the new codes in the CLI so error
classification and recovery hints keep matching:
- 500002783 -> 400002477 (mapping invalid)
- 500002784 -> 400002478 (target schema mismatch)
- 500002785 -> 400002479 (operation not allowed)
- 500002786 -> 400002480 (task not found)
- 500002787 -> 400002481 (invalid task id)
- 500002788 -> 400002482 (source table not found)
- 500002789 -> 400002483 (target table not found)
Category, subtype, hint text, and behavior are unchanged; 500002776
(online DDL ban) is untouched.
* fix(apps): tighten db-sync preview validation and pretty output
Address review follow-ups on the db-sync shortcuts:
- +db-sync-get pretty output no longer prints <nil> for a missing
schema_only nor Go map syntax for statistics; render a bare bool and
deterministic key=value pairs instead.
- Reject a non-array field_maps in +db-sync-create --preview as well as
commit, so the malformed shape is caught locally rather than forwarded
to the backend.
- Clarify in lark-apps-db.md that +db-sync-create --preview needs no
confirmation and only a real create requires --yes.
- Harden the db-sync dry-run validation tests to assert exit code 2 and
the structured stderr envelope (type/subtype/param), and add coverage
for the preview non-array field_maps rejection and batch pretty output.
* fix(apps): guard db-sync preview output and neutralize update hint
Address the next db-sync review round:
- +db-sync-create --preview --output no longer writes a "null" file and
exits success when the response omits data.config; project config into
a typed object and return internal/invalid_response without writing.
- Make the 400002482 code hint command-neutral so +db-sync-update is not
steered into a create-only recovery path that risks duplicate tasks.
- lark-apps-db.md: carry --environment on the update lifecycle examples
and split failure recovery by streaming (can update) vs batch (cannot
update; recreate instead), removing the batch/update contradiction.
---------
Co-authored-by: TRAE CLI <noreply@bytedance.com>
* docs(slides): use built-in --jq in cli examples and fix workflow link labels
* docs(slides): use built-in --jq instead of external jq pipe for output filtering
Agents misread colloquial pronouns when creating events. Two fixes in
the calendar skill:
- SKILL.md: pronouns that are field values (attendees, meeting owner)
do not participate in `--as` identity selection, so "会议 owner 为我"
no longer flips bot-identity creation to user identity. "我" = the
logged-in user, "你" = the application (bot).
- create.md: the raw calendar events/attendees API does not auto-add the
calling identity as +create does; guide callers to add the caller's
open_id as a user attendee when using the full API flow.
* feat(base): require --fields on +table-create
A table created without --fields gets the platform default schema. Those
default fields then sit in the table alongside every field the caller adds
afterwards, and no field command removes them all, so the only clean recovery
is to drop the table and start over.
Make --fields required so the schema is declared up front, the way
+base-create already recommends via --table-name + --fields.
- Mark --fields Required on +table-create, and reject blank / non-array /
empty-array values in Validate: cobra's MarkFlagRequired only checks that the
flag was set, so --fields "" and --fields "[]" would still reach the API with
no fields body and fall back to the default schema.
- Validate runs ahead of the dry-run branch, so --dry-run can no longer preview
an invocation the real call would reject.
- Update the lark-base skill, e2e coverage notes and the live e2e helper.
BREAKING CHANGE: `lark-cli base +table-create --base-token <t> --name <n>`
without --fields now fails with a validation error instead of creating a
default-schema table. Callers that relied on create-empty-then-add-fields must
pass the schema to --fields.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* test(base): pin typed metadata on the invalid-schema rejection
Address review feedback on the +table-create schema validation.
- The invalid-fields-JSON rejection now asserts category, subtype and param
through assertInvalidArgumentValidation, plus the preserved *json.SyntaxError
cause, instead of only asserting that some error came back.
- Document why the missing-flag test asserts cobra's text rather than errs
metadata, and pin that layer boundary: cobra's ValidateRequiredFlags emits a
plain error and the dispatcher types it later (cmd/root_test.go). The test now
fails if that boundary moves, so the weaker assertion cannot silently outlive
its reason.
- Reword the --fields tip and the lark-base skill note: both described the
fieldless path as if it were still reachable through +table-create. They now
say the command rejects omitted / blank / empty schemas up front, while
keeping why the schema must be declared here.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* docs(base): enrich +table-create field example and drop redundant tips
The cobra Required declaration and flag Desc already advertise the
--fields requirement, so the tips paragraph restating it (and its
SKILL.md / coverage.md echoes) is dropped. The select-field example
now carries multiple/hue/lightness so agents copy a complete option
shape.
* chore: remove useless example
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
These two files are CLI command references, not XML schema references.
Move them from references/xml/ to references/cli/ and update all cross-file
links in SKILL.md, cli/*.md, workflow/*.md, and the compatibility stubs.
Add drive +member-remove for removing one collaborator permission from Drive documents, files, folders, wiki nodes, and Miaoda apps. The shortcut validates resource and member contracts before issuing the high-risk DELETE request, preserves structured CLI errors, and documents identity and wiki permission behavior.
Key features:
- Resolve resource type from supported URLs or require it for bare tokens
- Accept Miaoda apps via /page/ URLs or explicit --type=apps
- Support user and bot identities with member-type-specific validation
- Require explicit confirmation and return stable removal metadata
- Reject unsupported slash-containing tokens and member IDs before API calls
- Add unit, dry-run E2E, live workflow, and skill documentation coverage
Normalize deterministic +replace-slide part aliases (replace→block_replace,
target_id→block_id, and payload field folding), and reject semantically
different actions up front.
Point whole-page actions at +update-slide now that it is GA:
- page_replace / slide_replace recovery guidance and the reference error
table now direct callers to `slides +update-slide` (in-place whole-page
rewrite) instead of the deprecated +replace-pages.
- drop the +replace-pages reference doc, completing the #2227 deprecation
(the command stays; its deprecation signal is carried by --help and the
output JSON `deprecated` field).
Generalize whole-page validation gating to the behavior rather than a
command name (SKILL.md, workflow/validation-xml.md): "整页写回后" instead of
"每次通过 +update-slide 整页写回后". Folds in PR #2255.
Handle Base Adapter task detail payloads that wrap the task object and report snake_case business error fields, then document the agent fallback rule for Base business errors.
The agents list verbs carried their own flat has_more / page_token fields on the
envelope meta, while the rest of the CLI had moved to the structured
Meta.Pagination block. That left two pagination vocabularies in one envelope.
Drop HasMore / PageToken from output.Meta and have listMetaPage populate
PaginationMeta instead. The agents verbs are a single-page cursor API — one call
fetches one page and returns a cursor — so Pages is fixed at 1 and Complete
reads as "this page is the last one". Count is left unset so Items is the single
source for the record count, avoiding the same number twice.
Non-paginated paths (the provider listing and catalog enumeration) still go
through listMeta and keep meta.count.
Update the affected assertions and the three skill reference docs, including the
consumption guidance that still pointed at meta.count on the paginated leaves.
Address the two blockers the pre-publication OPSEC review raised against this
subtree, and drop the in-repo demo provider that is not meant to ship.
Remove the example provider:
- delete agents/example (the offline demo backend and onboarding template)
- migrate the cmd-layer tests onto a new catalog-type test fake (fakecat,
registered beside the existing instance fakes), so the catalog-specific
paths stay covered — unknown agent id, per-agent capability differences,
declared send params, per-operation brand scoping
- delete the example provider skill doc; retarget the remaining docs at base,
using output captured from real `agents list` / `agents card` runs, and
relabel the samples that can no longer be verified as structural examples
Publication norms:
- translate every user-facing string in the subtree to English (command help,
flag usage, error messages, hints, meta.next labels), plus test messages
and fixtures. Chinese now remains only in skills/**.md, the sanctioned
convention, and in the test that asserts against those docs
- drop the dangling internal design-doc section references that a public
reader cannot resolve; public-standard RFC citations are kept
- re-sync the quoted CLI output in the skill docs with the new messages
Behavior changes carried in this commit:
- run the capability gates before the --dry-run branch, so a preview is no
longer a capability bypass: --answer / --file against an agent declaring
input_required=false / file_input=false now report unsupported_capability
under dry-run too, matching what a real send would answer. The --file
confirmation gate stays after dry-run, which uploads nothing
- raise the meta.next watch window from 30s to 90s. A typical backend task
runs ~60s, so a 30s window made a caller re-issue --watch two or three
times, and each new process restarted the poll backoff with no gap between
calls — enough to self-hammer the backend into a rate limit
Also trim the longest narrative comment blocks in the subtree.
Move base:agent:execute from the deleted Provider.RequiredScopes onto
the user IdentitySpec (base is user-only, so the set is unchanged),
assert the bot identity resolves no scopes, and update the provider doc
wording from provider-level to per-identity preflight.
Replace the provider-level flat RequiredScopes set with per-identity
declaration (IdentitySpec.Scopes, never serialized into the card): user
and bot each declare their own set, and the preflight resolves the
required scopes from the RESOLVED identity before reading the granted
list, so an identity with no declared scopes skips the check entirely --
for bot that also skips the app-version TenantScopes fetch.
Register now rejects duplicate identity types (ambiguous once scopes
attach to identities) and empty/duplicate scopes within one identity;
agenttest conformance mirrors the same rules. The check itself is
unchanged: all-or-nothing per identity, missing_scope error shape and
identity-specific remediation hints as before, --dry-run untouched.
New fixture fakesplit (user declares one scope, bot none) pins the
per-identity resolution and the bot fetch skip. Skill docs updated to
the per-identity wording.
Normalize the first valid log ID from the response body or transport headers before API error classification. Cover nested, whitespace, invalid-type, and header fallback cases.
Keep static agent cards discoverable while preventing unsupported identities from reaching dynamic provider operations. Apply the same validation to online agent enumeration and dry runs.