Commit Graph

201 Commits

Author SHA1 Message Date
huarenmin13 9b231d9825 fix(base): improve record history output and validation (#2298)
* fix(base): make record history queries explicit and readable

1. Require an explicit confirmed record ID and document deterministic record resolution
2. Add shared-format pretty history output and projection aliases without changing default JSON
3. Reject explicitly non-positive history cursors and cover request, help, formatting,
    and dry-run behavior

```ai-signature
改动范围: 将 Codebase MR 1438 的最终十文件差异移植到 GitHub PR 2298,覆盖 record history 命令、record list 投影别名、Base 引导文档与对应单元及 dry-run 回归测试
思考过程: 以 GitHub 最新 main 为基线执行三方内容合并,保留 GitHub 独立演进;删除原 PR 针对 base_history_003 的 Skill 改动,并仅按 GitHub 当前分母重算 coverage 指标
改动原因: GitHub PR 原先承载的是已确认应撤回的单题文档方案,需要由已评审的 MR 1438 最终通用实现完整替换,同时避免复制 Codebase 的多轮提交与 revert 历史
Break Change: 行为 breaking | record history 要求显式确认的 record ID,且显式非正 --max-version 现在返回 typed validation error
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: 5bf66267670f435c7d577d2444b61b209322eb5a73f15e47e671a5b8dd2603bf

* fix(base): validate history cursors and NDJSON dry runs

1. Reject missing or invalid next_max_version values before emitting pagination guidance
2. Use the execution page size in NDJSON dry runs and assert the requested output path
3. Cover valid and invalid history cursors and prove a 2000-row request is capped to a 500-row first
    page

```ai-signature
改动范围: shortcuts/base/record_history_list.go、record_ops.go 及邻近单元和 dry-run E2E 测试,仅处理 PR 2298 中有效 CodeRabbit 评论,不新增 live E2E 或修改主 Skill
思考过程: 分页提示只接受解码后的正整数游标;NDJSON dry-run 复用执行阶段的五百行页大小,并以两千行请求验证真实截断而非相等值偶然通过,同时直接断言导出路径契约
改动原因: 原实现可能把不可用的服务端游标打印成命令,同时 dry-run 对 201 到 500 条请求报告的首屏大小与真实执行不一致,初版测试也未真正证明五百行上限或 search 输出路径
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: a362c47d739d6de5676b87431fbc015d1eaa3d47d46d711e2535baa4dad7ffce

* docs(base): simplify record history prerequisites

1. Condense the record history prerequisites into generic record selection rules.
2. Remove the positive and negative examples from the reference.

```ai-signature
改动范围: 仅修改 skills/lark-base/references/lark-base-record-history-list.md,精简使用前置并删除正反例章节。
思考过程: 保留调用前确认同表 record_id、不得自行选择记录或扩展整表扫描的核心约束,移除具体链接、视图位置和命令示例以提升通用性。
改动原因: 用户要求使用前置更精简、表述更通用,并删除正反例;本次不涉及命令行为或测试。
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: 7e69951d47c1535cd5fe0fa9ebd7018af2e17373e5851c229dac46d68eaefa84

* fix(base): align history guidance with affordance

* fix(base): keep history guidance in the existing reference

1. Remove the new Base affordance file and its Markdown-only tests
2. Keep selection and field quoting guidance in the existing record history reference
3. Restore the pre-existing command tips and retain only behavior-focused regressions

```ai-signature
改动范围: 删除新增 affordance/base.md 与对应 source/help 文案测试,调整 record-history 现有 reference、覆盖说明和原命令 Tips
思考过程: 用户要求不新增 Base affordance 文件且 Markdown 不需要专门测试,因此保留运行时代码测试,把跨命令选行和字段引号说明收敛到已有 reference
改动原因: 新增 affordance 与文案测试扩大了 PR 改动面,并重复承载已有 history reference 的 agent 工作流说明
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: 4c7e3db10025f412d4b2cb4443d94724b5997b72b7e8f97fafdae2f08633b5a2

* docs(base): clarify record history target selection

1. Describe the record ID as uniquely resolving the user-selected target
2. Avoid implying that users must confirm an opaque internal record ID directly

```ai-signature
改动范围: 仅调整现有 lark-base record-history reference 的一处目标记录选择表述
思考过程: CLI 运行时只要求有效 record_id,agent 工作流要求用户确认目标而不是直接确认内部 ID,因此需要区分产品事实和操作约束
改动原因: 原表述可能被误解为 base-cli 能验证 record_id 的用户确认来源,与实际产品边界不一致
Break Change: 否
```

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
AI-SHA256: bd6c9584cc38689e1c3d9f26514425e3e38ffe2f2cff7b2bb0c65ff316aaf8dc

* docs(base): preserve serial history queries

---------

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
2026-08-18 12:06:02 +08:00
zhaojunlin0405 b6d04738e5 test(docs): align fetch help comment expectations (#2363)
* fix(docs): restore fetch comment help guidance

* test(docs): align fetch help comment expectations
2026-08-17 17:07:25 +08:00
SunPeiYang996 525a98270f feat(docs): support comments and block mutation ranges (#2341)
* feat(docs): support comments and block mutation ranges

* fix(docs): address CI and review feedback
2026-08-15 00:58:51 +08:00
tianyouskrrr 0c5530dc63 feat(slides): auto-upload @path images in +update-slide (#2346)
Bring +update-slide in line with +create and +add-slide: <img src="@local">
placeholders in --content are now extracted, validated, uploaded to the target
presentation, and rewritten to file_token before the page is replaced. This
removes the manual +media-upload round-trip that was tripping agents into
passing unresolved local paths to the backend.

- Validate rejects missing files and directory placeholders locally, before any
  API call, and gates docs:document.media:upload as a conditional scope.
- Execute uploads once per unique path (deduped) and, on partial failure,
  appends a progress hint so a retry does not silently re-upload every image.
- DryRun plans the upload steps ahead of the replace and reports
  images_to_upload so the irreversible half is visible up front.
- Skill reference documents the placeholder pipeline, CWD resolution, the
  docs:document.media:upload scope on 1061004/403, and images_uploaded output.

The command Description and skill intro stay scoped to WHAT the command does;
the @path capability is surfaced through the flag help, Tips, and the reference
section rather than restated in the one-line Description or a cross-command note.
2026-08-14 17:59:54 +08:00
chendaxin-tk 5a72b989c1 feat(sheets): accept the --range / --cells / border shapes callers actually send (#2338)
* feat(sheets): read a sheet prefix in --range as the sheet selector

Eval traces: 707 calls to +cells-get / +csv-get / +csv-put / +cells-set /
+cells-clear died on "specify at least one of --sheet-id or --sheet-name",
and 53% of them had already named the sheet inside --range
("Sheet1!A1:D20"). The sheet was known, only the flag was missing — so the
prefix now fills the selector and the bare A1 range goes to the tool.
Wired on both paths: a PreRunE stage in the sheets ergonomics layer for
standalone commands, and the sub-op translator for +batch-update.

The grammar follows the front-end ref lexer (byted-sheet TractorLexer):
the full-width ! is an equal separator, an unquoted name can contain
neither width (so splitting on the first one is safe), and a quoted name
keeps its doubled-quote escape and may itself contain a "!". Unquoted
names with spaces are accepted here though the lexer rejects them — a
--range flag has none of a formula's tokenizing ambiguity.
sheetNameFromA1 delegates to the same splitter instead of carrying a
second, looser grammar.

Scope guards: an explicit --sheet-id / --sheet-name stays authoritative
and --range passes through untouched, so a disagreeing prefix cannot
silently retarget a write; only --range carries the rewrite, since
+range-copy / +range-move / +range-fill name their destination sheet with
--target-sheet-id.

* fix(sheets): 边框粗细词兜底补齐 hair 与数字线宽

07-28 只修了 border_styles.<side>.style 里的 thin/medium/thick,同族的另外两种
写法仍在报错。对 596 条 trace 做频次统计,边框取值的错法就这几种:

  weight 槽 "hair"   476 次 / 19 个用例   ← 本次新增
  style  槽 "thin"  1795 次 / 39 个用例   (07-28 已修)
  style  槽 "hair"    76 次 /  2 个用例   ← 本次新增
  weight 槽 数字        10 次 /  2 个用例   ← 本次新增(07-28 报告 Case 2)
  width  键(GSheets) 35 次 /  3 个用例   ← 本次新增

根因是契约把一个视觉概念拆成 style(线型)× weight(粗细)两个字段,而 openpyxl
把两者塞进一个词 Side(border_style="thin"),于是同几个粗细词在两个槽位都会出现。
borderWeightWord 一个函数同时服务两个槽位,挂在 expandBorderAllShorthand 这个唯一
漏斗上,四条载体路径(--border-styles / --cells 内联 / --styles 载荷 /
+workbook-create)一起生效。

weight 先于 style 归一是有意的:{"style":"thin","weight":"1"} 只有等 "1" 先变成
"thin",style 那步才看得出显式 weight 与词义一致而非冲突。显式冲突
(thin + thick)保持报错,不替用户选。

刻意不收:openpyxl 完整线型表(dashDot / mediumDashed / slantDashDot)、VBA
xlContinuous、CSS hidden、Google Sheets SOLID_THICK、line_style / thickness 等
键别名、style 与 weight 装反、px/pt 后缀 —— trace 里全是 0 次;solid_thin、
border_width、border_color 各只有 1 个用例。它们继续走 enum 报错(报错带允许值
和 did-you-mean,一轮能改对),符合本文件顶部的静默别名准入门槛:真实词汇 **且**
跨批次/≥3 任务复现。新增用例里有一条反向断言把这条线钉住。

TestCellsSetStyle_BorderWeightNumberNamesEnum 的探针从数字换成布尔——数字现在会被
归一化,不再走报错路径,enum-over-skeleton 那条文案规则改用布尔来钉。

* feat(sheets): accept the openpyxl-habit --cells shapes and prescribe the rest

The --cells shape family is the single largest client-side rejection cluster
for +cells-set in the eval corpus. Traced against 14,024 real calls it splits
into two habits, and each gets the treatment its ambiguity allows.

Accepted outright, both unambiguous, both on the existing jsonFlagNormalizers
seam (so --writes items and +batch-update sub-ops get them too):

  - {"cells": […]} envelope — an agent generating the payload in a script
    writes json.dump({"cells": cells}, f), mistaking the flag name for a JSON
    key. 11 of 21 traced `expected type "array", got "object"` rejections are
    this exact shape. Only a lone "cells" key unwraps; siblings mean the
    object is the whole tool input and dropping them would write elsewhere.
  - bare scalars in cell slots — the openpyxl / gspread habit of passing a
    plain values matrix, which real rows mix with cell objects as soon as a
    formula appears (["1","电动大门",10331.00,{"formula":"=D2*E2"}]).

  null is deliberately left failing: {} (leave the cell alone) and
  {"value":""} (write an empty string) are both plausible readings, and the
  normalizer only rewrites what is beyond doubt.

Renamed silently on the same grounds: --values is what gspread calls the
payload, and what this CLI's own +workbook-create calls its untyped 2D data.
Because bare scalars now lift into {"value":…}, the plain matrix a --values
caller passes ('[["工作内容"]]') is already accepted verbatim under --cells —
the name was the only thing wrong, which puts it in commandFlagAliases rather
than the prescription table. That drops the round trip a prescription costs
(eval F8: 170 hits, 1.9% of failures) and covers the +batch-update sub-op
path, which reads the same alias table and would otherwise get no hint at all
(a prescription only rides on cobra's unknown-flag branch).

Inferred, matching the libraries these callers arrive from: a bare
single-cell --range is now an anchor, sized from the payload — the same
inference +csv-put already does for --start-cell. The range resolves locally
and ships in full, so the server still gets the strict match it enforces. An
explicit extent ("A1:A1", "A1:C10") is never inferred over.

Prescribed, because it cannot be guessed safely: the cells-vs-range mismatch
(132 rejections across 93 case-runs) now reports both axes at once and hands
back the range that fits the payload, plus the inclusive-end note that
explains its biggest sub-bucket — A1:C10 being 10 rows. Growing the range
would overwrite rows the caller never mentioned and shrinking it would drop
data, so the choice stays with the caller. Ragged rows get their own message
instead of being reported as a range mismatch.

Supporting refactor: parseCellRange replaces the prefix-strip / split-on-":"
/ splitCellRef triplication (rangeDimensions becomes a thin wrapper, its
error wording kept byte-for-byte since +styles-put surfaces it verbatim), and
cellsExtent is the one authority on whether a payload is rectangular, so the
anchor expansion and the dimension check cannot disagree. Two bugs fell out
of the new tests: a leading space before the sheet name survived into every
rendered range, and a payload of empty rows would have rendered a malformed
suggestion.

* fix(sheets): parse the sheet part of a range with the ref lexer's grammar

parseCellRange cut the sheet off with strings.Index(range, "!"), which
disagrees with the grammar splitRangeSheetPrefix already implements from the
front-end ref lexer (byted-sheet TractorLexer.ts). Two spellings the lexer
treats as ordinary therefore failed to parse at all:

  --range '甘特图!B3'        full-width separator (ExclamationMark accepts it)
  --range "'Q1!Actual'!B3"   quoted name owning a "!" (quotes delimit, so it may)

An unparsable range is deliberately deferred ("the range validator's job"),
so the failure was silent in both directions: the anchor never expanded and
the dimension mismatch never got its prescription. Reachable whenever the
prefix survives to the shortcut — an explicit --sheet-id/--sheet-name keeps
it (the selector rewrite only fires when the pair is empty), as do
--source-range / --target-range, which that rewrite deliberately skips.

The grammar now lives in one place. scanSheetQualifier reports the parsed
sheet name AND the byte offset just past the separator; splitRangeSheetPrefix
is rewritten on top of it (all 20 of its grammar cases unchanged), and
parseCellRange slices the qualifier off at that offset. The offset is the
point: a range rendered from a parse is both shipped to the server and
printed for the caller to paste back, so the qualifier has to survive
verbatim — quotes, full-width separator and all — which a name parsed and
re-quoted could not promise.

Naming, while here: cellRange.prefix said where the field sits, not what it
holds. It is now sheetQualifier (verbatim, separator included) alongside
sheetName (parsed, unquoted) — the sheet a range names is what the type is
about, and the next caller that needs it should not reach for the raw string.

* fix(sheets): close the four gaps the PR review found

Anchor expansion no longer sizes a sheet-qualified range. Such a range only
reaches expandAnchorRange beside an explicit --sheet-id / --sheet-name, since
all three entry points fold the prefix into the selector when none was given —
so the prefix is one that disagrees with the selector, and sizing it shipped
{"range":"Sheet1!A1:B2","sheet_name":"Other"} where the pre-anchor CLI had
failed locally with the cells-vs-range mismatch. Trading a local prescription
for a wire payload whose two halves name different sheets is the wrong
direction; a qualified anchor stays a mismatch.

--writes items now really do get the payload rewrites. cellsSetWritesOps gives
each item the standalone pipeline through a per-item flag view, but that runs
after requireJSONArray has validated the array, so an item spelling its payload
"values" or wrapping it in a {"cells": …} envelope died on the array schema
while the identical +batch-update sub-op was accepted. The rewrites move onto
the jsonFlagNormalizers seam for --writes, one step ahead of the schema, so the
two spellings of the same write agree. values → cells only when "cells" is
absent: two spellings with different payloads stays normalizeSubOpInputKeys'
conflict to report.

The derived selector is left as the only spelling of itself.
normalizeSubOpInputKeys keeps a duplicate key whose two values agree rather
than erroring, and two empty strings agree — so an input carrying both
"sheet-name":"" and "sheet_name":"" kept the hyphen form, which lookupRaw finds
first and which then shadowed the sheet_name just derived from the range
prefix, failing as "specify at least one of --sheet-id or --sheet-name".

Test coverage the review asked for: a +batch-update dry-run case for the prefix
rewrite (the sub-op path had unit coverage but no E2E), and the two tests that
grepped a rendered envelope now decode the dry-run body and assert the fields
that reach the wire.

* test(sheets): cover the accepted input shapes against a real spreadsheet

The dry-run E2E pins what the CLI builds; nothing pinned that the backend
takes it. That gap matters more for rewrites than for ordinary flags: each one
turns a caller spelling into a wire payload the caller never sees, so a payload
the server rejects would be a worse outcome than the client-side error it
replaced.

TestSheets_CallCompatWorkflow writes through a sheet-qualified --range with no
selector flag at all, with bare scalars in the cell slots and a bare A1 acting
as an anchor — three rewrites composed in one call — then reads back through
the same prefix and stamps an openpyxl "hair" border over the result. The sheet
is named with a space in it so the prefix takes its quoted form, the spelling
the ref-lexer grammar exists for and the one a first-ASCII-"!" split would cut
in half.

The read-back compares values collected out of the decoded payload rather than
a fixed path: get_cell_ranges' response nesting is the backend's to change and
is pinned nowhere in this repo, while the values having survived the round trip
is the actual claim. The number is compared numerically for the same reason.

Self-contained: it builds its own workbook, and createSpreadsheet's cleanup
tears it down. Skips without tenant credentials, so local runs are unaffected
and CI's e2e-live job is what exercises it.

* feat(sheets): answer +sheet-list instead of failing the guess

Callers reach for +sheet-list on their own: the sheets surface has a whole
+sheet-* family (+sheet-create / +sheet-copy / +sheet-delete / +sheet-info),
so "list the sheets" spells itself that way. The miss does not self-correct
either, because internal/suggest ranks shared prefixes first: the "did you
mean" hint points at +sheet-create and its siblings, never at +workbook-info.

Add it as a read-only projection over get_workbook_structure emitting the bare
sheets array, entry-for-entry identical to what +workbook-info nests under
sheets. Hidden from `sheets --help` here, and from the lark-sheets skill docs
via sheet-skill-spec's doc_hidden_shortcuts, so neither surface offers a second
name for what +workbook-info already does; the command only ever answers a
caller who typed it anyway.

data/flag-defs.json and flag_defs_gen.go carry the new shortcut's flag entry,
sourced from sheet-skill-spec's spec-tables.

* feat(sheets): prescribe the real command for invented subcommand names

Callers reach for subcommand names this CLI does not have, borrowed from
neighbouring ecosystems. The framework answers an unknown name by edit distance
over the group's children; that ranking is prefix-weighted, so it cannot settle
a name whose answer shares no prefix with it, or one whose same-prefix siblings
crowd the answer out. Those names now get a curated prescription instead: the
command they meant plus its exact retry form, so the next attempt needs no
--help round trip.

Prescribed, never rewritten. Unlike a flag, silently resolving a subcommand
would run a write the caller never named, and the same information fits in the
error the failed call already returns. Every entry is a naming miss rather than
a missing capability — each intent already has a command — and a rare spelling
stays with the ranker rather than growing the table.

The hook is the group's Args validator, which cobra runs before the group's
RunE. That ordering is what keeps this inside sheets: the framework's
unknown-subcommand guard installs on RunE and never touches Args, so the two
compose and every unclaimed name still reaches the ranked "did you mean one
of: …" unchanged. The message stays byte-identical to the guard's, since the
name genuinely does not exist; only the hint and the machine-readable
suggestion change.

Targets resolve against the live tree rather than the table. All of them are
write commands, so a concealed distribution or a user policy of max_risk: read
replaces one with a hidden deny stub; prescribing it then would name a command
that can only answer command_unavailable, and that the ranker has already
stopped suggesting. The check mirrors the ranker's filter, and doubles as a
runtime backstop when a target vanishes in a rename.

Known gap: +batch-update validates sub-op shortcut names against its own
allow-list, so an invented name inside --operations still gets the generic
"not allowed" dump instead of the prescription.

Tests pin the two invariants that make the table safe to extend — a target must
exist, and a key must not shadow a real command (checked against backward's
aliases too, which mount on the same group) — plus the registration itself, so
deleting the wiring fails the suite instead of silently reverting the CLI to
generic suggestions.

* fix(sheets): keep a non-finite line width off the thickness mapping

strconv.ParseFloat answers yes to "Inf" / "Infinity" / "NaN", so a quoted
non-finite weight entered the numeric-width branch and came back out as
"thick" with exit 0 — the CLI guessing at input that means nothing. NaN
only escaped that by accident (every comparison against it is false).
borderLineWidth now reports a non-finite result as "not a width", which
puts both back on the enum error path that names thin / medium / thick.

Also closes the review's test-coverage gaps: hair in the style slot pins
the canonical style ("solid") next to the weight in both the corpus and
the dry-run e2e, the numeric-width table gains its two ends (3 is where
thick starts, 0 keeps its own type error), and splitRangeSheetPrefix
covers the backslash-escaped separator after a quoted name.

* fix(sheets): parse --ranges prefixes with the shared grammar, budget every cells shape

Three gaps the review found, each reproduced against a built binary first.

--ranges kept its own strings.Index("!") splitter, so the four separator
spellings the rest of the PR unified on stopped at the flag boundary:
"工作表1!A1:B2" was rejected as carrying no sheet prefix at all, and
"'My Sheet'!A1:B2" shipped sheet_name "'My Sheet'" — quotes included — for
the backend to fail on as sheet-not-found. Both the up-front prefix check
and splitSheetPrefixedRange now go through scanSheetQualifier /
splitRangeSheetPrefix, which keeps the two error messages' division of
labour: no qualifier at all is "must include a sheet prefix", an empty
side is "must use sheet!range form".

estimatedBatchOpCells ran before the translator's normalizers but read the
wire shape only, so a {"cells": …} envelope, a lone cell object, and a
payload spelled "values" each scored zero cells and materialized outside
the batch-wide safety budget. It unwraps the shape now — no mutation, the
per-cell rewrites stay the translator's and change no count.

sheetNameFromA1 lost "Sheet1!" when it moved onto splitRangeSheetPrefix,
which requires a non-empty range; a prefix with no range still names a
sheet, and pivotPlacementWarn is more use naming it than falling back to
the generic wording. It reads the qualifier directly instead.

* fix(sheets): make the +cells-put prescription validate, and type the range assertions

The +cells-put hint replaces the ranked candidate list, so it is the whole
of what a caller gets back — and it prescribed a 1×2 matrix against A1:B2,
which fails the cells-vs-range check the same call would hit, plus prose
forbidding the bare scalars this branch now accepts. It spells a matching
2×2 scalar matrix and both accepted cell forms instead.

TestPrescribedExamplesActuallyValidate pulls the flags back out of the hint
and runs them through +cells-set, so the prose cannot drift from what the
validator takes; restoring the old hint fails it with the very error the
caller would have seen.

splitSheetPrefixedRange's rejection cases asserted only that an error came
back, which an untyped one would satisfy. They now go through
requireValidation and pin the --range attribution and the offending input
in the message.
2026-08-14 17:43:30 +08:00
zgz2048 cb1bb1d004 docs(base): restructure skill routing and analysis guidance (#2320)
* docs(base): add common filter condition examples

* docs(base): restructure skill routing and guidance

* docs(base): simplify identity selection guidance

* docs(base): restore concise recovery contracts

* docs(base): condense recovery guidance

* docs(base): retain only high-value recovery guidance

* docs(base): clarify full field update semantics

* docs(base): prioritize common text filter examples

* docs(base): deduplicate auto number update guidance

* fix(base): align ndjson dry-run page size

* docs(base): align permission identity guidance

* test(base): align ndjson dry-run page size

* docs(base): recommend dynamic select option reuse

* docs(base): require SOP for record reads

* docs(base): streamline record read guidance

* docs(base): clarify record format guidance

* docs(base): reduce hidden record flag exposure

* docs(base): merge record read gate

* docs(base): reduce table discovery guidance

* docs(base): centralize block discovery guidance

* docs(base): restore table analysis chain

* docs(base): restore table resource guidance

* docs(base): use resource-specific list commands

* docs(base): restore folder listing command

* docs(base): minimize probe stdout
2026-08-14 13:35:44 +08:00
xiaomi-bytedance 723f884e9b feat(base): support BaseApp application mode (#2231)
* feat(base): add BaseApp workspace, page and block shortcuts

Implement the CLI layer of the BaseApp CLI/OpenAPI protocol design: 17 new
shortcuts covering workspace entities, blank app creation, page CRUD and page
block CRUD, plus skill references and dry-run E2E for each.

The data_config validator moves to a neutral block_data_config.go with chart
logic unchanged; list and richText dispatch are new branches, so dashboard
behaviour is untouched. Command spaces stay separate — dashboard commands never
take --app-token and app block commands never take --dashboard-id. The one
exception is +app-block-get-data, which shares the dashboard endpoint, execute
and dry-run hooks and therefore takes --base-token instead of --app-token.

This phase ships no +app-block-delete and no page arrange command; both the
help text and the skill docs spell out that a block type cannot be changed
after creation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(base): implement app mode shortcuts and list components

* feat: support deleting BaseApp via drive delete

* fix: 修复 workspace scope

* fix: correct BaseApp permission scopes

* feat: 新增 moveIn workspace 逻辑

* feat(base): support multi-datasource data_config for BaseApp charts

BaseApp page charts follow section 8 图表协议 of the App CLI RPC 协议,
which differs from dashboard charts by supporting multiple data sources:
base_token is a single top-level value shared by every source, while
table_name/series/count_all/group_by/filter move into each data_sources[]
element (plus top-level data_source_mode and sort). The per-source value
semantics are identical to dashboard charts, so each data_sources[] element
reuses normalizeDataConfig / validateChartDataConfig; the wrapper only adds
the top-level structure. Dashboard charts keep the flat shape; the list
protocol is untouched.

- block_data_config.go: add normalizeAppChartDataConfig /
  validateAppChartDataConfig / validateAppBlockDataConfig
- app_block_create/update: route chart blocks to the multi-datasource
  normalize/validate; refresh tips and examples
- reference doc: rewrite the chart section for the multi-datasource shape
- unit + e2e tests: migrate chart cases to data_sources; add a
  multi-datasource combo case

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(base): map BaseApp richText block type to the wire type "text"

The rich-text widget's API type is "text" (App CLI RPC 协议 §10), but the
CLI exposes the friendlier "richText" alias and was sending it verbatim, so
the backend rejected +app-block-create --type richText with "type is
invalid". Map richText -> text when building the request body; the
user-facing --type richText is unchanged. Add TestAppRichTextTypeMapsToText.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(base): remove internal protocol doc link/reference from skills

The BaseApp skill references pointed at an internal Lark doc (deep link with
a private token) as the source of truth, which must not ship in this repo.
Drop the link and the doc name entirely from the reference markdown and from
code comments; describe behavior in neutral terms ("服务端协议 / 服务端返回和校验")
instead. No functional change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat: create workspace for BaseApp when omitted

* fix(base): align BaseApp block protocol

* fix: 删掉废弃的 workspace-entity-remove

* fix(base): align app get reference response

* fix(base): align app shortcuts with API contract

* fix(base): refine app mode shortcut contracts

* fix: use entity_type for workspace entity filtering

* fix: return workspace URLs for base workspace ops

* fix(base): enforce unique app block names

* fix(base): use chart token for app block data

* 明确baseapp边界,不导向到dashboard-arrange

* docs(base): clarify app copy is unsupported

* docs(base): define unsupported app page operations

* fix(base): align app block text type with dashboard

AppMode 的文本组件此前对外叫 richText,发送时再映射成 wire 上的 text,
而读取方向没有反向映射:写进去用 richText、读回来是 text,同一个 CLI
表面自相矛盾,回填或幂等复建时会被枚举校验拒掉。

统一成 text,与 Dashboard 文本组件同名同义:
- appBlockTypes/isAppBlockType/textBlockTypes 去掉 richText
- 删除 appBlockBody 里的 richText → text 发送期映射
- help、枚举、示例、tips 与 baseapp block data_config reference 同步
- 新增回归测试,确保 richText 不再被接受也不再出现在枚举里

richText 不保留别名:+app-* 尚未随已发布版本对外,无存量调用方。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(base): resolve BaseApp URLs

Migrate the net changes from bitable/base_cli!1308 onto the current BaseApp development branch.

* fix(base): remove app block list type filter

* docs: preserve explicit intent when reusing BaseApp blocks

* fix(base): route +app-block-get-data to base_apps endpoint

Move the shortcut off the dashboard route and onto the dedicated
BaseApp block-data endpoint:

- URL: /open-apis/base/v3/base_apps/:app_token/blocks/:block_id/data
- base_token is passed as a required query parameter per the new IDL
- Refresh --block-id description and tips to list all producers of the
  chart_token (create/list/get) and note the cht… prefix
- Update the dryrun test to expect the new URL

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: clarify unsupported BaseApp copy paths

* fix: front-load BaseApp copy stop rule

* fix: surface unsupported PageGroup operations

* fix: preserve PageGroup support boundary

* docs(base): clarify unsupported app block handling

* docs(base): clarify how to read text block content

Text blocks have no /data endpoint; calling +app-block-get-data on
one returns a generic server 500. Point readers at +app-block-get,
whose data_config.text carries the Markdown source.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs: clarify immutable BaseApp block types

* docs(base): correct chart date filter format

* docs(base): explain inaccessible app pages

* docs: require workspace removal lookup

* fix(base): return workspace move-in result faithfully

* chore(base): adapt BaseApp changes to upstream main

* fix(base): align app mode changes with upstream scope

* fix(base): address app mode review feedback

* test(base): assert app data transport error contract

* fix(base): satisfy app mode merge requirements

* refactor(base): align app mode filenames

* docs: fix BaseApp rename guidance

* fix(base): remove unsupported workspace icon

* docs(base): clarify app mode concepts and config reuse

---------

Co-authored-by: weibiao.x <weibiao.x@bytedance.com>
Co-authored-by: zhangbinkai.zbk <zhangbinkai.zbk@bytedance.com>
Co-authored-by: yurunjie <yurunjie.xx@bytedance.com>
Co-authored-by: Codex <codex@example.com>
2026-08-13 16:20:34 +08:00
zgz2048 8b824b03ae feat(base): add typed NDJSON workflows for professional data analysis (#2196)
* feat(base): add typed NDJSON data analysis workflow

* docs(base): simplify cloud pagination guidance

* docs(base): clarify Link relation IDs

* feat(base): query NDJSON records with jq

* docs(base): route analysis through built-in jq

* docs: clarify per-table local analysis limit

* docs: simplify base analysis guidance

* docs: centralize base analysis routing

* docs(base): refine cell value and datetime guidance

* test(base): align ignored field fixtures

* docs(base): add semantic analysis routing

* feat(base): improve NDJSON analysis workflow

* docs(base): centralize filter predicate examples

* feat(base): clarify record get export scope

* feat(base): improve ndjson analysis workflow

* docs(base): refine local analysis guidance

* feat(base): resolve record search limit by format

* refactor(base): keep ndjson jq handling local
2026-08-11 20:27:37 +08:00
jinjiuzhe 158d15b3fd feat: add apps database sync shortcuts for Base-to-database import (#2251)
* 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>
2026-08-11 18:21:32 +08:00
zhangjun-bytedance 6d6b6889a9 feat: support bot identity (#2288) 2026-08-11 15:43:58 +08:00
CarolSum d289566ad0 feat(base): require --fields on +table-create (#2221)
* 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>
2026-08-11 10:56:41 +08:00
liujinkun2025 a18ccd6c4e feat(wiki): improve node creation and terminal errors (#2266) 2026-08-11 10:05:11 +08:00
zhicong666-bytedance 841953496b feat: support shared vc live references and document context (#2249)
* feat: parse document context meeting events

Source-Branch: features/F-larksuite-cli-document-context
Source-Commit: 427cbd6492
Source-Subject: fix: normalize mail triage filters (#2068)
Repo: larksuite-cli
Synced-By: bytedance
Timestamp: 20260803_093803Z

* test: assert ndjson section path semantically

Source-Branch: features/F-larksuite-cli-document-context
Source-Commit: 9b8bafeff4a5063347aa1e0c32e501669381e217
Source-Subject: feat: parse document context meeting events
Repo: larksuite-cli
Synced-By: bytedance
Timestamp: 20260803_095353Z

* fix: nest document context output and align slides aliases

Source-Branch: features/F-larksuite-cli-document-context
Source-Commit: 94ad66b5d4
Source-Subject: test: assert ndjson section path semantically
Repo: larksuite-cli
Synced-By: bytedance
Timestamp: 20260803_124142Z

* fix: preserve meeting event output contract

* fix: remove document context fallback projection

* refactor: reuse meeting event actor extraction

* refactor: simplify document context event handling

* refactor: reuse meeting timeline ordering

* test: align document context timeline order

* feat: support BOE endpoint overrides

* feat: share vc meeting references across skills

* docs: clarify shared document event correlation

* revert: remove local BOE endpoint overrides

* fix: compact document context events consistently

* docs: prefer pretty format for meeting events

* fix: harden document context event rendering

* docs: clarify active meeting discovery in vc skill

* fix: improve vc meeting event guidance

* fix: clarify live meeting content routing

* fix: require pretty output for meeting content

* docs: explain meeting event json cost

* fix: compact transcript speaker labels

* fix: correct vc identity guidance

---------

Co-authored-by: renaocheng <renaocheng@bytedance.com>
2026-08-10 23:25:56 +08:00
zhaojiaxing-coding bd38b9b3c4 feat(drive): add +member-remove shortcut (#1994)
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
2026-08-10 21:23:26 +08:00
tianyouskrrr 7cfbea68d9 feat(slides): normalize replace-slide part aliases (#2225)
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.
2026-08-10 19:28:03 +08:00
zhoujunteng-max 5b734238d7 feat: report upload file events (#2093)
* feat: report upload file events

* test(drive): skip import workflow without tenant token

* docs: document upload report helpers

* docs: improve function documentation coverage

* docs: complete incremental function documentation

* docs: complete function documentation coverage

* fix: report every upload file event

* refactor: move file event reporting to internal package

* fix: harden upload file event reporting

* fix: omit upload mode from file event reports

* test: fix workbook import dry-run token assertion
2026-08-10 19:12:38 +08:00
zcc 7be247614d feat(drive): add wiki url/token support to +download and +preview (#2220)
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.
2026-08-08 11:02:16 +08:00
wangweiming-01 0f82591303 feat: preflight export permission for downloads (#2218)
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
2026-08-07 18:41:34 +08:00
SunPeiYang996 a6f3e635d4 feat(docs): add local authoring and resource workflows (#1921)
* feat(docs): add local authoring and resource workflows

Add docs +script workflows for isolated draft initialization and tolerant XML/Markdown profiling.

Support local and remote document resources across create and update flows with safe, bounded-concurrency uploads, binding verification, and cleanup.

Synchronize shared credential-source selection during concurrent uploads, refresh lark-doc guidance, and expand unit, dry-run, and live E2E coverage.

* docs(lark-doc): clarify genre reference paths

* fix(docs): address PR validation feedback

* docs(lark-doc): clarify remote image handling

* feat: streamline docs draft workflow

* fix(docs): clarify script input and resource cleanup

* fix(docs): align script dry-run test with auth flow

* fix(docs): authenticate local script e2e test

* fix(docs): refine script diagnostics and image preflight

* fix(docs): align draft workspace cleanup with VFS

* fix(docs): route workspace cleanup through FileIO

* docs(lark-doc): simplify profile check guidance
2026-08-07 18:15:28 +08:00
liangshuo-1 0d6f2c65d7 fix(im): harden resource downloads with validated ranged streams (#2223) 2026-08-07 17:45:40 +08:00
R0bynZhu 46e2186adf feat(slides): accept slide XML files in +create (#2197)
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.
2026-08-07 17:03:03 +08:00
tianyouskrrr bc0ba2252a fix(slides): restore update-slide skill guidance (#2227) 2026-08-07 16:36:17 +08:00
yxy-bd db102ab314 feat (apps): add miaoda app collaborator management (#2230)
* feat: add miaoda app collaborator management

* refactor: remove collaborator list pagination

* fix: make external invite setting read-only

* fix: make Miaoda copy setting read-only

* docs: record BOE collaborator verification

* fix: remove unsupported Miaoda setting flags
2026-08-07 16:20:02 +08:00
xiongyuanwen-byted be2a96f490 feat(sheets): harden error prescriptions, batch updates, and read workflows
Aggregate the sheets work from feat/lark-sheets-develop:

- Improve validation errors with schema hints, aggregated issues, enum guidance, and prescriptive flag/style-field messages.
- Harden +batch-update input contracts, key normalization, style vocabulary handling, and resource-budget checks.
- Add read offload and truncation handling for cells, csv, and table-get, with typed output-path errors and safer jq/output-path semantics.
- Correct freeze semantics by emitting full-state freeze/unfreeze operations and adding --rows/--cols for +dim-freeze.
- Improve +styles-put and shared --styles parsing for styles, merges, row/column sizing, freeze, and sheet-prefixed range validation.
- Fix dim-insert inherit-style mapping, table-get date/time handling, table-put style anchors, and CSV path-shaped input guards.
- Update lark-sheets skill docs, scripts, tests, and generated flag data.

Tested with:
- go test ./shortcuts/common ./shortcuts/sheets/...
- go test ./shortcuts/... ./internal/...
- python3 -m py_compile skills/lark-sheets/scripts/*.py
2026-08-07 11:21:12 +08:00
木杉 5919e861cc feat: add frontend as third app type for apps domain (#2072)
* 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.
2026-08-07 00:11:11 +08:00
liujinkun2025 f7d0326bfe fix: reject truncated wiki node tokens (#2203) 2026-08-06 22:08:26 +08:00
zcc d92ad6ea2c feat(drive): add +copy shortcut (#2129)
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.
2026-08-06 17:45:22 +08:00
zcc 010029cfe4 feat(drive): add +update-title shortcut (#2172) 2026-08-06 17:10:40 +08:00
arnold9672 b546516bea feat: support bot identity for search shortcuts (#2194)
* feat: support bot identity for search shortcuts
sa: safe
doc: skills/lark-im, skills/lark-minutes
cfg: none
test: unit test, dry-run e2e, live TAT smoke

* test: assert dry-run search identities

* fix: validate bot search filters and enrichment scopes

sa: safe
doc: skills/lark-im
cfg: none
test: unit test, dry-run e2e

* Revert "fix: validate bot search filters and enrichment scopes"

This reverts commit 34ddb5bfc3.
2026-08-06 10:59:49 +08:00
evandance 09feefe96b fix: make agent recovery and concealment reliable (#2189) 2026-08-05 19:44:11 +08:00
BD-ZERO 7363eb5448 feat(slides): support explicit screenshot output paths (#2180)
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.
2026-08-05 16:50:10 +08:00
fongwave 8e884928f6 feat: add Base table copy shortcuts (#2019)
* feat: add Base table copy shortcuts

* test: cover Base table copy edge cases

* fix: preserve table copy task state

* fix: align table copy recovery with API errors

* fix: preserve table copy auth recovery

* test: verify copied table schema

---------

Co-authored-by: fongwave <272393974+fongwave@users.noreply.github.com>
2026-08-05 15:43:10 +08:00
tianyouskrrr b20f374c18 fix(slides): name the wrong --parts field instead of "non-empty replacement" (#2174)
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.
2026-08-04 18:06:30 +08:00
BD-ZERO 2297435452 fix(slides): improve missing screenshot selector guidance (#2177)
- require a slide ID or slide number for screenshot requests
- reject explicitly empty slide IDs
- remove unreachable dry-run validation
- document full-deck screenshot batching
- add unit and dry-run E2E coverage
2026-08-04 16:48:19 +08:00
ethan-zhx 3b66d470f1 fix(slides): migrate SML namespace from HTTP to HTTPS (#2169)
* fix(slides): preserve requested lint input path

* fix(slides): migrate SML namespace from HTTP to HTTPS

- Change canonical namespace to https://www.larkoffice.com/sml/2.0
  in protocol schema, production code, docs, and tests
- Keep HTTP and /sml/2.0 as legacy readback compat in validator
- Fix sml_prefixed_tag check to cover all accepted SML namespaces
- Add regression test for legacy HTTP namespace acceptance
2026-08-04 14:24:05 +08:00
BD-ZERO b2997944c4 fix(slides): add agent-friendly aliases for screenshot flags (#2156)
Add agent-friendly aliases for slides +screenshot while preserving the canonical flag behavior.

- Support presentation and slide selector aliases, including --presentation-id, --slides, --slide-ids, --slide-numbers, and --slide.
- Route digits-only --slide values to page numbers and other values to slide IDs.
- Merge and deduplicate same-type selectors, reject mixed ID/number requests, and report the caller’s actual flag names in structured validation errors.
- Clarify selector exclusivity in the screenshot reference.
- Add unit, dry-run E2E, and self-contained live E2E coverage for aliases, validation, screenshot output, and cleanup.
2026-08-04 12:22:17 +08:00
tianyouskrrr 56fd29e611 feat(slides): add +update-slide for whole-page updates (#2143)
Add an in-place whole-page slide update shortcut with validation, aliases, docs, unit tests, and dry-run E2E coverage.

Deprecate the superseded +replace-pages: the binary keeps the command working for a deprecation window, with the replacement named in its --help description and in a `deprecated` field on every output (dry-run, validate-only and real runs), while the skill no longer routes to it. Multi-page updates now call +update-slide once per page. The XML/revision helpers it shared with +add-slide / +delete-slide move to slides_shared.go so its eventual removal cannot break them.

+add-slide and +delete-slide now declare --presentation through the shared presentation-ref flag, so they accept the same alias spellings (--token, --url, ...) as every other slides shortcut.
2026-08-04 11:06:59 +08:00
liangshuo-1 e8202c2f1c fix(slides): restore presentation aliases (#2164) 2026-08-03 21:12:55 +08:00
R0bynZhu ba104380ee feat(slides): add +add-slide and +delete-slide shortcuts (#2120)
Add two single-page slide shortcuts on top of the raw
xml_presentation.slide create/delete APIs.

slides +add-slide appends or inserts one page into an existing
presentation. It accepts --presentation as a token, a /slides/ URL or a
/wiki/ URL (resolved via wiki.spaces.get_node and checked for
obj_type=slides), takes the page XML through --slide as a literal, @file
or stdin so the document never has to be escaped into JSON and then into
the shell, and auto-uploads <img src="@./local.png"> placeholders,
replacing them with the returned file_token. Omitting --before-slide-id
appends to the end; the field is dropped from the body rather than sent
empty, which the backend rejects as an unknown slide.

slides +delete-slide removes one page by slide_id with the same
--presentation resolution. It is deliberately Risk "write" rather than
the raw command's high-risk-write, so it does not require --yes: it
targets a single explicit page and the deck keeps its version history.

Both take one page at a time so that batching stays an explicit loop and
every call has an unambiguous outcome.

The image placeholder validation used by +create is extracted into a
shared helper so both commands fail before any API call when a referenced
file is missing, is not a regular file or exceeds the 20 MB upload limit.

Covered by unit tests and by dry-run e2e tests through the built binary,
which is the only layer that proves a full <slide> document survives flag
parsing intact. Reference docs are added for both commands and the
existing slides skill docs now route to them.
2026-08-03 20:03:01 +08:00
liangshuo-1 2a1613484a feat: add framework flag aliases and unified IM pagination (#2146)
* feat: add framework flag aliases and unified IM pagination

Introduce declarative exact-name flag aliases at the shortcut framework boundary while keeping semantic compatibility domain-owned. Add a shared, format-aware IM pagination pipeline with consistent flags, metadata, safety bounds, resumable cursors, and request throttling.

* fix: align alias attribution and pagination contracts

* fix: align alias contracts and documentation

* test: remove environment-dependent contact bot e2e

* test: restore contact bot e2e

* docs: reduce IM pagination guidance noise

---------

Co-authored-by: liangshuo-1 <266696938+liangshuo-1@users.noreply.github.com>
2026-08-03 19:20:40 +08:00
Yuxuan Zhao 2dafa0371e test: pass contact list params explicitly (#2150) 2026-08-03 17:35:26 +08:00
wangweiming-01 7946e5c81d feat: support source file preview artifacts (#2085) 2026-07-31 17:52:31 +08:00
zhouyue-bytedance 5cf09ecfda docs(base): clarify form and file operation routing (#2110)
* docs(base): clarify form and file operation routing

* docs: clarify complete base role table rules

* docs: clarify base advanced permission status

* docs: clarify base form field lifecycle

* docs: guide base form question creation

* fix(base): address form dry-run review findings

* docs(base): add complete editable role example

* fix(base): validate form question create inputs
2026-07-31 15:23:03 +08:00
zhaojiaxing-coding 0f35676a28 feat(drive): extend permission shortcuts for Miaoda (#2070)
* feat(drive): support Miaoda apps in permission shortcuts

Extend Drive permission shortcuts to accept Miaoda page URLs and the apps resource type while keeping each endpoint's accepted resource contract explicit.

Key features:

- Infer apps from /page/ URLs and accept explicit --type=apps in +apply-permission, +member-add, +member-list, and +permission-get-setting

- Decouple secure-label target parsing so expanding apply-permission does not widen secure-label support

- Align skill guidance and unit/dry-run coverage with the new resource type

* test(drive): cover apps permission target validation

Add focused coverage for Miaoda apps target handling across apply-permission and secure-label boundaries.

Exercise malformed page URLs, explicit apps bare tokens, typed validation errors, and command-level rejection so future resource-type changes cannot silently widen unsupported secure-label behavior.

* fix(drive): parse permission markers from URL paths

Keep drive +apply-permission resource inference aligned with URL component boundaries. Parse and validate URL inputs before extracting tokens so query strings and fragments cannot redirect permission requests to a different resource.

Key fixes:

- Match document and apps markers only against the parsed URL path

- Reject malformed URLs with a typed --token validation error

- Cover /page/ markers found only in query strings or fragments

* docs(skills): redact Miaoda page token example

Replace the concrete Miaoda page token with a representative pagcn placeholder. This keeps the token shape recognizable while avoiding exposure of a real resource identifier in the skill documentation.

* fix(drive): harden permission target resolution

Make Drive shortcut targets unambiguous before they reach read or write API paths. URL inputs now bind to a recognized root path and a single validated token segment, preventing encoded separators, dot segments, and type conflicts from silently changing the addressed resource.

Key fixes:

- Reject non-root URLs, dot/traversal tokens, and URL/type conflicts for secure-label and permission-apply writes

- Keep permission-setting URL parsing and pretty output reversible for every supported command-local resource kind

- Add unit and dry-run E2E regressions plus aligned permission-apply guidance
2026-07-31 12:16:22 +08:00
wangweiming-01 946964e093 fix(drive): use title for default download filename (#2089) 2026-07-31 12:12:11 +08:00
zcc ba95252019 feat(drive): add comment-operation shortcuts (#1898)
Add comment-domain shortcuts: +batch-query-comments, +resolve-comment,
+restore-comment, +add-reply, +list-replies, +update-reply, +delete-reply
and +react-reply, sharing one target resolver with per-endpoint file_type
sets.

Flatten the comment reference docs by dropping the comments-guide routing
layer and folding its cross-command knowledge into the command refs:
comment-card model, comment/reply/interaction counting and sorting rules
into lark-drive-list-comments.md; the --solved-status prerequisite into
lark-drive-restore-comment.md; the apps exception into
lark-drive-add-comment.md. Comment intents now route straight from the
drive SKILL.md Shortcuts table to each command ref.

Cover the new shortcuts with unit tests, dry-run e2e and live workflow
e2e behind LARK_DRIVE_MD_COMMENT_E2E=1, and register them in
tests/cli_e2e/drive/coverage.md.
2026-07-30 21:53:21 +08:00
zhouyue-bytedance 4a16139348 fix(base): resolve Base URL block types accurately (#2099)
* fix: resolve Base URL block types accurately

* fix: resolve Base block selection from Wiki URLs

* fix(base): guide resolved folder and docx blocks

* fix(base): avoid field fallback for untyped URL blocks

* docs(base): specify URL example fence language

* test(base): cover unmatched URL block resolution
2026-07-30 20:29:47 +08:00
sang-neo03 a575a8ba60 feat(contact): add bot search shortcut (#2083) 2026-07-30 17:03:49 +08:00
yballul-bytedance 68a77eee5c feat: support visible_rule for form questions (#1891)
Form questions can now carry a visible_rule (display condition) so a question shows only when earlier questions match the rule. The rule shares the exact same structure as the view filter, so extract that structure into a single shared reference (lark-base-filter-condition.md) that both view-set-filter and visible_rule point to.

- create/update shortcuts: document visible_rule in --questions help and transcribe the questions body (including visible_rule) into dry-run output
- document that form question updates use full overwrite semantics and must preserve existing fields via read-modify-write
- skill refs: add visible_rule sections to form-questions create/update, note it is only needed when the user asks for a display condition, and clarify that the shared tuple filter protocol does not apply to data-query filters
- tests: pin flag help, verbatim visible_rule passthrough on create/update/list, and add dry-run E2E coverage

Co-authored-by: yballul-bytedance <273011618+yballul-bytedance@users.noreply.github.com>
Co-authored-by: TRAE CLI <noreply@bytedance.com>
2026-07-30 12:37:24 +08:00
zhaojiaxing-coding 7988515e1c feat(drive): add +permission-get-setting shortcut (#1738)
* feat(drive): add +permission-get-setting shortcut

Add a Drive shortcut for reading public permission settings across supported documents, files, folders, and wiki nodes. Resolve URLs into typed resources, preserve permission_public output for machine consumers, and document the shortcut in the permission-governance workflow.

Key features:

- Infer resource type and token from supported Drive URLs while requiring --type for bare tokens

- Query the Drive v2 public permission endpoint with typed validation and user or bot identity

- Support folder permission inspection without recursing into child resources

- Add unit, dry-run E2E, live workflow, output, and skill guidance coverage

* fix(drive): harden permission get setting contract

Harden +permission-get-setting after review findings so callers receive only the documented permission payload and folder support is verified against the live workflow. This prevents malformed responses from being presented as permission settings and keeps the command guidance aligned with the shortcut contract.

Key fixes:
- Reject responses without data.permission_public instead of projecting arbitrary payload fields
- Render complete permission settings in pretty output and mark --token required
- Exercise a created Drive folder in the live workflow and add the command reference
- Correct folder resolution guidance while retaining the shortcut's documented URL forms

* feat/drive-folder-permission-get
2026-07-29 17:57:24 +08:00