feat:移除了弹窗,服务器添加sls

This commit is contained in:
2026-09-08 22:39:45 +08:00
commit 6a295f9a7a
4082 changed files with 1322534 additions and 0 deletions

View File

@ -0,0 +1,234 @@
---
name: lark-sheets
version: 3.0.2
description: "飞书电子表格:创建和操作电子表格。支持创建表格、管理工作表与行列结构(增删/合并/调整尺寸/隐藏/冻结)、读写单元格(值/公式/样式/批注/单元格图片)、查找替换、多操作原子批量更新,以及图表、透视表、条件格式、筛选器、迷你图、浮动图片等对象的创建与维护。当用户需要创建电子表格、管理工作表、批量读写或编辑数据、统计汇总与可视化、表格美化、公式计算(含 Excel 公式迁移)、金融/财务建模DCF、三张表、预算、Sensitivity 等)等任务时使用。若用户是想按名称或关键词搜索云空间(云盘/云存储)里的表格文件,请改用 lark-drive 的 drive +search 先定位资源。当用户给出 doubao.com 的 /sheets/ URL/token 时,也应直接使用本 skill不要因为域名不是飞书而回退到 WebFetch路由依据是 URL 路径模式和 token而不是域名。"
metadata:
requires:
bins: ["lark-cli"]
siblings: ["lark-shared"]
cliHelp: "lark-cli sheets --help"
---
# sheets
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md),其中包含认证、权限处理。**
## 术语约定
下列词在本 skill 各文档中可能交替出现,但**指同一对象**;解析用户口语时按此映射,不要当成不同概念:
| 标准用语 | 同义 / 口语(均指同一对象) | 说明 |
| --- | --- | --- |
| 工作表sheet | 子表、tab、标签页 | spreadsheet 内的单张表;`sheet_id` 是其稳定标识 |
| 电子表格spreadsheet | 工作簿、表格 | 顶层容器;由 `--url``--spreadsheet-token` 定位 |
| reference_id | id | **表内对象**的稳定标识,即各对象主键 flag 接受的值(见下表)。⚠️ 与 `lark-sheets-float-image``--image-uri`(图片上传句柄)不是一回事,后者不属于 reference_id |
每类对象用各自的主键 flag 定位(命名不统一,按此表对照,不要凭直觉拼):
| 对象 | 主键 flag | 对象 | 主键 flag |
| --- | --- | --- | --- |
| 工作表 sheet | `--sheet-id` | 条件格式规则 | `--rule-id` |
| 图表 chart | `--chart-id` | 筛选视图 | `--view-id` |
| 透视表 pivot | `--pivot-table-id` | 迷你图(按组) | `--group-id` |
| 浮动图片 | `--float-image-id` | | |
## 飞书表格编辑准则(动手前必守,所有编辑类任务一律生效)
下列准则横切所有飞书表格任务,**动手前先过一遍**——即使你是被索引直接路由进某个工具参考也一律生效。每条只给一句话纲要,展开与边界见括注的 reference。
1. **最小改动**:除任务要改的单元格 / 列外原表其它单元格、行列结构、Sheet 名、合并区、格式 1:1 保持;中间结果放原数据右侧或新建空白 Sheet**禁止删 / 改名 / 隐藏 / 移动已存在 Sheet**;改写类任务精确圈定行列,不该转的原值 1:1 保留。
2. **真实写回 + 回读校验**:交付必须是对在线表格的真实写入,写完用 `+csv-get` / `+cells-get` / `+<对象>-list` 回读确认实际生效——**写操作返回 `ok` 只代表请求被接受、不代表结果符合预期**;写公式后查错误码、筛选 / 排序后核对前几行、删除 / 清空后确认已空。禁止只在文本里声称"已完成"。
3. **读全再写**:批量填充 / 补齐 / 修正类任务先确认真实数据末行再写,只探前 N 行会漏写表尾(确定末行流程见 `lark-sheets-read-data`)。
4. **公式优先于硬编码**:能用公式表达的计算(总计 / 占比 / 增长率 / 提取 / 查找)一律写公式而非静态值;**凡可由表内其它单元格推导的派生值默认就用公式,即使用户没说"联动 / 自动更新"**;写任何飞书公式前先读 `lark-sheets-formula-translation`,而且**只要公式真实写入表格,收尾默认就要继续跑 `lark-sheets-formula-verify``+formula-verify`,直到 `status='success'`**。
5. **续写 / 扩展继承样式**:续写、补齐、复制区块、新增行列时禁止只读值只写值,必须连带 `cell_styles` + `border_styles` + 合并 + 行高一起继承(清单见 `lark-sheets-write-cells`,四边框最易漏)。
6. **多步写入合并 `+batch-update`**:多个连续写入、或同一工具对多区域重复调用,合并为单次原子 `+batch-update`(语义见 `lark-sheets-batch-update`)。
7. **分组汇总用透视表**"按 X 统计 Y / 分组汇总 / 各类数量金额"用 `+pivot-{create|update|delete}`,禁止用 SUMIF / 本地脚本拼一张假透视表。
8. **拆成可验证 checklist**:落地前把指令拆成所有"独立可验证子要点",逐点 `assert` 全过才交付(多维排序每维一点、多目标每目标一点、范围类核起 / 末 / 边界);只做第一个要点属违规。
9. **全量处理前置断言条数**:翻译 / 打标 / 批量公式落地等逐条任务,先把预期条数硬编码再 `assert actual == expected`,禁止输出"已完成前 N 条,剩余继续"的半成品。
> 上述准则的实操展开——读取路径、原生工具优先级、脚本配合、易漏陷阱——见下方「执行要点」节;端到端工作流为:了解结构(`+workbook-info`)→ 读数据 → 理解语义 → 原生工具优先 → 写入 → 回读验证。
## 场景 → 命令速查(拿不准命令名先查这里,别按直觉拼)
把高频意图映射到**真实存在**的 shortcut / flag。agent 常从 Excel / Google Sheets / 飞书 OpenAPI 误迁移命令名或 flag先对照本表避免一次必然失败的试错。完整 shortcut 见各工具参考。**选定命令后别急着写——先读「动手前读」列指向的 reference 再动手**:命令名对得上不代表用法对,写入 / 清除 / 透视类尤其容易漏掉 reference 里的防错、类型与样式继承规则。
| 你要做的事 | ✅ 正确写法 | 动手前读 | ❌ 不存在(会被 cobra 拒) |
| --- | --- | --- | --- |
| 读数据(纯值 / CSV | `+csv-get`(范围用 `--range` | `lark-sheets-read-data` | `+get-range``+range-get``+cells-read` |
| 读值 + 公式 / 样式 / 批注 | `+cells-get --include value,formula,style,comment,data_validation` | `lark-sheets-read-data` | `+get-cell``+cell-get``--with-styles``--with-merges``--include-merged-cells` |
| 写纯文本值(整块 CSV 平铺;列里**没有**需字面保真的数值 / 日期标签 / 编号——点分日期 `12.10`、编号 `001` 会被 csv-put 数值化,不算纯文本) | `+csv-put`(定位用 `--start-cell`,单个左上角锚点格;也接受 `--range` 别名,区间自动取左上角) | `lark-sheets-write-cells` | 把含点分日期(`12.10`)/编号(`001`)的列裸灌 `+csv-put`——会被数值化(`12.10``12.1``001``1`,尾零/前导零丢失),改用 `+table-put` 声明 `dtypes:object` |
| 写带类型的数据到**已有**表(列里有数字 / 金额 / 百分比 / 日期 / 计数等**本质是量值**的数据——不看当下要不要排序 / 求和,量值一律走这里) | `+table-put --sheets` 完整 payload `{"sheets":[{...}]}`(列名走 `columns`、二维数据走 `data`、列 pandas dtype 走 `dtypes`、列展示格式走 `formats`;来源不限 DataFrame——Counter / dict / list 同理;要同时美化加 `--styles` 一步带样式(区域底色 / 边框 / 列宽 / 行高 / 合并不必事后再刷payload 里不存在的 sheet 名会自动建子表,详见 write-cells | `lark-sheets-write-cells` | 在本地把数字拼成 `"$1,234"` / `"30.5%"` 字符串再 `+csv-put`(会落成文本、丢失计算能力;常见借口见下方 ⚠️) |
| **新建**电子表格并写带类型的数据(类型保真需求同上,但目标表还不存在) | `+workbook-create --sheets`(协议与 `+table-put` 同构、一步建表 + typed 写入,无需先建空表再 `+table-put`date / number 不丢;`--styles` 同样可在建表同一步带全套样式,详见 workbook | `lark-sheets-workbook` | 用 `--values` 灌日期 / 数字(会落成文本、丢类型) |
| 写公式 / 富写入(样式 · 批注 · 图片 · 富文本),或需精确矩形定位的值 | `+cells-set`(定位用 `--range`;批注 / 图片 / 富文本只能用它,公式也可;**公式落表后继续 `+formula-verify` 收尾** | `lark-sheets-write-cells` | — |
| 插图:图片**绑定到某条记录**、随行走(凭证 / 证件照 / 商品图 / 头像 / 二维码 / 每行配图) | `+cells-set-image`(单格 `--range`,嵌入单元格内) | `lark-sheets-write-cells` | — |
| 插图:**自由摆放、不绑数据**的装饰 / 标识logo / 水印 / 封面大图 / banner | `+float-image-create`(浮动图片,自由定位 + 尺寸 + 层级) | `lark-sheets-float-image` | — |
| 查找 / 替换文本 | `+cells-search`(找,关键字用 `--find`)、`+cells-replace`(替换) | `lark-sheets-search-replace` | `+cells-find``+find``--query` |
| 看子表结构(合并 / 行高列宽 / 冻结 / 隐藏) | `+sheet-info` | `lark-sheets-sheet-structure` | `+sheet-get``+structure-get``+sheet-structure-get` |
| 看工作簿 / 子表清单 | `+workbook-info` | `lark-sheets-workbook` | `+sheet-list``+workbook-get``+workbook-list` |
| 复核某次AI编辑改了什么 / 取两个版本间的变更 | `+changeset-get --start-revision <编辑前版本>`(省略 `--end-revision` 取到最新;版本差 ≤ 20 | `lark-sheets-changeset` | — |
| 取当前文档 revision版本号 | `+revision-get` | `lark-sheets-workbook` | — |
| 导出 xlsx / 单表 csv | `+workbook-export` | `lark-sheets-workbook` | — |
| 导入本地 xlsx/xls/csv 文件为飞书电子表格 | `+workbook-import --file ./x.xlsx`(本地表格文件 → 飞书电子表格的正解;仅要导成多维表格 bitable 时才用 `drive +import --type bitable` | `lark-sheets-workbook` | `drive +import`(导电子表格时绕了 drive 通道、还要多给 `--type`,应直接用 `+workbook-import`)、把 .xlsx 在本地读成数据再 `+workbook-create` 重灌(多此一举,应直接 `+workbook-import`)、要把文件并入某个**已有在线工作簿**给它加子表却用它——import 只会新建独立表,加子表应走 `+sheet-copy` / `+sheet-create` |
| 参考某个**已有在线表**、把多个本地文件 / 数据各作为一张子表**追加**进去(不另起独立表) | 先 `+workbook-info` 拿模板子表 `sheet_id``+sheet-copy` 逐张复制模板子表(公式 / 合并 / 分组底色 / 列宽 / 条件格式全继承)再用 `+cells-*` 只改数据;无模板可继承时 `+sheet-create` 建空子表 + `+table-put --sheets/--styles` 写入 | `lark-sheets-workbook` | 把文件 `+workbook-import` / `+workbook-create` 另起一张**独立新表**(目标是并入已有工作簿时就跑偏了;这两条只产新表、不接受已有表定位) |
| 清除内容 / 格式 | `+cells-clear`(范围维度用 `--scope`,取值 content / formats / all | `lark-sheets-range-operations` | `--type` |
| 批量清除多区域 | `+cells-batch-clear``--scope` | `lark-sheets-batch-update` | `--target` |
| 调整列宽 / 行高 | `+cols-resize` / `+rows-resize`(行、列是两个独立命令) | `lark-sheets-range-operations` | `--dimension`(无此 flag |
| 分组汇总 / 透视 | `+pivot-create`(默认不传落点 flag → 自动新建子表,零覆盖) | `lark-sheets-pivot-table` | 用 SUMIF / 本地脚本拼一张假透视表 |
| 画图表 / 可视化(柱 / 折线 / 饼 / 条 / 散点 / 组合…) | `+chart-create` | `lark-sheets-chart` | matplotlib / 本地画图再贴图(原生图表可交互、随数据更新) |
| 条件高亮 / 数据条 / 色阶 / 重复值标记 | `+cond-format-create` | `lark-sheets-conditional-format` | `+highlight``+conditional-format`、逐格 `+cells-set-style` 硬凑 |
| 筛选 / 只看符合条件的行 | `+filter-create` | `lark-sheets-filter` | pandas filter 后覆盖写回(会毁原数据;要保存多份筛选状态用 `+filter-view-create` |
> ⚠️ **动手前的触发式必读(按动作判定,不看主场景)**:本次操作只要**涉及样式 / 美化**(底色 / 边框 / 字号 / 对齐 / 数字格式 / 汇总行 / 配色 / 列宽行高),动手前先读 `lark-sheets-visual-standards`;只要**要写飞书公式**,动手前先读 `lark-sheets-formula-translation`(飞书函数与 Excel 有差异,凭直觉迁移易错),**写完后再读 `lark-sheets-formula-verify` 并执行 `+formula-verify` 收尾**。哪怕主任务是"建表 / 展开数据 / 录入",只要动作里含美化或写公式就适用——别因"这不算专门的美化 / 公式任务"而跳过。
> ⚠️ **两种图片别选错**:图若**绑定某条记录、要随行排序 / 筛选 / 增删**(凭证 / 证件照 / 每行配图,话里带「对应 / 每行 / 这列」等绑定词)→ 单元格图片 `+cells-set-image`只是自由摆放的装饰logo / 水印 / 封面)→ 浮动图片 `+float-image-create`。别因「浮动图更好控制 / 更熟」默认选浮动图。
> ⚠️ **纯文本还是数值语义(看数据本质,不看当下用途)**:金额 / 百分比 / 比率 / 计数 / 日期等**本质是量值**的数据 → 一律数值写入,常规二维表用 `+table-put``dtypes` 声明类型 + `formats` 设展示格式),版式装不下(多级 / 合并表头的宽表 leaderboard 等)改用 `+cells-set` 传数字(百分比传小数 `0.4`+ `number_format`,照样显示 `40%` 且数值无损。只有编号 / 身份证 / 单据号这类**本质是标识符**、要字面保真的才用 `+csv-put` 平铺。**几个常见借口都不成立**——"只是 leaderboard / 报表展示不用算""版式复杂""样式以后再刷、先铺文本"都不是把百分比写成 `"40%"` 字符串灌 `+csv-put` 的理由(展示不改变它是数值;类型不能后补,落成文本就回不来)。判据与操作展开见 `lark-sheets-write-cells`「数字还是文本」。
> ⚠️ **要新建子表 / 整表美化 → 别默认「`+csv-put` 写值再事后刷样式」**`+table-put` / `+workbook-create` 的 `--styles` 能在写数据的**同一步**带全套样式(区域底色 / 边框 / 列宽 / 行高 / 合并),且 `+table-put` 的 payload 里若 sheet 名不在工作簿中会自动新建子表——**纯文本表要新建子表 + 美化时同样走这里**`--styles` 与列是否 typed 无关),比「`+csv-put` 写值 + 多次 `+cells-batch-set-style` / `+*-resize` 刷样式」少好几次调用(冻结行列等 sheet 级属性仍需 `+dim-freeze` 单独一步)。
> ⚠️ **定位 flag**`+cells-get` / `+cells-set` / `+csv-get` 用 `--range``+csv-put` 规范用 `--start-cell`(单个左上角锚点格),也接受 `--range` 别名(区间自动取左上角),二者择一即可。
> ⚠️ **读取附加信息**一律走 `+cells-get --include …`**没有** `--with-styles` 这类 flag**看合并单元格**用 `+sheet-info` 的 `merged_cells`,不要在 `+cells-get` 里找 merge flag。
## 执行要点(读取 / 原生工具 / 陷阱)
准则的实操展开。端到端工作流:了解结构 → 读数据 → 理解语义 → 原生工具优先 → 写入 → 回读验证。
### 读取:按需求选路径(细则见 `lark-sheets-read-data`
| 用户需求 | 读取路径 |
|---|---|
| "完善 / 补齐 / 填空 / 修正所有 XX"、分析 / 清洗 / 大数据 | 原生优先(公式 / `+pivot` / `+filter`);表达不了再分批 `+csv-get` 导出 + 脚本处理 + 分批回写(默认覆盖所有对应数据行,不以用户选区为准) |
| "查一下 / 看看 / 统计 / 汇总"等只读 | `+csv-get` 读到上下文 |
| 需要公式 / 样式 / 批注 | `+cells-get` |
| 续写 / 扩展已有内容 | `+csv-get` 看结构 + `+cells-get` 读源区样式 + `+sheet-info --include row_heights,merges`(见准则 5 |
> "补齐 / 填空"类用只读路径探 10 行就写会漏写表尾——写入前先按 `lark-sheets-read-data` 确认真实数据末行(准则 3
### 计算:原生工具优先,代码兜底(强化准则 7
| 用户需求 | 用原生 | 禁止的替代 |
|---|---|---|
| 按 X 统计 Y、分组汇总 | `+pivot-{create\|update\|delete}` | pandas groupby → 写值 |
| 求和 / 计数 / 平均 / 占比 | 公式 | Python 算 → 写静态值 |
| 图表 / 可视化 | `+chart-*` | matplotlib |
| 条件高亮 / 色阶 | `+cond-format-*` | 逐格设样式 |
| 筛选 | `+filter-*` | pandas filter → 覆盖写入 |
| 文本提取 / 转换 / 查找 | 公式REGEXEXTRACT / TEXT / VLOOKUP 等) | Python → 写静态值 |
只有多步清洗、统计建模、公式试错 3 次仍失败时才用代码。
### 用脚本配合 CLI 时
- **只读 stdout**CLI 数据走 stdout、诊断走 stderr解析 JSON 别 `2>&1`(警告混入会解析失败),用管道或单独重定向 stdout。
- **喂 CLI 的 CSV / JSON 用 UTF-8 无 BOM**;临时文件放系统临时目录、勿落项目目录。
- **命令失败先读 stderr 再调整**,别原样重发。
- **回写纯单元格值**:剥离 `值(V-Align: bottom)` 这类"值(样式)"串与残留引号再写;排序优先 `+range-sort` 原生工具,别"读出本地排完再整列写回"。
### 易漏陷阱
- **`+dim-insert` 不继承行高**:只继承值 / 公式 / 边框,新行回落默认高度截断长文本;插行填长文本前读相邻行 `row_height`,用 `+batch-update``+rows-resize` 补齐。
- **公式容错**:日期 / 查找 / 数值转换公式用 `IFERROR` 包裹;写完读结果列首末各 5 行查 `#VALUE!` / `#REF!` / `#DIV/0!`,然后继续跑 `+formula-verify` 直到 `status='success'`;同一方案试错上限 3 次。
- **循环引用**:聚合公式引用范围不能含目标 cell 自身或其传递依赖。
- **隐藏行列**`+csv-get` 默认含隐藏行列;设 `--skip-hidden=true` 只看可见,但返回行序号与实际行号不再对应。
- **跨 sheet 对象**:图表 / 条件格式 / 透视表 / 浮动图片可能分布在多个子表,操作前先 `+workbook-info` 掌握全局。
- **NLP 任务分批**:语义理解 / 翻译 / 改写 / 分类等用 NLP 处理(代码只做分批 / 行号映射 / 写回);数据量大必须分批(通常 30 行 / 批),每批处理完即时写回,单批生成通常 ≤ 300 行,多批用 `+batch-update`
## References
本 skill 的 reference 分两组:先读**通用方法与规范**(横切所有任务的样式、公式规则,不含具体 shortcut它们规定了"怎么做对";再按操作对象进入**工具参考**查具体 shortcut 与调用细节。编辑类任务务必先过一遍通用方法与规范,连同上方「飞书表格编辑准则」对所有工具参考一律生效。
### 通用方法与规范(先读,横切所有任务,不含具体 shortcut
| Reference | 描述 |
| --- | --- |
| [飞书表格样式与配色规范](references/lark-sheets-visual-standards.md) | 飞书表格样式与配色规范:表头/数据区/汇总行的颜色、字号、对齐、边框、数字格式等取值标准,以及从零新建表格的版式美化、新增汇总行、追加行列继承原表风格、已有区域美化等典型场景的决策流程与样式要点。工具调用参数细节请参考对应的 lark-sheets-write-cells / lark-sheets-range-operations / lark-sheets-batch-update。条件格式高亮、标红、数据条、色阶请使用 lark-sheets-conditional-format。 |
| [飞书表格公式生成规则](references/lark-sheets-formula-translation.md) | Excel 公式到飞书表格公式的迁移与生成规则。核心目标不是保留 Excel 原语法,而是按飞书表格可执行规则重写公式,并在结果上尽量对齐 Excel。当用户要求把 Excel 公式改写成飞书表格公式,或需要生成飞书公式(尤其涉及 ARRAYFORMULA、原生数组函数、INDEX/OFFSET、MAP/LAMBDA、日期差、多层范围结果与二次展开时使用。本文只负责把公式写对落表后的强制收尾请接 `lark-sheets-formula-verify`。 |
### 按对象的工具参考(含 shortcut
| Reference | 描述 |
| --- | --- |
| [Lark Sheet Formula Verify](references/lark-sheets-formula-verify.md) | 公式写入 / 批量填充 / `--copy-to-range` 扩展 / 导入含公式工作簿后的强制自检入口。对指定子表(或整本工作簿)扫描公式与单元格值,聚合所有 Excel 错误(#REF! / #DIV/0! / #VALUE! / #NAME? / #NULL! / #NUM! / #N/A同时合并最近一次写入留下的编译失败formula_errors输出统一 JSON 让 AI 一次拿到完整健康度报告。只要任务涉及写公式,落表后就应调用 +formula-verify 收敛到 zero-error`status='errors_found'``status='partial'` 时禁止把链路标为完成。 |
| [Lark Sheet Workbook](references/lark-sheets-workbook.md) | 管理飞书表格的工作簿结构(子表列表及元数据)。当用户提到"看看这个表格有什么"、"表格结构"、"有哪些 sheet"、"新建一个 sheet"、"删除这个工作表"、"重命名"、"复制一份"、"移动到前面"时使用。 |
| [Lark Sheet Sheet Structure](references/lark-sheets-sheet-structure.md) | 管理飞书表格的子表结构与布局。适用场景:查看行高、列宽、隐藏行列、合并单元格等布局信息,以及"插入一行"、"删除这列"、"隐藏行"、"冻结表头"、行列分组(大纲折叠/展开)等操作。行列大纲仅在用户明确提到"行分组"、"列分组"、"大纲"、"outline"时才触发,"按XXX分组"等数据分组场景请使用 lark-sheets-pivot-table。如需在表尾追加数据应先通过此 skill 插入行,再通过 lark-sheets-write-cells 写入。 |
| [Lark Sheet Read Data](references/lark-sheets-read-data.md) | 读取飞书表格中的单元格数据。当用户需要"看看数据"、"分析数据"、"统计/汇总"时使用;也适用于需要查看公式、样式、批注等详细信息的场景。 |
| [Lark Sheet Search & Replace](references/lark-sheets-search-replace.md) | 在飞书表格中搜索和替换文本,支持限定范围、大小写匹配、精确匹配、正则表达式。当用户需要"查找"、"搜索"、"定位"某个值,或"替换"、"批量修改文本"、"把 A 改成 B"时使用。不要用于理解表格结构(应读取数据)、不要用于数据分析(应读取数据后计算)、不要把用户操作动作中的关键词(如"汇总金额""统计数量")当作搜索词。 |
| [Lark Sheet Write Cells](references/lark-sheets-write-cells.md) | 向飞书表格的指定区域批量写入值、公式、样式、批注或单元格图片。适用场景:填写数据、设置公式、修改格式、添加批注、嵌入单元格图片(如需操作浮动图片,请使用 lark-sheets-float-image若只需把一块 CSV 批量铺到表格上(值或公式,不带样式/批注),直接使用 `+csv-put` 更短更快。追加数据需先通过 lark-sheets-sheet-structure 插入行列。只要这次写入真实落了公式,收尾默认继续执行 `lark-sheets-formula-verify`。 |
| [Lark Sheet Range Operations](references/lark-sheets-range-operations.md) | 对飞书表格中指定区域执行结构性操作(不涉及写入单元格数据值)。适用场景:清除内容或格式("清空"、"删除内容"、"去掉格式")、合并/取消合并单元格、调整行高列宽("加宽列"、"自适应列宽")、移动/复制/填充/排序数据("移动数据"、"复制到"、"自动填充"、"按某列排序")。写入单元格数据请使用 lark-sheets-write-cells。 |
| [Lark Sheet Batch Update](references/lark-sheets-batch-update.md) | 将多个飞书表格写入操作合并为一次批量执行,按顺序依次完成。适合需要连续执行多个写入操作的场景(如先修改结构再写入数据)。 |
| [Lark Sheet Chart](references/lark-sheets-chart.md) | 管理飞书表格中的图表(柱形图、折线图、饼图、条形图、面积图、散点图、组合图、雷达图等)。当用户需要创建图表、修改图表样式或数据源、查看已有图表配置、删除图表时使用。也适用于用户提到"数据可视化"、"画个图"、"趋势分析"、"对比图"、"占比分析"、"做个图表"等数据可视化相关场景。 |
| [Lark Sheet Pivot Table](references/lark-sheets-pivot-table.md) | 管理飞书表格中的数据透视表。当用户需要创建透视表、修改透视表的行列字段/聚合方式/筛选条件、查看已有透视表配置、删除透视表时使用。也适用于用户提到"分组汇总"、"交叉分析"、"按XXX统计"、"按字段分组"、"再分下组"、"多维分析"、"数据透视"等场景。 |
| [Lark Sheet Conditional Format](references/lark-sheets-conditional-format.md) | 管理飞书表格中的条件格式规则(重复值高亮、单元格值比较、数据条、色阶、排名、自定义公式等)。当用户需要创建条件格式、修改已有规则的范围或样式、查看当前条件格式配置、删除规则时使用。也适用于用户提到"高亮"、"标红"、"颜色标记"、"数据条"、"色阶"、"条件样式"等场景。 |
| [Lark Sheet Filter](references/lark-sheets-filter.md) | 管理飞书表格中的筛选器filter。当用户需要筛选数据按文本/数值/颜色/日期条件过滤行)、查看已有筛选配置、修改或删除筛选器时使用。也适用于"只看"、"筛选出"、"仅保留符合条件的"等场景。 |
| [Lark Sheet Filter View](references/lark-sheets-filter-view.md) | 管理飞书表格中的筛选视图filter view。当用户需要"建一个 XX 视图"、"保存这个筛选状态"、"切换不同筛选"、维护一个 sheet 上多份独立筛选配置时使用。视图与筛选器filter相互独立可在同一 sheet 共存;视图的隐藏行仅在用户进入该视图时本地生效,不影响其他协作者。 |
| [Lark Sheet Sparkline](references/lark-sheets-sparkline.md) | 管理飞书表格中的迷你图(折线迷你图、柱形迷你图、胜负迷你图)。当用户需要在单元格内嵌入小型图表来展示数据趋势时使用。也适用于"趋势线"、"单元格内图表"、"迷你图"等场景。注意:不等同于被禁用的 SPARKLINE() 公式函数。 |
| [Lark Sheet Float Image](references/lark-sheets-float-image.md) | 管理飞书表格中的浮动图片。当用户需要在表格中插入浮动图片、调整图片位置和大小、查看已有浮动图片、删除图片时使用。也适用于"插入图片"、"添加 logo"、"放一张图"等场景。注意:如果用户需要将图片嵌入到某个单元格内部(单元格图片),请阅读 lark-sheets-write-cells。 |
| [Lark Sheet History](references/lark-sheets-history.md) | 查询飞书表格的历史版本并回滚到指定版本。当用户需要查看一张表的编辑历史版本列表、回滚到某个历史版本、或查询回滚的异步状态(进行中/成功/失败)时使用。回滚为异步操作,发起后通过状态查询轮询结果。仅针对飞书表格。 |
| [Lark Sheet Changeset](references/lark-sheets-changeset.md) | 读取两个版本CS revision之间的 changeset原始变更操作清单用于复核某次编辑——尤其是 AI 编辑——是否真实满足用户诉求。传入起始版本(编辑前基线),可选结束版本(省略取最新),版本差上限 20返回里最外层带当前表格最新版本号。当用户需要"看看这次改了什么"、"核对 AI 改动"、"对比两个版本的变更"时使用。 |
## 公共 flag 速查
各 reference 的每个 shortcut 标题下用一行徽章标注该 shortcut 支持的公共 / 系统 flag例如
- `_公共四件套 · 系统:--dry-run_` — URL/token + sheet 定位(两组各**必给一个**,详见下方「公共 flag」`--dry-run`
- `_公共URL/token无 sheet 定位) · 系统:--yes、--dry-run_` — 只接 URL/token常见于 `+batch-update` 等不强制 sheet 定位的 shortcut
徽章里只列名字。type / 必填 / 描述都在本段统一声明:
### 公共 flag定位资源
**公共四件套** = `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`,分成两组 XOR**每组都必须给且只能给一个**XOR = 二选一必填,不是"可选"
1. **spreadsheet 定位(必填)**`--url``--spreadsheet-token` 二选一,**必须给其中之一**。两个都不给 → 校验报错 `specify at least one of --url or --spreadsheet-token`;两个都给 → 互斥冲突。
- **`--url` 解析 `/sheets/``/spreadsheets/``/wiki/` 三种链接**(从路径里抽出 token也可以直接把裸 token 传给 `--spreadsheet-token`)。其它形态的链接不会被解析成表格 token。
- **`/wiki/` 知识库链接可直接传 `--url`**:会自动定位到链接背后的电子表格;若该链接背后不是电子表格(而是文档 / 多维表格等),则报错。
- **例外**`+workbook-create`(新建表 + 可选写入数据)与 `+workbook-import`(把本地文件导入为新表)都产出一张**还不存在**的表格,**不接受任何 spreadsheet / sheet 定位 flag**——`+workbook-create` 只有 `--title` / `--folder-token` / `--values` / `--styles` / `--sheets``+workbook-import` 只有 `--file`(必填)/ `--folder-token` / `--name`
2. **sheet 定位(公共四件套 shortcut 必填)**`--sheet-id``--sheet-name` 二选一,**必须给其中之一**。两个都不给 → 校验报错 `specify at least one of --sheet-id or --sheet-name`
- ⚠️ **不确定 sheet 名时禁止直接猜 `Sheet1`**:除非用户对话明确说出 sheet 名 / id或上下文之前的工具调用 / URL 锚点 `?sheet=xxx`)已经出现过具体值,否则**第一步先调 `+workbook-info --url "..."`**(或 `--spreadsheet-token`)拿 `sheets[].sheet_id` / `sheets[].title` 列表再选。中文环境下子表常叫"数据" / "Sheet"(无数字)/ "工作表 1" / 业务名,猜 `Sheet1` 大概率撞 `sheet not found`,比先查多耗一次失败调用 + 重试。
- ⚠️ **`--range` 里的 `Sheet1!` 前缀不能替代 sheet 定位**:即使写了 `--range 'Sheet1!A1:B2'`,仍**必须**额外传 `--sheet-id``--sheet-name`,否则照样报上面的错。
- ⚠️ **A1 reference 含 `!`**`--source` / `--range` / `--ranges`**:整段用单引号包裹**,如 `--range 'Sheet1!A1:B2'`——单引号能挡住 bash 的 history expansion`!` 被拦成 `event not found`;双引号挡不住;别改用 `set +H`,原因见下方「复合 JSON / 大入参」。sheet 名含特殊字符(`-` / 空格 / 非 ASCII需在内部按 A1 标准再包一层单引号时,用 `'\''` 转义保持外层单引号,如 `--source ''\''Sales-2025'\''!A1:D100'`
- **例外**:徽章标为 `_公共URL/token无 sheet 定位…_` 的 shortcut`+workbook-info` / `+workbook-export` / `+batch-update` / `+dropdown-update|delete` / `+cells-batch-set-style` / `+cells-batch-clear` / `+sheet-create`**不接受也不需要** sheet 定位,只给一组 spreadsheet 定位即可。`+pivot-create``--target-sheet-id` / `--target-sheet-name`XOR可都不传落点细节见 `lark-sheets-pivot-table`)。
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--url` | string | 二选一必填(与 `--spreadsheet-token` | spreadsheet 或 wiki URL |
| `--spreadsheet-token` | string | 二选一必填(与 `--url` | spreadsheet token |
| `--sheet-id` | string | 二选一必填(与 `--sheet-name`;仅公共四件套 shortcut | 工作表 reference_id |
| `--sheet-name` | string | 二选一必填(与 `--sheet-id`;仅公共四件套 shortcut | 工作表名称 |
**统一调用范式**(公共四件套 shortcut 的所有示例都遵循此形状,两组定位缺一不可):
```bash
lark-cli sheets <shortcut> <workbook 定位> <sheet 定位> <其它 flag>
# workbook 定位:--url "..." 或 --spreadsheet-token "..." (二选一,必给)
# sheet 定位: --sheet-id "$SID" 或 --sheet-name "<真实表名>" (二选一,必给;占位符不要原样填)
# 例lark-cli sheets +csv-get --url "https://.../sheets/shtXXX" --sheet-name "<真实表名>" --range "A1:F30"
# 注意:真实表名不要直接填 "Sheet1"——大多数表的子表不叫这个;先 +workbook-info 拿 sheets[].title 再代入。
```
### 系统 flag
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dry-run` | bool | 否 | 零副作用:仅打印请求路径与参数模板,不发起调用;多步操作会输出每个子操作的请求模板 |
| `--yes` | bool | 是(仅 `high-risk-write` | 二次确认;不带时退出码 10。详见 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md) 高风险审批协议 |
| `--print-schema` | bool | 否 | 本地打印复合 JSON flag 的 JSON Schema 并退出,不发起任何调用、不需要其它 required flag。与 `--flag-name <name>` 搭配指定要查哪个 flag省略 `--flag-name` 时列出该 shortcut 所有可查询的 flag。**仅在 shortcut 含复合 JSON flag 时有效**——判断方法:该 shortcut 的 Flags 表里出现类型标注为「复合 JSON」的 flag`--cells` / `--properties` / `--operations` / `--border-styles` / `--sort-keys` / `--options`)即支持;纯标量 flag 的 shortcut 不支持。 |
| `--flag-name` | string | 否 | 配合 `--print-schema` 使用,指定要打印 JSON Schema 的 flag 名(不带 `--` 前缀,如 `cells` / `properties` / `operations`)。 |
**Agent 使用提示**:写复合 JSON flag`--cells` / `--properties` / `--operations` / `--border-styles` / `--sort-keys` / `--options` 等)时,如果对结构不确定,先跑 `lark-cli sheets <shortcut> --print-schema --flag-name <name>` 把完整 JSON Schema 读出来再构造 payload比靠 reference 的速查表更精确也避免因为字段拼写或缺失被服务端拒绝。reference 的 `## Schemas` 段只给一层结构,深层只能靠 `--print-schema``## Examples` 的真实示例。
### flag 内容类型与输出约定(术语速记)
- flag 表里 JSON 类入参标三类:**复合 JSON** = 深层嵌套对象(用 `--print-schema` 取完整结构);**简单 JSON** = 一维 / 二维标量数组(如 `["sheet1!A1:B2",...]` / `[["alice",95]]`,结构简单无需 print-schema**非 JSON 文本** = 原样文本(如 CSV`--print-schema` 只对**复合 JSON** flag 有效(同一 shortcut 的简单 JSON flag 如 `--colors` 不在此列)。
- **envelope**:所有 shortcut 返回统一外层结构 `{ok, identity, data, ...}`。正文里 `envelope.data` 指业务数据层(如 `+csv-get``annotated_csv`);写操作不会自动回读,如需校验请自行调用对应的 `+*-list` / `+*-get` / `+cells-get`
## 复合 JSON / 大入参:优先 stdin
flag 帮助里标注支持 **Stdin** 的入参,当 payload 较大、含换行 / 引号等特殊字符,或已经落在某个文件里时,优先用 stdin`-`)传入,避免命令行超长与 shell 转义问题。
推荐写法payload 写到用户项目目录之外的临时文件(放系统临时目录,避免污染项目),再用 stdin 喂进去:
```bash
# TMPFILE 指向系统临时目录下的 payload 文件(脚本里用 tempfile.gettempdir() / os.tmpdir() 等取临时目录)
lark-cli sheets +cells-set --url "..." --sheet-name "Sheet1" --range "A1:B2" --cells - < "$TMPFILE"
```
**参数含特殊字符(`!` / 引号 / 空格 / 非 ASCII用单引号包裹该参数即可不要起手 `set +H` 之类的 shell 开关来防转义。** `set +H`(关 bash history expansion`sh` / `dash` 下是非法选项(`set: Illegal option -H`)、会让整条命令直接失败;而单引号挡得住 `!` 的 history expansion否则报 `event not found`),对 bash 与 `sh` / `dash` 一致安全。参数本身含单引号、或 payload 较大时,按上文走 stdin。
**`@file` 接绝对路径会被拒,且被拒后不要照报错提示做。** `@file` 出于安全只接受 cwd 下的相对路径,传 cwd 之外的绝对路径会被拒。此时报错会建议"先 cd 到目标目录,或改用相对路径"——**两条都不要照做**cd 过去、或把临时文件写进用户项目目录,都会污染工作目录。正解是改用 stdin`--<flag> - < 文件`)。

View File

@ -0,0 +1,200 @@
# Lark Sheet Batch Update
## 写入边界 + 回读校验
`+batch-update` 把多次写入打包成单次请求,但每个子操作仍受编辑类任务硬性默认规则约束:
1. **目标 range 必须落在用户授权范围内**:除用户明示要修改的区域外,子操作禁止扩张到无关单元格 / 列 / Sheet。规划 range 时先确认每个子操作的边界。
2. **批次完成后必须回读校验**:整个 `+batch-update` 执行成功后,用 `+csv-get``+cells-get` 抽样回读受影响区域,至少校验 3-5 个代表性单元格(首 / 中 / 末),与本地脚本预先计算的预期值对照。
3. **预期条数前置断言**:涉及"批量填充 N 行"或"对 M 个区域分别写入"时,先把 N、M 硬编码进代码,回读后断言实际等于预期;不一致就再发一轮 `+batch-update` 补齐,禁止交付半成品。
若本次 `+batch-update` 的任一子操作写入了公式、复制了公式模板、或导入了含公式的数据块,**回读校验之后还必须继续执行 `+formula-verify`**。`+batch-update` 的原子提交只保证“写入动作都执行了”,不保证整批公式运行结果 zero-error。
## 使用场景
写入。批量执行多个写入工具操作。将多个工具调用合并为一次请求,按顺序依次执行。适合需要连续执行多个写入操作的场景(如先修改结构再写入数据)。注意:不支持嵌套 `+batch-update`
**不可放进 `--operations` 的写 shortcut**`shortcut` 枚举不含它们,强行写入会被校验拒):`+cells-set-image`(需本地上传图片)、`+dropdown-update` / `+dropdown-delete` / `+cells-batch-set-style` / `+cells-batch-clear`(自身已是批量入口,不可再嵌套)、`+dim-move`。这些操作需在 `+batch-update` 之外单独调用。
**⚠️ 何时必须使用 `+batch-update`(硬性要求)**
- 需要对**多个**不同区域执行 `+cells-{merge|unmerge}` 时(如按分组合并多列相同内容)
- 需要先插入行列再写入数据时(`+dim-{insert|delete|hide|unhide|freeze|group|ungroup}` + `+cells-set`
- 需要对多个区域执行不同写入操作时(多次 `+cells-set` + `+cells-clear` 等组合)
**行高列宽批量不走这里**:多行 / 多列不同尺寸直接用 `+rows-resize --heights` / `+cols-resize --widths` 的 map 形态(如 `--widths '{"A":100,"C:E":120}'`,见 `lark-sheets-range-operations`一次调用原子完成map 形态不可作为 `--operations` 子操作嵌入(子操作里仍可用单区间形态 `range` + `height`/`width`)。
当同一工具需要对多个区域重复调用时,**必须**改用 `+batch-update` 合并为单次请求——`+batch-update` 是原子提交(要么全成功要么整批回滚);逐个调用非原子,中途失败会留下半成品。
**公式相关批处理的默认闭环**
- 写前:先读 `lark-sheets-formula-translation`,把公式改写成飞书可执行语义。
- 写时:用 `+batch-update` 一次性完成插行/写公式/复制模板等原子动作。
- 写后:抽样回读之外,继续跑 `lark-sheets-formula-verify`,直到 `+formula-verify` 返回 `status='success'`
**`+dropdown-update` 的选项模式(`--options` / `--source-range` 二选一)+ 配色规则**`--colors` 长度可短不能长、必须配 `--highlight=true` 才生效、不传按内置 10 色色板循环补色)见 [`lark-sheets-write-cells`](./lark-sheets-write-cells.md) 的「Dropdown 选项 + 配色」节,本文不重复。`+dropdown-delete` 不涉及这些 flag。
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+batch-update` | high-risk-write | 批量 |
| `+cells-batch-set-style` | write | 批量 |
| `+dropdown-update` | write | 对象 |
| `+dropdown-delete` | high-risk-write | 对象 |
| `+cells-batch-clear` | high-risk-write | 批量 |
## Flags
### `+batch-update`
_公共URL/token无 sheet 定位) · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--operations` | string + File + Stdin复合 JSON | required | JSON 数组:[{"shortcut":"+xxx-yyy","input":{...}}, ...]。shortcut 用 CLI 名input 是该 shortcut 的入参集——含子表定位 sheet_id或 sheet_name但不含 spreadsheet token/url后者只在顶层 --url/--spreadsheet-token 给一次;+batch-update 顶层没有 --sheet-idinput 的键是该 shortcut 的 flag 展平成 JSON如 "range":"A11:B12"),不是再套一层嵌套。基础 flag 查 --help复合 JSON flag 查 --print-schema --flag-name <flag>;不要手填 operation 字段(由 CLI 按 shortcut 自动注入)。默认严格事务(首个失败即整批中断),传 --continue-on-error 切换为软批量(遇失败仍继续);不支持嵌套;按数组顺序串行执行 |
| `--continue-on-error` | bool | optional | 遇子操作失败时继续执行剩余操作;默认 false首个失败即整批中断 |
### `+cells-batch-set-style`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--ranges` | string + File + Stdin简单 JSON | required | 目标范围 JSON 数组(最多 100 个),每项必须带 sheet 前缀(如 `["Sheet1!A1:B2","Sheet2!D1:D10"]`,前缀裸写不加引号);前缀必须与 sheet 真实显示名完全一致(含大小写),不接受 sheet reference_id支持跨 sheet所有 range 应用同一组 style |
| `--background-color` | string | optional | 背景颜色(十六进制,如 `#ffffff` |
| `--font-color` | string | optional | 字体颜色(十六进制,如 `#000000` |
| `--font-family` | string | optional | 字体名称(如 `Arial``微软雅黑` |
| `--font-size` | float64 | optional | 字体大小px10、12、14 |
| `--font-style` | string | optional | 字体样式(可选值:`normal` / `italic` |
| `--font-weight` | string | optional | 字重(可选值:`normal` / `bold` |
| `--font-line` | string | optional | 字体线条样式(可选值:`none` / `underline` / `line-through` |
| `--horizontal-alignment` | string | optional | 水平对齐(可选值:`left` / `center` / `right` |
| `--vertical-alignment` | string | optional | 垂直对齐(可选值:`top` / `middle` / `bottom` |
| `--word-wrap` | string | optional | 换行策略(可选值:`overflow` / `auto-wrap` / `word-clip` |
| `--number-format` | string | optional | 数字格式(例:文本 `@`、数字 `0.00`、货币 `$#,##0.00`、日期 `mm/dd/yyyy` |
| `--border-styles` | string + File + Stdin复合 JSON | optional | 边框配置 JSON结构同 +cells-set-style |
### `+dropdown-update`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--ranges` | string + File + Stdin简单 JSON | required | 目标范围 JSON 数组(最多 100 个,如 `["Sheet1!A2:A100","Sheet1!C2:C100"]`,前缀裸写不加引号),每项必须带 sheet 前缀;前缀必须与 sheet 真实显示名完全一致(含大小写),不接受 sheet reference_id |
| `--options` | string + File + Stdin复合 JSON | xor | 下拉选项 JSON 数组,例如 `["opt1","opt2"]`。服务端不限制选项数量,也不限制单个选项长度;含逗号的选项可以接受(写入时会自动转义)。大量选项建议改用 `--source-range`。 |
| `--colors` | string + File + Stdin简单 JSON | optional | 下拉胶囊背景色RGB hex 数组(如 `["#1FB6C1","#F006C2"]`)。长度可短不可长——超长 Validate 拦截(`--colors length (N) must not exceed dropdown source size (M)`),未指定项按内置 10 色色板循环补色。**单独传即生效**`--highlight=false` 时被忽略。 |
| `--multiple` | bool | optional | 启用多选 |
| `--highlight` | bool | optional | 下拉胶囊背景色高亮开关。**不传 = 开**(按内置 10 色色板循环上色);`--highlight=false` 关闭得到纯白下拉。配色用 `--colors` 覆盖。 |
| `--source-range` | string | xor | listFromRange 模式的下拉源 rangeA1 表示法 + sheet 前缀(如 `'Sheet1'!T1:T3`)。映射到 server `data_validation.range`,搭配 server `data_validation.type='listFromRange'` 自动生效。跟 `--options` 二选一:传 `--options` 走 inline 列表type=list传本 flag 走 range 引用type=listFromRange`--colors` 长度规则不变(≤ 源 range 单元格数),`--highlight` / `--multiple` 行为相同。当 `--highlight` 开启且 source 覆盖单元格数超过 2000 时,服务端会将该下拉判为 option-error这是不支持的组合CLI 会向 stderr 输出 warning。如需取消`--highlight=false`。 |
### `+dropdown-delete`
_公共URL/token无 sheet 定位) · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--ranges` | string + File + Stdin简单 JSON | required | 目标范围 JSON 数组(最多 100 个,如 `["Sheet1!E2:E6"]`,前缀裸写不加引号),每项必须带 sheet 前缀;前缀必须与 sheet 真实显示名完全一致(含大小写),不接受 sheet reference_id |
### `+cells-batch-clear`
_公共URL/token无 sheet 定位) · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--ranges` | string + File + Stdin简单 JSON | required | 目标范围 JSON 数组(最多 100 个),每项必须带 sheet 前缀(如 `["Sheet1!A2:Z1000","Sheet2!A2:Z1000"]`,前缀裸写不加引号);前缀必须与 sheet 真实显示名完全一致(含大小写),不接受 sheet reference_id支持跨 sheet对所有 range 执行同一 scope 的清除 |
| `--scope` | string | optional | 清除范围 enum`content`(默认,仅清内容)/ `formats`(仅清格式)/ `all`(清内容 + 格式)(可选值:`content` / `formats` / `all` |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+batch-update` `--operations`
_要批量执行的 CLI shortcut 操作列表按声明顺序串行执行任一失败立即中断_
**数组项**(类型 object
- `shortcut` (enum) — CLI shortcut 名(不是底层 MCP tool 名) [+cells-set / +cells-set-style / +cells-clear / +cells-merge / +cells-unmerge / +cells-replace / +csv-put / +dropdown-set / +dim-insert / +dim-delete / +dim-hide / +dim-unhide / +dim-freeze / +dim-group / +dim-ungroup / +rows-resize / +cols-resize / +range-move / +range-copy / +range-fill / +range-sort / +sheet-create / +sheet-delete / +sheet-rename / +sheet-move / +sheet-copy / +sheet-hide / +sheet-unhide / +sheet-set-tab-color / +sheet-show-gridline / +sheet-hide-gridline / +chart-create / +chart-update / +chart-delete / +pivot-create / +pivot-update / +pivot-delete / +cond-format-create / +cond-format-update / +cond-format-delete / +filter-create / +filter-update / +filter-delete / +filter-view-create / +filter-view-update / +filter-view-delete / +sparkline-create / +sparkline-update / +sparkline-delete / +float-image-create / +float-image-update / +float-image-delete]
- `input` (object) — 该 shortcut 的入参集——含子表定位 sheet_id或 sheet_name但不含 spreadsheet token/url后者只在顶层 …
### `+cells-batch-set-style` `--border-styles`
_单元格边框配置含 top/bottom/left/right 四个方向,每个方向的结构相同(见 top_
**顶层字段**
- `top` (object?) { style?: enum, weight?: enum, color?: string }
- `bottom` (object?) { style?: enum, weight?: enum, color?: string }
- `left` (object?) { style?: enum, weight?: enum, color?: string }
- `right` (object?) { style?: enum, weight?: enum, color?: string }
### `+dropdown-update` `--options`
_列表选项_
**数组项**(类型 string
- 标量string
## Examples
公共四件套:`--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(前两者 XOR`+batch-update` 本身不强制 sheet-id子操作各自携带
### `+batch-update`
示例:
```bash
lark-cli sheets +batch-update --url "https://example.feishu.cn/sheets/shtXXX" --yes \
--operations @ops.json
# ops.json array<{shortcut, input}>shortcut 用 CLI 名):
# [
# {"shortcut": "+dim-insert", "input": {"sheet_id":"...","position":10,"count":3}},
# {"shortcut": "+cells-set", "input": {"sheet_id":"...","range":"A11:B12","cells":[[{"value":"a"},{"value":"b"}],[{"value":"c"},{"value":"d"}]]}}
# ]
```
> ⚠️ **子操作定位规则**
> - spreadsheet 定位(`--url` / `--spreadsheet-token`**只在顶层给一次**`+batch-update` 顶层**没有** `--sheet-id` / `--sheet-name`,在顶层传不生效。
> - **每个子操作的子表定位 `sheet_id`(或 `sheet_name`)写进它自己的 `input`**(见上方 ops.json 每个 item
> - `input` 的键是该 shortcut 的 flag **展平**成 JSON`"range":"A11:B12"`、`"position":11`),不要把整组 `--operations` 再套一层嵌套 JSON。
> **常见组合:插列 + 写表头 + 整列回填**——一次原子提交,不要拆成 N 次独立调用。批量回填同一列 **只需一次** `+cells-set`range 写整列范围、cells 写 N×1 矩阵),不需要逐行循环。
>
> ```jsonc
> // 在 C 列前插入新列 → 写表头 C1 → 回填 C2:C100 共 99 行
> [
> {"shortcut": "+dim-insert",
> "input": {"sheet_name": "Sheet1", "position": "C", "count": 1}},
> {"shortcut": "+cells-set",
> "input": {"sheet_name": "Sheet1", "range": "C1:C100",
> "cells": [[{"value":"score"}], [{"value":95}], [{"value":87}], /* ... 97 more rows ... */ ]}}
> ]
> ```
### `+cells-batch-set-style`
多 range 应用同一组 style服务端走 `+batch-update` 原子事务):
```bash
# 表头行 + 汇总行同时刷成蓝底白字
lark-cli sheets +cells-batch-set-style --url "..." \
--ranges '["sheet1!A1:F1","sheet1!A30:F30"]' \
--background-color "#1E5BC6" --font-color "#FFFFFF" --font-weight bold
```
### `+cells-batch-clear`
多 range 一次性清除(服务端走 `+batch-update` 原子事务);`--scope``+cells-clear``content` / `formats` / `all`,默认 `content``high-risk-write` 强制 `--yes`
```bash
# dry-run 先看清除范围
lark-cli sheets +cells-batch-clear --url "..." \
--ranges '["sheet1!A2:Z1000","sheet2!A2:Z1000"]' --scope all --dry-run
# 执行
lark-cli sheets +cells-batch-clear --url "..." \
--ranges '["sheet1!A2:Z1000","sheet2!A2:Z1000"]' --scope all --yes
```
### Validate / DryRun / Execute 约束
- `Validate``+batch-update``--operations` 必须合法 JSON且为非空数组逐个子操作 `shortcut` / `input` 字段必填校验;**禁止嵌套 `+batch-update`**。`+cells-batch-set-style``--ranges` 必须 JSON 数组、每项带 sheet 前缀;样式 flag 至少一个非空(或带 `--border-styles`)。`+cells-batch-clear``--ranges` 同样必须 JSON 数组、每项带 sheet 前缀,`high-risk-write` 强制 `--yes``--dry-run``--scope` 默认 `content`)。
- `DryRun`:按顺序输出每个子操作的目标 API + 请求 body 模板;首个失败则整批 fail-fast不实际执行任何后续
- `Execute`:按声明顺序串行执行;任一子操作失败立即中断并回滚到该子操作前状态(具体回滚能力取决于子操作类型,沿用 `+batch-update` 的语义)。

View File

@ -0,0 +1,105 @@
# Lark Sheet Changeset
## 使用场景
读取两个版本之间的 **changeset变更操作清单**,用于**复核某次编辑(尤其是 AI 编辑)是否真实满足用户诉求**。
典型场景AI agent 对表格做了一批编辑后,想确认它"说做的"和"真正落到表格上的"是否一致——拉取编辑前版本到编辑后版本之间的 changeset逐条核对 action 是否覆盖了用户要求的修改、有没有多改 / 漏改。
## 版本revision语义
- 这里的"版本"指表格的 **CS revision**(每次提交单调递增的修订号),不是文档历史里的命名版本。
- `--start-revision` 是复核基线,即你认定的"编辑前"版本。
- `--end-revision` 是"编辑后"版本;**省略时默认取最新 revision**,返回从 start 到最新的全部 changeset。
- **版本差上限 20**`end - start + 1 ≤ 20`,超出会被拒绝(服务端同样以 20 兜底)。复核大跨度变更时请分段拉取。
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+changeset-get` | read | 变更记录 |
## Flags
### `+changeset-get`
_公共URL/token无 sheet 定位_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--start-revision` | int | required | 起始版本(编辑前基线,>= 1 |
| `--end-revision` | int | optional | 结束版本(省略取最新) |
## 返回结构
返回一个 JSON 对象,`changesets` 数组按版本顺序排列,每个元素是一次提交的**原始 action 列表**与元信息:
```json
{
"spreadsheet_token": "shtcnXXXX",
"latest_revision": 142,
"start_revision": 120,
"end_revision": 135,
"changesets": [
{
"revision": 121,
"create_time": "2026-06-12T10:00:00Z",
"actions": [
{ "action": "setCellRange", "sheetId": "...", "value": { /* ... */ } }
],
"is_self_edit": false,
"is_ai_edit": true
}
]
}
```
- 最外层 `latest_revision` 是**当前表格的最新版本号**(与查询区间无关),便于判断表格当前停在哪个版本、`--start-revision` 该取多少。
- `actions` 是**未经语义渲染的原始操作对象**,按提交内的执行顺序排列。复核时逐条比对:每个 action 改了哪个 sheet、哪个区域、改成什么是否对应用户的诉求。
- `revision` / `create_time` 用于判断"这次改动属于哪个版本、什么时候做的"。
- `is_self_edit` 表示该 changeset 是否由当前请求用户提交committer 与请求用户相同),即"是不是我自己提交的编辑"。
- `is_ai_edit` 表示该 changeset 是否由 AI 客户端提交(`member_id` 为 10 / 11。复核时 `is_ai_edit=true` 即为 AI 写入的编辑(而非用户手动编辑),是核对 AI 是否完成诉求的主要对象。
## 复核工作流(判断 AI 是否真实完成诉求)
1. 记下 AI 开始编辑前的 revision编辑前 `+workbook-info` 或上一次工具返回的 revision 即可作为 `--start-revision`)。
2. AI 编辑完成后,跑 `+changeset-get --url <表格> --start-revision <编辑前版本>`(不传 end → 取到最新)。
3. 遍历 `changesets[].actions`,核对:
- 用户要求的每一处修改是否都有对应 action
- 有没有越权 / 多余的修改(动了用户没让动的 sheet / 区域);
- action 的目标区域、值是否与诉求一致。
4. 若版本跨度可能 > 20分段拉取`start..start+19``start+20..` …)。
## 注意
- `+changeset-get` 是**只读**操作,不改动表格。
- 大跨度 / 大批量编辑的 changeset 可能体积较大;输出在传输层已 gzip。必要时缩小版本区间。
- 该工具走只读 scope `sheets:spreadsheet:read`,需要对表格有查看权限。
## Examples
### `+changeset-get`
公共:`--url` / `--spreadsheet-token`(二选一,无 sheet 定位。changeset 是工作簿级历史,不接受 sheet 定位 flag。
示例:
```bash
# 只传起始版本 → 返回从该版本到最新的全部 changeset最常用复核 AI 编辑前后的差异)
lark-cli sheets +changeset-get --url "https://example.feishu.cn/sheets/shtXXX" --start-revision 120
# 传起始 + 结束版本(版本差 end-start+1 ≤ 20
lark-cli sheets +changeset-get --spreadsheet-token shtXXX --start-revision 120 --end-revision 135
```
输出契约envelope.data
- `latest_revision` — 当前表格最新版本号(与查询区间无关)
- `start_revision` / `end_revision` — 实际查询区间(省略 `--end-revision``end_revision` = 最新版本)
- `changesets[]` — 按版本顺序排列;每项含 `revision` / `create_time` / `actions`(原始操作列表)/ `is_self_edit` / `is_ai_edit`
### Validate / DryRun / Execute 约束
- `Validate` 阶段只做 XOR 检查(`--url` / `--spreadsheet-token` 二选一)与版本上限校验(`--start-revision ≥ 1`,传了 `--end-revision``end ≥ start``end - start + 1 ≤ 20`**禁止**联网。
- `DryRun` 输出请求模板,不实际拉取 changeset。
- `Execute` 阶段才发起 changeset 查询;省略 `--end-revision` 时由服务端解析为最新 revision。

View File

@ -0,0 +1,332 @@
# Lark Sheet Chart
## 真对象硬约束
当用户要求"画个图 / 数据可视化 / 趋势图 / 对比图 / 占比图"时,**必须**通过 `+chart-{create|update|delete}` 创建真实的图表对象。**禁止**用本地脚本调 matplotlib / seaborn 生成图片再插入到表格代替——静态图片无法随源数据更新,且失去交互能力。判断标准:交付后 `+chart-list` 必须能返回该对象。
## 使用场景
读写图表对象。本 reference 覆盖 4 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看已有图表 | `+chart-list` | 获取图表的类型、数据源和样式配置 |
| 创建/更新/删除图表 | `+chart-{create|update|delete}` | 对图表对象执行写入操作 |
典型工作流:先读取现有图表了解配置 → 执行创建/更新/删除 → 再次读取验证结果。
## 需求→图表类型映射(创建前必查)
| 用户说 | 图表类型 | 备注 |
|--------|---------|------|
| "占比"、"比例"、"各XX占多少" | 饼图pie | 单维度占比首选 |
| "对比"、"各XX的YY" | 柱形图column纵向 | 多类别数值对比;横向条形用 `bar` |
| "趋势"、"变化"、"走势" | 折线图line | 时间序列首选 |
| "堆积"、"组成构成" | 堆积柱形图column + stack | 多系列累加 |
| "分布"、"相关性" | 散点图scatter | 两变量关系 |
**多图表需求**:当用户同时提到多种分析(如"统计占比 + 对比数量"),必须创建多个图表,每个对应一种类型,不要只做一个。
**`--properties` 结构锚点(构造前必读)**`--properties` 顶层只有 `position` / `offset` / `size` / `snapshot` 四个字段,**没有**顶层 `data`,也没有再嵌一层 `properties`。图表数据配置全部挂在 `snapshot.data` 下——下文及示例里出现的 `refs` / `headerMode` / `dim1` / `dim2` / `nameRef` 一律指 `snapshot.data.refs` / `snapshot.data.headerMode` / `snapshot.data.dim1` / `snapshot.data.dim2`(及其下的 `serie.nameRef` / `series[].nameRef`);样式 / 堆叠 / 数据标签等在 `snapshot.plotArea` 下。完整结构以 `lark-cli sheets +chart-create --print-schema --flag-name properties` 为准。
**常见配置错误(必须注意)**
- **图表类型选择错误**:用户说"堆积柱形图/百分比堆积"时,应在 `properties.snapshot.plotArea.plot.extra.stack` 中配置堆叠;百分比堆叠需在该 stack 下设置 `percentage: true`。用户说"占比/比例"时,优先考虑饼图或百分比堆积图。注意区分 `column`(柱形图,纵向)与 `bar`(条形图,横向)是两个不同的 type 取值,"对比/各 XX" 类纵向柱默认用 `column`
- **数据标签开关**`plotArea.plot.labels` 对象的**存在性即开关**——
- 用户需要看到具体数值/类别时:传入 `labels` 并配置 `value` / `category` / `series` / `percentage` 等显示位。
- 用户明确说"不要数据标签 / 关掉标签"时:**整个 `labels` 字段省略**。不要用 `labels: { value: false, category: false, series: false }` 这种"全部置 false"的写法关闭——只要传了 `labels`,系统就会显示数据标签(且默认兜底显示 value
- **数据源范围与系列名来源要对齐**
- **默认情况inline 模式)**`refs` 范围**应包含表头行**(首行/首列即系列名),且范围要精确覆盖目标数据,不要多选或少选。
- **合并标题行要跳过**:如果表格在表头上方存在合并的标题行(如"员工统计表"横跨多列的大标题),`refs` 必须跳过标题行、从真正的列标题行开始。例如表头在第 3 行、数据在第 4-20 行,则 `refs` 应为 `A3:G20` 而非 `A1:G20`。包含合并标题行会导致列名识别错误、表头被当作数据参与聚合计算。
- **数据与表头分离时必须用 detached 模式**:当 `refs` 只覆盖完整数据的一个子集(按筛选/分组只画其中一段),而真正的语义表头在该子集之外时,**必须**设置 `snapshot.data.headerMode='detached'`refs 仅传纯数据范围,维度名/系列名通过 `snapshot.data.dim1.serie.nameRef` / `snapshot.data.dim2.series[].nameRef` 指向真正的表头单元格。详见下文"硬性规则:数据与表头分离场景必须使用 detached 模式"。
- **axes[].label 不接受 `format` / `number_format` 字段**:想给坐标轴数值加千分位、百分号等格式化时,不要在 `axes[i].label` 里传 `format``number_format`schema 未定义,会报 `unexpected property "format" is not defined in schema`)。数值格式化统一在源数据单元格的 `cell_styles.number_format` 里设置(写 `+cells-set` 时),图表会沿用单元格格式。**日期轴同理**:横轴显示成 `45297` 这类 Excel 序列号,是因为源日期列没设日期格式——给源列设 `number_format="yyyy-mm-dd"` 后横轴才会显示成日期(反例:折线图横轴日期显示为序列号)。大数值轴显示科学计数法同理,给源列设整数 / 千分位格式(反例:透视表数值轴显示科学计数法)。
- **轴口径要对齐用户要的指标**:用户要"占比 / 比例"时,**纵轴应是百分比**——用饼图,或柱 / 条形图设 `stack.percentage: true` 让纵轴变 %,并把数据源指向占比列 / 让数据标签显示百分比;不要交付纵轴仍是原始计数的图(反例:要求看各类占比,却用普通堆积柱、纵轴是 0350 的人数而非百分比)。
- **创建后必须验证**:图表创建后必须调用 `+chart-list` 验证配置是否正确
> **⚠️ 硬性规则:当用户通过列标题名称(而非列索引)指定横轴/纵轴系列时,必须先读取表格首行(表头)来确定列名与列索引的对应关系,再设置 `dim1`/`dim2` 的 `index`。**
> 例如用户说"横轴为车型系列纵轴为Q1-Q4的销量",你不能猜测列索引,必须先通过读取表格数据源范围的首行内容(使用 `lark-sheets-read-data` 的 `+cells-get` 或其他读取单元格的工具),确认"车型系列"是第几列、"Q1"~"Q4"分别是第几列,然后再将正确的列索引填入 `dim1.serie.index` 和 `dim2.series[].index`。
> **⚠️ 硬性规则:数据与表头分离场景必须使用 detached 模式。** 当 `refs` 仅覆盖数据的一个子集,而真正的语义表头行/列位于该子集之外时,**必须** `snapshot.data.headerMode='detached'` 并配上 `nameRef`。不能用 inline 模式 + 把 refs 多带 1 行兜底表头来替代——那种写法已废弃。否则图表会把错误的首行/首列当系列名,或图例显示成"系列1/系列2"等默认名,或者 refs 里混入相邻分组的数据。
>
> **触发该规则的典型信号**(满足任意一条都必须走 detached
> - 用户要求"针对 X 类的数据画图"、"只看某个分组"、"只画筛选后的部分",而 X 类对应的行段在数据中间或末尾,与表头不连续;
> - 用户要求"按 X 分别画图"、"按某个维度(部门/品类/地区/时间段等)拆图"——**多张图共享同一组表头**
> - `refs` 起始行 > 表头行(如表头在第 1 行,但 `refs` 从第 11 行开始);
> - `refs` 起始列 > 表头列(如表头在 A 列,但 `refs` 从 C 列开始)。
>
> **正确做法**
> 1. 在 `data` 下显式设置 `"headerMode": "detached"`
> 2. `refs` **只覆盖该子集的纯数据**,不要向上/向左多带 1 行/列,也不要把全局表头整段并进来(否则会把其它分组的数据混进图);
> 3. **`nameRef` 必填**:给 `dim1.serie.nameRef` 写真正表头中"类别名"那一格的 A1 引用(如 `'Sheet2'!A1`sheet 名按 A1 标准单引号包裹),给每个 `dim2.series[i].nameRef` 写对应数值列的 A1 引用(如 `'Sheet2'!C1`、`'Sheet2'!D1`)。任一缺失会被校验拦下并报 `headerMode=detached requires ... nameRef`
> 4. `refs[i].value` 必须是单元格或普通矩形范围CELL / NORMAL不接受整行/整列/开区间;`direction='column'` 时起始行必须 > 0`direction='row'` 时起始列必须 > 0
> 5. `index` 仍按 `refs` 内的列/行号填,从 1 开始。
>
> **两种场景对照(互斥,二选一)**
>
> | 场景 | 何时命中 | 写法 |
> |---|---|---|
> | A. 表头与数据连在一起 | 单张图、refs 首行/首列就是表头(典型整段画图) | **省略 headerMode**(默认 inlinerefs 含表头,**不写 nameRef** |
> | B. 表头与数据分离 | 上面 4 条信号任一命中(数据子集、按维度拆图等) | **`headerMode='detached'`**refs 仅纯数据,**`nameRef` 必填** |
>
> **反向约束**:场景 A 下不要写 `nameRef`——首行命名已经生效,多写反而冗余。`nameRef` 仅在场景 B 下使用(且必填)。
## ⚠️ chart 数据源引用 pivot 时必须排除总计行
当 chart 要基于刚创建的 pivot 产物画图时,**禁止凭猜写 `refs`**。pivot 默认启用 `show_row_grand_total` / `show_col_grand_total`,产物最后一行/一列通常是"总计"。如果 `refs` 把总计行一并框进去:
- **柱形图**末尾会多一根天文数字柱子(=所有数据求和),把其他柱子压扁到看不见
- **饼图**会多一个"总计"扇区占 33%+,真实类别的比例完全失真
**正确流程**
1. `+pivot-create create` 返回 `sheet_id` + `pivot_table_id`
2.`+csv-get(sheet_id, 'A1:E30')``+pivot-list` 读 pivot 产物的**实际数据范围**
3. 识别并排除"总计"/"小计"行(通常最后一行;嵌套 pivot 还要排除中间层小计)
4. `+chart-create create``snapshot.data.refs` 精确到数据行(如 pivot 占 A1:D9、总计在 row9 → chart 用 `A1:D8`
## 图表位置选择(创建前必做)
凭感觉挑列号/行号会被 API 拒(`position is out of sheet range`)。按以下四步走:
1. **查尺寸**`+workbook-info` 拿该 sheet 的 `row_count` / `column_count`(下文记为 rowCount / columnCount`+sheet-info` 只返回布局,不含行列总数)。
2. **估跨度**:默认单元格 **105 px 宽 × 27 px 高**`needCols = ceil(width/105)``needRows = ceil(height/27)`
3. **校验**`position.row + needRows ≤ rowCount``col_idx + needCols ≤ columnCount``position.row`**0-based**:首行 = `row:0`,与 A1 区间 / `+dim-insert --position` 的 1-based 行号不同col 按 A=0、B=1、…、Z=25、AA=26… 换算)。
4. **不够就先扩表**,二选一,禁止硬塞越界位置:
- **优先**放数据下方空区:`position = {row: data_end_row + 2, col: "A"}`
- 否则先调 `+dim-insert``lark-sheets-sheet-structure`)扩行/列,再 create。
⚠️ **图表落点禁止压在已有数据矩形内**——必须落在数据区**右侧或下方的空白**,否则图表浮层会遮挡原始数据被判失败(反例:折线图落在数据区中间,遮挡了下方原始数据)。
**示例**21 列 sheet 放 600×400 图 → `needCols=6, needRows=15`
-`{row: 0, col: "W"}` — col=22 越界
-`{row: 42, col: "A"}` — 放数据下方
- ✅ 先 `+dim-insert --position V --count 6`(在 V 列前插 6 列,即 U 列之后),再放图到 `{row: 0, col: "V"}`
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+chart-list` | read | 对象 |
| `+chart-create` | write | 对象 |
| `+chart-update` | write | 对象 |
| `+chart-delete` | high-risk-write | 对象 |
## Flags
### `+chart-list`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--chart-id` | string | optional | 指定单个图表 reference_id 过滤 |
### `+chart-create`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | required | 图表完整配置 JSON。顶层字段为 `position` / `offset` / `size` / `snapshot`(无顶层 `data`,也无再嵌一层 `properties`);图表数据配置在 `snapshot.data` 下(含 `refs` / `headerMode` / `dim1` / `dim2`);必须至少含 `snapshot.data.dim1.serie.index``dim2.series[].index` 之一,否则 server 拒。结构嵌套深,完整结构跑 `--print-schema --flag-name properties` |
### `+chart-update`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--chart-id` | string | required | 目标图表 reference_id |
| `--properties` | string + File + Stdin复合 JSON | required | 完整或足够完整的图表配置 JSON`+chart-list` 回读再 patch |
### `+chart-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--chart-id` | string | required | 目标图表 reference_id |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+chart-create` `--properties` / `+chart-update` `--properties`
_创建/更新的图表属性_
**顶层字段**
- `position` (object?) — 必填 { row: number, col: string }
- `offset` (object?) — 可选 { row_offset?: number, col_offset?: number }
- `size` (object?) — 必填 { width: number, height: number }
- `snapshot` (object?) — 图表快照配置 { title?: object, subTitle?: object, style?: object, legend?: oneOf, plotArea: object, …共 6 项 }
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR 规则同 `+csv-get`)。
### `+chart-list`
输出契约:返回按工作表分组的图表列表,每个图表含 `chart_id` / `position` / `details.snapshot` 等。
### `+chart-create`
> **`snapshot.data` 必填 `dim1.serie.index` 或 `dim2.series[].index` 之一**1-based对应 `refs.value` 范围内的列序。schema 允许传空 `{}` 但 server 运行时强制:缺则被拒为 `snapshot.data.dim1.serie.index and dim2.series[].index are both missing; at least one must be set`,即便侥幸通过也只会渲染空图。
> ⚠️ **含 `'Sheet'!` 前缀的 `--properties` 必须走 stdin 或 `@file`,不要用 inline 单引号**。`refs` / `nameRef` 里的 sheet 前缀带单引号(`'Sheet1'!A1`),若塞进 inline 的 `--properties '{...}'`bash 会把内层那对单引号吃掉sheet 名带空格还会被拆成多个词JSON 直接被破坏。下面示例统一用 `--properties - <<'JSON' … JSON`heredoc 定界符加引号 = 不做 shell 替换),或 `--properties @file.json``@` 只接 cwd 下相对路径)。
最小可用列图inline 模式refs 含表头行):
```bash
lark-cli sheets +chart-create --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --properties - <<'JSON'
{
"position":{"row":42,"col":"A"},
"size":{"width":600,"height":400},
"snapshot":{
"data":{
"refs":[{"value":"'Sheet1'!A1:B10"}],
"dim1":{"serie":{"index":1}},
"dim2":{"series":[{"index":2}]}
},
"plotArea":{"plot":{"type":"column"}}
}
}
JSON
# 或落到 cwd 下相对路径文件再用 @file
lark-cli sheets +chart-create --url "..." --sheet-name "Sheet1" --properties @chart-config.json
```
**饼图专属示例**`sectors` 必须嵌在 `plotArea.plot.series[i].sectors.sector[]`,且 `sector[].index` 1-based
饼图比 column / bar 更复杂:`sectors` 是 object里面再包一个**单数** `sector` 数组——CLI 不替你 normalize写错路径会被 server schema 直接拒。
```bash
lark-cli sheets +chart-create --url "..." --sheet-name "Sheet1" --properties - <<'JSON'
{
"position":{"row":24,"col":"F"},
"size":{"width":600,"height":450},
"snapshot":{
"title":{"text":"各部门员工人数占比"},
"plotArea":{"plot":{
"type":"pie",
"series":[{
"index":1,
"sectors":{"sector":[{"index":1,"offsetRadius":0.05}]}
}]
}},
"data":{
"refs":[{"value":"'Sheet1'!A1:B11"}],
"dim1":{"serie":{"index":1,"aggregate":true}},
"dim2":{"series":[{"index":2,"aggregateType":"sum"}]}
}
}
}
JSON
```
**数据与表头分离(必须用 `detached` + `nameRef`**
场景:周度销量明细表,真实表头在第 1 行A1=周次、C1=订单量、D1=退款量),数据按 B 列"店铺"分段;用户只要"3 号店"那一段(第 1117 行)。
```bash
lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties - <<'JSON'
{
"position":{"row":7,"col":"F"},
"size":{"width":600,"height":360},
"snapshot":{
"title":{"text":"3 号店周度订单/退款"},
"plotArea":{"plot":{"type":"column"}},
"data":{
"headerMode":"detached",
"direction":"column",
"refs":[{"value":"'Sheet2'!A11:D17"}],
"dim1":{"serie":{"index":1,"nameRef":"'Sheet2'!A1"}},
"dim2":{"series":[
{"index":3,"nameRef":"'Sheet2'!C1"},
{"index":4,"nameRef":"'Sheet2'!D1"}
]}
}
}
}
JSON
```
约束:
- `refs` 只覆盖纯数据 `A11:D17`**不要**把表头行 A1 并进来
- `nameRef` 在 detached 模式下**必填**,缺了被校验报 `headerMode=detached requires ... nameRef`
- `index` 按 refs 内的列序算A=1、B=2、C=3、D=4**不是**全表列号
- `nameRef` 必须配对应的 `index`;单写 `nameRef` 不传 `index` 直接报参数错
**多张图共享同一组表头(按维度拆图,必须用 detached**
场景:销售明细表头在 A1:E1月份/区域/销售额/订单数/客单价),数据按区域分 3 段(华北 A2:E9、华东 A10:E17、华南 A18:E25要分别画 3 张图。
❌ 常见错误:
```jsonc
// 错误 1refs 含全局表头但跨段 —— 多个区域被混进同一张图
{"data":{"refs":[{"value":"'Sheet'!A1:E17"}], ... }} // 华东图混进华北 8 行
// 错误 2inline + refs 只取数据段、不写 detached/nameRef —— 图例显示成具体数据值
{"data":{"refs":[{"value":"'Sheet'!A10:E17"}],"dim1":{"serie":{"index":1}}, ... }}
```
✅ 正确模式3 张图各自 detached、refs 干净不重叠:
```jsonc
// 图 1华北
{"data":{
"headerMode":"detached","direction":"column",
"refs":[{"value":"'Sheet'!A2:E9"}],
"dim1":{"serie":{"index":1,"nameRef":"'Sheet'!A1"}},
"dim2":{"series":[
{"index":3,"nameRef":"'Sheet'!C1"},
{"index":4,"nameRef":"'Sheet'!D1"}
]}
}}
// 图 2华东 —— refs 改 'Sheet'!A10:E17其余同上
// 图 3华南 —— refs 改 'Sheet'!A18:E25其余同上
```
> `--properties` JSON 关键字段:
> - `position.row` / `position.col` 必须留足空间,越界会被 API 拒(按本文件"图表位置选择"四步走)
> - `snapshot.data.headerMode`:默认 inline当 refs 仅覆盖数据子集而语义表头在子集之外,必须 `detached` + `nameRef`
> - chart 引用 pivot 输出时,`snapshot.data.refs` 必须排除总计 / 小计行
### `+chart-update`
**Update 三步法**(缺一步会丢字段):
1. `+chart-list --chart-id <id>` 拿到完整 snapshot
2. 在拿到的 snapshot 上**局部**修改要改的字段,其余保持不变
3. 把**完整 snapshot** 整个回写到 `--properties.snapshot`
```bash
lark-cli sheets +chart-update --url "..." --sheet-id "$SID" --chart-id "chrXXX" \
--properties '{
"position":{"row":0,"col":"A"},
"size":{"width":480,"height":320},
"snapshot": <完整快照(由 +chart-list 取回后局部修改)>
}'
```
> 关键:**不能只提交局部 snapshot**,否则未传字段会被还原为默认值。`+chart-update` 的语义是 PUT整体覆盖不是 PATCH。
### `+chart-delete`
示例:
```bash
# dry-run 先看会删什么sheet 定位必填)
lark-cli sheets +chart-delete --url "https://example.feishu.cn/sheets/shtXXX" --sheet-id "$SID" \
--chart-id "chrXXX" --dry-run
# 真正执行
lark-cli sheets +chart-delete --url "https://example.feishu.cn/sheets/shtXXX" --sheet-id "$SID" \
--chart-id "chrXXX" --yes
```
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+chart-create` / `+chart-update``--properties` 必须能解析为合法 JSON`+chart-delete`high-risk-write校验 `--yes``--dry-run` 至少一个。
- `DryRun``+chart-create` / `+chart-update` 输出"将要 POST 的 body 模板"`+chart-delete` 输出"将要删除的 chart_id 及隶属 sheet",零网络副作用。
- `Execute`:写操作执行后不自动回读;如需确认,自行调用 `+chart-list` 比对结果。
> `+chart-create` / `+chart-update` 是 write 级别,按需可用 `--dry-run` 预览,不要求 `--yes`。只有 `+chart-delete`high-risk-write必须 `--yes`。

View File

@ -0,0 +1,181 @@
# Lark Sheet Conditional Format
## 真对象硬约束 + 触发词清单
用户出现以下口语指令时,**强制**走 `+cond-format-{create|update|delete}`**禁止**用 `+cells-set` 写静态背景色 / 字体色代替:
- **颜色动作**"标红 / 标黄 / 标绿 / 上色 / 染色 / 涂色 / 表红色 / 表黄色"
- **视觉强调**"高亮 / 突出 / 标记 / 标注 / 区分"
- **条件触发**"重复的标出来 / 异常的圈出来 / 过期的染红 / 大于 X 的标黄 / 不达标的标红"
- **联动语义**"颜色随数据变 / 联动 / 自动更新 / 改了数据颜色也跟着变"
- **数值可视化**"数据条 / 色阶 / 渐变色 / 进度条样式"
飞书表格的"颜色标记"语义 = 条件格式规则 ≠ 静态背景色。如果用 `+cells-set` 写静态,源数据变化时颜色不会跟着变(典型反例:用户要求"过期单元格标红"时,模型用静态填充——日期变化后单元格颜色不再准确反映过期状态)。
**判断标准**:交付后 `+cond-format-list` 必须能返回该规则;否则视为违规。
**大数据量首选**:当数据量 > 1000 行时,条件格式是首选——它由飞书自身渲染,比"本地脚本逐行计算 + `+cells-set` 写静态背景色"更高效、更稳(颜色还能随源数据自动联动)。
## 使用场景
读写条件格式对象。本 reference 覆盖 4 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看已有条件格式 | `+cond-format-list` | 获取规则类型、范围和样式配置 |
| 创建/更新/删除条件格式 | `+cond-format-{create|update|delete}` | 对条件格式规则执行写入操作 |
典型工作流:先读取现有条件格式了解配置 → 执行创建/更新/删除 → **必须再次读取验证结果**
**常见配置错误(必须注意)**
- **创建后必须验证**:条件格式创建后必须调用 `+cond-format-list` 验证规则是否生效。如果验证发现规则未生效或配置不正确,应立即修复并重试
- **范围要精确**:条件格式的应用范围必须精确覆盖用户指定的列/行,不要遗漏
- **`style.back_color` vs `style.fore_color` 的中文语义**:用户中文语境下的"**标红/高亮/染色/标记**"指**单元格背景色**,用 `back_color`"**文字红/字体红/把字变红**"才用 `fore_color`。默认无说明时选 `back_color`。把过期数据涂红、重复值高亮等都应该是 `back_color: "#FFE6E6"`(或类似浅红)配合可选的 `fore_color` 加深字体
- **日期/空值比较必须防空**:用户说"过期的标红"时,除了 `TODAY()`,公式必须排除空单元格,否则空白格也会被误判为"早于今天"而全表标红。正确公式:`=AND(E1<>"", E1<=TODAY())`;错误公式:`=E1<=TODAY()`(空值会被当作 0 判为过期)
- **公式条件注意引用方式**:自定义公式条件中的单元格引用需要根据实际场景选择相对/绝对引用(如 `=E1<=TODAY()` 而非 `=$E$1<=TODAY()`,后者只比较一个格)
⚠️ **用户明确要求"辅助列+条件格式"两步走时,禁止用 `expression` 绕过**:当用户说以下任意一种表达时,必须按两步走(先建辅助列 → 再基于辅助列做条件格式),**禁止**直接用一个 `rule_type: "expression"` 公式一步完成:
- "**增加辅助列**,再/然后标记……"
- "**先计算/判断** XX **是否** YY**再**标记……"
- "**新建一列**放结果,再用结果染色"
- 明确要求用 "辅助列"、"辅助字段"、"判断列"、"标记列"
**正确做法(两步走)**
Step 1 的 `+cells-set``--copy-to-range` 等 flag 以 `lark-sheets-write-cells` 为准。
```
Step 1: `+cells-set` 在新列写判断公式(形成"是/否"或布尔辅助列)
range="H2", cells=[[{formula: "=IF(A2>B2, \"是\", \"否\")"}]], --copy-to-range="H2:H100"
Step 2: 基于辅助列值做条件格式(用 cellIs 或引用辅助列的 expression
`+cond-format-{create|update|delete}` create
rule_type: "expression"
ranges: ["A2:H100"] // 整行高亮
attrs: [{formula: ["=$H2=\"是\""]}] // 引用辅助列
style: {back_color: "#FFECEC"}
```
**错误做法(一步走绕过辅助列)**
```
`+cond-format-{create|update|delete}` create
rule_type: "expression"
ranges: ["2:145"]
attrs: [{formula: ["=$O2>$H2"]}] ← 虽然逻辑等价,但产物里缺辅助列 → 不满足用户明确要求的"辅助列"诉求
```
为什么禁止一步走:用户明确要求辅助列是有**业务意图**的——让人肉眼能在表里看到"是/否"列;条件格式只是视觉辅助。一步 expression 虽然效果对了,但用户打开表格看不到辅助列,被视为"操作不完整/未采用公式"。
`expression` 单独使用的场景是:用户**没有**明确要求辅助列、只要"标红符合条件的行"时。
⚠️ **创建条件格式前必须读数据行确认列对应**:仅读首行表头(`+csv-get range="A1:Z1"`)不够——如果表头语义含糊(比如"时间"、"日期"这种多列同义词formula 里引用的列字母可能张冠李戴。必须再读 3-5 行**数据样本**(如 `range="A2:Z6"`)确认:①列名对应的实际值;②字段含义匹配用户描述;③数据类型是日期/数字/文本。特别是比较类条件格式(`=$A2>$B2` 这种),列字母选错整条规则就废了。
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+cond-format-list` | read | 对象 |
| `+cond-format-create` | write | 对象 |
| `+cond-format-update` | write | 对象 |
| `+cond-format-delete` | high-risk-write | 对象 |
## Flags
### `+cond-format-list`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--rule-id` | string | optional | 按规则 id 过滤 |
### `+cond-format-create`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | required | 规则配置 JSON`style`(命中样式,必填)和 `attrs?`(规则参数列表,因 `rule_type` 不同结构而异)/ `has_ref?``rule_type``ranges` 已拎为独立 flag |
| `--rule-type` | string | required | 条件格式规则类型;优先级高于 `--properties` 中同名字段(可选值:`duplicateValues` / `uniqueValues` / `cellIs` / `containsText` / `timePeriod` / `containsBlanks` / `notContainsBlanks` / `dataBar` / `colorScale` / `rank` / `aboveAverage` / `expression` / `iconSet` |
| `--ranges` | string + File + Stdin简单 JSON | required | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--properties` 中同名字段 |
### `+cond-format-update`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--rule-id` | string | required | 目标规则 id |
| `--properties` | string + File + Stdin复合 JSON | required | 规则配置 JSON结构同 `+cond-format-create``--properties`update 是整组覆盖式 |
| `--rule-type` | string | required | 条件格式规则类型;优先级高于 `--properties` 中同名字段(可选值:`duplicateValues` / `uniqueValues` / `cellIs` / `containsText` / `timePeriod` / `containsBlanks` / `notContainsBlanks` / `dataBar` / `colorScale` / `rank` / `aboveAverage` / `expression` / `iconSet` |
| `--ranges` | string + File + Stdin简单 JSON | required | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--properties` 中同名字段 |
### `+cond-format-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--rule-id` | string | required | 目标规则 id |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+cond-format-create` `--properties` / `+cond-format-update` `--properties`
_创建/更新的条件格式属性_
**顶层字段**
- `rule_type` (enum) — 条件格式规则类型 [duplicateValues / uniqueValues / cellIs / containsText / timePeriod / containsBlanks / notContainsBlanks / dataBar / colorScale / rank / aboveAverage / expression / iconSet] — ⚠️ 已拎为独立 flag `--rule-type`,请勿在此 JSON 内重复填写(同名以独立 flag 为准)
- `ranges` (array<string>) — 应用条件格式的 A1 范围列表 — ⚠️ 已拎为独立 flag `--ranges`,请勿在此 JSON 内重复填写(同名以独立 flag 为准)
- `style` (object) — 命中规则时应用的单元格样式 { back_color?: string, fore_color?: string, text_decoration?: enum, font?: enum }
- `attrs` (array<oneOf>?) — 规则参数列表
- `has_ref` (boolean?) — 可选
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR
### `+cond-format-list`
```bash
# 列出当前 sheet 全部条件格式规则(拿 rule_id 供 update/delete
lark-cli sheets +cond-format-list --url "..." --sheet-id "$SID"
```
### `+cond-format-create`
`--rule-type` / `--ranges` 是独立 flag不要再放 `--properties``style` / `attrs` 等结构走 `--properties`
```bash
# 重复值高亮
lark-cli sheets +cond-format-create --url "..." --sheet-id "$SID" \
--rule-type duplicateValues --ranges '["A1:A100"]' \
--properties '{"style":{"back_color":"#FFD7D7"}}'
# 数据条
lark-cli sheets +cond-format-create --url "..." --sheet-id "$SID" \
--rule-type dataBar --ranges '["B2:B100"]' \
--properties @rule.json
```
### `+cond-format-update`
整组覆盖式:先 `+cond-format-list --rule-id <id>` 拿当前完整配置,改后整组传回。
### `+cond-format-delete`
```bash
lark-cli sheets +cond-format-delete --url "..." --sheet-id "$SID" --rule-id "$RULE_ID" --yes
```
> 一次只删一个 `--rule-id`。要删**多个**条件格式时,先 `+cond-format-list` 拿到各 `rule-id`,再用 `+batch-update` 把多个 `+cond-format-delete` 合并为单次原子提交,不要逐个调用。
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`--rule-type` / `--ranges` 必填;`--properties` 必须能解析为合法 JSON`--rule-type` 检查必填子字段(`cellIs``attrs.operator` + `attrs.value``expression``attrs.formula``colorScale``min/mid/max` 配色等);`+cond-format-delete` 强制 `--yes``--dry-run`
- `DryRun`:写操作输出"将要 POST/PATCH/DELETE 的 conditional_format 请求模板"。
- `Execute`:写后不自动回读;如需确认,自行调用 `+cond-format-list --rule-id <id>` 比对规则 / 范围 / 样式。

View File

@ -0,0 +1,137 @@
# Lark Sheet Filter View
## 概念回顾
筛选视图是 sheet 内的多份独立筛选配置,每个视图持有自己的 `range``rules`,由独立 `view_id`10 位随机字符串)标识。一个 sheet 可有多个视图,视图的隐藏行仅在用户进入该视图时本地生效,不影响其他协作者,也不与该 sheet 上可能并存的筛选器filter互相影响。
`+filter-view-{create|update|delete}` 负责视图本身的 CRUDcreate / update / delete视图的"进入 / 退出"(激活态)是本地状态,不在工具语义内。
## 使用场景
读写筛选视图对象。本 reference 覆盖 4 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看已有筛选视图 | `+filter-view-list` | 获取 sheet 上所有视图(视图名、范围、规则) |
| 创建 / 更新 / 删除筛选视图 | `+filter-view-{create|update|delete}` | create / update / delete 三个独立 shortcut |
典型工作流:先读取现有视图了解配置 → 执行创建 / 更新 / 删除 → **必须再次读取验证结果**
**常见配置错误(必须注意)**
- **视图范围必须覆盖表头行**:视图的 range 必须从表头行开始(如 `A1:F100`),不能只包含数据行
- **更新前先读取**:用户说"调整这个视图"时,先用 `+filter-view-list` 拉到目标视图当前 rules**只改差异列**再回写
- **多次 create 不能复用 view_id**:复用应走 `update`,重复 `create` 会产生新视图
- **筛选不支持正则表达式**:飞书表格筛选器不支持正则表达式,传入正则会当成普通文本处理
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+filter-view-list` | read | 对象 |
| `+filter-view-create` | write | 对象 |
| `+filter-view-update` | write | 对象 |
| `+filter-view-delete` | high-risk-write | 对象 |
## Flags
### `+filter-view-list`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--view-id` | string | optional | 按筛选视图 reference_id 过滤(命中即只返回单个视图) |
### `+filter-view-create`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | required | 筛选视图规则 JSON`rules?`(列级筛选规则数组)和 `filtered_columns?``range``view_name` 是独立 flag |
| `--range` | string | required | 筛选视图作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段create 必填,必须覆盖表头行 |
| `--view-name` | string | optional | 筛选视图名称;不传时系统自动分配;优先级高于 `--properties` 中同名字段 |
### `+filter-view-update`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--view-id` | string | required | 目标筛选视图 reference_id |
| `--properties` | string + File + Stdin复合 JSON | required | 筛选视图规则 JSON`rules?``filtered_columns?`update 是整组覆盖式(先 `+filter-view-list` 回读再 patch传空 `rules: []` 清空)。`range``view_name` 是独立 flag |
| `--range` | string | optional | 筛选视图作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段update 时省略表示保留当前 range |
| `--view-name` | string | optional | 筛选视图名称create 不传时系统自动分配update 不传时保留原名;优先级高于 `--properties` 中同名字段 |
### `+filter-view-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--view-id` | string | required | 目标筛选视图 reference_id |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+filter-view-create` `--properties` / `+filter-view-update` `--properties`
_create / update 的视图属性_
**顶层字段**
- `view_name` (string?) — 可选 — ⚠️ 已拎为独立 flag `--view-name`,请勿在此 JSON 内重复填写(同名以独立 flag 为准)
- `range` (string?) — 视图作用的单元格范围A1 表示法) — ⚠️ 已拎为独立 flag `--range`,请勿在此 JSON 内重复填写(同名以独立 flag 为准)
- `rules` (array<object>?) — 列级筛选规则列表,每一项对应一个具体列的筛选条件 each: { column_index: string, conditions: array<oneOf>, filtered_rows?: array<number> }
- `filtered_columns` (array<string>?) — 可选
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR`view_id` 是 10 位随机字符串,每个 sheet 可有多个视图。
### `+filter-view-list`
```bash
# 列出某个 sheet 的全部筛选视图
lark-cli sheets +filter-view-list --url "..." --sheet-id "$SID"
# 按 view_id 精确定位
lark-cli sheets +filter-view-list --url "..." --sheet-id "$SID" --view-id vAbcde1234
```
### `+filter-view-create`
`--range`(必填)/ `--view-name`(可选)是独立 flag`rules``--properties`
```bash
lark-cli sheets +filter-view-create --url "..." --sheet-id "$SID" \
--view-name "活跃用户" --range "A1:F1000" \
--properties '{"rules":[{"column_index":"C","conditions":[{"type":"number","compare_type":"greaterThan","values":[100]}]}]}'
```
**`conditions[].type` × `compare_type` 取值**`type` 决定可用的 `compare_type`;两者均必填):
| `type` | 可用 `compare_type` | `values` |
|---|---|---|
| `text` | `contains` / `doesNotContain` / `beginsWith` / `doesNotBeginWith` / `endsWith` / `doesNotEndWith` / `equals` / `notEquals` | 字符串数组 |
| `number` | `equal` / `notEqual` / `greaterThan` / `greaterThanOrEqual` / `lessThan` / `lessThanOrEqual` / `between` / `notBetween` | 数值(或数值字符串)数组;`between` / `notBetween` 传两个边界 |
| `multiValue` | `equal` / `notEqual` | 字符串数组(精确匹配其中任一值) |
| `color` | `backgroundColor` / `foregroundColor` | 不传 `values`(按单元格颜色筛选) |
> ⚠️ `text` 用 `equals` / `notEquals`**带 s**`number` / `multiValue` 用 `equal` / `notEqual`**不带 s**)——别混。完整 schema 跑 `+filter-view-create --print-schema --flag-name properties`。
> `--range` **必须覆盖表头行**(如 `A1:F1000`),不能只包含数据行;`--view-name` 重名时服务端自动改名。
### `+filter-view-update`
> ⚠️ update 是整组覆盖PUT 语义):`--properties` **必传**,未在请求里出现的 rules / filtered_columns 会被清空。如要保留已有 rules先 `+filter-view-list` 读回再合并写回。`--range` 变更会丢弃已有筛选规则属预期行为rules 跟当前 range 绑定)。重复 `+filter-view-create` 不会复用 view_id会产生新视图。
### `+filter-view-delete`
> ⚠️ 删除**已存在**的视图不可逆;目标 view_id **不存在**时按幂等成功返回(不报错)。先 `--dry-run` 看 view_id 确认。
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+filter-view-create` 校验 `--range` 起始行为表头(第一行);`+filter-view-update` 必须先 `+filter-view-list` 确认 view 存在,`--properties` 必传(整组覆盖式);`+filter-view-delete` 强制 `--yes``--dry-run`
- `DryRun`:输出"将要 POST/PATCH/DELETE 的 view 请求模板",零网络副作用;`--sheet-name` 在 dry-run 输出里生成为 `<resolve:Sheet1>` 占位符。
- `Execute`:写后不自动回读;如需确认,自行调用 `+filter-view-list --view-id <new>` 比对当前 range + rules。

View File

@ -0,0 +1,130 @@
# Lark Sheet Filter
## 真对象硬约束 + 数量校验
1. **真对象**:当用户要求"筛选 / 只看 / 仅保留 X"时,**必须**通过 `+filter-{create|update|delete}` 创建真实的筛选器对象。**禁止**用"删除不符合条件的行" / "新建子表只放符合条件的行" / 用 `+cells-set` 覆盖原表来代替——这些做法会让原数据丢失或不可恢复。
2. **筛选数量必校**:执行筛选后**必须**回读,断言 `len(visible_rows) == expected_count``expected_count` 来自先用本地脚本在源数据上独立复现该筛选条件得到的结果数。两者不一致时禁止交付,需排查筛选条件 / 数据列类型问题。
3. **混合文本列禁止字面比较**:筛选 key 是公式文本(如 `1000+200=1200`)或带单位的混合文本时,先在辅助列里抽出纯数值再筛选;不能直接用文本比较。
## 使用场景
读写筛选器对象。本 reference 覆盖 4 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看已有筛选器 | `+filter-list` | 获取筛选器的范围、规则和条件配置 |
| 创建/更新/删除筛选器 | `+filter-{create|update|delete}` | 对筛选器执行写入操作 |
典型工作流:先读取现有筛选器了解配置 → 执行创建/更新/删除 → **必须再次读取验证结果**
**只读场景例外**:用户只是想知道哪些数据满足条件、并不要求修改表格展示时,可以走 `lark-sheets-read-data` 读后文本回答,不必创建筛选器。
**常见配置错误(必须注意)**
- **筛选范围必须覆盖表头行**:筛选器的 range 必须从表头行开始(如 `A1:F100`),不能只包含数据行。缺少表头会导致筛选条件无法正确匹配列
- **更新已有筛选器前先读取**:如果子表上已存在筛选器,直接创建会报错或覆盖原有配置。应先用 `+filter-list` 查看是否存在筛选器,存在时使用 update 而非 create
- **筛选条件的列索引要精确**:筛选条件中的列标识必须与实际数据列精确对应,不要凭猜测填写
- **”调整筛选逻辑”要先读旧配置**:用户说”调整筛选”时,先读取现有筛选器的完整配置,理解当前规则后再修改,不要从零创建
- **创建后必须验证**:调用 `+filter-list` 确认筛选器配置正确且生效
- **筛选不支持正则表达式**:飞书表格筛选器不支持正则表达式,传入正则会当成普通文本处理。
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+filter-list` | read | 对象 |
| `+filter-create` | write | 对象 |
| `+filter-update` | write | 对象 |
| `+filter-delete` | high-risk-write | 对象 |
## Flags
### `+filter-list`
_公共四件套 · 系统:`--dry-run`_
_仅含公共 / 系统 flag。_
### `+filter-create`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 筛选范围A1 表示法,含表头行,如 `A1:F1000`);不要重复写入 `--properties` 中的 range 字段 |
| `--properties` | string + File + Stdin复合 JSON | optional | 筛选规则 JSON`rules`(列级筛选规则数组)+ `filtered_columns?`(激活列索引提示)。`--properties` 整体可选——传它时 `rules` 不可为空;不传则只在 `--range` 上建立空筛选器(无列条件)。`range` 是独立 flag不要再放此 JSON 里) |
### `+filter-update`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | required | 筛选规则 JSON`rules``filtered_columns?`update 是整组覆盖式(传空 `rules: []` 清空)。`range` 已拎为独立 flag |
| `--range` | string | required | 筛选作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段 |
### `+filter-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
_仅含公共 / 系统 flag。_
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+filter-create` `--properties` / `+filter-update` `--properties`
_创建/更新的筛选器属性_
**顶层字段**
- `range` (string) — 筛选对象作用的单元格范围A1 表示法) — ⚠️ 已拎为独立 flag `--range`,请勿在此 JSON 内重复填写(同名以独立 flag 为准)
- `rules` (array<object>) — 列级筛选规则列表,每一项对应一个具体列的筛选条件 each: { column_index: string, conditions: array<oneOf>, filtered_rows?: array<number> }
- `filtered_columns` (array<string>?) — 可选
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR`filter_id` 等同于 `sheet_id`(每个工作表至多一个筛选器)。
### `+filter-list`
```bash
# 查看当前 sheet 的筛选器配置filter_id 等于 sheet_id
lark-cli sheets +filter-list --url "..." --sheet-id "$SID"
```
### `+filter-create`
`--range` 是独立 flag含表头行`rules``--properties`
```bash
lark-cli sheets +filter-create --url "..." --sheet-id "$SID" \
--range "A1:F1000" \
--properties '{"rules":[{"column_index":"B","conditions":[{"type":"multiValue","compare_type":"equal","values":["北京","上海"]}]}]}'
```
**`conditions[].type` × `compare_type` 取值**`type` 决定可用的 `compare_type`;两者均必填):
| `type` | 可用 `compare_type` | `values` |
|---|---|---|
| `text` | `contains` / `doesNotContain` / `beginsWith` / `doesNotBeginWith` / `endsWith` / `doesNotEndWith` / `equals` / `notEquals` | 字符串数组 |
| `number` | `equal` / `notEqual` / `greaterThan` / `greaterThanOrEqual` / `lessThan` / `lessThanOrEqual` / `between` / `notBetween` | 数值(或数值字符串)数组;`between` / `notBetween` 传两个边界 |
| `multiValue` | `equal` / `notEqual` | 字符串数组(精确匹配其中任一值) |
| `color` | `backgroundColor` / `foregroundColor` | 不传 `values`(按单元格颜色筛选) |
> ⚠️ `text` 用 `equals` / `notEquals`**带 s**`number` / `multiValue` 用 `equal` / `notEqual`**不带 s**)——别混。完整 schema 跑 `+filter-create --print-schema --flag-name properties`。
### `+filter-update`
> ⚠️ update 是覆盖式:`--properties` 中传新 `rules` 会替换旧组。如只想加一条,要带上已有的全部条件再追加。必填 `--range`。
### `+filter-delete`
```bash
lark-cli sheets +filter-delete --url "..." --sheet-id "$SID" --yes
```
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+filter-create` 校验 `--range` 至少 2 行(表头 + 至少 1 行数据);`+filter-update` 必须先 `+filter-list` 确认目标存在;`+filter-delete` 强制 `--yes``--dry-run`
- `DryRun`:输出"将要 POST/PATCH/DELETE 的 filter 请求模板"。
- `Execute`:写后不自动回读;如需确认,自行调用 `+filter-list` 查看当前筛选条件 + 已过滤行数。

View File

@ -0,0 +1,159 @@
# Lark Sheet Float Image
> **选浮动图还是单元格图?只看一条**:这张图是不是**属于某条记录、要随那行一起排序 / 筛选 / 增删**
> - **是 → 单元格图片**(不在本 reference嵌进单元格、随行走。用 `+cells-set-image`(或 `+cells-set` 的 `rich_text` + `type: "embed-image"`,见 lark-sheets-write-cells。典型凭证 / 证件照 / 商品图 / 头像 / 二维码 / 每行配图;话里带「对应 / 每行 / 每条 / 这列」等绑定词即属此类。
> - **否 → 浮动图片**(本 reference自由摆放、不绑数据的装饰 / 标识logo / 水印 / 封面大图 / banner
> - ⚠️ 别凭"浮动图位置尺寸更好控制 / 更熟"就选它——那是按操作便利选,不是按场景选;用浮动图承载"对应某记录"的图会在增删行 / 排序后错位。
## 真对象硬约束
当用户要求"插入图片 / 添加 logo / 放一张图"时,**必须**通过 `+float-image-{create|update|delete}`(浮动图片)或 `+cells-set-image` / `+cells-set``embed-image`(单元格图片)创建真实的图片对象。**禁止**只在文本回复中给出图片链接 / 描述图片内容代替插入。判断标准:交付后 `+float-image-list` 或单元格 `rich_text` 必须能读到该图片对象。
## 使用场景
读写**浮动图片**对象(悬浮在单元格上方的图片,不属于单元格内容)。本 reference 覆盖 4 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看已有浮动图片 | `+float-image-list` | 获取浮动图片的位置、大小和层级配置 |
| 创建/更新/删除浮动图片 | `+float-image-{create|update|delete}` | 对浮动图片执行写入操作 |
典型工作流:先读取现有浮动图片了解配置 → 执行创建/更新/删除 → **必须再次读取验证结果**
**常见配置错误(必须注意)**
- **单元格图片 vs 浮动图片选择错误(最易选错)**:图与某条记录一一对应、要随行排序 / 筛选 / 增删时,应走 `+cells-set-image`(见顶部判别),用浮动图会错位。
- **图片位置参数要精确**:锚点单元格的行列索引和偏移量决定了图片位置,设置不当会导致图片遮挡数据
- **创建后必须验证**:调用 `+float-image-list` 确认图片位置和大小正确
图片来源有三种方式,`+float-image-create` 上三者 **XOR、必给其一**`--image` / `--image-token` / `--image-uri`
- **`--image <本地路径>`(首选,最省事)**直接给本地图片文件路径PNG/JPEG/GIF/BMP/HEIC 等。CLI 会自动把它以 `parent_type=sheet_image` 上传,拿到 file_token 后创建浮动图,**不用你手动上传 / 取 token**。路径规则同其它本地文件 flag必须是当前工作目录内的相对路径绝对路径会被 Validate 拒,`--dry-run` 也会拦)。
- `--image-token`:复用**已存在**的图片 file_token。常见来源`+float-image-list` 返回的 `image_token`(适合"换皮不换位置"复用同一张图);② `+cells-set-image` 成功返回里的 `file_token`(它也是 `sheet_image` 上传句柄)。适合"同一张图复用到多处",省去重复上传。
- `--image-uri`:图片 URI上传链路返回的句柄**非**表内对象 reference_id由系统自动转 file_token。
> ⚠️ **`--image` 仅 `+float-image-create` 支持**。`+float-image-update` 换图仍只接受 `--image-token` / `--image-uri`,而且**图片源是 update 唯一可省的部分**——三者全不传则保留原图。但 `--image-name` / `--position-{row,col}` / `--size-{width,height}` 在 update 时和 create 一样**必填**`+float-image-update` 强制要求这套核心字段,且 `+float-image-list` 不回传 `image_name` 供 CLI 回填)。要在 update 里换一张本地新图,先用 `+cells-set-image` 上传到任意临时单元格、从返回取 `file_token`,再把它传给 update 的 `--image-token`;用完清除该临时单元格,避免残留多余图片。
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+float-image-list` | read | 对象 |
| `+float-image-create` | write | 对象 |
| `+float-image-update` | write | 对象 |
| `+float-image-delete` | high-risk-write | 对象 |
## Flags
### `+float-image-list`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--float-image-id` | string | optional | 按 id 过滤;省略时列工作表全部 |
### `+float-image-create`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--image-name` | string | required | 图片名称,含扩展名(如 `logo.png` |
| `--image-token` | string | xor | 图片 file_token`--image-uri` 二选一)。常见来源:`+float-image-list` 返回的 `image_token` |
| `--image-uri` | string | xor | 图片 URI上传链路返回的句柄非表内对象 reference_id`--image-token` 二选一);系统自动转换为 file_token |
| `--position-row` | int | required | 图片左上角所在行0-based |
| `--position-col` | string | required | 图片左上角所在列(列字母,如 `A` / `B` |
| `--size-width` | int | required | 图片宽度(像素) |
| `--size-height` | int | required | 图片高度(像素) |
| `--offset-row` | int | optional | 在 `--position-row` 基础上的行内偏移(像素) |
| `--offset-col` | int | optional | 在 `--position-col` 基础上的列内偏移(像素) |
| `--z-index` | int | optional | 图片 Z 轴层级,控制重叠顺序 |
| `--image` | string | xor | 本地图片路径PNG/JPEG 等CLI 自动上传为 sheet_image 并用返回的 file_token省去手动拿 token与 --image-token / --image-uri 三选一) |
### `+float-image-update`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--float-image-id` | string | required | 目标图片 id |
| `--image-name` | string | required | 图片名称,含扩展名(如 `logo.png` |
| `--image-token` | string | optional | 可选图片 file_token`--image-uri` 互斥,二者均省略时保留原图。常见来源:`+float-image-list` 返回的 `image_token` |
| `--image-uri` | string | optional | 可选图片 URI上传链路返回的句柄非表内对象 reference_id`--image-token` 互斥,二者均省略时保留原图;系统自动转换为 file_token |
| `--position-row` | int | required | 图片左上角所在行0-based |
| `--position-col` | string | required | 图片左上角所在列(列字母,如 `A` / `B` |
| `--size-width` | int | required | 图片宽度(像素) |
| `--size-height` | int | required | 图片高度(像素) |
| `--offset-row` | int | optional | 在 `--position-row` 基础上的行内偏移(像素) |
| `--offset-col` | int | optional | 在 `--position-col` 基础上的列内偏移(像素) |
| `--z-index` | int | optional | 图片 Z 轴层级,控制重叠顺序 |
### `+float-image-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--float-image-id` | string | required | 目标图片 id |
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR。浮动图片是 sheet 级对象——和单元格内嵌图片不同(后者走 `+cells-set`)。
### `+float-image-list`
```bash
lark-cli sheets +float-image-list --url "..." --sheet-id "$SID"
```
### `+float-image-create`
所有字段拍平为独立 flag图片来源 `--image` / `--image-token` / `--image-uri`(三选一 XOR/ `--image-name` / `--position-{row,col}` / `--size-{width,height}` / `--offset-{row,col}` / `--z-index`
```bash
# 首选直接给本地图片路径CLI 自动上传(无需手动拿 token
# 注意:--image-name 是 required即使路径 basename 已经是 logo.png 也要显式传)
lark-cli sheets +float-image-create --url "..." --sheet-id "$SID" \
--image ./logo.png --image-name "logo.png" \
--position-row 2 --position-col B --size-width 300 --size-height 200 --z-index 1
# 用已有 file_token从 +float-image-list 的 image_token 或 +cells-set-image 返回的 file_token
lark-cli sheets +float-image-create --url "..." --sheet-id "$SID" \
--image-name "logo.png" --image-token "$TOKEN" \
--position-row 0 --position-col A --size-width 200 --size-height 150
# 用 image URI上传链路返回的句柄非表内对象 reference_id与 --image-token 二选一)
lark-cli sheets +float-image-create --url "..." --sheet-id "$SID" \
--image-name "logo.png" --image-uri "$IMAGE_URI" \
--position-row 2 --position-col B --size-width 300 --size-height 200 --z-index 1
```
### `+float-image-update`
> **update ≈ create只有图片源可省**`+float-image-update` 的 update 要求和 create 相同的核心字段——`--image-name`、`--position-{row,col}`、`--size-{width,height}` **全部必填**;唯一区别是**图片源(`--image-token` / `--image-uri`)可以全省**,省略即保留原图。这**不是**"只发改动字段"的 patch缺任一核心字段会被拒绝`+float-image-list` 不回传 `image_name`CLI 无法替你回填)。
>
> 推荐流程:先 `+float-image-list --float-image-id <id>` 回读当前 position / size再带上 `--image-name` 和完整的 position / size 调一次 `+float-image-update`。
```bash
# 调整位置 + 尺寸,保留原图(不传图片源)
lark-cli sheets +float-image-update --url "..." --sheet-id "$SID" \
--float-image-id "$IMG_ID" --image-name "logo.png" \
--position-row 5 --position-col C --size-width 300 --size-height 200
# 换图:额外带 --image-token核心字段同样要给全
lark-cli sheets +float-image-update --url "..." --sheet-id "$SID" \
--float-image-id "$IMG_ID" --image-name "new-logo.png" --image-token "$NEW_TOKEN" \
--position-row 5 --position-col C --size-width 300 --size-height 200
```
### `+float-image-delete`
```bash
lark-cli sheets +float-image-delete --url "..." --sheet-id "$SID" --float-image-id "$IMG_ID" --yes
```
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+float-image-create` 要求 `--image` / `--image-token` / `--image-uri` **恰好给一个**`--position-row/col``--size-width/height` 必填且为合法整数;传 `--image` 时还会校验路径安全(绝对路径 / 越出工作目录会被拒,`--dry-run` 同样拦)。`+float-image-update` 必须 `--float-image-id`,并和 create 一样必填 `--image-name` / `--position-{row,col}` / `--size-{width,height}`(缺任一核心字段本地直接报错,不会静默发 0图片源 `--image-token` / `--image-uri` 可省(省略保留原图),给则二选一;`+float-image-delete` 强制 `--yes``--dry-run`
- `DryRun`:写操作输出"将要 POST/PATCH/DELETE 的 float_image 请求模板";传 `--image` 时会多打印一步本地图片上传(`POST /open-apis/drive/v1/medias/upload_all``parent_type=sheet_image`)。
- `Execute`:写后不自动回读;如需确认,自行调用 `+float-image-list --float-image-id <id>` 比对新位置 / 尺寸。

View File

@ -0,0 +1,276 @@
# 飞书表格公式生成规则
> **本文定位**:飞书公式正确性的**唯一权威**——书写任何飞书公式、或把 Excel 公式迁移到飞书前,先读本文。涵盖公式书写约定(绝对引用、范围语法)、投影 vs spill、ARRAYFORMULA / 数组语义、高风险引用函数、日期差、不支持函数清单。
> **边界**:本文只讲"公式怎么写对";公式**怎么写入表格**`+cells-set` / 模板单元格 + `--copy-to-range` / 容错回读)见 `lark-sheets-write-cells`。**公式写入完成后的强制收尾**见 `lark-sheets-formula-verify`:不要把"翻译对了"误当成"已经交付完成"。本文不含 shortcut通用编辑准则见主 SKILL.md「飞书表格编辑准则」。
**核心原则:飞书不像 Excel 365 那样默认 spill溢出展开。飞书普通公式遇到区域时默认"投影"(只取当前行/列对应的单个值),必须显式使用 `ARRAYFORMULA` 或原生数组函数才能逐项展开。**
## 公式书写约定(写任何公式都先满足)
- **绝对引用 `$`**:向下 / 向右填充前判断哪些引用要锁定——用户指定的固定 cell`$C$3`)、要固定的数据范围(`$A$2:$B$5`)、锁列不锁行(`$A2`)、锁行不锁列(`B$1`)。填充前检查是否需固定汇率 / 税率 / 查找表 / 权重表,以及同列 / 同行公式结构是否一致。
- **公式字符串用飞书范围语法**:写 `H:H``A2:B5`**禁止** `H2:H` / `2:2`。要在公式里引用整行,用显式范围(如 `$A2:$Z2`)替代禁用的 `2:2`。这与 CLI 工具参数(如 `--range` / `--copy-to-range`)的 A1 表示法写法不同:参数侧合法的 `D3:D``1:1``3:6` 在公式串里反而非法。**公式串 ≠ CLI 参数**,两套规则别互相照搬,混用会导致调用失败或公式报错。
## 翻译后必做:代码复现校验
公式语法翻译完之后,**必须**用本地脚本在源数据上独立复现一份"等价计算结果"再写入。流程:
1. **挑 3-5 个代表性输入行**(首行 / 中段 / 末行 / 含空值 / 含异常格式各一)
2. **用 Python 复现 Excel 原公式的语义**(不是飞书译文的语义,而是用户原本想要的结果)
3. **写入飞书译文公式后回读这几行的实际值**
4. **三方对照**`Excel 原公式语义 == Python 复现 == 飞书译文回读值`,全部一致才交付;不一致先排查(数组语义?日期差?范围引用?)
**理由**Excel→飞书的语法翻译很容易在 spill / 数组 / 日期差 / 范围引用上出现等价性偏差,仅靠语法转换通过不足以保证业务结果正确。
## 落表后的默认交接
本文解决的是"公式怎么写对",不是"写进表里后一定能零错误运行"。因此:
1. 按本文完成公式改写后,用 `lark-sheets-write-cells` / `lark-sheets-batch-update` 把公式真实写入表格。
2. 公式一旦落表,就默认进入 `lark-sheets-formula-verify` 的收尾阶段。
3. 最终必须跑 `+formula-verify` 收敛到 `status='success'``errors_found` / `partial` 都不算完成。
## 决策流程
1. 最终结果是**标量**(单值)→ 通常不需要 `ARRAYFORMULA`
2. 最终结果是**一维或二维数组**
- 公式中**包含**飞书原生数组函数(如 FILTER、XLOOKUP、MAP 等)→ 无需加 `ARRAYFORMULA`,数组语义会自动传播到整个公式,包括原生数组函数外层接的标量运算(如 `+1``*100`
- 公式中**不包含**任何原生数组函数,但在对区域做标量计算 → 加 `ARRAYFORMULA(<整个表达式>)`
3. Excel 依赖 `ROW(range)` 逐项驱动 `SUBTOTAL/INDIRECT/OFFSET` → 改用 `MAP(ARRAYFORMULA(ROW(...)), LAMBDA(r, ...))`
4. 内层 `INDEX/INDIRECT/OFFSET` 返回范围,外层 `SUMIF/COUNTIF/SUMIFS` 还要继续吃这些范围 → 改用 `MAP(..., LAMBDA(...))``REDUCE(..., LAMBDA(...))`
5. 公式意图是"对多个区域分别计算再汇总"(例如用 INDIRECT/OFFSET 对每行生成一个范围,再对所有范围聚合)→ 飞书不能直接返回"区域的列表",必须明确降维:用 `VSTACK` 垂直合并、`HSTACK` 水平合并、`TOCOL/TOROW` 展平,或 `REDUCE` 归约成标量
6. 算日期差 → 不要写 `DAY(end-start)`,用 `DAYS``DATEDIF` 或直接 `end-start`
## 飞书的投影行为(不是默认 spill
飞书普通公式对引用区域默认"投影"而不是"spill"
- 单列区域 → 按当前公式所在行取值
- 单行区域 → 按当前公式所在列取值
- 二维区域 → 只有当前公式位置能映射到该区域时才取值,否则报错
- 数组常量 `{...}` 或函数返回矩阵,在普通标量上下文里通常只取左上角
因此:
- `=A1:A2` 在飞书普通公式里不会 spill只会投影到当前行
- `=ABS(A2:B2)` 不会得到一整行,要写 `=ARRAYFORMULA(ABS(A2:B2))`
- `=TRUNC({1.1111,2.222},{1,2})` 要得到一整行,写 `=ARRAYFORMULA(TRUNC({1.1111,2.222},{1,2}))`
## ARRAYFORMULA 使用规则
**前提:以下规则适用于公式中没有任何原生数组函数的情况。** 若公式中已有原生数组函数(如 FILTER、XLOOKUP、MAP 等),数组语义会自动传播到整个公式的求值过程,后续标量运算无需额外包 `ARRAYFORMULA`(见下一节)。
需要加 `ARRAYFORMULA` 的典型场景(公式中无原生数组函数时):
- 算术运算:`+ - * / ^ %`
- 比较运算:`= <> > >= < <=`
- 标量数学函数:`ABS ROUND INT TRUNC MOD LOG LN SQRT SIN COS TAN ...`
- 文本函数:`LEN LEFT RIGHT MID UPPER LOWER TRIM TEXT VALUE ...`
- 日期函数:`YEAR MONTH DAY DATE TIME EDATE EOMONTH ...`
- 条件函数:`IF IFS IFERROR IFNA NOT ISNUMBER ISTEXT ISBLANK ...`
- 引用函数(高风险):`INDEX OFFSET COLUMN ROW MATCH`
### 公式中有原生数组函数时,整个公式已进入数组模式
飞书的数组语义会在整个公式求值过程中累积传播:一旦某个原生数组函数运行,后续所有运算符和函数也会自动逐元素处理,无论它们出现在哪一层。
因此,以下写法**无需**额外包 `ARRAYFORMULA`
- `=FILTER(A2:A10,B2:B10="x")+1`
- `=XLOOKUP(E2:E10,A2:A10,B2:B10)*100`
- `=ABS(FILTER(A2:A10,B2:B10>0))`
- `=MAP(A2:A10,LAMBDA(x,x*2))-1`
对比:**没有原生数组函数**时必须加:
- `=A2:A100*B2:B100``=ARRAYFORMULA(A2:A100*B2:B100)`
- `=IF(A2:A100>0,B2:B100,"")``=ARRAYFORMULA(IF(A2:A100>0,B2:B100,""))`
## 飞书原生数组函数清单
以下函数按数组语义工作,通常**不需要额外包 `ARRAYFORMULA`**
`ARRAYFORMULA` `ARRAY_CONSTRAIN` `BYCOL` `BYROW` `CELL` `CHOOSECOLS` `CHOOSEROWS` `DROP` `EXPAND` `FILTER` `FLATTEN` `FREQUENCY` `GROWTH` `HSTACK` `IMPORTDATA` `IMPORTFEED` `IMPORTHTML` `IMPORTRANGE` `IMPORTXML` `LINEST` `LOGEST` `LOOKUP` `MAKEARRAY` `MAP` `MINVERSE` `MMULT` `MUNIT` `QUERY` `RANDARRAY` `REDUCE` `REGEXEXTRACT` `SCAN` `SEQUENCE` `SORT` `SORTBY` `SORTN` `SPLIT` `SUMPRODUCT` `SWITCH` `TAKE` `TEXTSPLIT` `TOCOL` `TOROW` `TRANSPOSE` `TREND` `UNIQUE` `VSTACK` `WRAPCOLS` `WRAPROWS` `XLOOKUP`
> **注意:`SWITCH` 在飞书里被当作原生数组函数处理,这与 Excel 行为不同,不需要额外包 `ARRAYFORMULA`。**
## IMPORTRANGE 跨工作簿引用限制
`IMPORTRANGE` 跨电子表格引用数据时有两条硬上限:
- **嵌套最多 5 层**:被引用的表里若又用 `IMPORTRANGE` 继续引下一张表,整条引用链最多 5 层。
- **每个工作表最多 100 个 `IMPORTRANGE` 引用**。
超限会让引用失效或报错。设计大量跨表汇总前先估算引用数,必要时先把数据落地到本表再计算。
## INDEX / OFFSET / COLUMN / ROW / MATCH 是高风险函数
这组函数容易让人误以为会自动把多值铺开,但在飞书里不能这样假设。
**高风险信号:**
- 行号 / 列号 / 偏移量本身是数组
- 结果本来应该是一行或一块二维区域
- 外层还有算术、比较、`IF` 等继续处理它
更稳的写法:
- `=ARRAYFORMULA(INDEX(...))`
- `=ARRAYFORMULA(OFFSET(...))`
- `=ARRAYFORMULA(COLUMN(...))`
- `=ARRAYFORMULA(ROW(...))`
**例外:** 如果返回值只是立刻交给聚合函数消费,不需要额外包:
- `=SUM(INDEX(A1:B2,0,1))`
## Excel 隐式逐项求值,飞书里要显式写 MAP
**典型特征:**
- 外层是 `SUMPRODUCT``SUM` 等聚合
- 内层用了 `SUBTOTAL``INDIRECT``OFFSET` 等更偏"单值/单引用"的函数
- Excel 会把中间结果逐项带进去算
- 飞书里直接照抄,往往不能得到同样的逐项语义
同类本质也包括:`INDEX/INDIRECT/OFFSET` 先返回范围,外层再把这些范围交给 `SUMIF``COUNTIF``AVERAGEIF``SUMIFS` 等范围感知函数 —— 飞书里这些外层函数不会自动二次展开内层范围。
这时不要只会补 `ARRAYFORMULA`,要显式写"遍历"。最常用模板:
```excel
=SUMPRODUCT(
MAP(
ARRAYFORMULA(ROW(目标范围)),
LAMBDA(r, 单行计算逻辑)
)
)
```
同类场景也优先考虑 `MAP`
- `INDIRECT("A"&ROW(...))`
- `OFFSET(...,ROW(...)-ROW(...),...)`
- `SUBTOTAL(...)`
- `SUMIF(内层返回范围, ...)`
- `COUNTIF(内层返回范围, ...)`
- `SUMIFS(内层返回范围, ...)`
- 任何"希望对每一行 / 每一列各算一次"的模式
## 多层范围结果与三维以上结果
飞书公式结果只能是二维区域,不能是"数组的数组"。
### 多层范围不能自动二次展开
内层 `INDEX/INDIRECT/OFFSET` 返回的是二维范围,外层还想继续对这些范围做范围计算时,不要假设飞书会"再展开一层"。改用:
- `MAP(..., LAMBDA(...))` 显式逐项算
- `REDUCE(..., LAMBDA(...))` 显式累加/归约
### 真正的三维或更高维结果不能直接返回
典型触发场景:想把多个不同区域或不同条件的结果合并展示,例如:
- 对 A 列、B 列、C 列分别做 FILTER想把三列结果并排展示
- 对多个月份分别生成数据行,想把所有月份上下堆叠展示
飞书无法直接返回"多个区域的集合",必须先决定降维方式:
- 上下堆叠:`=VSTACK(slice1, slice2, slice3)`
- 左右拼接:`=HSTACK(slice1, slice2, slice3)`
- 压成单列:`=TOCOL(...)`
- 压成单行:`=TOROW(...)`
- 只保留聚合值:`=REDUCE(slice1, {slice2,slice3}, LAMBDA(acc,x,acc+x))`
不要替用户"偷定"第三维展示方式;如果用户没有明确说明怎么展示,至少先把结果改写成可见的二维形状。
## 不能机械照抄的 Excel 语法
### `@` 隐式交叉
Excel`=@A1:A10`(强制单值,取当前行对应的值)
飞书没有 `@` 运算符。飞书普通公式对引用区域默认就有投影语义,去掉 `@` 即可:
- Excel: `=@A1:A10`
- 飞书: `=A1:A10`
### `#` spill range
Excel`=A1#`(引用 A1 公式溢出的整片区域)
飞书没有此语法,迁移方式:
- spill 区域已知 → 改成明确范围
- spill 区域未知 → 回到源公式重写,或用 `TAKE` / `DROP` / `ARRAY_CONSTRAIN`
### 结构化引用
Excel`=SUM(Table1[Amount])`
飞书不支持结构化引用,改成显式 A1 区域:`=SUM(A2:A100)`
### 老式 CSE 花括号
Excel`{=A1:A10*B1:B10}`Ctrl+Shift+Enter 输入)
飞书改为:`=ARRAYFORMULA(A1:A10*B1:B10)`
## 日期序列与日期差
飞书日期序列:`0 = 1899-12-30``1 = 1899-12-31`,没有 Excel 的 1900 年闰年兼容问题。
**错误写法(不要用):**
- `=DAY(B2-A2)` ✗ — 差值会被当成日期序列号再拆字段
- `=MONTH(B2-A2)`
- `=YEAR(B2-A2)`
**正确写法:**
- 天数差:`=DAYS(B2,A2)``=DATEDIF(A2,B2,"D")``=B2-A2`
- 月份差:`=DATEDIF(A2,B2,"M")`
- 年份差:`=DATEDIF(A2,B2,"Y")`
- 工作日差:`=NETWORKDAYS(A2,B2)`
## 飞书不支持的函数
> 本段是"飞书不支持函数"的**唯一权威清单**。以下函数在飞书里不存在或被禁用,禁止主动使用;用户明确要求时应拒绝并提供替代方案:
- `STOCKHISTORY` — 实时股票数据,飞书无等价函数,需手动导入数据
- `WEBSERVICE` — 外部 HTTP 请求,飞书无等价函数
- CUBE 系列(`CUBEVALUE``CUBEMEMBER``CUBESET``CUBERANK` 等)— OLAP cube 函数,飞书不支持
- `GOOGLEFINANCE``GOOGLETRANSLATE` 等 Google 特有函数 — 无等价函数
- `FORECAST.ETS` 系列(`FORECAST.ETS``FORECAST.ETS.STAT` 等)— 飞书不支持
- `INFO``RTD` — 系统信息 / 实时数据函数,飞书不支持
- `PIVOT` — 用 `+pivot-{create|update|delete}` 透视表对象替代
- `AMORDEGRC``PHONETIC``DETECTLANGUAGE` — 飞书不支持
- `LET`、命名自定义函数(名称管理器里定义的 LAMBDA、独立调用的 `LAMBDA`(如 `=LAMBDA(x,x+1)(5)`)— 会报 `#NAME?`;改用嵌套 IF / 辅助列。**例外**`LAMBDA` 作为 `MAP` / `REDUCE` / `BYROW` / `BYCOL` / `SCAN` / `MAKEARRAY` 的内联参数时**支持**(见上方「飞书原生数组函数清单」)
## 代表性改写示例
- 基础逐项计算
- Excel: `=A2:A100*B2:B100`
- 飞书: `=ARRAYFORMULA(A2:A100*B2:B100)`
- 条件判断
- Excel: `=IF(A2:A100>0,B2:B100,"")`
- 飞书: `=ARRAYFORMULA(IF(A2:A100>0,B2:B100,""))`
- 原生数组函数(无需改动)
- Excel: `=FILTER(A2:C100,B2:B100="East")`
- 飞书: `=FILTER(A2:C100,B2:B100="East")`
- 原生数组函数 + 标量运算(无需改动,数组语义自动传播)
- Excel: `=XLOOKUP(E2:E10,A2:A10,B2:B10)*100`
- 飞书: `=XLOOKUP(E2:E10,A2:A10,B2:B10)*100`
- 高风险引用函数
- Excel: `=INDEX(A1:D2,{2,1},0)`
- 飞书: `=ARRAYFORMULA(INDEX(A1:D2,{2,1},0))`
- 日期差
- 错误: `=DAY(B2-A2)`
- 推荐: `=DAYS(B2,A2)``=DATEDIF(A2,B2,"D")``=B2-A2`
- Excel 隐式逐项求值
- Excel: `=SUMPRODUCT(SUBTOTAL(103,INDIRECT("E"&ROW($E$16:$E$387))))`
- 飞书: `=SUMPRODUCT(MAP(ARRAYFORMULA(ROW($E$16:$E$387)),LAMBDA(row,SUBTOTAL(103,INDIRECT("E"&row)))))`
- 多层范围 / 二次展开
- 错误思路: `=SUMIF(INDIRECT("E"&ROW($E$16:$E$387)),">0")`
- 飞书: `=MAP(ARRAYFORMULA(ROW($E$16:$E$387)),LAMBDA(r,SUMIF(INDIRECT("E"&r),">0")))`
- 三维降二维(保留所有层)
- 飞书: `=VSTACK(slice1,slice2,slice3)``=HSTACK(slice1,slice2,slice3)`
- 三维降二维(只保留聚合值)
- 飞书: `=REDUCE(slice1,{slice2,slice3},LAMBDA(acc,x,acc+x))`

View File

@ -0,0 +1,77 @@
# Lark Sheet Formula Verify+formula-verify
> **本文定位**:飞书表格"公式写入后是否真的零错误"的自检入口,也是所有写公式任务的**强制收尾步骤**。公式的书写规则与 Excel→飞书迁移的语义规则一律以 `lark-sheets-formula-translation` 为唯一权威,本文不重复;本文聚焦"写完了之后怎么用一次调用确认 zero-error"。
>
> **边界**:本文不讲公式怎么写(去 `lark-sheets-formula-translation`),也不讲公式怎么写入表格(去 `lark-sheets-write-cells` / `lark-sheets-batch-update`)。本文只讲一件事:**只要任务里发生了公式落表、批量填充公式、`--copy-to-range` 扩展公式、导入含公式 workbook收尾就必须用 `+formula-verify` 自检到 zero-error 才能交付**。
## 为什么需要自检
飞书在线表格已经实时算好结果,但"算出来"和"算对了"是两件事。常见缺口:
- 公式编译失败 → 单元格落成文本(写入类 shortcut 返回的 `formula_errors[]` 是**编译失败**信号)。
- 公式编译成功但**运行时错误**`#REF!` / `#DIV/0!` / `#VALUE!` / `#NAME?` / `#NULL!` / `#NUM!` / `#N/A`——这一类只看 `formula_errors[]` 看不到,必须扫单元格值。
`+formula-verify` 把两路信号合并成一份统一 JSON一次调用聚合全表错误清单 + 编译失败清单 + 每类错误的定位与样本AI 一眼就能定位修复,链路也能据 `status` 强制收敛到 `success`
## 调用契约
最小调用形态:
| 入参 | 含义 |
|---|---|
| `--url` / `--spreadsheet-token` | 表格定位XOR 二选一,必填) |
| `--sheet-id` / `--sheet-name` | 限定子表mutually exclusive省略则扫全部可见子表 |
| `--range` | 限定 A1 范围;省略则用各 sheet 的 `current_region` |
| `--max-locations` | 每类错误样本上限,默认 20 |
| `--exit-on-error` | `status='errors_found'` 时返回非 0 退出码CI 网关用) |
返回核心字段:
- `status``success` / `errors_found` / `partial`——**唯一可机读的健康度判据**。
- `total_errors` / `total_formulas` / `scanned_cells`——本次扫描规模指标。
- `has_more`——为 true 表示扫描被内部上限截断(详见后文「截断与续读」),未覆盖完整范围。
- `error_summary[<错误类型>]`——每类错误的 `count` / `locations[]` / `samples[].{address,formula,depends_on}`
- `compile_errors[]`——合并最近一次写入留下的编译失败清单,与运行时错误并存时同时出现。
- `warning_message`——仅在 `has_more=true` 时出现,告知调用方需要缩小 `--range` / 拆 `--sheet-id` 续读。
## 写入收尾收敛规则
任何批量公式 / 含公式列写入完成后调用 `+formula-verify` 直到 `status='success'` 才能交付。不要等用户显式说"校验一下公式"才想到这里;**只要任务动作包含写公式,这一步默认就该做**。触发场景:
- `+cells-set` / `+csv-put`
- `+cells-set --copy-to-range` / 模板单元格向整列或整块扩展公式
- `+workbook-import`
- `+batch-update` 中含写入子操作
- `+table-put`(任意列含公式时)
- `+workbook-import`(导入的 xlsx 含公式时)
收敛规则:
1. `status='success'` → 通过;可以把链路标完成。
2. `status='partial'` → 扫描被内部上限截断。先缩小 `--range` 或拆 `--sheet-id` 续扫,**不允许**把 `partial` 当作 `success`
3. `status='errors_found'``compile_errors[]` 非空 → **先解决编译失败**:根据 `compile_errors[].reason` 修正公式语法(飞书函数名 / 范围语法 / 引用样式),用 `+cells-set` 重写后再调一次 `+formula-verify`
4. `status='errors_found'` 且只剩运行时错误 → 按 `error_summary``samples[].formula` + `depends_on` 排查根因(零除?空值参与运算?引用越界?日期差写法?数组语义?),修复后重新自检。
5. 同一处错误连续修复 3 次仍未通过 → 改用 `IFERROR` 包裹兜底,或退回纯值写入;不要在 `errors_found` 状态下扩展 `+cells-set --copy-to-range`、追加批量写入。
注意:
-`status='errors_found'` 的状态下调用 `+cells-set --copy-to-range` 继续扩展会把错误复制放大。
- "编译失败但运行时无报错"不是 zero-error编译失败的单元格此刻是文本不是公式源数据一变就再也算不出值
- 跳过自检直接交付、靠肉眼读首末 5 行确认是不可靠的——表中段、隐藏行、合并区里的错误这样根本看不到。
## 截断与续读
后端有一个内部硬上限对总扫描单元格数做截断(不暴露给调用方),超过后立即返回 `has_more=true` + `warning_message``error_summary` / `compile_errors` 仅覆盖已扫描部分。处理路径:
- 把工作簿按 `--sheet-id` / `--sheet-name` 拆成多次调用。
- 同 sheet 内按 `--range` 切片(如先 `A1:Z200``AA1:AZ200`),逐块自检。
- 每块都跑到 `has_more=false``status='success'` 才算通过。
## 常见陷阱
| 坑 | 应对 |
|---|---|
| 错误字符串本地化 | 后端按内部 `error_kind` / `compute_status` 字段识别错误类别,不走字符串匹配;调用方拿到的 7 类英文错误代码由后端统一规范输出,与 locale 无关。 |
| `formatted_value` 可能隐藏错误 | 某些条件格式 / 自定义数字格式会把 `#DIV/0!` 显示成空白。后端直接读 cell `error_kind`,不依赖 `formatted_value`,绕开此类被遮蔽。 |
| 把 `partial``success` | `partial` 仅表示**已扫描部分**无错误,剩余区域未知。必须续扫直到 `has_more=false``status='success'` 才能算通过。 |
| 编译失败 vs 运行时错误 | 同一份报告里 `compile_errors[]``error_summary` 并存。语义层先解决 `compile_errors[]`、再做运行时自检。 |

View File

@ -0,0 +1,93 @@
# Lark Sheet History
## 概念回顾
每张飞书电子表格保留一串历史版本(`minor_histories`)。每个版本由 `history_version_id` 标识,并附带创建时间(`create_time`)、动作(`action`)与块修订信息(`all_block_revision`)。历史是**工作簿级**的(针对整张电子表格,不针对单个子表)。
回滚revert把电子表格的当前内容覆盖回某个历史版本——这是一个**高风险写入**操作,且为**异步**:发起后立即返回受理标识,真正的回滚在后台进行,需通过状态查询轮询最终结果(进行中 / 成功 / 失败)。
`+history-list` 读取版本列表以挑选目标;`+history-revert` 发起回滚;`+history-revert-status` 轮询回滚结果。若只是想拿**当前文档版本号revision**当作 recover / undo / `+changeset-get` 的起点锚点,直接用 `+revision-get` 更轻量。
## 使用场景
读取历史版本、发起回滚、查询回滚状态。本 reference 覆盖 3 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看历史版本列表 | `+history-list` | 返回 `minor_histories`,每条含 `history_version_id` / `create_time` / `action` / `all_block_revision` 四个字段;支持向前分页(可选 `--end-version` |
| 回滚到指定历史版本 | `+history-revert` | 传入 `--history-version-id`;异步受理,返回可查询标识 |
| 查询回滚状态 | `+history-revert-status` | 传入 `--transaction-id`(取自 `+history-revert` 的异步受理标识);轮询某次回滚的进行中 / 成功 / 失败状态 |
典型工作流:`+history-list` 拿到目标版本的 `history_version_id`(必要时翻页拉取更早历史)→ `+history-revert` 发起回滚并取回 `transaction_id``+history-revert-status --transaction-id <transaction_id>` 轮询直到成功或失败。
**注意事项(必须了解)**
- **回滚是高风险写入操作**:会用历史版本内容覆盖当前表格,执行前应明确告知用户影响。
- **回滚是异步的**`+history-revert` 返回的是 `transaction_id`(受理标识),不代表回滚已完成;必须用 `+history-revert-status --transaction-id <transaction_id>` 确认最终结果。
- **`history_version_id``transaction_id` 不是同一个**`history_version_id` 用于 `+history-revert`(取自 `+history-list``transaction_id` 用于 `+history-revert-status`(取自 `+history-revert` 的输出)。
- **历史是工作簿级**:定位只需 `--url` / `--spreadsheet-token`XOR不需要子表选择器。
- **`+history-list` 倒序分页**:首次查省略 `--end-version`,返回最新一页;若响应里附带 `next_end_version``has_more=true`,把 `next_end_version` 作为下一次的 `--end-version` 即可继续向更早翻页;当响应**不包含**这两个字段时表示已到最早一页,不必再翻。
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+history-list` | read | 历史版本 |
| `+history-revert` | high-risk-write | 历史版本 |
| `+history-revert-status` | read | 历史版本 |
## Flags
### `+history-list`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--end-version` | int | optional | 分页查询的最大版本(倒序);首次查询省略,下一页传上一页返回的 next_end_version。 |
### `+history-revert`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--history-version-id` | string | required | 要回滚到的历史版本(取自 +history-list |
### `+history-revert-status`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--transaction-id` | string | required | 异步回滚的受理标识(取自 +history-revert |
## Examples
公共定位:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token`XOR二选一`+history-revert``--history-version-id`(取自 `+history-list``+history-revert-status``--transaction-id`(取自 `+history-revert` 的异步受理标识)。
### `+history-list`
```bash
# 列出某张电子表格的最新一页历史版本
lark-cli sheets +history-list --url "https://sample.feishu.cn/sheets/SHTxxxxxx"
# 用原始 spreadsheet token 定位
lark-cli sheets +history-list --spreadsheet-token "SHTxxxxxx"
# 翻到下一页:把上次响应里的 next_end_version 作为 --end-version 传入
lark-cli sheets +history-list --url "https://sample.feishu.cn/sheets/SHTxxxxxx" --end-version 12345
```
### `+history-revert`
```bash
# 回滚到指定历史版本(异步受理)
lark-cli sheets +history-revert --url "https://sample.feishu.cn/sheets/SHTxxxxxx" --history-version-id "<id-from-history-list>"
```
### `+history-revert-status`
```bash
# 查询某次回滚的当前状态(进行中 / 成功 / 失败)
lark-cli sheets +history-revert-status --url "https://sample.feishu.cn/sheets/SHTxxxxxx" --transaction-id "<transaction-id-from-history-revert>"
```

View File

@ -0,0 +1,171 @@
# Lark Sheet Pivot Table
## 真对象硬约束
当用户要求"透视表 / 分组汇总 / 交叉分析 / 按 X 统计 Y"时,**必须**通过 `+pivot-{create|update|delete}` 创建真实的透视表对象。**禁止**用 `SUMIFS` / `COUNTIFS` 等普通公式 + `+cells-set` 在原表中拼一张"看起来像透视表的汇总表"来代替。判断标准:交付后 `+pivot-list` 必须能返回该对象。
## 使用场景
读写透视表对象。本 reference 覆盖 4 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看已有透视表 | `+pivot-list` | 获取透视表的结构、数据源和配置 |
| 创建/更新/删除透视表 | `+pivot-{create|update|delete}` | 对透视表执行写入操作 |
典型工作流:先读取现有透视表了解配置 → 执行创建/更新/删除 → **必须再次读取验证结果**
## 行/值字段映射(创建前必做)
创建透视表前先识别用户需求中的分组维度和聚合指标,**不要搞反**
- **rows行字段** = 分组维度,即"按什么分组"。例:部门、地区、医生、产品类别
- **values值字段** = 聚合指标,即"统计什么数值"。例:销售额(聚合方式 `sum`)、订单数(聚合方式 `count`
- **columns列字段** = 交叉维度(可选),即"再按什么横向展开"。例:月份、性别
| 用户说 | rows | values | columns |
|--------|------|--------|---------|
| "按部门统计人数" | 部门 | 姓名(`summarize_by: "count"` | — |
| "按医生统计费用和结余" | 主管医生 | 费用(`"sum"`)、结余(`"sum"` | — |
| "各部门男女人数" | 部门 | 姓名(`"count"` | 性别 |
**常见配置错误(必须注意)**
- **数据源范围必须精确**:透视表的数据源范围必须包含表头行,且精确覆盖全部数据行列。范围过大(包含空行/空列)或过小(遗漏数据列)都会导致透视表结果错误
- **行列字段选择要匹配用户意图**:用户说"按商品统计金额"→ 行字段=商品,值字段=金额(`summarize_by: "sum"`)。不要把行列字段搞反
- **聚合类型要匹配**:用户说"统计数量"→ `summarize_by: "count"`"统计总额"→ `"sum"`"统计平均"→ `"average"`。完整合法值:`sum` / `count` / `average` / `max` / `min` / `product` / `countNums` / `stdDev` / `stdDevp` / `var` / `varp` / `distinct` / `median`。按用户意图选聚合方式,不要拿 `count` 顶替 `sum`
- **参数长度限制**:如果透视表配置 JSON 过长(数据源范围跨越大量行列),可能导致工具调用失败。此时应先确认数据范围的精确边界,避免传入过大的 range
- **落点不能覆盖任何已有数据(不只是 `--source` 范围)**:透视表创建后会向右下**展开**,展开区域哪怕只盖到一个已有单元格(即便已避开源数据),也会报「目标位置不能与数据源重叠」并产生 `#REF!`。创建前无法精确预知展开尺寸,故**强烈优先默认策略**(不传 `--target-sheet-id/-name``--target-position`/`--range`,后端自动新建空白子表),零覆盖风险;非要落到已有子表,必须挑一片足够大的纯空白区
- **创建后必须校验(用 `info` 读取展开后的真实占用区域)**:创建后调用 `+pivot-list``info.error_state``info.content_range`/`page_range`——`error_state``None`(如 `Cover` 盖到其它内容 / `Shrink` 展不开)说明落点冲突,应删除后重建到空白区;`content_range`/`page_range` 是展开后**实际占用区域**,可用 `+csv-get` 抽查其边缘外有没有盖掉原有数据,确认结构正确
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+pivot-list` | read | 对象 |
| `+pivot-create` | write | 对象 |
| `+pivot-update` | write | 对象 |
| `+pivot-delete` | high-risk-write | 对象 |
## Flags
### `+pivot-list`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--pivot-table-id` | string | optional | 按 id 过滤 |
### `+pivot-create`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | required | JSON{"rows":[...],"columns":[...],"values":[...],"filters":[...],"show_row_grand_total":true,"show_col_grand_total":true}(数据源走 --source不要再放进 properties.source |
| `--target-position` | string | optional | 透视表落点子表内的起始 cellA1 格式,如 `A1`),映射到顶层 `target_position`,默认 `A1`(值为 A1 时不下发)。它与 `--range` 都表达落点但落在不同 wire 字段,避免两者同时给冲突值 |
| `--target-sheet-id` | string | xor | 透视表落点目标子表的 reference_id`--target-sheet-name` 互斥,优先于 --target-sheet-name都不传时自动新建一张子表放置透视表——推荐。与数据源 sheet 区分:数据源 sheet 写在 --source 的 A1 引用里(带 sheet 前缀,形如 `'Sheet1'!A1:D100`)。 |
| `--target-sheet-name` | string | xor | 透视表落点目标子表的名称(与 `--target-sheet-id` 互斥;都不传时自动新建一张子表放置透视表——推荐)。与数据源 sheet 区分:数据源 sheet 写在 --source 的 A1 引用里(带 sheet 前缀,形如 `'Sheet1'!A1:D100`)。 |
| `--source` | string | required | 透视表源数据区域A1 表示法,格式 `'SheetName'!StartCell:EndCell`,如 `'Sheet1'!A1:D100` |
| `--range` | string | optional | 透视表左上角放置位置A1 单值,如 `F1`,仅 create 生效),映射到 `properties.range`;省略时放在落点子表(默认新建子表)的左上角。它与 `--target-position` 都表达落点但落在不同 wire 字段,避免两者同时给冲突值 |
### `+pivot-update`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--pivot-table-id` | string | required | 目标透视表 id |
| `--properties` | string + File + Stdin复合 JSON | required | 完整或足够完整的配置(先 `+pivot-list --pivot-table-id <id>` 回读再 patch |
### `+pivot-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--pivot-table-id` | string | required | 目标透视表 id |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+pivot-create` `--properties` / `+pivot-update` `--properties`
_创建/更新的透视表属性_
**顶层字段**
- `range` (string?) — 放置透视表的左上角单元格 A1 地址(例如:'F1')(仅 create 时有效) — ⚠️ 已拎为独立 flag `--range`,请勿在此 JSON 内重复填写(同名以独立 flag 为准)
- `source` (string?) — 源数据区域地址,格式为 'SheetName!StartCell:EndCell'(例如:'Sheet1!A1:D100' — ⚠️ 已拎为独立 flag `--source`,请勿在此 JSON 内重复填写(同名以独立 flag 为准)
- `rows` (array<object>?) — 纵向分组字段(行字段) each: { field: string, display_name?: string, sort?: object, filter?: object, condition_filter?: object, …共 6 项 }
- `columns` (array<object>?) — 横向分组字段(列字段) each: { field: string, display_name?: string, sort?: object, filter?: object, condition_filter?: object, …共 6 项 }
- `filters` (array<object>?) — 筛选区域字段(页字段) each: { field: string, display_name?: string, filter?: object, condition_filter?: object, group?: object }
- `values` (array<object>?) — 要汇总的字段(至少需要 1 个) each: { field: string, display_name?: string, summarize_by?: enum, show_data_as?: enum, base_field?: string }
- `auto_fit_col` (boolean?) — 是否自动调整列宽以适应内容
- `show_row_grand_total` (boolean?) — 是否显示行总计(默认 true
- `show_col_grand_total` (boolean?) — 是否显示列总计(默认 true
- `show_subtotals` (boolean?) — 是否显示分类小计(默认 true应用于所有字段
- `repeat_row_labels` (boolean?) — 是否显示重复项标签
- `calculated_fields` (array<object>?) — 计算字段列表 each: { name: string, formula: string, summarize_by?: enum }
- `collapse` (object?) — 行字段展开/折叠状态:字段名 -> 要折叠的项目列表
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`,其中 `--sheet-id` / `--sheet-name``+pivot-update` / `+pivot-delete` / `+pivot-list` 上是公共四件套语义(定位透视表所在 sheetXOR 必传一个)。
**`+pivot-create` 例外**placement 选择器用 `--target-sheet-id` / `--target-sheet-name`XOR两个都不传时后端自动新建子表存放产物强烈推荐绝不碰源数据。数据源 sheet 写在 `--source``'SheetName'!Range` 里,不靠 sheet 选择器 flag。
### `+pivot-list`
```bash
lark-cli sheets +pivot-list --url "..." --sheet-id "$SID"
```
> **返回值含 `info`(展开后的占用区域与状态)**:每个透视表对象除 `position` / `snapshot` 外,还返回 `info`,标明它在 sheet 上的平铺区域与状态——`info.page_range`(筛选/分页区 A1、`info.content_range`(主体数据区 A1、`info.span_range`(空表合并区 A1、`info.error_state`(错误状态,如 `None`/`Cover`/`Shrink`/`Loading`)、`info.is_empty` / `info.is_hidden`、`info.row`/`info.col`(锚点)等。
> **用途 1判断改值还是改配置**:当用户描述某个单元格要改动时,先 `+pivot-list` 拿到 `info`,判断该单元格是否落在 `page_range` / `content_range` 内——**落在区域内 = 属于透视表,应走 `+pivot-update` 改配置**(透视表单元格不能直接 `+cells-set` 改值);**落在区域外 = 普通单元格,正常 `+cells-set` 改值**。
> **用途 2创建后校验覆盖**:建完透视表用 `info.error_state` 判断有没有冲突(非 `None` 即落点/展开区与已有数据重叠或展不开),用 `info.content_range`/`page_range` 拿到展开后真实占用区域再核对是否盖到原有数据。
### `+pivot-create`
> 数据源 `--source` 必须从表头行开始;空行 / 汇总行会被当作数据参与聚合,需提前用 `+csv-get` 确认起止边界。`--source` 和 `--range` 是独立 flag不要再放 `--properties``rows` / `columns` / `values` 等数组字段走 `--properties`。
>
> **先理清 `+pivot-create` 上 4 个位置类入参(语义不同,别混)**
> - `--source`**必填****源数据**区域,须自带 `Sheet!` 前缀(如 `'Sheet1'!A1:D100`sheet 名按 A1 标准单引号包裹)。源 sheet 的名字在 `--source` 字符串里,**不**通过单独 flag 传。
> - `--target-sheet-id` / `--target-sheet-name`**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。
> - `--target-position`可选A1 表示法,默认 `A1`):落点 sheet 内的起始 cell映射到顶层 `target_position`。
> - `--range`可选A1 单值,仅 create 生效):跟 `--target-position` 表达同一意图但映射到 `properties.range`**两者不要同时给**。
>
> **落点 3 种策略(互斥,选其一)**
> 1. **默认(强烈推荐)**`--target-sheet-id` / `--target-sheet-name` / `--target-position` / `--range` **全都不传** → 服务端**自动新建子表**存放产物,绝不碰任何已有数据。
> 2. **放进指定的已有子表**:传 `--target-sheet-id <落点子表 id>`(或 `--target-sheet-name`),可选 `--target-position <子表内起点 cell>`。⚠️ **若落点子表就是源数据所在的 sheet**,必须配 `--target-position` 或 `--range` 指向源数据范围**之外**的位置,否则产物默认从 A1 起会盖在源数据上。
> 3. **`--range`**:跟策略 2 等价(同样需要 `--target-sheet-id` / `--target-sheet-name` 指定落点子表,不然落到自动新建子表),只是用 `properties.range` 那条 wire 路径表达位置。同样的覆盖风险,同样需要避开源数据范围。
>
> 一般用策略 1默认新建子表即可零覆盖风险无需任何 `--target-*` / `--range` flag。
```bash
# 策略 1强烈推荐不传任何落点 flag → 后端自动新建子表,零覆盖风险
lark-cli sheets +pivot-create --url "..." \
--source "'Sheet1'!A1:D100" --properties @pivot.json
# 策略 2落进指定的已有目标子表注意目标 sheet ≠ 源 sheet否则要配 --target-position 避开源数据)
lark-cli sheets +pivot-create --url "..." \
--source "'Sheet1'!A1:D100" --target-sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json
```
### `+pivot-update`
> 不允许改 `--source` / `--range`(透视表创建后位置/数据源固定);只能用 `--properties` 改 rows / columns / values / filters 等。先 `+pivot-list --pivot-table-id <id>` 回读再 patch避免漏字段。
### `+pivot-delete`
```bash
lark-cli sheets +pivot-delete --url "..." --sheet-id "$SID" --pivot-table-id "$PID" --yes
```
### Validate / DryRun / Execute 约束
- `Validate``--url` / `--spreadsheet-token` XOR 必填;`+pivot-{update,delete,list}``--sheet-id` / `--sheet-name` XOR 必填一个;`+pivot-create` 例外(用 `--target-sheet-id` / `--target-sheet-name` 表达落点,两个都可空时触发 backend auto-create 子表,两个都给则报 mutually exclusive`+pivot-create``--source` 必填且必须含表头行;`--properties``rows` / `columns` / `values` 至少非空之一;`+pivot-delete` 强制 `--yes``--dry-run`
- `DryRun`:写操作输出"将要 POST/PATCH/DELETE 的 pivot 请求模板"+ 预估输出尺寸(行数 × 列数)。
- `Execute`:写后不自动回读;如需确认,自行调用 `+pivot-list --pivot-table-id <id>` 并用 `+csv-get` 抽样读透视产物核对输出尺寸 + 总计行位置。
> ⚠️ pivot 输出包含总计 / 小计行;后续 chart 引用 pivot 时,`snapshot.data.refs` 必须排除这些行(见 `lark-sheets-chart` 的「⚠️ chart 数据源引用 pivot 时必须排除总计行」段)。

View File

@ -0,0 +1,297 @@
# Lark Sheet Range Operations
## 结构性操作影响面预检(清除 / 合并 / 排序 / 移动前必做)
`+cells-clear``+cells-{merge|unmerge}``+range-{move|copy|fill|sort}`(移动 / 复制 / 排序 / 自动填充)都会让既有引用关系发生偏移或失效。**操作前必须**先确认以下两点;否则禁止执行:
1. **打印当前合并单元格 + 公式引用 + 数据验证范围**:用 `+sheet-info --include merges` + `+cells-get` 抽样目标区域和它周边的公式 / 透视表 / 图表 / 条件格式 / 筛选器的数据源;评估操作后这些引用是否仍指向正确数据。
2. **`+cells-clear` 不得侵入用户授权范围之外**:清除范围只能是用户明示要清的区域;不要顺手清除"看起来没用"的相邻单元格。
排序场景的存储类型识别 + 辅助列抽数值的细则见下方「sort 操作前必读」章节。
## 使用场景
写入。对指定区域执行结构性操作。本 reference 覆盖 9 个 shortcut按 4 类用途组织:
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 清除内容/格式 | `+cells-clear` | "清空"、"删除内容"、"去掉格式" |
| 合并/取消合并单元格 | `+cells-{merge|unmerge}` | "合并单元格"、"取消合并" |
| 调整行高/列宽 | `+rows-resize / +cols-resize` | "加宽列"、"调整行高"、"自适应列宽" |
| 移动/复制/填充/排序 | `+range-{move|copy|fill|sort}` | "移动数据"、"复制到"、"自动填充"、"按某列排序" |
注意:
- **`--range` 两种语法别混**`+cells-clear` / `+cells-{merge|unmerge}` / `+range-*` 用单元格 A1 矩形(如 `A2:A10``+rows-resize` / `+cols-resize` 用纯行 / 列区间(行 `2:10`、列 `A:C`),不要给 resize 传 `A2:A10`
- 用户说"这行 / 整行 / 首行"时,优先使用整行范围如 `1:1`"这列 / 整列"时使用 `J:J`。不要截断为局部矩形
- 合并后只保留左上角单元格的内容,其余清除。写入合并区域用 `+cells-set` 对左上角单元格操作
- 调整行高列宽时,先读取相邻行列尺寸再决定像素值,不要随意猜测
- `--copy-to-range``+cells-set` 的参数)复制的是值/公式/样式,不含行高列宽。需要统一尺寸时另行调用 `+rows-resize / +cols-resize`
## 写入后列宽自适应(防内容遮挡)
写入文本 / 数值后**必须**主动检查列宽是否适配,否则会出现"内容被截断 / 长数字显示为科学计数法 / 文本溢出被相邻列遮挡"等用户感知问题:
1. **写入后回读最长内容字符数**:用 `+csv-get` 读目标列的实际写入内容,统计最长单元格的字符数(`max(len(cell) for cell in col)`)。汉字按 2 字符宽度估算,半角字母数字按 1 字符。
2. **判定阈值**:当前列宽(用 `+sheet-info --include row_heights,col_widths` 拿)≥ 最长字符数 × 字体宽度系数 + buffer 才算适配。默认列宽 11 通常只够 11 个半角字符或 5-6 个汉字,写长文本前必扩宽。
3. **修复二选一**
- **扩列宽**:用 `+rows-resize / +cols-resize` 把目标列宽设为 `max(表头字符数, 内容采样最长字符数) × 8 + 16` 像素(经验值)
- **自动换行**:在 `+cells-set` 时给单元格设置 `cell_styles.word_wrap="auto-wrap"`(可选值:`overflow` / `auto-wrap` / `word-clip``cell_styles` 字段见 `lark-sheets-write-cells`),并用 `+rows-resize / +cols-resize` 调高对应行的行高
4. **新增列默认列宽规则**:新增列宽度 ≥ `max(表头字符数, 内容采样最长字符数) × 8 + 16` 像素,**禁止**用默认 11 直接交付。
**典型反例**:默认列宽 11 但内容含 12+ 字符的中文 / 含单位的数值(如 `109.10μmol/L`/ 长数字未设 `number_format` 显示为科学计数法 —— 用户在结果表里看不到完整原值。
**打印场景控制总宽(用户说"适合打印 / A4 / 打印范围"时必做)**:扩单列宽防截断的同时,**所有列宽之和要落在纸张可打印宽度内**——A4 横向约 ≤ 102 个半角字符(约 1000px纵向约 ≤ 70 个字符。超宽时不要无限加宽,改用 `cell_styles.word_wrap="auto-wrap"` + 调高行高,或缩窄非关键列,让整表在一页内(反例:总列宽远超 A4 可打印宽度,且长文本行高不够被截断)。
**只加宽承载新内容的列,不改动原有列的列宽**:列宽自适应**只针对新增 / 真正放不下新内容的列**;原表已有列的列宽**禁止重新计算、禁止缩小**——即便你估算的"理想宽度"与原值不同,只要原内容没被截断就不要动它。无差别地把所有列重设一遍宽度(哪怕只 ±1都属于破坏原文件视觉格式反例填完数据后顺手把原有列的列宽从 16 改成 17与原附件不一致破坏了原视觉格式
**⚠️ 合并单元格安全操作规则**`+cells-{merge|unmerge}` 必读):
1. **先读后写**:操作前必须用 `+sheet-info --include merges``+cells-get` 识别已有合并区域(特征:多个连续单元格中只有左上角有值,其余为空)。
2. **不要对已合并区域重复 merge**:对已合并的区域再次调用 merge 会报错或产生不可预期结果。
3. **修改合并区域的正确顺序**:先 `unmerge` → 修改内容/样式 → 再 `merge`
4. **对合并区域设置样式**:只对完整 range 设置一次 `cell_styles`(写在左上角单元格),其余位置用 `{}` 占位。
5. **新增合并时数据保护**:合并前确认目标区域只有左上角有数据,其余单元格为空,否则合并会导致非左上角的数据丢失。
6. **批量取消合并一次调用即可**:当一个范围(整列 `A:A`、整行 `3:3`、矩形 `A1:D100`)内存在多个合并区域,直接调一次 `+cells-unmerge` 传入这个大范围,会一次性取消该范围内所有合并区域;**不要**为每个合并区域单独调用 unmerge也不要用 `+batch-update` 拆成多次 unmerge。
**⚠️ 批量操作必须用 `+batch-update`**:对**多个**不同区域执行 `+cells-merge` 时,禁止逐个调用,合并为单次原子 `+batch-update`(语义与 `--operations` 入参格式见 `lark-sheets-batch-update`)。行高列宽**不需要** `+batch-update`:多行 / 多列不同尺寸直接用 `+rows-resize --heights` / `+cols-resize --widths` 的 map 形态,一次调用原子完成。
**唯一例外**`+cells-unmerge` 原生支持传一个大 range 一次性取消其中所有合并区域,应直接单次调用,**不要**拆进 `+batch-update`
**⚠️ sort 操作前必读:确认目标列的数据类型**
排序按单元格的**存储类型**比较:纯数字按数值排序;文本字符串按**字典序**`"1000"` 排在 `"999"` 之前,与数值相反);日期按时间戳排序。
以下形态**看起来像数字但实际是字符串**,直接 sort 会得到错误结果:
| 示例 | 说明 |
|------|------|
| `843688.69+20042.35=863731.04` | 表达式文本(无前导 `=` 不是公式,整串按字典序比较) |
| `¥1,234.56` / `$1,234` | 带货币符号 |
| `1.2万` / `3.5亿` / `100kg` | 带中文 / 英文单位 |
| 前后含空格或不可见字符的数字串 | 被当文本 |
| 同列混文本和数字 | 排序后分块 |
**硬性流程**
1. sort 前先用 `+csv-get` 抽样目标列的前 35 行确认原始值形态,不要只看列名和用户问题就直接排。
2. 若是纯数字或日期 → 直接 sort。
3. 若是带符号 / 表达式 / 单位的文本 → **不要直接排**
- 简单场景(货币、千分位、单位前缀):新增辅助列,用公式提取数值(如 `=VALUE(SUBSTITUTE(SUBSTITUTE(A2,"¥",""),",",""))`),按辅助列排序,排完可按需清除辅助列。
- 复杂场景(多段表达式、中文单位、混合格式):分批 `+csv-get` 读到本地,按数值排序后用 `+csv-put` / `+cells-set` 分批回写。
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+cells-clear` | high-risk-write | 单元格 |
| `+cells-merge` | write | 单元格 |
| `+cells-unmerge` | write | 单元格 |
| `+rows-resize` | write | 工作表 |
| `+cols-resize` | write | 工作表 |
| `+range-move` | write | 区域 |
| `+range-copy` | write | 区域 |
| `+range-fill` | write | 区域 |
| `+range-sort` | write | 区域 |
## Flags
### `+cells-clear`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 清除范围A1 格式) |
| `--scope` | string | optional | 清除范围 enum`content`(默认,仅清内容)/ `formats`(仅清格式)/ `all`(清内容 + 格式)(可选值:`content` / `formats` / `all` |
### `+cells-merge`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 待合并 / 取消合并的范围A1 格式) |
| `--merge-type` | string | optional | 合并方向(仅 `+cells-merge`)(可选值:`all` / `rows` / `columns`)(默认 `all` |
### `+cells-unmerge`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 待合并 / 取消合并的范围A1 格式) |
### `+rows-resize`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--height` | int | xor | 统一行高像素30 / 40 / 60不是磅/points`--range` 使用。传了 `--height` 就是像素模式,可以省略 `--type`;显式 `--type pixel` 也行(等价)。多行不同高用 `--heights` |
| `--heights` | string + File + Stdin复合 JSON | xor | 差异化行高 map一次原子调用给多行设置不同高度键为单行`"1"`)或行闭区间(`"2:20"`),值为像素高(如 30 / 50`"auto"`(自适应内容)或 `"standard"`(重置默认)。⚠️ 单位是像素,不是磅/points。与 `--range` / `--height` / `--type` 互斥 |
| `--type` | string | xor | 尺寸方式 enum`pixel`(需配 `--height`/ `standard`(重置为默认行高)/ `auto`(自动适应内容)。常规写法直接给 `--height` 即可省略本 flag`--type standard` / `--type auto` 不能与 `--height` 同时给(可选值:`pixel` / `standard` / `auto` |
| `--range` | string | xor | 要调整行高的行闭区间1-based 行号如 `2:10` 或单行 `5`。统一尺寸形态必填(配 `--height``--type`map 形态(`--heights`)不传 |
### `+cols-resize`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--width` | int | xor | 统一列宽像素80 / 120 / 200不是 Excel 字符单位),配 `--range` 使用。传了 `--width` 就是像素模式,可以省略 `--type`;显式 `--type pixel` 也行(等价)。多列不同宽用 `--widths` |
| `--widths` | string + File + Stdin复合 JSON | xor | 差异化列宽 map一次原子调用给多列设置不同宽度键为单列`"A"`)或列闭区间(`"C:E"`),值为像素宽(如 80 / 120 / 200`"standard"`(重置默认)。⚠️ 单位是像素,不是 Excel 字符单位(像素 ≈ 字符数×8+16。与 `--range` / `--width` / `--type` 互斥 |
| `--type` | string | xor | 尺寸方式 enum`pixel`(需配 `--width`/ `standard`(重置为默认列宽)。常规写法直接给 `--width` 即可省略本 flag`--type standard` 不能与 `--width` 同时给(可选值:`pixel` / `standard` |
| `--range` | string | xor | 要调整列宽的列闭区间;列字母如 `A:E` 或单列 `C`。统一尺寸形态必填(配 `--width``--type`map 形态(`--widths`)不传 |
### `+range-move`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--source-range` | string | required | 源 A1 范围 |
| `--target-sheet-id` | string | optional | 目标子表 id省略时同源 sheet |
| `--target-range` | string | required | 目标 A1 范围(传起点 cell 即可,按源尺寸自动推断) |
### `+range-copy`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--source-range` | string | required | 源 A1 范围 |
| `--target-sheet-id` | string | optional | 目标子表 id省略时同源 sheet |
| `--target-range` | string | required | 目标 A1 范围(传起点 cell 即可,按源尺寸自动推断) |
| `--paste-type` | string | optional | 粘贴内容(仅 `+range-copy`)(可选值:`values` / `formulas` / `formats` / `all`)(默认 `all` |
### `+range-fill`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--source-range` | string | required | 填充模板范围(系列起始 cells |
| `--target-range` | string | required | 目标填充范围A1 格式) |
| `--series-type` | string | optional | 填充序列类型(可选值:`auto` / `linear` / `growth` / `date` / `copy`)(默认 `auto` |
### `+range-sort`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 排序范围A1 格式;含或不含表头由 `--has-header` 决定) |
| `--sort-keys` | string + File + Stdin复合 JSON | required | JSON 数组:`[{"column":"<列字母>","ascending":<bool>}, ...]` |
| `--has-header` | bool | optional | 第一行是表头不参与排序,默认 false |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+rows-resize` `--heights`
_行 → 高度 map_
- type: object
### `+cols-resize` `--widths`
_列 → 宽度 map_
- type: object
### `+range-sort` `--sort-keys`
_排序条件列表仅 sort 操作_
**数组项**(类型 object
- `column` (string) — 排序依据的列字母(如 "C"、"D"),必须在 range 范围内
- `ascending` (boolean) — 是否升序排序
## Examples
> ⚠️ 本 reference 派生的 shortcut 跨 3 个分组:`+rows-resize` / `+cols-resize` → 工作表,`+cells-*` → 单元格,`+range-*` → 区域。这里统一从区域操作视角讲解。
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR
### `+cells-clear`
> ⚠️ **`--scope all` 清整表是不可逆的大范围破坏**:会一并抹掉该区域的合并单元格、原公式,以及图表 / 透视表引用的数据源列(这类列常在主数据区右侧,视觉上"看着没用"却被图例 / 系列引用)。**"美化 / 规范化一张已有表"永远不需要 clear 原表再重写**——若你打算"清空原表 → 写入重排后的版本",说明走错了路径,应改为原地只刷样式(见 `lark-sheets-visual-standards` 场景三)。
> **删不掉嵌入对象**`+cells-clear`(任何 `--scope`,含 `all`)只清单元格的值 / 格式,**删不掉**压在范围内的透视表 / 图表等嵌入对象——后端会报 `can not find embedded block`。删透视表用 `+pivot-delete`、删图表用 `+chart-delete`(先用 `+pivot-list` / `+chart-list` 拿对象 id
> 需要一次清除**多个不连续 range**(如把内容搬走后批量去掉散落各处的边框/底色)时,改用 `lark-sheets-batch-update` 的 `+cells-batch-clear`,避免对 `+cells-clear` 逐个 range 调用。
```bash
# dry-run 先看
lark-cli sheets +cells-clear --url "..." --sheet-id "$SID" --range "A2:Z1000" --scope all --dry-run
# 执行
lark-cli sheets +cells-clear --url "..." --sheet-id "$SID" --range "A2:Z1000" --scope all --yes
```
### `+cells-merge` / `+cells-unmerge`
```bash
# 合并 A1:C1可选 --merge-type all/rows/columns
lark-cli sheets +cells-merge --url "..." --sheet-id "$SID" --range "A1:C1"
# 取消合并:传大 range 一次性取消其中所有合并区域
lark-cli sheets +cells-unmerge --url "..." --sheet-id "$SID" --range "A1:C100"
```
### `+rows-resize` / `+cols-resize`
行高列宽分两条 shortcut避免行 / 列在底层 schema 的差异(行支持 `auto`,列不支持)混在一起。两种形态:
- **统一尺寸**`--range` + `--height`/`--width <px>`(省略 `--type`,等价于 `--type pixel`)。非像素模式走 `--type standard` / `--type auto`,此时不能再带像素值。
- **差异化尺寸**`--heights`/`--widths` 一个 JSON map键为单行/列或闭区间、值为像素或模式字符串,**一次调用原子完成多行 / 多列不同尺寸**——不要拆多次调用,也不要用 `+batch-update`
```bash
# 统一尺寸:把第 2-10 行设为固定 30 px
lark-cli sheets +rows-resize --url "..." --sheet-id "$SID" --range "2:10" --height 30
# 统一尺寸:把 A-C 列设为固定 120 px
lark-cli sheets +cols-resize --url "..." --sheet-id "$SID" --range "A:C" --width 120
# 差异化尺寸:多列不同宽,一次调用(值可混用 "standard" 重置某列)
lark-cli sheets +cols-resize --url "..." --sheet-id "$SID" \
--widths '{"A": 100, "B": 358, "C:E": 120, "G": "standard"}'
# 差异化尺寸:多行不同高,值可混用 "auto" / "standard"
lark-cli sheets +rows-resize --url "..." --sheet-id "$SID" \
--heights '{"1": 50, "2:20": 30, "21": "auto"}'
# 第 1 行行高自动适应内容(列宽不支持 auto
lark-cli sheets +rows-resize --url "..." --sheet-id "$SID" --range "1" --type auto
# 重置 A-E 列为默认列宽
lark-cli sheets +cols-resize --url "..." --sheet-id "$SID" --range "A:E" --type standard
```
**⚠️ 单位是像素,不是 Excel 字符单位 / 磅**:列宽常见 60~400px如果你按 Excel 字符单位openpyxl / xlsxwriter 的 `width`)心算,先换算 `px ≈ 字符数 × 8 + 16`——写 `{"A": 10}` 得到的是 10px 的不可用窄列CLI 会拒绝 < 20px 的列宽并提示换算)。行高是像素不是磅points默认行高约 24px
**列宽没有 auto-fit**需要"列宽自适应内容""写入后列宽自适应"一节的公式估算像素值`max(表头字符数, 内容最长字符数) × 8 + 16`后用 `--widths` 显式设置
> 同时出现在 `lark-sheets-sheet-structure.md` —— 行高 / 列宽调整也算行列结构层动作。
### `+range-move` / `+range-copy`
> `+range-move` 会**清空源区域**move = copy + clear_source`+range-copy` 不动源。
### `+range-fill`
```bash
# 用 A1:A2 的序列规律向下填充到 A3:A100target 区域不能与 source 重叠,否则后端报 source overlaps destination
lark-cli sheets +range-fill --url "..." --sheet-id "$SID" --source-range "A1:A2" --target-range "A3:A100" --series-type auto
```
### `+range-sort`
```bash
# 按 C 列降序排 A1:E100首行为表头不参与
lark-cli sheets +range-sort --url "..." --sheet-id "$SID" --range "A1:E100" --has-header --sort-keys '[{"column":"C","ascending":false}]'
```
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套`+cells-clear` 强制 `--yes` `--dry-run``+range-*` 校验源 / 目标 range 在同一 spreadsheet`+range-sort` `--sort-keys` 必须合法 JSON 数组且 col 都在 `--range` `+rows-resize` / `+cols-resize` 两种形态二选一——统一形态必须给 `--range` 且至少给 `--height`/`--width` `--type` 之一`--type standard`/`auto` 不能与像素 flag 同给`--type pixel` 共存 OKmap 形态`--heights`/`--widths`不能与 `--range`/`--height`/`--width`/`--type` 混用map 键必须与命令维度一致行数字 / 列字母)、不得重复值为正整数像素或模式字符串列宽 < 20px 拒绝疑似 Excel 字符单位`+cols-resize` 不接受 `auto`列宽不支持自适应)。map 形态在 `+batch-update` 子操作里不可用它本身就是原子批量)。
- `DryRun`所有写操作输出"将要 PATCH range + 受影响 cell 数估算"。
- `Execute`写后不自动回读如需确认自行调用 `+cells-get --range <影响范围>` 抽样比对

View File

@ -0,0 +1,235 @@
# Lark Sheet Read Data
## 列格式多样性预探(写公式 / 排序 / 筛选前必做)
> 本节给出"写公式 / 排序 / 筛选前先探清列格式多样性"的正确流程,是主 SKILL.md「飞书表格编辑准则」准则 3读全再写在 read_data 工具层的落地。
对参与后续**计算 / 排序 / 筛选 / 公式提取**的列,**必须**先 sample **至少 50 行**(小表则全量),识别该列所有值类型变体后再设计公式 / 条件。只看前 10 行不够,因为下列差异通常潜伏在表尾或中段:
- **日期列同时出现多种格式**`YYYYMM``YYYY-MM-DD``YYYY/M/D`、带时间戳、文本"未知"
- **数值列混入公式文本 / 单位 / 注释**`1000+200=1200``100元``/(合同未明确)``#N/A`
- **空值与 0 / "0" 混杂**
- **大小写 / 全角半角差异**"办公费" vs "办公费 "、"Sales" vs "sales"
预探后必须在公式 / 筛选条件里用 `IFERROR` / `IFS` / 提取数值的辅助列处理所有变体;不能为了通过 head(10) 的样本就直接落地。一旦设计的逻辑只覆盖 sample 中出现的格式,就属于违规。
⚠️ **大数字15 位以上的身份证 / 参考号 / 流水号)做去重 / 比较时禁止用 `+csv-get` 的显示值**`+csv-get` 返回的是**格式化显示值**15 位以上数字会被显示成 `1.04E+14` 这类科学计数法——多个本不相同的号在显示层全变成同一个 `1.04E+14`,拿去判重会**整列误判为重复**。比较 / 去重 / 匹配大数字时必须改用 `+cells-get`(取原始精确值)或把该列读为文本,禁止用 csv-get 的科学计数显示值(反例:大批长参考号被显示成科学计数后,互不相同的号全变成同一个值,被当成整列重复并错误高亮)。
## 使用场景
读取。从飞书表格中读取单元格数据。本 reference 覆盖 4 个 shortcut按读取目的选择
| 读取目的 | 用这个 shortcut | 数据去向 | 说明 |
|---------|----------------|---------|------|
| 快速查看纯值数据、批量处理 | `+csv-get` | 对话上下文 | 返回 CSV 文本(每行带 `[row=N]` 前缀);大表请按 `--range` 行窗口分批读(截断时看 `has_more` |
| 按列类型结构化读出(喂 DataFrame / round-trip 回 `+table-put` | `+table-get` | 对话上下文 | 返回 typed 协议(`columns:[列名]` + `data` + `dtypes`/`formats` + `range`),输出形状对齐 pandas split可一行 `pd.DataFrame(sheet["data"], columns=sheet["columns"]).astype(sheet["dtypes"])` 还原 DataFrame或直接 round-trip 回 `+table-put`。不带 `--range` 时读**完整 used range**(跨过表中部空行 / 空列),每个子表回传实际读取范围 `range` 供完整性校验。注意这与下文 `current_region` "遇表中部空行截断"不矛盾:`+table-get` 读的是子表物理 used range飞书记录的已用矩形含中间空行`current_region` 是从锚点连通扩展、遇整行空行就断 |
| 查看公式、样式、批注、数据验证 | `+cells-get` | 对话上下文 | 返回单元格完整信息token 开销较大 |
| 查看某区域的下拉框(数据验证)选项 | `+dropdown-get` | 对话上下文 | 返回该 A1 范围已配置的下拉列表选项 |
**选择原则**
- 只看值或做数据处理 → `+csv-get`;大表分批读取,避免一次拉全表撑爆上下文
- 要按列类型结构化读出(喂 DataFrame / round-trip 回 `+table-put`)→ `+table-get`
- 需要公式/样式/批注 → `+cells-get`
- 只想知道某区域下拉框有哪些选项 → `+dropdown-get`
⚠️ **大数据优先落盘、别灌进上下文**`+csv-get` / `+cells-get` 都受调用方 Bash / 终端的单命令 stdout 输出上限约束(常见默认约 30000 字符,超过会被截断或转存为文件)。纯值分析优先 `+csv-get --format csv``--range` 行窗口(`A1:Z500` / `A501:Z1000` …)分批重定向到文件 + 本地脚本处理 + `+csv-put` 分批回写;若确实要让结果直接进上下文又不想触发转存,给任一命令把 `--max-chars`(默认 500000调小到略低于该上限`25000`CLI 改为优雅截断 + `has_more` 分页。
**`+csv-get` 返回值核心设计**
- `annotated_csv`**CSV 数据唯一入口**。每一逻辑行前加 `[row=N] ` 前缀N = 真实表格行号)。任何需要行号的下游操作(合并、写入、清空、格式化、插入/删除、条件格式、筛选、图表/透视表范围、搜索替换等),**行号一律直接从 `[row=N]` 读取**。若需要纯 CSV如喂给本地脚本做解析去前缀即可`line.replace(/^\[row=\d+\] /, '')`
- `col_indices`**定位列字母唯一入口**。在表头中找到目标字段是第 j 个0-based`col_indices[j]` 取列字母。**禁止手数逗号**——列数超过 10 时极易 off-by-one例如把 W 误判为 X
- `row_indices` — 程序化引用的备用数组。LLM 推理请用 `annotated_csv` 的前缀,不要查这个数组里的 index把行号当数值用容易心算出错
- `current_region` — 从请求范围扩展到被空行空列包围的连续数据区域(等价于 Excel Ctrl+Shift+*),适合先读少量行探表头。⚠️ 它**遇表中部整行空行 / 整列空列就截断**,可能小于真实数据范围(漏掉空行之后的行);**不能**直接当整表末行用,判断整表是否读全要拿 `+workbook-info` 的物理 `row_count` / `column_count` 当上界交叉核对(见下方「按 row_count 盲读空行」与「确定数据范围的正确流程」)。
注意:
- `+csv-get``+cells-get` 支持分页/截断,注意检查 `has_more` / `truncated` 标志;两者在处理返回数据之前都必须先读 `warning_message`(上游 schema 要求先读它再用其它字段,内含定位与截断续读提示),`+cells-get` 还要用每个 range 的 `actual_range` / `row_indices` / `col_indices` 判断真实位置
- 隐藏行列默认包含在返回结果中(`--skip-hidden=false`),如需只看可见数据设为 `true`。读取原语本身不标注哪些行列被隐藏:若要识别隐藏区间(以决定是否过滤、或如何解读混入的隐藏数据),用 `+sheet-info --include hidden_rows,hidden_cols` 取隐藏行列集合,再结合 `+csv-get` / `+cells-get` 返回的 `row_indices` / `col_indices` 判断每行 / 每列是否隐藏
**常见配置错误(必须注意)**
- **全量读取导致上下文溢出**:不要对大表(数百行以上)直接用 `+csv-get``+cells-get` 读取全部数据到上下文。大表场景必须分批读取:用 `--range` 切行窗口逐块读(`+csv-get` / `+cells-get` 单次返回量由 `--max-chars` 自动兜底,截断时返回 `has_more`);过大时考虑导出到本地文件后用脚本处理再分批回写
- **了解结构 ≠ 读取全量数据**:探表不用读全表,但必须同时探两个方向的表头:
- **横向(列头)**:先读前几行,且**列范围必须覆盖所有列**——用 `+workbook-info` 拿总列数,`range` 末列填到最后一列(例如总列数是 N`range: "A1:[列N]10"`)。列范围截短会遗漏右侧字段、后续写入列定位错误。
- **纵向(行标)**:若左侧 1-2 列是行标签(日期/类别/编号枚举每行含义,典型交叉表/透视布局),**必须再读 `A:A``A:B` 把行标列读到底**,拿全部行标。只读前几行会看不全表尾的行,导致批量写入漏改——这是"只改前 N 行、其余未更新"的主要成因。扁平列表(每行独立记录、列是字段)可跳过这一步,但仍要按下方「确定数据范围的正确流程」用 `+workbook-info` 的物理 `row_count` 交叉核对末行(`current_region` 遇空行会截断,不能单独兜底)。
- 数据量大或会进入上下文上限时,分批读 + 本地处理 + 分批回写,不要一口气拉全表到上下文。
- **`+cells-get` 滥用**:当只需要数据值时,使用 `+csv-get`token 开销约为 `+cells-get` 的 1/5。只有确实需要公式、样式或批注时才用 `+cells-get`
- **忽略分页标志**:读取返回 `has_more=true` 时,说明还有更多数据。如果任务需要完整数据,必须继续分页读取,不能只处理第一页就开始写入
- **直接按 `+cells-get` 返回二维数组下标推导真实位置**`ranges[n].cells[i][j]` 里的 `i/j` 只是返回数组下标,不等于真实表格行列。定位真实行号必须用 `ranges[n].row_indices[i]`,定位真实列字母必须用 `ranges[n].col_indices[j]`;若 `--skip-hidden=true`、请求范围越界被裁剪,或最后一行是部分返回,错误地自己数下标会立刻错位
- **CSV 行号计数错误**`+csv-get` 返回的 CSV 遵循 RFC 4180 标准,被双引号 `"..."` 包裹的字段中的换行符属于**字段内容的一部分**(即单元格内换行),不代表新的一行。计算行号时必须按**逻辑记录**计数,而非按物理换行符 `\n` 计数
- **手动数列确定列号**:禁止通过在 CSV 表头中手动数逗号/字段来确定目标列的列字母。当列数超过 10 时,手动计数极易产生 off-by-one 偏移(例如把 W 列误判为 X 列)。**必须使用 `col_indices`**:先在 CSV 表头中找到目标字段名是第 j 个字段0-based再用 `col_indices[j]` 获取该列的实际列字母
- **用数据列的值推导行号(常被巧合掩盖)**CSV 中常见"序号 / ID / 编号 / No."等形似行号的列,其值与实际表格行号**没有任何绑定关系**——序号可能跳号1,2,3,5,6...)、可能从非 1 开始、可能有重复或被中途重置。此规则适用于**所有需要行号的下游操作**:合并单元格、区间写入/清空/格式化、插入/删除行、条件格式范围、筛选器范围、图表数据源、透视表范围、搜索替换范围等等——**凡是要把行号填进任何工具参数的场景,行号一律从 `annotated_csv` 中目标行开头的 `[row=N]` 前缀直接读取**,禁止用"序号=行号"、"表头占 1 行所以数据从第 2 行开始"、"第 N 个序号就在第 N+1 行"等心算,也禁止先心算再"事后核对"。**危险特征**:前几十行中序号恰好等于表格行号(典型成因:表头 +1 与一次跳号 -1 的偏移互相抵消形成巧合),模型一旦把这个巧合当作规律,会在后续所有行沿用;而中间再出现跳号时,从该行起整块区域全部错位,且错位不自查很难发现。**正确工作流**:①在 `annotated_csv` 里定位目标逻辑行(按字段内容匹配);②直接读取该行开头的 `[row=N]` 前缀得到真实表格行号;③把这个行号填进下游工具参数。区间操作时,起始行用 start 行的 `[row=N]`、结束行用 end 行的 `[row=N]`。**自检**:动手前,在 `annotated_csv` 靠后位置再抽 1~2 行,核对 `[row=N]` 是否与首列"序号"一致——不一致(典型:`[row=57] 58,...`)即说明有跳号/隐藏行,更要严格从 `[row=N]` 取值,不要被序号列迷惑
- **`row_count``current_region` 都不能单独定末行**`+workbook-info``row_count` 是 sheet 的**网格物理行数**(常是 200 / 1000 等默认值),通常**大于**真实数据末行——直接按它把 `--range` 拉到 `S200` 会读回大片空行,浪费上下文。反过来,`+csv-get` 返回的 `current_region` 是从锚点扩展、被空行空列围住的连续块,**遇表中部整行空行就截断**,可能**小于**真实数据范围漏掉空行之后的行典型反例180 行有数据、81 行空、82 行起还有数据,`current_region` 只到 8082 行起整段被漏读)。正确做法:把 `row_count` 当**上界**、`current_region` 当**起点参考**,在二者之间按下方「确定数据范围的正确流程」确认真实末行(含跨过中间空行的核对),不要只信其一。
- **current_region 当作纯数据范围**`current_region` 返回的是从请求范围向四周扩展到被空行空列包围的**连续非空区域**,等价于 Excel 的 Ctrl+Shift+\*。它包含该区域内**所有非空行**——不仅包含数据行,还可能包含标题行、汇总行(如"总计")、签名行(如"编制人/审批人")、脚注等非数据内容。**严禁直接将 `current_region` 的末尾行作为数据范围的结束行**。正确做法见下方「确定数据范围的正确流程」
### 确定数据范围的正确流程(排序、筛选、批量写入等操作前必做)
当后续操作需要精确的数据范围(如排序、筛选、删除、批量写入)时,仅靠 `current_region` 探测到的范围是不够的——它**两头都可能不准**:表中部有整行空行时会被截断(末行偏小、漏数据),表尾有汇总 / 签名行时又会偏大。必须同时确认数据的**起始行**和**结束行**。具体步骤:
1. **确认起始行**:读取前 5~10 行,识别表头行位置,数据起始行 = 表头行 + 1
2. **确认结束行**(关键步骤,不可跳过):
- **先防截断(漏数据)**:拿 `+workbook-info` 的物理 `row_count` 当上界,与 `current_region` 末行对比。若 `current_region` 末行 **远小于** `row_count`(差出很多空间),不要直接采信——在 `current_region` 末行之后再探一段(如往下读到 `row_count`,或分段扫到首个连续空白区),确认空行之后确实没有数据;典型反例:`row_count=327``current_region` 只到第 80 行,第 81 行空、82 行起还有数据,只读到 80 就漏了一大段。
- **再排尾部非数据行**:读取确认到的末行附近若干行(建议末尾 5~10 行),逐行排除:
- **汇总行**:内容为"合计"、"总计"、"小计"、"总计:"等
- **签名/审批行**:内容为"编制人"、"审核人"、"部门负责人"等
- **空行或分隔行**:整行为空或仅有边框
- **备注/脚注行**:注释性文字、说明文字等
3. **最终数据范围** = 起始行 ~ 最后一条有效数据行(跨过中间空行、排除尾部非数据行)
**示例**`current_region` 返回 `A1:N51`,读取 Row 48~51 发现:
- Row 49: 序号=47, 姓名=xxx, 有正常数据 → ✅ 数据行
- Row 50: "总计", 有合并单元格 → ❌ 汇总行
- Row 51: "总经理:...", "编制人:..." → ❌ 签名行
- **正确数据范围 = A3:N49**(而非 A3:N51
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+cells-get` | read | 单元格 |
| `+dropdown-get` | read | 对象 |
| `+csv-get` | read | 单元格 |
| `+table-get` | read | 单元格 |
## Flags
### `+cells-get`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | A1 范围,如 `A1:F10`(不带 sheet 前缀;用 `--sheet-id` / `--sheet-name` 指定 sheet |
| `--include` | string_slice | optional | 要返回的信息类别,逗号分隔多个(可选值:`value` / `formula` / `style` / `comment` / `data_validation` |
| `--max-chars` | int | optional | 单次返回字符上限,默认 500000兜底防爆。大数据通常宜重定向落盘做分析仅当要让结果直接进上下文、又不触发文件转存时才调小如 25000以 has_more 分页 |
| `--skip-hidden` | bool | optional | 跳过隐藏行列,默认 `false` |
### `+dropdown-get`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | A1 范围,如 `A2:A100`(不带 sheet 前缀;用 `--sheet-id` / `--sheet-name` 指定 sheet |
### `+csv-get`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | A1 范围,如 `A1:F30`(不带 sheet 前缀;用 `--sheet-id` / `--sheet-name` 指定 sheet |
| `--max-chars` | int | optional | 单次返回字符上限,默认 500000兜底防爆。大数据通常宜重定向落盘做分析仅当要让结果直接进上下文、又不触发文件转存时才调小如 25000以 has_more 分页 |
| `--include-row-prefix` | bool | optional | 是否在每行前加 `[row=N]` 前缀,默认 `true` |
| `--skip-hidden` | bool | optional | 跳过隐藏行列,默认 `false` |
### `+table-get`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--sheet-id` | string | optional | 只读该子表(按 id省略则读所有子表 |
| `--sheet-name` | string | optional | 只读该子表(按名);省略则读所有子表 |
| `--range` | string | optional | 读取的 A1 范围;省略则读每个子表的完整 used range会跨过表中部的整行空行 / 整列空列,不会被截断) |
| `--no-header` | bool | optional | 把第一行当数据而非表头(列名取 col1/col2 …) |
## Examples
### `+csv-get`
公共四件套:`--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(前两者 XOR后两者 XOR
示例:
```bash
# 简单读sheet 定位必填:--sheet-name 或 --sheet-id 必给一个range 的 Sheet1! 前缀不能替代它)
lark-cli sheets +csv-get --url "https://example.feishu.cn/sheets/shtXXX" --sheet-name "Sheet1" --range "A1:F30"
# 用 sheet-name 模糊定位(运行时框架会先解析到 sheet-id
lark-cli sheets +csv-get --spreadsheet-token shtXXX --sheet-name "销售明细" --range "A1:F30"
```
输出契约envelope.data
- `annotated_csv` — 含 `[row=N]` 前缀的 CSV 主入口
- `col_indices` / `row_indices` — 列字母 / 行号映射数组
- `current_region` — 从锚点扩展到被空行空列包围的连续区域的 A1 范围。⚠️ **它不是整表真实边界**:遇表中部整行空行 / 整列空列会截断、可能小于真实数据范围;表尾的汇总 / 签名 / 脚注又可能让它大于纯数据范围。判断整表是否读全须拿 `+workbook-info` 的物理 `row_count` 当上界交叉核对(见上方「`row_count``current_region` 都不能单独定末行」)
- `row_count` / `col_count`**本次返回的行 / 列数**= `actual_range` 的尺寸,随 `--range` 变),**不是整表物理总行列数**;整表物理尺寸取 `+workbook-info`
- `has_more` — 当前 `--range` 是否因 `--max-chars` 被截断(截断后续读接着用 `--range`);它**只反映本次 range 内是否读完**`has_more=false` **不代表整表已读全**range 之外的数据不在判断内)
> 要按列类型结构化读出(喂 DataFrame、或 round-trip 回 `+table-put`)用 `+table-get`(见下);`+csv-get` 给的是带 `[row=N]` 前缀的纯值快照,下游需要行号/列坐标时直接从前缀与 `col_indices` 取。
### `+cells-get`
示例:
```bash
# 读 A1:F10 的公式 + 样式sheet 定位必填)
lark-cli sheets +cells-get --url "https://example.feishu.cn/sheets/shtXXX" --sheet-name "Sheet1" \
--range "A1:F10" --include formula,style
```
> ⚠️ 调用方在 `cells[i][j]` 中**不能**用下标推真实行列:必须读 `ranges[n].row_indices[i]` / `ranges[n].col_indices[j]`。
### `+table-get`(飞书 → DataFrame类型保真读出
`+table-put`(写入侧,见 write-cells reference的镜像把表格读回与 `--sheets` 完全同构的 typed 协议(`sheets[]` + `columns:[列名]` + `data:[[行]]` + `dtypes:{列名:pandas_dtype}` + `formats?:{列名:number_format}` + `range`),可直接喂回 `+table-put` 或一行还原 DataFrame。
**默认(不带 `--range`)读取整张子表的完整 used range**:会跨过表中部的整行空行 / 整列空列,覆盖到真实数据边界。每个子表都回传实际读取的 `range`(如 `A1:F10`)——`+table-get` 不返回分页 / 截断标志,这个 `range` 是判断是否读全的唯一信号:拿它和源 xlsx 行列数、关键末行 / 末日期交叉核对,确认读取完整。仍要精确控制范围时显式传 `--range`
列类型从每列 `number_format` 推断(日期格式→`date`/`datetime64[ns]`、数值→`number`/`float64`、bool→`bool``date` 列的序列号转回 ISO `yyyy-mm-dd`——日期、数字往返不丢类型。**列类型只在该列所有非空值一致时才定(`number` / `date` / `bool`);一列混了类型(如数字列混入「暂无」、日期列混入裸数字)会降为 `string`dtypes 输出 `object`),让 `dtypes``data` 里每个值自洽——能 round-trip 回 `+table-put`、不让 pandas `astype` 崩。降级是无损的(脏值原样保留为文本);若要把零星脏值转成数值列,交给调用方在 pandas 侧做(`to_numeric(errors='coerce')`),那里原始值仍在、可追溯。** 默认读所有子表、第一行当表头(`--no-header` 把首行当数据、列名取 `col1` / `col2` …)。
```bash
# 默认读所有子表 → sheets[](与 +table-put 的 --sheets 同构,可喂回或转 DataFrame
lark-cli sheets +table-get --url "<表URL>"
# 可选:--sheet-name / --sheet-id 限定只读某一个子表(不给则读全部)
lark-cli sheets +table-get --url "<表URL>" --sheet-name "销售"
```
#### 输出 → DataFrame用 `sheet_to_df` helper
输出形状对齐 pandas split`columns` 是列名数组、`data` 是二维数据、`dtypes``{列名: pandas_dtype_str}` 映射。直接喂给 `pd.DataFrame(...).astype(...)` 就能一次性还原所有列类型(不必逐列 `to_datetime` / `to_numeric`)。本 skill 把这段 2 行 helper 打包成可 import 的 [`scripts/sheets_df.py`](../scripts/sheets_df.py)(含 `df_to_sheet``sheet_to_df`,写入 / 读回成对):
```python
from sheets_df import sheet_to_df
# 单 sheet
df = sheet_to_df(out["data"]["sheets"][0])
# 多 sheet——按名字取
sheets = {s["name"]: sheet_to_df(s) for s in out["data"]["sheets"]}
df_sales = sheets["销售"]
```
> 显示格式(千分位、百分比、自定义日期)在 `sheet["formats"]`pandas 不消费;改完数据 round-trip 回去时透传给 `+table-put` 即可,飞书侧显示不变。
#### round-trip读 → 改 → 写回(写读对偶)
`sheet_to_df``df_to_sheet` 一对镜像 helper[`scripts/sheets_df.py`](../scripts/sheets_df.py))让 round-trip 三段读 / 改 / 写各一行:
```python
import json, subprocess
from sheets_df import df_to_sheet, sheet_to_df
# 1. 读
out = json.loads(subprocess.check_output(
["lark-cli","sheets","+table-get","--url",URL,"--sheet-name","销售"]))
sheet = out["data"]["sheets"][0]
df = sheet_to_df(sheet)
# 2. 改pandas 操作)
df["营收"] = df["营收"] * 1.1
# 3. 写回formats 是飞书侧显示格式pandas 不消费,透传保留显示)
payload = {"sheets": [df_to_sheet(df, sheet["name"], formats=sheet.get("formats"))]}
subprocess.run(["lark-cli","sheets","+table-put","--url",URL,"--sheets","-"],
input=json.dumps(payload).encode(), check=True)
```
`sheet_to_df(sheet)` 消费 `(columns, data, dtypes)``df_to_sheet(df, name, formats=...)` 重新生成同样三个字段——读 / 写完全对偶,只有 `formats` 需要手工透传一次。
### Validate / DryRun / Execute 约束
- `Validate` 阶段只做 XOR 检查、Enum 合法性、防爆参数上限校验;**禁止**联网(如不能用 `--sheet-name` 提前去查 `sheet-id`)。
- `DryRun` 输出请求模板:`--sheet-name` 在 dry-run 输出里生成为 `<resolve:销售明细>` 占位符,不实际解析。
- `Execute` 阶段才进行 sheet-name → sheet-id 解析与 API 调用。

View File

@ -0,0 +1,111 @@
# Lark Sheet Search & Replace
## 替换前 dry-run + 范围明确(替换前必做)
`+cells-replace` 的副作用是不可逆的(除非另写代码回滚)。执行前必须:
1. **明确替换范围**:必须显式说明"只替换 X 列 / X 区域,还是全表替换"。**禁止**默认全表替换——容易误改无关列。范围应由用户指令决定,模糊时主动询问。
2. **dry-run 命中数量**:先用 `+cells-search` 在同一范围、同一关键词、同一匹配选项(大小写 / 精确 / 正则)下统计命中数量。把数量和**期望命中数**(用户明示的或基于业务理解推断的)对照——一致才进入 `+cells-replace`,不一致先排查(关键词太宽?范围太大?)。
3. **替换后回读校验**:执行后再次 `+cells-search` 旧关键词,预期为 0并对替换后的若干代表性单元格回读确认值符合预期。
## 使用场景
读写。在飞书表格中搜索和替换文本。本 reference 覆盖 2 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 搜索/定位文本 | `+cells-search` | 返回匹配的单元格位置,支持正则、精确匹配等 |
| 查找并替换文本 | `+cells-replace` | 批量替换文本;`--regex` 模式下 `--replacement` 可用 `$1``$2` 引用 `--find` 的捕获组 |
**常见配置错误(必须注意)**
- **不要把操作动词当搜索词**:用户说"汇总金额"是一个操作动作(求和),不是要搜索"汇总金额"这个文本。只有当确实需要定位某个文本值的位置时才用 `+cells-search`
- **不要用搜索来了解表格结构**:要了解表头和数据结构时,应使用 `+csv-get` 读取前几行,而不是用 `+cells-search` 逐个猜测字段名
- **注意正则特殊字符**:使用正则匹配时,`.``*``(``)` 等特殊字符需要转义
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+cells-search` | read | 单元格 |
| `+cells-replace` | write | 单元格 |
## Flags
### `+cells-search`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--find` | string | required | 待查找文本(与 `--regex` 配合时按正则解释) |
| `--range` | string | optional | 查找范围A1 格式);省略时整表 |
| `--match-case` | bool | optional | 大小写敏感 |
| `--match-entire-cell` | bool | optional | 完全匹配整个单元格 |
| `--regex` | bool | optional | 把 `--find` 按正则解释 |
| `--include-formulas` | bool | optional | 也在公式文本中搜索 |
| `--max-matches` | int | optional | 防爆,默认 5000隐藏 flag不在 `--help` 列出,但可正常传入) |
| `--offset` | int | optional | 跳过前 N 个匹配(分页用),默认 0 |
### `+cells-replace`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--find` | string | required | 待替换文本 |
| `--replacement` | string | required | 替换为;传空字符串 `""` 等价于「删除内容」 |
| `--range` | string | optional | 替换范围A1 格式);省略时整表 |
| `--match-case` | bool | optional | 大小写敏感 |
| `--match-entire-cell` | bool | optional | 完全匹配整个单元格 |
| `--regex` | bool | optional | 把 `--find` 按正则解释 |
| `--include-formulas` | bool | optional | 也在公式文本中替换 |
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR 规则)。
### `+cells-search`
示例:
```bash
# 普通查找
lark-cli sheets +cells-search --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --find "张三"
# 正则 + 范围限定
lark-cli sheets +cells-search --spreadsheet-token shtXXX --sheet-id "$SID" \
--find "^[A-Z]{2}-\\d{4}$" --regex --range "A2:A1000"
```
输出契约envelope.data
- `matches` — 命中 cell 列表,每条含 `address`A1+ `value` + `sheet_id`
- `total_matches` — 匹配总数
- `has_more` / `next_offset` — 分页游标(命中数超过单页上限时用于继续读取)
### `+cells-replace`
示例:
```bash
# 先 dry-run 预览
lark-cli sheets +cells-replace --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --find "v1" --replacement "v2" --dry-run
# 确认后执行
lark-cli sheets +cells-replace --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --find "v1" --replacement "v2"
# 正则捕获组:把 "2026-03" 重排成 "03/2026"$1/$2 引用 --find 的捕获组)
lark-cli sheets +cells-replace --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --regex --find "(\\d{4})-(\\d{2})" --replacement "$2/$1" --dry-run
```
> `+cells-replace` 虽然 Risk = write但范围大或正则错可能改一堆。**强烈推荐工作流**:先 `+cells-search` 看匹配数,再 `+cells-replace --dry-run` 预览,最后真正执行。
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`--find` 非空;正则模式下 `--find` 必须是合法正则。
- `DryRun``+cells-search` 输出请求模板;`+cells-replace` 额外返回预估替换数(`would_replace_count`)。
- `Execute`:写后不自动回读;如需确认,自行用 `+cells-search` 复查旧值是否已不再命中。

View File

@ -0,0 +1,212 @@
# Lark Sheet Sheet Structure
## 结构性操作影响面预检(插入 / 删除行列前必做)
插入 / 删除行列、隐藏 / 取消隐藏、冻结、行列分组都会让原表的引用关系发生偏移。**操作前必须**先打印以下三类信息,并评估操作是否会让它们失效;否则禁止执行:
1. **当前合并单元格范围**(来自 `+sheet-info``merged_cells`):插入行 / 列时,跨过插入位置的合并区域可能扩张或断裂;删除行 / 列时合并区域可能直接消失。
2. **现有公式的引用范围**(用 `+cells-get` 抽样附近行 + 跨表引用 + 透视表 / 图表 / 条件格式 / 筛选器的数据源 range插入 / 删除会导致 `=SUM(B4:B13)` 这种相对引用偏移;如果操作发生在引用范围内部,可能产生 `#REF!`
3. **数据验证(下拉列表)规则的应用范围**:列表来源是某个区域时,区域被部分删除会让规则失效。
不可逆的影响必须先在回复中告知用户,得到确认再执行。
## 使用场景
读写。管理子表结构与布局。本 reference 覆盖 9 个 shortcut按用途分两类
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看子表布局 | `+sheet-info` | 获取行高、列宽、隐藏行列、行列分组、合并单元格等信息 |
| 变更子表结构 | `+dim-{insert|delete|hide|unhide|freeze|group|ungroup|move}` | 插入/删除/隐藏/取消隐藏/冻结/分组/移动行列 |
注意:
- 当表格存在合并单元格时,应结合返回的 `merged_cells` 判断表头、分组标题和区域语义
- 不要把合并区域中非左上角的空白单元格理解为"无内容";通常应将左上角单元格的内容视为整个合并区域的语义内容
- 插入用 `+dim-insert``--position`(插入位置;行用 1-based 行号如 `3`,列用字母如 `C`,新行/列插在此位置**之前**+ `--count`(插入数量,>0。新行/列样式继承用 `--inherit-style``before`/`after`/`none`
- 例如"在第 20 行后新增 116 行"`--position 21 --count 116`"第 20 行后"即 1-based 行号 21
**区间表达统一为 A1 风格**:所有涉及"一段连续行/列"的 shortcut 都用同一套 A1 闭区间字符串语法,**不存在 inclusive / exclusive / 0-based / 1-based 跨命令差异**
| 命令 | 用什么 flag 表达区间 / 位置 | 例子 |
| --- | --- | --- |
| `+dim-insert` | `--position` + `--count` | `--position 3 --count 5`(在第 3 行前插 5 行)/ `--position C --count 2`(在 C 列前插 2 列) |
| `+dim-delete` / `+dim-hide` / `+dim-unhide` / `+dim-group` / `+dim-ungroup` / `+rows-resize` / `+cols-resize` | `--range` | `"3:7"`(第 3-7 行,闭区间)/ `"C:F"`C-F 列,闭区间)/ `"5"``"C"`(单行/列) |
| `+dim-move` | `--source-range`(源区间)+ `--target`(目标位置) | `--source-range "3:7" --target 12`(把第 3-7 行移到第 12 行前)/ `--source-range "C:F" --target H` |
行用 1-based 数字、列用字母——跟 Excel / 飞书 UI 看到的行号、列字母完全一致。
**常见配置错误(必须注意)**
- **插入列直接用字母**`+dim-insert``--position` 在列场景直接传字母(如 `C`),不要把列字母换算成 0-based 索引
- **插入后引用偏移**:插入行/列后,原有数据的行号 / 列字母会发生偏移。如果插入后还需要对原有区域执行写入操作,必须重新计算偏移后的位置
- **删除行列前先确认范围**:删除操作不可逆,执行前应确认 `--range` 精确无误。可先用 `+csv-get` 读取目标区域验证内容(`+csv-get` / `+cells-get``lark-sheets-read-data`
- **"在 D 列左侧新增一列"的正确写法**`--position D --count 1`(新列插在 D 列之前);要继承左侧列样式加 `--inherit-style before`
- **`+dim-move` 同维度约束**`--source-range` 是行区间时 `--target` 必须是行号(数字),是列区间时 `--target` 必须是列字母——不可一行一列混用
- **插入列后必须检查多行表头合并区域**:很多表格有 2-3 行的合并表头。插入列后,原有的合并区域不会自动扩展到新列。必须先用 `+sheet-info --include merges` 读取合并区域,插入后将跨越插入位置的合并区域重新设置(用 `+cells-{merge|unmerge}`),否则新列的表头会是空的、格式不连续
- **公式写入范围跳过表头行**:写入公式时从数据行开始(不是第 1 行)。先确认表头占几行(可能 1-3 行),公式的起始行 = 表头行数 + 1
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+sheet-info` | read | 工作表 |
| `+dim-insert` | write | 工作表 |
| `+dim-delete` | high-risk-write | 工作表 |
| `+dim-hide` | write | 工作表 |
| `+dim-unhide` | write | 工作表 |
| `+dim-freeze` | write | 工作表 |
| `+dim-group` | write | 工作表 |
| `+dim-ungroup` | write | 工作表 |
| `+dim-move` | write | 工作表 |
## Flags
### `+sheet-info`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--include` | string_slice | optional | 要返回的结构信息类别,逗号分隔多个(可选值:`merges` / `row_heights` / `col_widths` / `hidden_rows` / `hidden_cols` / `groups` / `frozen` |
| `--range` | string | optional | 限定只返回该 A1 范围的结构信息;省略时返回整表 |
### `+dim-insert`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--inherit-style` | string | optional | 新行/列样式继承策略 enum`before`(继承前一行/列)/ `after`(继承后一行/列)/ `none`(默认)(可选值:`before` / `after` / `none` |
| `--position` | string | required | 插入位置(在此行/列**之前**插入):行用 1-based 行号如 `3`;列用字母如 `C` |
| `--count` | int | required | 插入数量(>0 |
### `+dim-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 要删除的行/列闭区间;行用 1-based 数字如 `3:7` 或单行 `5`,列用字母如 `C:F` 或单列 `C` |
### `+dim-hide`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 要隐藏的行/列闭区间;行如 `3:7`,列如 `C:F` |
### `+dim-unhide`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 要取消隐藏的行/列闭区间;行如 `3:7`,列如 `C:F` |
### `+dim-freeze`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--count` | int | required | 冻结前 N 行/列;传 0 解除冻结 |
### `+dim-group`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--depth` | int | optional | 嵌套分组的层级(创建到第几层),默认 1 |
| `--group-state` | string | optional | 分组初始展开状态(可选值:`expand` / `fold`)(默认 `expand` |
| `--range` | string | required | 要创建分组的行/列闭区间;行如 `3:7`,列如 `C:F` |
### `+dim-ungroup`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--depth` | int | optional | 要取消的分组层级,默认 11=最外层,数字越大越内层) |
| `--range` | string | required | 要取消分组的行/列闭区间;行如 `3:7`,列如 `C:F` |
### `+dim-move`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--source-range` | string | required | 要移动的源行/列闭区间;行如 `3:7`,列如 `C:F` |
| `--target` | string | required | 目标位置(移到此行/列**之前**):行用 1-based 行号如 `12`,列用字母如 `H`。必须与 `--source-range` 同维度(行/列) |
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR
### `+sheet-info`
输出契约:返回子表的行高 / 列宽 / 隐藏 / 合并 / 分组等布局元信息。
### `+dim-insert`
```bash
# 在第 10 行前插 3 行,继承上方样式
lark-cli sheets +dim-insert --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-id "$SID" --position 10 --count 3 --inherit-style before
# 在 C 列前插 2 列
lark-cli sheets +dim-insert --url "..." --sheet-id "$SID" --position C --count 2
```
### `+dim-delete`
```bash
# 删除第 5-7 行
lark-cli sheets +dim-delete --url "..." --sheet-id "$SID" --range "5:7" --yes
# 删除 D-F 列
lark-cli sheets +dim-delete --url "..." --sheet-id "$SID" --range "D:F" --yes
```
### `+dim-hide` / `+dim-unhide`
```bash
lark-cli sheets +dim-hide --url "..." --sheet-id "$SID" --range "5:7"
lark-cli sheets +dim-unhide --url "..." --sheet-id "$SID" --range "5:7"
lark-cli sheets +dim-hide --url "..." --sheet-id "$SID" --range "C:F"
```
### `+dim-move`
```bash
# 把第 3-7 行移到第 12 行前
lark-cli sheets +dim-move --url "..." --sheet-id "$SID" --source-range "3:7" --target 12
# 把 C-F 列移到 H 列前
lark-cli sheets +dim-move --url "..." --sheet-id "$SID" --source-range "C:F" --target H
```
### `+rows-resize` / `+cols-resize`
> ⚠️ 这两条 shortcut 来自 `lark-sheets-range-operations` 的 `+rows-resize / +cols-resize` tool分组在"工作表"是为了发现性)。详细参数和示例在 `lark-sheets-range-operations.md`。
>
> 常规写法:行高走 `--range` + `--height <px>`、列宽走 `--range` + `--width <px>`,无需再传 `--type`(等价于 `--type pixel`);多行 / 多列不同尺寸用 map 形态 `--heights` / `--widths`(如 `--widths '{"A":100,"C:E":120}'`)一次原子完成,不要拆多次调用或走 `+batch-update`。`--type standard` / `--type auto` 用于非像素模式,不能与像素 flag 同给。`+cols-resize.--type` 不接受 `auto`(列宽不支持自动适应)。⚠️ 单位是像素(不是 Excel 字符单位 / 磅)。
### `+dim-freeze`
```bash
# 冻结前 1 行(--count 传 0 解除冻结)
lark-cli sheets +dim-freeze --url "..." --sheet-id "$SID" --dimension row --count 1
```
### `+dim-group` / `+dim-ungroup`(大纲)
> 仅当用户明确说"行分组 / 列分组 / 大纲 / outline"时触发;按字段做数据分组用 `+pivot-create`。
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`--range` / `--source-range` 必须是合法 A1 闭区间(行用数字、列用字母,不可混用);`+dim-insert``--count` > 0`+dim-move``--target` 必须与 `--source-range` 同维度(行 vs 列);`+dim-delete` 强制 `--yes``--dry-run``+rows-resize` / `+cols-resize` 的统一形态(`--range` + `--height`/`--width``--type`)与 map 形态(`--heights`/`--widths`)二选一、不可混用;详见 `lark-sheets-range-operations.md`
- `DryRun`:写操作输出"将要 PATCH 的目标范围 + 目标参数"。
- `Execute`:写后不自动回读;如需确认,自行调用 `+sheet-info --include row_heights,col_widths,hidden_rows,hidden_cols,groups,frozen` 查看受影响的范围。

View File

@ -0,0 +1,149 @@
# Lark Sheet Sparkline
## 真对象硬约束
当用户要求"迷你图 / 趋势线 / 单元格内图表"时,**必须**通过 `+sparkline-{create|update|delete}` 创建真实的迷你图对象。**禁止**用文本字符(如 `▁▂▃▅▇`)拼接在单元格里、或用 `SPARKLINE()` 公式函数(已禁用)代替。判断标准:交付后 `+sparkline-list` 必须能返回该对象。
## 使用场景
读写迷你图对象。本 reference 覆盖 4 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看已有迷你图 | `+sparkline-list` | 获取迷你图的类型、数据源和样式配置 |
| 创建/更新/删除迷你图 | `+sparkline-{create|update|delete}` | 对迷你图执行写入操作 |
典型工作流:先读取现有迷你图了解配置 → 执行创建/更新/删除 → **必须再次读取验证结果**
**常见配置错误(必须注意)**
- **数据源范围要精确**:迷你图的数据源范围必须与实际数据行列精确对应,范围偏移会导致图形展示错误
- **不要与 SPARKLINE() 公式混淆**:飞书表格的 `SPARKLINE()` 公式函数已被禁用,迷你图只能通过 `+sparkline-{create|update|delete}` 的对象方式创建
- **创建后必须验证**:调用 `+sparkline-list` 确认迷你图配置正确
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+sparkline-list` | read | 对象 |
| `+sparkline-create` | write | 对象 |
| `+sparkline-update` | write | 对象 |
| `+sparkline-delete` | high-risk-write | 对象 |
## Flags
### `+sparkline-list`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--group-id` | string | optional | 按 group_id 过滤 |
### `+sparkline-create`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | required | JSON`{config共享样式配置, sparklines迷你图数组}`;完整字段结构跑 `--print-schema` |
### `+sparkline-update`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--group-id` | string | required | 目标组 id |
| `--properties` | string + File + Stdin复合 JSON | required | JSON`{config, sparklines}`;先 `+sparkline-list --group-id <id>` 回读再 patch完整字段结构跑 `--print-schema` |
### `+sparkline-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--group-id` | string | required | 目标组 id |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+sparkline-create` `--properties` / `+sparkline-update` `--properties`
_创建/更新/部分删除的迷你图属性_
**顶层字段**
- `config` (object?) — 迷你图样式配置, 相同 groupId 的迷你图共享相同的样式 { theme_type?: enum, non_num_show_as?: enum, empty_show_as?: enum, contain_hidden_cells?: boolean, series_color?: string, …共 13 项 }
- `sparklines` (array<object>?) — 迷你图项列表 each: { sparkline_id?: string, position?: object, source?: string, source_range?: object }
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR。迷你图用 **两层 id** 管理——`group_id` 选组(一组同形态的迷你图共享类型 / 样式 / 数据源映射),`sparkline_id` 在组内选具体某一项。注意:不等同于已禁用的 `SPARKLINE()` 公式函数。
> **何时需要先 `+sparkline-list`**
> - `+sparkline-update`**总是**需要——拿到组内每一项的 `sparkline_id`,回填到 `properties.sparklines[i]`server 用它做映射。
> - `+sparkline-delete`**不需要** `sparkline_id`——CLI 仅支持按 `--group-id` 整组删除(该 shortcut 没有 `--properties`)。
### `+sparkline-list`
```bash
# 列出整张子表的所有迷你图组
lark-cli sheets +sparkline-list --url "..." --sheet-id "$SID"
# 钉到单组:返回该组每一项的 sparkline_idupdate / partial-delete 必需)
lark-cli sheets +sparkline-list --url "..." --sheet-id "$SID" --group-id "grpA"
```
### `+sparkline-create`
> `--properties` 顶层只有 `config`(同组共享样式,如 `line_width` / `points` / `extremum_max` / `extremum_min`)和 `sparklines`(迷你图项数组)两个字段。`sparklines[i]` 每项必须含 `position`(落点 cell`row` + `col`+ `source`(数据 A1 范围,与 `source_range` 二选一create 时 `sparkline_id` 可省略,由系统生成。
```bash
lark-cli sheets +sparkline-create --url "..." --sheet-id "$SID" --properties @sparkline.json
```
`sparkline.json` 示例(在 F 列嵌入两行折线迷你图,数据分别来自 A2:E2 和 A3:E3
```jsonc
{
"config": { "line_width": 2 },
"sparklines": [
{"position": {"row": 1, "col": "F"}, "source": "'Sheet1'!A2:E2"},
{"position": {"row": 2, "col": "F"}, "source": "'Sheet1'!A3:E3"}
]
}
```
### `+sparkline-update`
> 两步式:先 `+sparkline-list --group-id <id>` 拿当前组的 `sparkline_id` 列表,再构造 `properties.sparklines[]`——**每项必须带 `sparkline_id`**。只改样式可只传 `properties.config`(不带 `sparklines`,整组样式覆盖式更新)。
```bash
# 假设 +sparkline-list 已返回 group_id=grpA组内 sparkline_id=sl_1 / sl_2
lark-cli sheets +sparkline-update --url "..." --sheet-id "$SID" --group-id "grpA" --properties '{
"sparklines": [
{"sparkline_id":"sl_1","source":"'Sheet1'!A2:A20"},
{"sparkline_id":"sl_2","source":"'Sheet1'!B2:B20"}
]
}'
```
### `+sparkline-delete`
> CLI 仅支持**整组删除**:传 `--group-id` 删掉该组全部迷你图。该 shortcut **没有** `--properties`,无法只删组内单项(需求上要"留一部分"时,改用 `+sparkline-update` 重写该组的 `sparklines` 列表,而不是 delete。强制 `--yes` 或 `--dry-run`;先 `--dry-run` 确认要删的目标组。
```bash
# 删整组
lark-cli sheets +sparkline-delete --url "..." --sheet-id "$SID" --group-id "grpA" --yes
```
### Validate / DryRun / Execute 约束
- `Validate`
- XOR 公共四件套;`+sparkline-{update,delete}` 必须 `--group-id`
- **`+sparkline-update`**:当 `properties.sparklines` 非空时,每一项必须含 `sparkline_id`CLI 预检,错误信息会指回 `+sparkline-list`,避免命中服务端的不可读拒绝);只传 `properties.config`config-only update合法、不触发 sparkline_id 检查。
- **`+sparkline-delete`**:只接 `--group-id`(整组删除),**没有** `--properties`,无法删组内单项。
- `--properties`(仅 `+sparkline-create` / `+sparkline-update`)顶层只接 `config`(同组共享样式)和 `sparklines`(迷你图项数组);`+sparkline-create` 要求每个 `sparklines[i]``position``source`(或 `source_range`,二选一)。
- `+sparkline-delete` 强制 `--yes``--dry-run`
- `DryRun`:写操作输出"将要 POST/PATCH/DELETE 的 sparkline group 请求模板"。
- `Execute`:写后不自动回读;如需确认,自行调用 `+sparkline-list --group-id <id>` 查看 `config` / `sparklines`

View File

@ -0,0 +1,205 @@
# 飞书表格样式与配色规范
> **本文定位**:飞书表格"正确视觉输出"的取值标准与美化决策流——配色、表头、对齐、数值格式、斑马纹、列宽行高、图表展示,以及新增 / 继承 / 美化已有区域三类场景的做法。
> **边界**:本文只讲"样式长什么样、怎么决策"**怎么调用工具写入样式**`cell_styles` / `border_styles` 字段、合并、resize 等参数)见 `lark-sheets-write-cells` / `lark-sheets-range-operations` / `lark-sheets-batch-update`。**条件格式**(高亮 / 标红 / 数据条 / 色阶)见 `lark-sheets-conditional-format`。本文不含 shortcut通用编辑准则见主 SKILL.md「飞书表格编辑准则」。
## 最高优先级原则
- **用户指令优先**:用户明确提出的格式要求(如"使用红色背景")具有最高权重,即使与通用审美冲突。
- **继承原表风格**:编辑前先采样原文件视觉特征(色系、边框、对齐、数字格式),新增内容必须与之对齐。严禁对已有风格的文件强行施加通用标准化格式。
- **扩展而非覆盖**:新增行列或追加数据时,目标是"扩展原模板"——继承邻近区域的表头风格、条纹节奏、边框层级、对齐方式、数字格式和列宽/行高策略。
- **美化只动样式属性,不动数据**:对**已有区域**做美化时,**只能**修改 `font` / `fill` / `border` / `alignment` / `number_format` 这 5 类样式属性。**禁止**改动原始单元格的 `value` / `formula`、合并区域、行列结构、Sheet 名称。如果美化需求需要改变数据布局(例如"汇总行加进表里"),必须把"加汇总行"和"美化"拆成两步,前者属于编辑动作、需另行得到用户授权。
- **不可见视觉属性也属保护对象**:原表的**合并范围、对齐方式H-Align/V-Align、行高列宽、数字格式**是用户能感知但不一定会明示的视觉属性。即使用户没说"保留这些"**禁止**因写入新内容而修改它们;写公式 / 写值 / 写新列时只传 `value` / `formula`,不要重置 `alignment` / `number_format` 等字段为默认值(重置等同于改动)。**例外**:用户明示要修改这些属性时(如"调整对齐 / 合并 / 列宽")才能动。
- **美化范围必须覆盖所有用户语义目标**:用户说"给表格加边框 / 美化整个表"时,范围 = 实际数据区域**含所有数据行**(含汇总行、总计行、表尾备注行),不能停在"看起来主体内容结束"的地方。落地前先用 `current_region` + 末尾 5~10 行核对真实末行(同 `lark-sheets-read-data` 的「确定数据范围的正确流程」),再设置美化范围。范围漏掉用户提到的目标行 / 列**直接判失败**。
## 美化任务 5 维度 checklist用户说"美化 / 整理 / 让表更清晰 / 适合打印"时必做)
当用户用"美化 / 整理表格 / 让表清晰 / 适合打印 / 调整样式"等口语表达**主动美化需求**时,**必须**遍历以下 5 个维度逐一落地,**只动一处就交付**(如只加边框)属于违规:
1. **表头格式区分**:表头行加粗 + 背景色填充(与数据行有色差)+ 居中对齐;多行表头时全部行同步处理
2. **对齐方式**:文本列左对齐、数值 / 货币 / 百分比列右对齐、日期 / 分类列居中;垂直方向统一居中
3. **数值格式**:每列统一小数位 + 千分位(用 `number_format`);金额列统一货币符号;同一列内**禁止**出现 0 位 / 1 位 / 2 位小数混杂
4. **边框**:覆盖范围按上方「美化范围必须覆盖所有用户语义目标」规则(含汇总 / 总计 / 表尾说明行),内外框线清晰
5. **列宽 + 行高 + 自动换行**:详细规则见 `lark-sheets-range-operations` 的「写入后列宽自适应」章节(按最长字符数扩列宽 / 长文本设置 `cell_styles.word_wrap="auto-wrap"` + 调高行高 / 长数字设置 `number_format` 防科学计数法)
**差异化标注场景**:用户要求"重复行 / 异常值 / 重要项视觉区分"时,标注列 / 行必须设置与普通数据**显著不同**的 `cell_styles`(背景色 + 加粗 + 字体色至少改一项),不能与普通数据格式完全一致。
**显式要求边框 / 表头 / 对齐时同样按上面标准落地**(不必等用户说"美化"):① 用户说"给某矩形区域加边框"必须**整个矩形含表头行、数据行、汇总行全部加内外框**,落地后核起 / 末行、末列三边界(反例:要求加边框的区域实际无任何边框);② **新建表头前先确认哪一行才是表头**——别把已有的第一行数据误当表头刷成蓝底白字,真正该加的表头列也要建出来(反例:把第一行数据误设成了表头样式);③ 新增 / 编辑区域的字号必须与原表一致,禁止 13 号与 14 号、10 号与 11 号混杂(反例:新列字号与原表不一致)。
## 通用样式规范
> 以下取值标准都在「最高优先级原则」的**继承原表风格 / 扩展而非覆盖**前提下生效:凡涉及"沿用原表"的条目,遵循该原则即可,本节不再逐条复述。
### 1. 表头样式
- 表头/汇总行须与数据区域有明确视觉区分。
- 使用低饱和度背景色搭配字体颜色(如深蓝 + 白字,浅蓝 + 黑字),文字加粗、水平居中。
- 表头覆盖多列时使用合并单元格。
### 2. 数据区域样式
- 减少垂直线条,优先使用水平浅灰细线。
- **对齐方式**:文本左对齐,数值/货币/百分比右对齐,日期或分类居中,所有内容垂直居中。
- 次要信息(备注、次要日期等)使用缩小字号或浅灰色。
- **Zebra Stripes**:数据行 > 10 行时可使用交替背景色引导视线。
- 设置前先清理原区域背景色为白色(#FFFFFF),再设置斑马纹色,避免新旧混杂。
- 优先直接设置单元格背景色,而非条件格式(除非用户要求)。
- 推荐配色:奇数行 #FFFFFF,偶数行 #F3F4F6#EBF1F8
### 3. 数值格式
- 百分比使用 `%` 符号,适当注明单位和货币符号(¥、$)。
- 大于 1000 的数字使用千分位符保留一致的小数位数12 位)。
- 涉及数据检索的须注明数据来源。
- 可使用数据条/色阶/条件格式增强可视化。
### 4. 整体结构
- 长表/宽表考虑冻结行列,方便滚动查看。
- **长文本处理**:启用自动换行,行高合理调整以确保阅读舒适,添加适当垂直留白,目标是清晰、专业、不拥挤的布局。
- 保持表格简洁,合理分组(可用合并单元格展示分组),在适当位置添加合计或汇总行。
- **区域分隔**:多阶段或多类别时,使用柔和背景色块进行逻辑分区,而非简单边框。
- **增删行列的样式规则**
- 新增整列继承同组列的表头样式、列宽、对齐和数字格式;新增整行继承同层级数据行或汇总行风格,避免写成表头风格。追加列时需判断是否应加入已有合并单元格(常见于顶部标题行)。
- 若追加位置紧邻汇总行、说明区或空白分隔区,先判断真实数据区域边界再操作,避免破坏原有结构。
- **Zebra Stripes 维护**:插入或删除行后若影响后续行奇偶性,须从受影响行往后重建条纹(先清理再重设)。少量增删用局部重建,大量变动用全局清理+统一重建。
- 具体采样与复制流程见下方「场景二:从已有区域继承美化」。
- **列宽 / 行高调整**(飞书 `+cols-resize` / `+rows-resize` 直接给像素值:统一尺寸用 `--range` + `--width`/`--height <px>`,多列 / 多行不同尺寸用 `--widths`/`--heights` map 一次原子完成,如 `--widths '{"A":100,"C:E":120}'`
- 禁止硬编码固定列宽,须根据该列实际内容长度估算像素。
- 经验估算:中文每字约 15-18px英文/数字每字约 7-9px外加 10-16px padding。
- 上下限建议 80~400px超上限启用自动换行`word_wrap: auto-wrap`+ 调整行高,而非无限加宽。
- 合并单元格不参与列宽计算,避免撑宽单列。
- 复制自原文件的列优先沿用原列宽,不重新计算覆盖。
### 5. 配色
- 优先沿用原表色板与明暗层级(见「继承原表风格」),新增区域不凭空换色,确保视觉连续。
- 背景填充选择柔和色(如浅蓝 `#DDEBF7`),区分颜色时优先同一主题色不同深浅,避免超过 3 种主题色。
### 6. 图表展示
- 遵循用户指令选择图表类型,或匹配用户意图(饼图/环形图 → 占比,折线图 → 趋势)。
- 包含必要元素:标题、图例、数据标签、坐标轴标题。
- 调整至合适大小,避免数据和标签过多堆叠。
- **图表放置防重叠**:新增图表前须计算放置区域,避免与已有图表重叠。具体步骤:
1. 调用 `+chart-list` 获取当前工作表所有已有图表的 `position`(锚点单元格:`col` 是列字母如 "A"/"B"、`row` 是 1-based 行号;以 `+chart-list` 实际返回字段为准)、`offset`(锚点内偏移:`row_offset``col_offset`,单位像素)以及 `size``width``height`,单位像素)。
2. 获取工作表的行高和列宽信息(像素)。
3. 根据每个图表的锚点 `position.row`/`position.col` + 偏移 `offset.row_offset`/`offset.col_offset` + 尺寸 `size.width`/`size.height`,结合行高列宽,计算出每个已有图表覆盖的像素矩形区域 `(x_min, y_min, x_max, y_max)`
4. 为新图表选定大小后,候选放置位置应避开所有已有矩形区域;若存在重叠则向下或向右偏移,直至找到无冲突位置。
5. 若工作表已无足够空间,优先向下方空白区域放置,保持图表间至少 1 行或 1 列的间距。
> 飞书表格中颜色需带 `#` 前缀(如 `#0070C0`),与 openpyxl 的无前缀写法不同。
> 具体工具调用参数格式,请读取对应工具 skill`lark-sheets-write-cells`、`lark-sheets-conditional-format`、`lark-sheets-range-operations` 等)。
---
## 场景化操作指南
### 场景一:新增独立样式
> 适用情况:在表格中创建全新的、具有独立视觉特征的区域,如汇总行、新表头、独立数据表等。
#### 1A. 添加汇总行 / 表头行
**决策流程:**
1. 先用 `+cells-get` 读取目标位置上方的数据区域,确认数据边界和已有样式(背景色、字体大小等)
2. 如果需要新增空行,先用 `+dim-{insert|delete|hide|unhide|freeze|group|ungroup}` 插入行
3.`+cells-set` 写入汇总公式 + 特殊样式(背景色区分 + 加粗 + 边框)
4. 如果汇总行标题需要跨列显示,追加 `+cells-{merge|unmerge}` 合并标题区域
**样式要点:**
- 汇总行使用比数据区域更深的同色系背景(如数据区 #EBF1F8 → 汇总行 #D6E4F0#4472C4 + 白字)
- 必须加粗,水平对齐方式与数据列一致(数值列右对齐,文本列左对齐)
- 上方加一条较粗的边框线,与数据区域形成视觉分隔
#### 1B. 添加独立数据表/独立区域
**决策流程:**
1. 新建 sheet或用 `+cells-get``+workbook-info` 确认已有表格的占用范围,找到空闲区域
2.`+cells-get` 采样已有表格的表头样式(背景色、字体大小、字重、对齐方式)和数据区域样式
3. 新表头复用已有表头的配色和字体参数(保持风格统一),但内容和列宽可独立
4. 新数据区域复用已有数据区域的对齐规则、边框风格、数字格式
5.`+cells-set` 一次性写入新表头 + 数据
**样式要点:**
- 必须复用:背景色色系、字体大小、字重、边框风格
- 可以独立:列宽、行高、具体数字格式(根据新数据的类型调整)
- 新旧表格之间至少留 1~2 行空白作为视觉分隔
### 场景二:从已有区域继承美化
> 适用情况:新增的行/列/区域与已有内容性质相同(数据类型、层级一致),需要无缝衔接已有格式。
#### 2A. 继续补充行/列(数据性质与已有内容一致)
**核心规则**:采样紧邻 2 行 → 判断并延续 Zebra Stripes 奇偶性 → 按 write-cells 的继承清单带齐样式写入。
**斑马纹延续要点**(本节只管"奇偶判断"这一标准,"带哪些样式字段写入"的机制见下方指针):
- 至少读 2 行(末行 + 倒数第二行)才能判断是否有斑马纹交替色
- 若倒数两行背景色不同(如 #FFFFFF#F3F4F6),新行按奇偶延续,不要固定一个色
> 具体继承哪些字段、怎么采样与写入(`+cells-get` 读源行 `cell_styles` + `border_styles`、`+sheet-info --include row_heights,merges` 读行高合并、带齐 6 类样式写入)见 `lark-sheets-write-cells` 的「新增列 / 新增行的样式继承」章节——`border_styles` 四边最易遗漏,以那里为准。
#### 2B. 基于模板区域的修改copy 保留所有格式)
**核心思路:三步分层法**
```
Step 1 — 格式铺开:`+batch-update` + `+range-copy`(或 `+range-fill`
└── 将模板行/区域的 **全部格式**(样式、边框、数字格式、数据验证等)复制到目标区域
└── 推荐用 `+range-copy --paste-type formats`(仅复制格式,目标值/公式保留),即"格式刷"
└── 若需连带公式平移填充(如公式列结构一致),改用 `+range-fill --series-type copy` 或 `+range-copy --paste-type all`
Step 2 — 内容覆写:`+batch-update` + `+cells-set`(仅传 value/formula不传任何样式
└── 将每行的实际数据写入cell_styles 全部省略,因为格式已在 Step 1 中就位
Step 3 — 微调收尾:`+rows-resize --heights` / `+cols-resize --widths`(行高列宽 map 一次原子完成)、`+batch-update` + `+cells-{merge|unmerge}` 等
└── 调整行高列宽、处理合并单元格、扩展条件格式范围等边缘情况
```
**关键注意事项:**
- Step 1 用 `+range-copy --paste-type formats` 时只铺格式、不动值/公式Step 2 再用 `+cells-set` 写值即可(`+cells-set` 默认覆盖,无需额外 flag若 Step 1 用 `--paste-type all` 连带复制了值/公式Step 2 写入同样会覆盖(默认行为)
- `+range-fill --series-type auto`(或 `linear`/`date`会自动递增数字序列1→2→3和日期序列`+range-fill --series-type copy` 则原样复制值但公式引用会自动平移
- 如果模板区域存在合并单元格copy/fill 不会复制合并状态,必须在 Step 3 中用 `+cells-{merge|unmerge}` 补全
- 如果模板区域有条件格式,需要在 Step 3 中通过 `+cond-format-update` 扩展 ranges
**场景:纯"格式刷"(用户说"把 A 列样式应用到 B 列"、"格式复制过去"、"只刷格式不改数据"**
单步即可,无需三步分层:调用 `+range-copy --paste-type formats``--source-range` 为样式来源、`--target-range` 为目标起点。参数细节见 `lark-sheets-range-operations`
### 场景三:已有区域格式美化
> 适用情况:对已存在数据的区域进行格式美化(不改变数据内容),重点处理表头、汇总行等特殊行的识别与格式设置,需特别注意合并单元格的安全操作。
#### 整体操作流程
```
1. 探查阶段
├── `+workbook-info` → 获取子表列表、行列数、冻结位置
├── `+sheet-info --include merges` → 获取合并区域
├── `+cells-get`(前几行 + 末尾几行,`--include style`)→ 采样表头/数据区/汇总行样式
└── 分析结果 → 建立区域地图(表头行号、数据起止行号、汇总行号、合并区域列表)
2. 规划阶段
├── 判断表头行:通常第 1 行或前 2 行,特征为加粗/背景色/合并/居中
├── 判断汇总行:通常最后 1~2 行,特征为加粗/SUM/AVERAGE 公式/更深背景色
├── 判断合并区域:从 `+cells-get` 返回中识别(多个单元格同值且样式相同通常暗示合并)
└── 制定美化方案:按区域分别设置样式
3. 执行阶段(按顺序)
├── 先处理合并单元格(如需取消合并再重新合并,必须先 unmerge 再 merge
├── 设置表头样式
├── 设置数据区域样式
├── 设置汇总行样式
└── 调整列宽行高
```
#### 美化中的合并单元格要点
- 编辑前先识别已有合并区域(见探查阶段),避免破坏原有语义分区。
- 美化表头/分组标题时,若需修改合并区域的范围或样式,遵循"先 `unmerge` → 修改 → 再 `merge`"顺序。
- 合并区域样式只写左上角,不要对合并内的其他单元格重复写入样式。
> 合并单元格完整的安全操作规则(含数据保护、样式占位等 5 条)见 `lark-sheets-range-operations` 的 `+cells-{merge|unmerge}` 章节。

View File

@ -0,0 +1,420 @@
# Lark Sheet Workbook
## Sheet 结构变更保守化(编辑类任务必做)
`+sheet-{create|delete|rename|move|copy|hide|unhide|set-tab-color}` 会改变原表的物理结构,是高副作用动作。执行前必须遵守:
1. **删除 / 重命名 / 隐藏 / 移动原 Sheet 需用户明示**:除非用户明示要这些操作,**禁止**擅自对**已存在**的 Sheet 执行 delete / rename / hide / move。新建 Sheet 是允许的(用于承载中间结果或透视表 / 图表对象),但应优先在原表右侧加列;只有当中间结果数量较大或会与原数据混淆时,才新建空白 Sheet同 R1
2. **Sheet 级操作前先列清单**:调用 `+sheet-{create|delete|rename|move|copy|hide|unhide|set-tab-color}` 之前,必须先调用 `+workbook-info`,把"当前所有 Sheet 名 + 可见性 + 行列数"列出来,再决定是否操作。禁止跳过列清单直接 create / delete / rename。
3. **删除 / 重命名前向用户确认**:删除是不可逆的,重命名会让其他公式 / 透视表 / 图表的数据源失效——执行前必须在回复里确认"将删除 / 改名 X影响 Y 个引用"。
## 使用场景
读写。管理工作簿结构。本 reference 覆盖 14 个 shortcut
| 操作需求 | 使用工具 | 说明 |
|---------|---------|------|
| 查看工作簿结构 | `+workbook-info` | 获取子表列表、名称、行列数、冻结位置等元数据 |
| 获取当前 revision | `+revision-get` | 获取当前文档 revision版本号可作为 recover / undo / changeset 复核的版本锚点 |
| 新建工作簿(可预填数据) | `+workbook-create` | 从内存数据建一张新表(`--values` / `--sheets` typed |
| 导入本地文件为新表 | `+workbook-import` | 把本地 `.xlsx` / `.xls` / `.csv` 导入为新的飞书电子表格 |
| 导出工作簿到本地 | `+workbook-export` | 导出为本地 `.xlsx`(整簿)或单子表 `.csv` |
| 变更工作簿结构 | `+sheet-{create|delete|rename|move|copy|hide|unhide|set-tab-color}` | 新建/删除/移动/重命名/复制/隐藏子表、修改标签颜色 |
| 切换子表网格线显隐 | `+sheet-show-gridline` / `+sheet-hide-gridline` | 显示 / 隐藏单个子表的网格线 |
注意:
- 如果用户请求包含多个动作,例如"先重命名,再新建工作表",请按顺序发起多次调用,覆盖全部动作
- `create` 时若用户指定了工作表名称,应显式传入 `sheet_name`;不要省略后依赖默认命名
-`+workbook-info` 返回包含 `warning_message`,说明部分 `sheet_id` 已失效(被删除/改名或输入错误),应停止复用这些 id重新不带 `sheet_ids` 全量获取结构后再继续操作
**常见配置错误(必须注意)**
- **获取结构是第一步**:任何表格操作前必须先调用 `+workbook-info`,不要跳过直接操作。返回的行列数、子表列表是后续所有操作的基础
- **sheet_id 不要写错**:从 `+workbook-info` 返回值中精确获取 `sheet_id`,不要手动拼写或从 URL 中猜测
- **优先使用 `sheet_id`**:虽然飞书表格不允许子表重名,但 `sheet_id` 是稳定标识符,跨多轮操作时不会因用户中途重命名而失效
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+workbook-info` | read | 工作簿 |
| `+revision-get` | read | 工作簿 |
| `+sheet-create` | write | 工作簿 |
| `+sheet-delete` | high-risk-write | 工作簿 |
| `+sheet-rename` | write | 工作簿 |
| `+sheet-move` | write | 工作簿 |
| `+sheet-copy` | write | 工作簿 |
| `+sheet-hide` | write | 工作簿 |
| `+sheet-unhide` | write | 工作簿 |
| `+sheet-set-tab-color` | write | 工作簿 |
| `+sheet-hide-gridline` | write | 工作簿 |
| `+sheet-show-gridline` | write | 工作簿 |
| `+workbook-create` | write | 工作簿 |
| `+workbook-export` | read | 工作簿 |
| `+workbook-import` | write | 工作簿 |
## Flags
### `+workbook-info`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
_仅含公共 / 系统 flag。_
### `+revision-get`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
_仅含公共 / 系统 flag。_
### `+sheet-create`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | required | 新工作表名称 |
| `--index` | int | optional | 插入位置0-based省略时附加到末尾 |
| `--row-count` | int | optional | 初始行数(默认 200上限 50000 |
| `--col-count` | int | optional | 初始列数(默认 20上限 200 |
| `--type` | string | optional | 新子表类型sheet电子表格默认 sheet可选值`sheet` |
### `+sheet-delete`
_公共四件套 · 系统:`--yes`、`--dry-run`_
_仅含公共 / 系统 flag。_
### `+sheet-rename`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | required | 新名称 |
### `+sheet-move`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--index` | int | required | 目标位置0-based |
| `--source-index` | int | optional | 源位置0-basedstandalone 调用时可选,未传时由 CLI runtime 根据 `--sheet-id` / `--sheet-name` 当前在工作簿中的 index 自动派生。但在 `+batch-update` 内不可省须显式传——batch 中途无法发起结构查询自动派生 |
### `+sheet-copy`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | optional | 副本名称;省略时由服务端生成 |
| `--index` | int | optional | 副本插入位置0-based省略时附加到末尾 |
### `+sheet-hide`
_公共四件套 · 系统:`--dry-run`_
_仅含公共 / 系统 flag。_
### `+sheet-unhide`
_公共四件套 · 系统:`--dry-run`_
_仅含公共 / 系统 flag。_
### `+sheet-set-tab-color`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--color` | string | required | Hex 色值如 `#FF0000`,传空 `""` 清除 |
### `+sheet-hide-gridline`
_公共四件套 · 系统:`--dry-run`_
_仅含公共 / 系统 flag。_
### `+sheet-show-gridline`
_公共四件套 · 系统:`--dry-run`_
_仅含公共 / 系统 flag。_
### `+workbook-create`
_系统`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | required | 新 spreadsheet 标题 |
| `--folder-token` | string | optional | 目标文件夹 token省略时放在云空间根目录 |
| `--values` | string + File + Stdin简单 JSON | optional | untyped 初始数据,一个 JSON 二维数组(表头并入第一行):`[["列A","列B"],["alice",95]]`;值原样写入、类型由飞书自动识别(日期 / 数字会落成文本,需类型保真改用 --sheets走与 --sheets 相同的分批 `+cells-set`;配 --styles 控制格式/颜色/合并/行列尺寸 |
| `--sheets` | string + File + Stdin复合 JSON | optional | 建表后写入的 typed 表格协议 JSON同 +table-put顶层 `{"sheets":[...]}`,每个数组项是一张子表 `{name, start_cell?, mode?, header?, allow_overwrite?, columns:["colA","colB",...], data:[[...]], dtypes?:{colA:pandasDtype, ...}, formats?:{colA:numberFormat, ...}}` —— `name` 与外层 `sheets` 数组都不可省。Agents 用 `scripts/sheets_df.py``df_to_sheet(df, name)` 把 DataFrame 转成一项再包 `{"sheets":[...]}`。与 --values 互斥;新表默认子表复用为第一个子表,日期/数字类型保真。 |
| `--styles` | string + File + Stdin复合 JSON | optional | 建表时同时写入的视觉处理操作 JSON顶层 `{styles:[...]}`,每项对应一个目标子表、含 `name`,并至少给 `cell_styles` / `row_sizes` / `col_sizes` / `cell_merges` 之一。`cell_styles` 用 A1 单元格 range + 扁平样式字段(字段同 +cells-set-style含 number_format / 颜色 / 对齐 / border_stylesrow/col sizes 用行/列范围 + type/sizemerges 用单元格 range + 可选 merge_type。与 --sheets 搭配时 styles 数组长度/顺序/name 必须与 --sheets.sheets 对应;与 --values 搭配时只给一个 styles 项(其 name 忽略)。完整 cell_styles 字段结构跑 `+workbook-create --print-schema --flag-name styles`。 |
### `+workbook-export`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--file-extension` | string | optional | 导出文件格式;`csv` 模式必须配 `--sheet-id`(可选值:`xlsx` / `csv`)(默认 `xlsx` |
| `--sheet-id` | string | optional | 仅 csv 模式必填:指定要导出哪张 sheet 为 CSV。这是 `+workbook-export` 专有 flag与公共四件套的 sheet 定位无关(本 shortcut 不接受公共 sheet 定位) |
| `--output-path` | string | optional | 本地保存路径;省略时**只触发并轮询导出任务、不下载文件**(返回 file_token / status便于稍后续传。要落盘传具体路径`./out.xlsx`)或目录(如 `.`,服务端给的文件名落在该目录下)。注意:对应的 `lark-cli drive +export --doc-type sheet``--output-dir` / `--file-name` / `--overwrite` 三 flag 且默认下载到当前目录——本 wrapper 把它们合成单一 `--output-path` 简化常见用例,但默认不下载,需要的话也可改用 `drive +export`。 |
### `+workbook-import`
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--file` | string | required | 本地文件路径(.xlsx / .xls / .csv |
| `--folder-token` | string | optional | 目标文件夹 token省略则导入到云空间根目录 |
| `--name` | string | optional | 导入后表格名称;省略则用本地文件名(去掉扩展名) |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+workbook-create` `--sheets`
_一个或多个子表的 typed 数据,每个数组元素写入一张子表;支持多 DataFrame → 多子表一次写入_
**数组项**(类型 object
- `name` (string) — 目标子表名
- `start_cell` (string?) — 写入起点单元格A1 记法,如 "B2"),默认 "A1"
- `mode` (enum?) — overwrite默认从 start_cell 起写「表头 + 数据」块append把数据追加到子表已有数据下方默认不重复表头 [overwrite / append]
- `header` (boolean?) — 是否写一行列名表头
- `allow_overwrite` (boolean?) — 为 false 时,若写入会落在非空单元格则拒写以保护原数据(返回 partial_success
- `columns` (array<string>) — 列名字符串数组,顺序与 `data` 中每行取值一一对应
- `data` (array<array<string|number|boolean|null>>) — 数据行;每行是一个数组,长度必须等于 `columns`
- `dtypes` (object?) — 可选
- `formats` (object?) — 可选
### `+workbook-create` `--styles`
**数组项**(类型 object
- `cell_merges` (array<object>?) — 单元格合并操作数组range 使用 A1 单元格范围merge_type 默认 all each: { merge_type?: enum, range: string }
- `cell_styles` (array<object>?) — 单元格样式操作数组;每项用 A1 单元格 range 指定范围,字段名与 +cells-set-style 对齐 each: { background_color?: string, border_styles?: object, font_color?: string, font_family?: string, font_line?: enum, …共 13 项 }
- `col_sizes` (array<object>?) — 列宽操作数组range 使用列范围如 A:Ctype 为 pixel/standardpixel 需要 size each: { range: string, size?: number, type: enum }
- `name` (string) — 子表名
- `row_sizes` (array<object>?) — 行高操作数组range 使用行范围如 1:3type 为 pixel/standard/autopixel 需要 size each: { range: string, size?: number, type: enum }
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR`+workbook-info` 只用前两者;`+sheet-*` 系列对单个工作表操作,需 `--sheet-id``--sheet-name`
### `+workbook-info`
输出契约:返回 `sheets[]`,每个含 `sheet_id` / `title`(工作表显示名;旧 payload 用 `sheet_name`,读取时优先取 `title`、缺失再回退 `sheet_name`/ `index` / `resource_type` / `row_count` / `column_count` / `is_hidden`,以及计数字段 `merged_cells_count` / `chart_count` / `pivot_table_count` / `float_image_count`(无 `frozen_*` 字段,冻结信息请用 `+sheet-info` 读取)。是操作飞书表格的第一步——任何后续 sheet 级动作都需要先拿这里的 sheet_id。
> **子表类型 `resource_type`**`sheet`(普通网格子表)/ `bitable`(内嵌的多维表格子表)/ `#UNSUPPORTED_TYPE`(其它暂不支持的嵌入子表)。
> - 网格类操作(读写单元格 / 区域 / 样式 / CSV / 筛选 / 透视 / 图表等)**仅适用于 `sheet`**。对 `bitable` / `#UNSUPPORTED_TYPE` 子表执行网格操作会被直接拒绝并返回明确报错,不再静默出错。
> - 要操作 `bitable` 子表里的数据:该子表条目会附带 `bitable_app_token` + `bitable_table_id` 两个字段,直接用多维表格命令操作,例如 `lark-cli base +record-list --base-token <bitable_app_token> --table-id <bitable_table_id>`(记录增删改查、字段、视图等整套 `lark-cli base` 命令均可用)。不要走 sheets 网格命令。
> - `bitable` / `#UNSUPPORTED_TYPE` 子表条目**只含** `sheet_id` / `sheet_name` / `index` / `resource_type`bitable 另加上述两个 token以及 `is_hidden` / `tab_color`**不输出** `row_count` / `column_count` / `merged_cells_count` / `chart_count` / `pivot_table_count` / `float_image_count` / `frozen_*` 等网格指标(对非网格子表无意义)。
> - tab 管理类操作(`+sheet-rename` / `+sheet-move` / `+sheet-delete` / `+sheet-hide` 等)对任意 `resource_type` 的子表都合法,不受此限制。
### `+revision-get`
输出契约:返回单个 `revision` 字段,即当前文档版本号。它是 recover / undo / `+changeset-get` 的版本锚点:如果刚执行过一次读写操作,也可以直接复用那次响应里的 `revision`;当只想单独取当前版本号、且不需要其它结构信息时,用 `+revision-get` 最直接。
### `+workbook-create`
新建电子表格可选预填数据。两种数据入口untyped `--values` / typed `--sheets` JSON**互斥**,按需选一——两者都走同一条分批写入:
```bash
# 1) untyped--values一个二维数组表头并入第一行值原样写、类型由飞书自动识别
# 日期会落成文本,配 --styles 控制格式)
lark-cli sheets +workbook-create --title "销售" \
--values '[["门店","销售额"],["北京",259874]]'
# 2) typed JSON--sheets一步建表 + 类型保真。date 列落成真日期(可排序/透视)、
# number 不丢精度、string 列保前导零(如订单号 00123多子表一次建。
lark-cli sheets +workbook-create --title "交易" --sheets '{
"sheets":[
{"name":"明细",
"columns":["日期","金额","单号"],
"dtypes":{"日期":"datetime64[ns]","金额":"float64","单号":"object"},
"formats":{"金额":"#,##0.00"},
"data":[["2024-01-15",1234.5,"00123"]]}
]}'
```
`--sheets` 协议与 `+table-put` 完全同构(字段含义见 lark-sheets-write-cells 的 `+table-put`,大 payload 走 stdin / `@file`)。关键差异:**新建工作簿的默认子表会被复用为第一个子表**(重命名后承载数据),不会残留空 `Sheet1`;其余子表按需新建。它把 `+table-put` 单独做不到的"建表 + typed 写入"合到一条命令是「pandas 算完直接落地一张带真日期的新表」的首选。回读校验用 `+table-get`(与 `--sheets` 同构、可 round-trip
> 💡 pandas DataFrame 走 `--sheets` 时直接 `from sheets_df import df_to_sheet`[`scripts/sheets_df.py`](../scripts/sheets_df.py),与 `+table-put` 共用同一份 helper多子表场景 helper 优势更明显:
> ```python
> payload = {"sheets": [df_to_sheet(income, "Income Statement"),
> df_to_sheet(balance, "Balance Sheet"),
> df_to_sheet(cashflow, "Cash Flow")]}
> ```
`--styles` 可在建表写入时同时写视觉处理。它和 `--sheets` 一样只有一种外层写法:顶层对象里放 `styles` 数组;数组每项对应一个子表,含 `name`,并按能力拆成四类可选数组:
- `cell_styles`:像 `+cells-set-style`,用 A1 单元格 `range` 加扁平样式字段(`font_weight` / `background_color` / `horizontal_alignment` / `vertical_alignment` / `number_format` 等)和可选 `border_styles`;这些样式会随内容在同一次写入里一并应用。完整字段跑 `+workbook-create --print-schema --flag-name styles`
- `cell_merges`:用 A1 单元格 `range` 设置合并,`merge_type` 默认为 `all`,可选 `rows` / `columns`
- `row_sizes`:用行范围(如 `1:3`)设置行高,`type``pixel` / `standard` / `auto``pixel` 需要 `size`
- `col_sizes`:用列范围(如 `A:C`)设置列宽,`type``pixel` / `standard``pixel` 需要 `size`
同一单元格命中多个 `cell_styles` 项时,后面的操作继续合并覆盖已传字段。`cell_merges` / `row_sizes` / `col_sizes` 在内容写入后顺序执行。
```bash
# 3) untyped仍用 {"styles":[...]}只有一个子表样式项name 忽略range 覆盖 --values 初始区域
lark-cli sheets +workbook-create --title "销售" \
--values '[["门店","销售额"],["北京",259874],["上海",198320]]' \
--styles '{
"styles":[
{"name":"Sheet1","cell_styles":[
{"range":"A1:B1","font_weight":"bold","background_color":"#f5f5f5","horizontal_alignment":"center","vertical_alignment":"middle"},
{"range":"B2:B3","number_format":"#,##0"}
]}
]
}'
# 4) typed 单子表:--styles.styles[0].name 必须对应 --sheets.sheets[0].name
lark-cli sheets +workbook-create --title "交易" --sheets '{
"sheets":[
{"name":"明细",
"columns":["日期","金额"],
"dtypes":{"日期":"datetime64[ns]","金额":"float64"},
"formats":{"金额":"#,##0.00"},
"data":[["2024-01-15",1234.5]]}
]}' --styles '{
"styles":[
{"name":"明细",
"cell_styles":[
{"range":"A1:B1","font_weight":"bold","background_color":"#f5f5f5",
"border_styles":{"bottom":{"style":"solid","weight":"thin","color":"#000000"}}},
{"range":"A2:A2","number_format":"yyyy-mm-dd"},
{"range":"B2:B2","number_format":"#,##0.00","font_color":"#0f7b0f"}
],
"cell_merges":[{"range":"A1:B1"}],
"col_sizes":[{"range":"A:B","type":"pixel","size":120}],
"row_sizes":[{"range":"1:1","type":"pixel","size":28}]}
]
}'
# 5) typed 多子表styles 数组和 sheets 数组长度、顺序、name 都必须一致
lark-cli sheets +workbook-create --title "经营看板" --sheets '{
"sheets":[
{"name":"收入","columns":["月份","收入"],"dtypes":{"收入":"int64"},"formats":{"收入":"#,##0"},"data":[["2026-05",1200000]]},
{"name":"成本","columns":["月份","成本"],"dtypes":{"成本":"int64"},"formats":{"成本":"#,##0"},"data":[["2026-05",730000]]}
]}' --styles '{
"styles":[
{"name":"收入","cell_styles":[
{"range":"A1:B1","font_weight":"bold","background_color":"#f0f7ff"},
{"range":"B2:B2","font_color":"#0f7b0f"}
]},
{"name":"成本","cell_styles":[
{"range":"A1:B1","font_weight":"bold","background_color":"#fff7ed"},
{"range":"B2:B2","font_color":"#b42318"}
]}
]
}'
```
> ⚠️ **`+workbook-create` 是把内存里的数据写成新表;要把已有的本地 Excel/CSV 文件原样导入成新表,用 `+workbook-import`**(见下),不要先在本地读出文件再 `+workbook-create` 重灌。
### `+workbook-import`
把已有的本地 `.xlsx` / `.xls` / `.csv` 文件导入为一个**新的**飞书电子表格(异步任务 + 内置轮询),与 `+workbook-export`(导出)对称,固定导入为电子表格类型。
```bash
# 导入到云空间根目录;表格名默认取本地文件名(去掉扩展名)
lark-cli sheets +workbook-import --file ./data.xlsx
# 指定目标文件夹与导入后表格名
lark-cli sheets +workbook-import --file ./report.csv --folder-token <FOLDER_TOKEN> --name "月度报表"
```
- **不接受任何 spreadsheet / sheet 定位 flag**(它是新建,不操作已有表):只有 `--file`(必填)/ `--folder-token` / `--name`
- 本地表格文件 → 飞书电子表格一律用本命令,**不要**用 `drive +import` 导电子表格——它是 sheets 之外的通用导入、还需额外指定 `--type`,绕路且更易错。只有要把本地表格导入成**多维表格**bitable才改用 `lark-cli drive +import --type bitable`
- 返回 `token` / `url`(导入完成的新表格)/ `ticket` / `ready` / `job_status`;未在内置轮询窗口内完成时返回 `timed_out=true` 与续查命令 `next_command`
### `+workbook-export`
把飞书电子表格导出为本地 `.xlsx`(整工作簿)或单子表 `.csv`(异步任务 + 内置轮询 + 可选下载)。
```bash
# 1) 只创建并轮询导出任务,不下载(默认):返回 file_token / status 便于稍后续传
lark-cli sheets +workbook-export --url "https://example.feishu.cn/sheets/shtXXX"
# 2) 下载到具体文件名
lark-cli sheets +workbook-export --url "..." --output-path ./report.xlsx
# 3) 下载到目录(保留服务端给的文件名)
lark-cli sheets +workbook-export --url "..." --output-path ./downloads/
# 4) csv 模式必须传 --sheet-idAPI 一次只导一张子表)
lark-cli sheets +workbook-export --url "..." --file-extension csv --sheet-id "$SID" --output-path ./sheet.csv
```
> ⚠️ **默认不下载**:省略 `--output-path` 时只触发并轮询导出任务,不写本地文件——给「先排队再续传」用例留出口。要落盘必须显式给 `--output-path`。
>
> **与 `drive +export --doc-type sheet` 的关系**:本 wrapper 是它的特化封装,固定 `--doc-type sheet`,并把 drive 的 `--output-dir` / `--file-name` / `--overwrite` 三 flag 折叠成单一 `--output-path` 简化常见用例。代价是默认值不同:`drive +export` 默认下载到当前目录、本 wrapper 默认不下载。需要细控目录/文件名/是否覆盖的,回退到 `drive +export --doc-type sheet`。
### `+sheet-create`
示例:
```bash
lark-cli sheets +sheet-create --url "https://example.feishu.cn/sheets/shtXXX" \
--title "汇总" --index 0
```
> 💡 `+sheet-create` 只建一张**空子表**。要在已有工作簿里建子表并一步写入 typed 数据和/或样式,用 `+table-put`payload 里命名的子表缺则自动新建)配合它的 `--sheets` / `--styles`,省掉先建表再 `+cells-set` / `+cells-set-style` 的二次往返。
### `+sheet-delete`
> ⚠️ 工作表删除不可逆;先 `--dry-run` 看输出 sheet_id + title 确认是要删的那张。
### `+sheet-rename`
```bash
lark-cli sheets +sheet-rename --url "..." --sheet-id "$SID" --title "汇总"
```
### `+sheet-move`
standalone 路径在缺 `--source-index` / 只给 `--sheet-name` 时会自动发起一次 `+workbook-info` 读把它们解出来。
> ⚠️ **在 `+batch-update` 内调用 `+sheet-move`**:必须同时显式传 `--sheet-id`、`--source-index` 和 `--index`目标位置。batch 中途无法发起结构查询,且 `--index` 不显式给会静默落到默认位置 0所以 batch translator 强制要求三者都显式。
### `+sheet-copy`
```bash
# --title 省略时由服务端生成副本名
lark-cli sheets +sheet-copy --url "..." --sheet-id "$SID" --title "副本"
```
> 💡 `+sheet-copy` 连**公式 / 合并 / 分组底色 / 列宽 / 条件格式**一起整表复制。"照一张现成子表批量造结构相同的新子表"(如参考模板给每份数据各建一张同构子表)时,先 `+sheet-copy` 复制模板再用 `+cells-*` 只改数据,比从零 `+sheet-create` + 重建公式 / 样式省一大截,也天然满足"公式 / 分组 / 颜色照搬"。要把本地文件 / 数据并入**已有工作簿**当子表时走它(或 `+sheet-create`),别用 `+workbook-import` / `+workbook-create`——那两条只会新建独立表。
### `+sheet-hide` / `+sheet-unhide`
```bash
lark-cli sheets +sheet-hide --url "..." --sheet-id "$SID"
lark-cli sheets +sheet-unhide --url "..." --sheet-id "$SID"
```
### `+sheet-set-tab-color`
```bash
# Hex 色值;传空字符串 "" 清除标签色
lark-cli sheets +sheet-set-tab-color --url "..." --sheet-id "$SID" --color "#FF0000"
```
### `+sheet-show-gridline` / `+sheet-hide-gridline`
```bash
# 切换子表网格线显隐;二态语义在命令名里,无需额外参数(同 +sheet-hide/+sheet-unhide
lark-cli sheets +sheet-show-gridline --url "..." --sheet-id "$SID"
lark-cli sheets +sheet-hide-gridline --url "..." --sheet-id "$SID"
```
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+sheet-create` 校验 `--title` 非空、`--row-count` ≤ 50000、`--col-count` ≤ 200`+sheet-delete` 必须 `--yes``--dry-run``+workbook-create``--sheets``--values` **互斥**,给了 `--sheets` 则按 typed 协议校验 payload其余约束同 `+table-put`)。
- `DryRun``+sheet-*` 写操作输出"将要 PATCH 的 sheet metadata"`--sheet-name` 在 dry-run 输出里生成为 `<resolve:Sheet1>` 占位符,不实际解析为 sheet-id。
- `Execute`:写操作不自动回读;如需确认目标 sheet 的新状态,自行调用 `+workbook-info`

View File

@ -0,0 +1,575 @@
# Lark Sheet Write Cells
## 写入边界 + 回读校验(编辑类任务必做)
1. **明确写入边界**:写入前必须能回答"目标 range 的起止行列号是多少?是否落在用户授权范围内?"。除用户明示要修改的区域外,禁止扩张到原数据列以外或新建 Sheet。
2. **完整性断言**:批量写入前先把"预期写入条数"硬编码到代码里(如要填 106 条翻译 → `expected = 106`),写完后回读断言 `actual == expected`。少于预期就继续写,禁止交付半成品。
3. **回读抽样校验**:写完关键值 / 公式后,用 `+csv-get``+cells-get` 重新读取写入区域,至少抽样 3-5 个代表性单元格(首 / 中 / 末),核对值与预期一致(与本地脚本计算的预期值对照)。公式特定的"先验证模板再 --copy-to-range / 修完再读回"细则见下方相关章节。
4. **护原表 · 派生产物落点(写排名 / 标记 / 汇总 / 改写列时易丢数据)**:派生结果一律写到**真实末列 +1 的全新空列**或新建子表,**禁止复用任何已有原数据列**——哪怕该列看起来"空",也要先 `+csv-get` 回读确认整列无原始数据再写。三条准则:① 不把新公式 / 新值写进原数据列(典型反例:把新算的排名公式写进了原本存放另一份原始数据的列,整列原始数据被覆盖丢失);② 不改写、不合并原表头字段名(典型反例:把几个独立表头字段合并成一列,原字段名丢失);③ 慎用 `--allow-overwrite`:它一旦让写入区盖到相邻原始列 / 行就是不可逆数据丢失,加它之前必须用 `+sheet-info` / `+csv-get` 核清目标 range 不含任何原始数据。
## 新增列 / 新增行的样式继承(防止视觉风格不一致)
新增列 / 新增行**必须**先用 `+cells-get` 读相邻原列 / 原行的完整样式作为模板,**禁止**只传 `value` 期望默认样式与原表一致——飞书新单元格默认对齐通常是 `H:right, V:bottom`,与多数原表的 `H:center, V:middle` 不一致。
**完整继承清单**(写新列 / 新行时 cells 数组必须同时携带):
1. `cell_styles.font_family` / `cell_styles.font_size` / `cell_styles.font_weight` / `cell_styles.font_color` / `cell_styles.font_style`(字体名称 / 字号 / 粗细 / 颜色 / 斜体等)
2. `cell_styles.horizontal_alignment` / `cell_styles.vertical_alignment`H-Align / V-Align—— 漏继承会导致新列对齐与原列不一致(常见)
3. `cell_styles.number_format`(小数位 / 千分位 / 百分比 / 日期格式)—— 漏继承会导致同列数值格式混乱
4. `cell_styles.background_color`(背景色)
5. `border_styles`(四边框)
6. **`merged_cells`(合并范围)**——续写场景必查:用 `+sheet-info --include merges` 读原数据区域的合并信息。**原行有跨列合并**(如标题行 `A1:G1` 合并)时,新行**必须**用 `+cells-{merge|unmerge}` 工具复制相同合并模式到新行(如续写第 3 个周报块的标题行 `A23:G23` 必须合并)。仅传 cells 数组的 5 类样式不够——合并范围要单独靠 `+cells-{merge|unmerge}` 工具落地(典型反例:续写多周记录表时,新增周次的标题行未合并,视觉上与原前几周风格不一致)
**采样模板的正确做法**
- 表头新列 → 读相邻表头单元格(如新加 D1 → 读 A1/B1/C1 任一)
- 数据新列 → 读相邻数据行单元格(如新加 M5:M100 → 读 L5 / L6 / L7
- 续写新行 → 读最近一行已有数据(如续写第 20 行 → 读 19 行所有列)
**反模式**(违规):
- 只传 `{"value": "四级菜单"}` 给 D1不传 `cell_styles` → D1 默认非加粗、非居中,与 A1/B1/C1 风格断裂
- 新列 M5 写入 `=SUM(F5:L5)` 时只传 `formula`,不传 `cell_styles.horizontal_alignment / vertical_alignment / number_format` → M 列对齐变 `H:right`,数字格式变默认
## 长数字防科学计数法(数值列写入必查)
写入或计算结果可能产生长数字(≥ 12 位整数 / 高精度小数)的列,**必须**在 `cell_styles.number_format` 显式设置非通用格式,否则飞书会自动用科学计数法显示,用户看到的就是"内容被截断 / 看不清原值"。
| 场景 | 必加的 `number_format` |
|---|---|
| 长整数(订单号 / 身份证 / 单据号) | `"0"``"@"`(强制文本,避免精度丢失) |
| 金额 / 千分位 | `"#,##0.00"` |
| 百分比 | `"0.00%"` |
| 数量 / 计数 | `"0"`(整数) |
| 日期 | `"yyyy-mm-dd"``"yyyy/m/d"` |
**典型反例**:长数字列(如审批单号、流水号)未设 `number_format`,飞书显示为 `1.23E+15`,用户复制出来已经丢失精度。
> **数字还是文本,按"数据本质是量值还是标识符"二选一 —— 不看当下要不要计算**:金额 / 百分比 / 比率 / 计数 / 度量这类**本质是量值**的数据,一律以**数字类型**写入(百分比存小数 `0.54` 配 `number_format:"0%"`**不要**设 `@` 文本格式。**这与"用户当下是否要排序 / 求和"无关**——数据类型由数据本质决定、不由当下用途决定:表格数据几乎总会被后续排序 / 图表 / 二次计算复用,`"54%"` 文本与数值列混排本就破坏一致性,且数字 + `number_format` 显示效果与文本**完全相同**,没有任何理由选文本。**最常见的误判就是"这只是 leaderboard / 报表 / 看板展示,又不用算,写成 `54%` 字符串就行"——这是错的,展示用途不改变"百分比是数值"的事实。**`+table-put` 用 `dtypes` 声明 `int64` / `float64`;版式 `+table-put` 装不下时用 `+cells-set` 传数字 + `number_format`;都别在本地拼成带 `$` / `%` 的字符串走 `+csv-put`。)反过来,编号 `001`、规格 `3-1`、身份证 / 电话 / 单据号等**本质是标识符 / 标签**、要原样保留不被飞书自动解释的内容(否则 `001`→`1`、`3-1`→日期、点分日期 `12.10`→`12.1`(尾零丢失)、长号→科学计数),才以**字符串类型**写入(`dtypes` 设 `object`)并把 `number_format` 设为 `"@"`(文本格式),字面保真。
## 使用场景
写入。向飞书表格的单元格区域写入值、公式、样式、批注、图片或下拉,也可批量写入 CSV / DataFrame。本 reference 覆盖 6 个 shortcut按数据来源 + 内容形态选:
| 场景 | 用这个 shortcut | 原因 |
|------|----------------|------|
| 模型手里已经有 CSV 文本(小规模手动构造、从 `+csv-get` 取到后简单加工) | `+csv-put` | 直接传 CSV 文本 + `--start-cell`,不用自己拼二维 cells 数组;必要时自动扩容行列 |
| 列里有数值语义的数据(数字 / 金额 / 百分比 / 日期 / 计数)→ 飞书要类型保真来源不限DataFrame、Counter、dict、list 都算) | `+table-put` | typed 协议(外层 `{"sheets":[{"name":"…","columns":[...],"data":[[...]],"dtypes":{...},"formats":{...}}]}`**只有这四件套字段**`dtypes` 用 pandas dtype 串声明列类型(`int64` / `float64` / `datetime64[ns]` / `bool` / `object``formats` 给每列展示格式(千分位 / 百分比 / 日期)。**date 落真日期、金额 / 百分比 / 计数等数值列保精度且带 `number_format`(可排序 / 求和 / 入图表)**、string 保前导零,多 sheet 一次写 |
| 写入含样式、批注、图片、数据校验等任意富写入 | `+cells-set` | 唯一支持完整富字段的 shortcut公式 `+csv-put` 也能写) |
| 只改已有 cell 的样式,不动 value/formula | `+cells-set-style` | 拍平 10 个样式字段为独立 flag不触发不必要的值写入 |
| 单 cell 嵌入图片 | `+cells-set-image` | 比 `+cells-set` 参数更简短 |
| 在**已有区域**局部补表头样式/边框 | 先用 `+csv-put` 写值,再用 `+cells-set-style` 补样式 | 分工配合,入参最短 |
| **新建子表 / 整表成套美化**(哪怕全是纯文本) | `+table-put --sheets … --styles …` 一步带值 + 全套样式(区域底色 / 边框 / 列宽 / 行高 / 合并payload 里不存在的 sheet 名自动建子表) | `--styles` 与列是否 typed 无关,纯文本同样适用;比「写值 + 多次刷样式」少好几次调用 |
**选命令按内容形态分流(不设"默认首选"**:① 列有数值语义(金额 / 百分比 / 日期 / 计数)→ `+table-put``dtypes` 声明类型 + `formats` 设展示格式),版式装不下时 → `+cells-set` 传数字 + `number_format`;② 要样式 / 批注 / 图片 / 富文本 → `+cells-set`;③ **仅**全文本、无数值语义的内容平铺 → `+csv-put`(入参最短)。判据详见上方「数字还是文本」。
⚠️ `+csv-put` 可写值或公式:以 `=` 开头的单元格会被当作公式计算(读回时 `formula` 字段保留、`value` 为计算结果)。**公式内部含逗号 / 引号 / 换行时必须按 RFC 4180 转义**——含逗号的字段整格用双引号包裹、字段内部的引号再翻倍:如 `=COUNTIF(D5:D22,"及格")` 必须写成 `"=COUNTIF(D5:D22,""及格"")"`(外层双引号包裹整格,内部 `"及格"` 的引号翻倍成 `""及格""`)。漏转义会被 CSV 解析器按逗号拆列、整块写入区域错位(如本该 `G4:H6` 错成 `G4:K4`),详见下方 `+csv-put` 示例。**因此含逗号 / 引号 / 换行的公式优先改用 `+cells-set`JSON 二维数组)写入——`cells[r][c].formula` 字段直接放公式串,零 CSV 转义负担,从根上避免拆列错位**`+table-put` 的 typed 协议只接受 `columns / data / dtypes / formats` 四件套、没有 `formula` 字段,公式写入只能走 `+cells-set` / `+csv-put`)。此外 `+csv-put` **不会**携带样式/批注/图片,也无法把 `=` 开头的内容当字面量文本写入;需要样式/批注/图片用 `+cells-set`(或"写值 + 补样式"两步法)。
⚠️ **`+csv-put` 会把数值落成文本**:把金额 / 百分比 / 计数等在本地拼成带 `$` / `%` / 千分位的字符串(如 `"$1,234.50"` / `"+30.5%"`)再 `+csv-put` 灌进去,单元格就是**文本**——丢失排序 / 求和 / 图表能力,且与数值列混排无法参与计算。数值该怎么写、何时 `+table-put`、版式装不下时何时退 `+cells-set` 传数字 + `number_format`,判据与分流见上方「数字还是文本」;核心一句:**准备把数字 format 成字符串再写时就是走错了路,数值一律以数字写入 + `number_format` 控制显示。**
⚠️ **`+csv-put` 也会把「看着像数字」的字段静默数值化**与上一条相反的另一半坑CSV 里语义是**日期标签 / 编号 / 标识符**、内容却全是数字字符的列,会被按数值解析——`12.10``12.1`(点分日期尾零丢失)、`3.0``3``001``1`、长号→科学计数。**这类列即使已攒好 CSV 文本也不能裸走 `+csv-put`**:优先 `+table-put` 把该列 `dtypes` 声明为 `object`(无年份的点分标签如 `12.10` / `3-1` 字面保真)或 `datetime64[ns]`(完整真日期),版式装不下再退 `+cells-set` + `number_format:"@"`。此类失真在「抽样首 / 中 / 末」回读时易被掩盖(`12.10` / `12.20` 等尾零行常不落在抽样窗口),日期 / 编号列回读要专挑带尾零 / 前导零的代表值核对。
⚠️ 大数据回写走"`+csv-get``--range` 行窗口分批读到本地 + 本地脚本处理 + `+csv-put` 分批回写"。
## `+cells-set` 写入要点(常用模式 / 公式 / 样式)
> 以下是用 `+cells-set`(及 `+cells-set-style`)做富写入时的常用模式与准则;选哪个 shortcut 见上方「使用场景」。
`+cells-set` 为一块区域设置值 / 公式 / 批注 / 样式,也支持 `rich_text``type: "embed-image"` 嵌入单元格图片。**关键:`cells` 二维数组的行列维度必须与 `range`(闭区间)严格一致,否则触发 `InvalidCellRangeError`**——维度计算示例见文末 `## Schemas``--cells`
> **单元格图片 vs 浮动图片(最易选错)**:图若**属于某条记录、要随那行排序 / 筛选 / 增删**(凭证 / 证件照 / 每行配图,话里带「对应 / 每行 / 这列」等绑定词)→ **单元格图片**(本工具):用 `+cells-set-image`(最短)或 `+cells-set` 的 `rich_text` + `type: "embed-image"`。只是自由摆放的装饰logo / 水印 / 封面)→ 浮动图片,见 lark-sheets-float-image。别因「浮动图更好控制 / 更熟」默认选浮动图——它承载"对应某记录"的图会随增删行 / 排序错位。
常用模式(**必须遵守,禁止逐行写入替代**
- 整列公式:先在 `H2` 写一个公式,再用 `--copy-to-range "H2:H100"``--copy-to-range "H:H"` 向下填充。**禁止对每一行单独调用 `+cells-set` 写入相同结构的公式**
- 整列格式:先在 `J1` 写一个带样式的模板单元格,再用 `--copy-to-range "J:J"`
- 首行样式:先在 `A1` 写一个模板单元格,再用 `--copy-to-range "1:1"`
- 用户说”这列 / 整列 / 这行 / 首行 / 向下复制”时,**必须**使用模板单元格 + `--copy-to-range`
- 多区域写入相同格式/公式结构时,优先写一个模板,再用 `--copy-to-range` 复制到所有目标区域
⚠️ **模板 `--range` 从数据行起算、别把表头圈进去**`--copy-to-range` 会把 `--range` 模板按目标区尺寸周期性平铺,模板里若含了表头行,表头会每隔几行重复铺进数据区。整列填充时模板只取一格数据样式(如 `H2`),不要取成 `H1:H2`
⚠️ **逐行写入公式是常见低效写法**:对每一行单独调用 `+cells-set` 写公式(如 26 次)既慢又易错,且不会自动平移公式引用。正确做法是 1 次模板写入 + 1 次 `--copy-to-range`(公式引用自动平移)。
💡 **写入公式前先按迁移规则改写**:如果公式来自 Excel 或包含数组场景,先读取并遵循 `lark-sheets-formula-translation` 的规则完成改写,再把最终公式写入 `formula` 字段。
💡 **内容与样式分离写入(推荐)**:当需要同时写入内容和样式时,`cells` 中每个单元格都带上 `cell_styles` / `border_styles` 会导致入参非常冗长。由于同一区域的样式通常高度重复(如整列统一背景色、统一边框),推荐拆成两步:
1. **先写内容**`+cells-set` 只传 `value` / `formula`,不带样式,`cells` 入参精简。⚠️ 这里"不带样式"指暂不带 `cell_styles`**不是**降级用 `+csv-put` 铺文本——数值列(百分比 / 金额 / 计数)仍必须以数字写入(百分比传 `0.44`):样式能后补,数据类型不能后补(见上方「数字还是文本」)。
2. **再批量刷样式**:对区域中的一个单元格写入目标样式作为模板,再用 `--copy-to-range` 将样式扩展到整列 / 整行 / 整个区域(`--copy-to-range` 会复制值、公式和样式,所以模板单元格应已包含正确的值)
示例:要对 A2:A100 写入数据并统一设置蓝色背景 + 边框:
```
Step 1: `+cells-set` — range="A2:A100", cells 只含 value无样式入参短
Step 2: `+cells-set` — range="A2", cells 含 value + cell_styles + border_styles单个模板, --copy-to-range="A2:A100"
```
这比在 99 个单元格中都重复写样式 JSON 高效得多。
💡 **样式更新是「部分合并」,不是整体覆盖**`+cells-set-style` / `+cells-batch-set-style`(以及 `+cells-set``cell_styles` / `border_styles`)只改你**显式传入**的样式属性,未传的属性保留原值。两个实用推论:
- **可分层叠加**:对同一区域先刷字体色、再单独刷背景色、再单独刷边框,后一步不会清掉前一步——美化已有区域时无需一次带齐所有字段,可拆成多次窄调用。
- **`border_styles` 按边合并**:只传 `{"top":{...}}` 只更新上边框,`bottom` / `left` / `right` 保留原状;不必为了「只改一条边」而把四边全部重传。(例外见上方「新增行的边框/样式禁止用 `{}` 跳过」:**全新行**底子里没有边框,仍需把要显示的边都显式传出。)
💡 **大批量数据分批写入(推荐)**:当需要写入大量行(如几十行以上)时,不要试图在一次调用中生成全部 `cells` 数据——`cells` 数组过大会让单次生成的内容过长,容易出错或被截断。应将数据拆分为多批,每批 20-50 行,分多次调用 `+cells-set` 逐批生成并写入(如先写 `A2:D21`,再写 `A22:D41`,依此类推)。每次只生成当前批次的数据,控制单次生成量。
注意:
- 不要把 `cells` 写成字符串化 JSON
- `+cells-set` 默认即覆盖非空 cell`--allow-overwrite` 默认 true若要**保护**非空 cell 不被覆盖,显式传 `--allow-overwrite=false`(遇非空 cell 报错)
- 若目标区域涉及合并单元格,不要向合并区域中的非左上角单元格写入数据;如需写入,应改写合并区域左上角单元格,或先调整/取消合并区域
- **构造 `range` 时行号必须基于逻辑行号**:如果之前通过 `+csv-get` 读取了数据CSV 中被双引号包裹的多行字段(如 `"2026年3月2日\n星期一"`)是**一个单元格**,不是两行。写入时的行号必须按逻辑记录计算,不能按物理换行符计数,否则 `range` 会整体偏移导致写入到错误位置
> 用户说"样式和原表一致 / 保持原表格式 / 边框继承"时同理:`cell_styles` 只覆盖字体和对齐、**不含边框**,边框必须用独立 `border_styles` 字段传——完整继承清单见上方「新增列 / 新增行的样式继承」。
⚠️ **公式写入必须自己校验结果(后端不会报语法错)**`+cells-set` 写公式时,即便公式有括号不配对(如 `=IFERROR(VALUE(REGEXEXTRACT(D5, "\d+"))), 0)` 比 IFERROR 多一个 `)`)或用了飞书不支持的函数(如 `GOOGLETRANSLATE` / `CUBEVALUE`**后端工具也会返回 `updated_cells_count=N, rc=0` 的"成功"**——错误会静默写进单元格显示为 `#VALUE!` / `#NAME?` / `#REF!`。因此:
1. **写完立即读回**`+cells-set` 后紧跟 `+csv-get`(或 `+cells-get`)读目标范围前几行,检查是否出现 `#VALUE!` / `#NAME?` / `#REF!` / `#N/A` / `#DIV/0!` / `#NUM!`
2. **看到 `#` 开头的错误值**立即修公式:`#NAME?` 多半是函数名拼错或用了飞书不支持的函数(如 `GOOGLETRANSLATE` / CUBE 系列;注意 `UNIQUE` / `FILTER` / `SPLIT` 飞书是支持的);`#VALUE!` 多半是类型不匹配或括号错位;`#REF!` 是引用错误;`~CIRCULAR~REF~` 是循环引用(公式引用了自身或会闭环)
3. **`--copy-to-range` 扩展前先验证模板**:模板单元格公式自己都算错,`--copy-to-range` 复制到 100 行就是 100 个错误
4. **去重 / 筛选函数**:飞书**支持** `UNIQUE` / `FILTER` / `SPLIT`(原生数组函数,详见 `lark-sheets-formula-translation`),可直接用;`DISTINCT` 不是飞书函数,去重用 `UNIQUE`。大数据量去重 / 分组也可用透视表(`+pivot-{create|update|delete}`,值字段聚合方式选 count
5. **循环引用预检**写聚合公式SUM / AVERAGE / COUNT 等)前必须明确**引用范围不包含目标单元格自身或其传递依赖**。典型反例:在 C3 写 `=SUMIF(B:B,LEFT(B3,9)&"*",C:C)`B 列匹配 B3 前 9 位时 C3 自己也命中,导致 C3 自引用 → `~CIRCULAR~REF~`。修法:用辅助列 / 显式排除自身(`SUMIFS(C:C, B:B, ..., A:A, "<>"&A3)`/ 缩小范围避开自己
6. **REGEX 模式覆盖率验证**:公式里的 `REGEXEXTRACT` / `REGEXMATCH` / `REGEXREPLACE` 等正则模式落地前必须用本地脚本在源列上跑一遍命中率统计(`df[col].str.contains(pattern).mean()`);命中率 < 100% 时必须扩展 pattern 或加多分支IFS / 多个 IFERROR 串联兜底**禁止**只覆盖样本前 N 行就交付典型反例 `REGEXEXTRACT(D5,"长(\d+)")` 只匹配带""前缀的尺寸文本"×"、"×"、"*"等其它分隔符直接漏匹配
7. **公式范围与用户指令字面对齐**用户说" F L 列求和"就必须写 `SUM(F2:L2)` `F2+G2+H2+I2+J2+K2+L2`**不能漏列多列错列**。写完用 `+cells-get` 拿回 `formula` 字符串与用户原话逐字对照参与求和的列名一致 / 起止列号一致 / 运算符一致不一致就是违规
8. **量纲 / 单位换算 / 数量乘项预检(公式不报错但结果整体偏倍数)**从文本提取数字做计算前先核对**单位是否统一是否漏乘数量口径是否一致**——这类错误公式能跑通 `#` 报错回读也看不出"像对的")。必须用本地脚本对 35 个代表行**离线手算一遍预期值**与公式结果逐格比对量级:① 单位不一致先统一再算典型反例尺寸 `320CM*337CM` 直接取数相乘除以 1e6 0.11正确是 CMMM 换算后得 10.78** 100 **);② "单件×数量"的量必须乘数量列典型反例侧面板面积漏乘 F 列数量F=2 的行只算了一半);③ 标准值口径对齐典型反例营养成分 mg/kg g/100g 口径混用整列放大 100 )。**口径 / 单位 / 数量任一项错整列计算结果就是错的这类错误公式不报错回读也不易看出必须靠离线手算对照。**
**公式写入的默认收尾不是停在回读,而是继续跑 `+formula-verify`**`+csv-get` / `+cells-get` 的抽样回读只能帮你快速发现明显错误但它覆盖不到整列中段隐藏行被条件格式遮蔽的错误也看不到 `partial` 截断。**只要这次 `+cells-set` / `--copy-to-range` / `+csv-put` 实际写入了公式收尾默认就是转到 `lark-sheets-formula-verify` `+formula-verify`直到 `status='success'`。** 不要等用户补一句再验证下公式才做
**收到 `formula_errors` 反馈后不要只打补丁**`+cells-set` 返回值里若出现 `formula_errors: [{cell, formula, error_type, detail}]`说明某些 cell 公式编译失败`error_type=compile_failed` 通常是函数语法错如 `SPLIT(x)[1]` 的下标取值飞书不支持SPLIT 本身支持取第 N 项用 `INDEX(SPLIT(...),N)``non_formula` `=` 开头但解析不通过)。此时**禁止只聚焦修报错点的局部语法**如仅把 `[1]` 换成 `INDEX(..,1)`必须
1. **重新审视整条公式的完整性** formula_errors 标出的那一行公式除了下标语法错还可能有其他先天缺陷字符清洗不全IFERROR 兜底漏条件引用列写错修完语法错后立即整体复核
2. **同步对称修复所有相似列**如果同一任务涉及多列相似处理" H 列面积" D 列尺寸、" I 列面积" E 列尺寸**修完一列必须把同样的清洗/兜底逻辑同步到所有相似列**禁止出现 H 列用 `SUBSTITUTE(长)+SUBSTITUTE(高)+SUBSTITUTE(×)` I 列只用 `SUBSTITUTE(×)` 这种不对称处理——会导致一列编译通过有值另一列编译通过但 IFERROR 全返回空用户看到的是"数据为空"而非"公式错"
3. **修完再读回验证**不只看 `formula_errors` 为空这只证明编译通过不证明运行时有值必须 `+csv-get` 读目标列前 3-5 确认**非空源数据对应的目标列有非空计算结果**
4. **核心心智**`formula_errors` "帮你暴露编译错"的工具不是"修掉它就收工"的通行证编译通过 + 运行时 IFERROR 兜底空 = 用户视角的"没算出来"
**新增行的边框/样式禁止用 `{}` 跳过**`cells` 数组里 `{}` 的语义是"**此单元格不做任何修改保留原状态**"。这在写入**已有行**时是安全的原有边框/样式保持不变但在写入**新行**比如表尾追加汇总行扩展行时是灾难新行底子里本来就没边框`{}` 不修改 = 保留无边框状态,导致该 cell 视觉断裂
**"汇总行"识别 `lark-sheets-visual-standards` 拿完整样式规范**下述双重条件**同时满足**才是汇总行禁止仅凭" AVERAGE"就判定
- **语义信号**二选一用户 prompt "合计/汇总/总计/统计/各科平均分/最下面加一行算…/底部总计"等意图词或上下文明确是"表尾追加一行做聚合"
- **结构信号**新行全行都在做聚合 `=SUM/AVERAGE/COUNT/MAX/MIN/SUBTOTAL(...)`支持 IFERROR 包裹**不是**单个 cell 算个参考值或每行都算的派生列
满足上述时**不要在本文里猜样式**直接去读 `lark-sheets-visual-standards` 场景一 1A. 添加汇总行 / 表头行章节按那里的样式要点配齐 `font.bold / horizontal_alignment / background_color / border_styles`
反例**不是**汇总行禁止自动加粗
- 用户说" H5 帮我算个 AVERAGE 参考"→ cell 计算
- 每行都有 `=AVERAGE(本行区间)` 的派生列 属数据列
- 用户明确说"不要加粗/样式和数据行保持一致"→ 遵循用户意图
**正确做法**二选一
- **做法 A推荐**按上方内容与样式分离写入两步法——先用模板单元格 + `--copy-to-range` **完整样式**`cell_styles` + `border_styles` 都要不能只铺 border否则新行字体 / 对齐 / 背景色全裸奔再单独 `+cells-set` value / formula汇总行的 `cell_styles` 要点bold / 背景色 / 上边框 `lark-sheets-visual-standards` 场景一 1A. 添加汇总行 / 表头行」。
- **做法 B**一次写入但每个 cell含空白格都显式带 `cell_styles` + `border_styles`**不能用 `{}`**。
**判断是不是"新行"**写入 range 超出 `+csv-get` 返回的 `current_region` / 下边界 `current_region=A1:H10` `A11:H11`即新行必须按上述做法补边框
## 富文本单元格:超链接 / @人 / @文档(`rich_text`
带显示文本的超链接@人@文档这类富内容**必须** `+cells-set` `rich_text` 字段`cells[].rich_text` 数组每段一个对象 `type`**不能**直接传普通字符串——纯字符串只会被当作纯文本存进单元格完整字段跑 `lark-cli sheets +cells-set --print-schema --flag-name cells`常用段类型
- **超链接带显示文本**`{"type":"link","text":"飞书","link":"https://www.feishu.cn"}` URL 不需要 `rich_text`直接写普通字符串即可
- **@人**`{"type":"mention","mention_token":"<userId>","notify":false}`。**仅支持同租户用户单次写入最多 50 。** `notify` **默认 `true`**会给被 @ 的人发通知不想发务必显式传 `false`
- **@文档**同样 `"type":"mention"``mention_token` 传文档 token `shtXXX`)。
`mention_type`类型编号等可选字段以 `--print-schema` 输出为准
> ⚠️ `rich_text` 一旦设置会**忽略**同一 cell 的 `value`;它与 `formula` / `multiple_values` 三者只能选其一作为内容字段(可叠加 `cell_styles` / `note` 等)。
## Dropdown 选项 + 配色(`+dropdown-set` / `+dropdown-update`
### 选项怎么来:`--options` 与 `--source-range` 二选一
| flag | 选项来源 | 适用场景 |
|---|---|---|
| `--options '["a","b","c"]'` | 写在命令里的固定列表 | 选项集是常量不需要事后维护 |
| `--source-range ''\''Sheet1'\''!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步想维护一张枚举值列后多处引用 |
两个 flag **必须传一个、且只能传一个**——同时传或都不传CLI 会立刻报错`--source-range` A1 + sheet 前缀写法 `'Sheet1'!T1:T3`sheet 名按 A1 标准单引号包裹可以指同 sheet 也可以指其它 sheet `'Refs'!A1:A10`)。
### 配色:默认即上色,三种意图三条线
下拉**默认带胶囊高亮**——什么 flag 都不传时所有选项按内置 10 色色板循环上色 UI 手动配下拉的默认行为对齐三种意图
| 想要的效果 | 怎么传 |
|---|---|
| 默认色板循环上色 | 都不传 `--highlight` / `--colors` |
| 按选项指定具体颜色 | 只传 `--colors '["#hex",...]'`不需要再传 `--highlight` |
| 纯白下拉不要高亮 | `--highlight=false`注意 `=false` 不能省单写 `--highlight` cobra 里等价于 true |
`--colors` 长度**可以短于**选项数list 模式短于 `--options` 长度listFromRange 模式短于 `--source-range` 的单元格数未指定的选项按内置色板循环补色**不能长于**——CLI Validate 阶段就会拦截错误形如 `--colors length (4) must not exceed dropdown source size (3)`
`--highlight=false` 显式关闭高亮时`--colors` 即使传了也会被忽略语义自相矛盾但不报错)。
### 最小用例
**`--options` 模式 默认色板最常见**
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range A2:A100 \
--options '["待开始","进行中","已完成","已取消"]'
```
**`--options` 模式 指定颜色**4 个选项配 3 个颜色 4 个按色板补
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range A2:A100 \
--options '["待开始","进行中","已完成","已取消"]' \
--colors '["#bff7d9","#FFE699","#bacefd"]'
```
**`--source-range` 模式**先在 `'Sheet1'!T1:T3` 维护//保密三行再让 `B2:B21` 引用它
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range B2:B21 \
--source-range ''\''Sheet1'\''!T1:T3' \
--colors '["#cce8ff","#ffd6e7","#e6e6e6"]'
```
**纯白下拉**明确告诉用户"不要彩色"时才用
```
lark-cli sheets +dropdown-set \
--url https://... --sheet-id <id> \
--range A2:A100 \
--options '["低","中","高"]' \
--highlight=false
```
> ⚠️ **`--source-range` 必须带 sheet 前缀**(即使跟 `--range` 同 sheet。注意一个坑回读这种 listFromRange 下拉单元格时,`data_validation.range` 看起来不带 sheet 前缀(形如 `$T$1:$T$3`),如果要把读出来的 range 反过来写回 `--source-range`**必须自己重新补上 sheet 前缀**,否则会被拒。
>
> ⚠️ **`--ranges` 类批量 flag 的 sheet 前缀必须「裸写」**——`+cells-batch-set-style` / `+cells-batch-clear` / `+dropdown-update` / `+dropdown-delete` 的 `--ranges` 解析器不接受引号:表名含点或空格(如 `2025.9`、`一月份`)也直接写 `2025.9!A1`,写成 `'2025.9'!A1` 会被当成表名一部分、报 `sheet not found`。**但 `--source-range`、透视表 `--source`、`--range` 走 A1 标准**sheet 名带单引号(如 `'Sheet1'!A1:B2`)是标准写法、裸写也接受,回读统一返回带引号形式——别把 `--ranges` 的裸写要求套到这些 flag 上。
`+dropdown-update` range 批量更新的所有 flag 语义与 `+dropdown-set` 完全一致只是目标 `--ranges` 由单值变成 JSON 数组每项带 sheet 前缀同一份选项 + 配色应用到所有 range
## Shortcuts
| Shortcut | Risk | 分组 |
| --- | --- | --- |
| `+cells-set` | write | 单元格 |
| `+cells-set-style` | write | 单元格 |
| `+cells-set-image` | write | 单元格 |
| `+dropdown-set` | write | 对象 |
| `+csv-put` | write | 单元格 |
| `+table-put` | write | 单元格 |
## Flags
### `+cells-set`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 写入区域A1 格式 |
| `--cells` | string + File + Stdin复合 JSON | required | JSON2D 数组 `[[{cell},...],...]`维度与 `--range` 完全一致每个 cell 可含 `value` / `formula` / `cell_styles` / `note` / `rich_text` `type="embed-image"` 单元格嵌图完整字段跑 `--print-schema` |
| `--allow-overwrite` | bool | optional | 允许覆盖非空 cell默认 true设为 false 时遇非空 cell 报错 |
| `--max-cells` | int | optional | 防爆默认 50000隐藏 flag不在 `--help` 列出但可正常传入 |
| `--copy-to-range` | string | optional | 复制范围A1 表示法 --range --cells 写入的内容/公式/样式取决于实际传入字段复制到该区域公式引用自动平移 C2=B2 C3=B3。适合先写一行/一块模板再扩展填充整列/整区域(如 --range A1:G1 写模板--copy-to-range A1:G100 填充 100 )。支持整行 3:6整列 C:E到列尾 D3:D到行尾 D3:3支持英文逗号分隔多个目标区域 C1:D2,E5:F6 |
### `+cells-set-style`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 目标范围A1 格式 `A1:B2` |
| `--background-color` | string | optional | 背景颜色十六进制 `#ffffff` |
| `--font-color` | string | optional | 字体颜色十六进制 `#000000` |
| `--font-family` | string | optional | 字体名称 `Arial``微软雅黑` |
| `--font-size` | float64 | optional | 字体大小px101214 |
| `--font-style` | string | optional | 字体样式可选值`normal` / `italic` |
| `--font-weight` | string | optional | 字重可选值`normal` / `bold` |
| `--font-line` | string | optional | 字体线条样式可选值`none` / `underline` / `line-through` |
| `--horizontal-alignment` | string | optional | 水平对齐可选值`left` / `center` / `right` |
| `--vertical-alignment` | string | optional | 垂直对齐可选值`top` / `middle` / `bottom` |
| `--word-wrap` | string | optional | 换行策略可选值`overflow` / `auto-wrap` / `word-clip` |
| `--number-format` | string | optional | 数字格式文本 `@`数字 `0.00`货币 `$#,##0.00`日期 `mm/dd/yyyy` |
| `--border-styles` | string + File + Stdin复合 JSON | optional | 边框配置 JSON`{ top: {style,color,weight}, bottom: ..., left: ..., right: ... }`4 方向结构相同 |
### `+cells-set-image`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 目标单元格A1 格式必须单 cell `A1`起止 cell 须相同 |
| `--image` | string | required | 本地图片路径支持 PNG / JPEG / JPG / GIF / BMP / JFIF / EXIF / TIFF / BPG / HEIC |
| `--name` | string | optional | 图片文件名含扩展名省略时取 `--image` basename |
### `+dropdown-set`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | required | 目标范围A1 格式 `A2:A100` |
| `--options` | string + File + Stdin复合 JSON | xor | 下拉选项 JSON 数组例如 `["opt1","opt2"]`服务端不限制选项数量也不限制单个选项长度含逗号的选项可以接受写入时会自动转义)。大量选项建议改用 `--source-range` |
| `--colors` | string + File + Stdin简单 JSON | optional | 下拉胶囊背景色RGB hex 数组 `["#1FB6C1","#F006C2"]`)。长度可短不可长——超长 Validate 拦截`--colors length (N) must not exceed dropdown source size (M)`未指定项按内置 10 色色板循环补色。**单独传即生效**`--highlight=false` 时被忽略 |
| `--multiple` | bool | optional | 启用多选默认 `false` |
| `--highlight` | bool | optional | 下拉胶囊背景色高亮开关。**不传 = 开**(按内置 10 色色板循环上色`--highlight=false` 关闭得到纯白下拉配色用 `--colors` 覆盖 |
| `--source-range` | string | xor | listFromRange 模式的下拉源 rangeA1 表示法 + sheet 前缀 `'Sheet1'!T1:T3`)。映射到 server `data_validation.range`搭配 server `data_validation.type='listFromRange'` 自动生效 `--options` 二选一 `--options` inline 列表type=list传本 flag range 引用type=listFromRange`--colors` 长度规则不变(≤ range 单元格数`--highlight` / `--multiple` 行为相同 `--highlight` 开启且 source 覆盖单元格数超过 2000 服务端会将该下拉判为 option-error这是不支持的组合CLI 会向 stderr 输出 warning如需取消 `--highlight=false` |
### `+csv-put`
_公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--start-cell` | string | required | 目标区域起点 A1 `A1``B5`不带 sheet 前缀 `--sheet-id` / `--sheet-name` 指定 sheet必须是单个单元格不接受范围写法终点按 CSV 实际行列数自动推断 |
| `--csv` | string + File + Stdin JSON 文本 | required | RFC 4180 CSV 文本可写值或公式= 开头的单元格按公式计算);不带样式 / 批注 / 图片需要这些用 +cells-set |
| `--allow-overwrite` | bool | optional | 允许覆盖默认 true设为 false 时若目标非空报错 |
| `--range` | string | optional | --start-cell 的别名 +csv-get / +cells-set 一致 --range 定位传区间 A1:H17时自动取其左上角单元格隐藏 flag不在 `--help` 列出但可正常传入 |
### `+table-put`
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--sheets` | string + File + Stdin复合 JSON | required | Typed 表格协议pandas-DataFrame-shapedJSON顶层 `{"sheets":[...]}`每个数组项是一张子表 `{name, start_cell?, mode?, header?, allow_overwrite?, columns:["colA","colB",...], data:[[...]], dtypes?:{colA:pandasDtype, ...}, formats?:{colA:numberFormat, ...}}` —— `name` 与外层 `sheets` 数组都不可省Agents `scripts/sheets_df.py` `df_to_sheet(df, name)` 一行把 DataFrame 转成一项多子表就 list 拼起来再包 `{"sheets":[...]}`)。`dtypes` 值是 pandas dtype 字符串`int64``float64``Int64``bool``boolean``datetime64[ns]``object`、...CLI 端映射成内部 string/number/date/bool —— 省略 `dtypes` 时该列按文本写入适合原始 CSV-shaped 数据)。`formats[col]` Excel number_format 字符串 `#,##0.00``0.0%``yyyy-mm`缺省时 date 列用 `yyyy-mm-dd`string 列用文本格式 `@` |
| `--styles` | string + File + Stdin复合 JSON | optional | 类型保真写入后再应用的视觉处理操作 JSON顶层 `{styles:[...]}`每项对应一个被写入的子表 `name`并至少给 `cell_styles` / `row_sizes` / `col_sizes` / `cell_merges` 之一`cell_styles` A1 单元格 range + 扁平样式字段字段同 +cells-set-style number_format / 颜色 / 对齐 / border_stylesrow/col sizes 用行/列范围 + type/sizemerges 用单元格 range + 可选 merge_typestyles 数组的长度/顺序/name 必须与被写入的子表对应 --sheets.sheets 一一对应)。完整 cell_styles 字段结构跑 `+table-put --print-schema --flag-name styles` |
## Schemas
> 复合 JSON flag 字段速查(只列顶层 + 一层嵌套)。深层结构看下方 `## Examples`,或用 `--print-schema` 读完整 JSON Schema用法见 SKILL.md「公共 flag 速查」与「Agent 使用提示」)。
### `+cells-set` `--cells`
_【维度】行列数必须与 range 完全一致:'A1:C2'→[[_,_,_],[_,_,_]]2行×3列'B5:B7'→[[_],[_],[_]]3行×1列'A1'→[[_]]1×1_
**二维数组项**类型 object
- `value` (oneOf?) 静态单元格值文本数字布尔
- `formula` (string?) '=' 开头的单元格公式例如'=SUM(A1:A10)'
- `note` (string?) 单元格批注/备注
- `cell_styles` (object?) 单元格样式属性包括字体颜色对齐方式和数字格式 { font_color?: string, font_family?: string, font_size?: number, font_weight?: enum, font_style?: enum, 11 }
- `border_styles` (object?) 单元格边框配置 top/bottom/left/right 四个方向每个方向的结构相同 top { top?: object, bottom?: object, left?: object, right?: object }
- `rich_text` (array<object>?) — 富文本内容 each: { type: enum, text: string, style?: object, link?: string, mention_token?: string, …共 17 项 }
- `multiple_values` (array<object>?) — 多值内容,用于支持多选的列表验证单元格 each: { value: oneOf, format?: string }
- `data_validation` (object?) — 数据验证配置 { type: enum, items?: array<string>, range?: string, operator?: enum, values?: array<oneOf>, …共 9 项 }
### `+cells-set-style` `--border-styles`
_单元格边框配置含 top/bottom/left/right 四个方向,每个方向的结构相同(见 top_
**顶层字段**
- `top` (object?) { style?: enum, weight?: enum, color?: string }
- `bottom` (object?) { style?: enum, weight?: enum, color?: string }
- `left` (object?) { style?: enum, weight?: enum, color?: string }
- `right` (object?) { style?: enum, weight?: enum, color?: string }
### `+dropdown-set` `--options`
_列表选项_
**数组项**(类型 string
- 标量string
### `+table-put` `--sheets`
_一个或多个子表的 typed 数据,每个数组元素写入一张子表;支持多 DataFrame → 多子表一次写入_
**数组项**(类型 object
- `name` (string) — 目标子表名
- `start_cell` (string?) — 写入起点单元格A1 记法,如 "B2"),默认 "A1"
- `mode` (enum?) — overwrite默认从 start_cell 起写「表头 + 数据」块append把数据追加到子表已有数据下方默认不重复表头 [overwrite / append]
- `header` (boolean?) — 是否写一行列名表头
- `allow_overwrite` (boolean?) — 为 false 时,若写入会落在非空单元格则拒写以保护原数据(返回 partial_success
- `columns` (array<string>) — 列名字符串数组,顺序与 `data` 中每行取值一一对应
- `data` (array<array<string|number|boolean|null>>) — 数据行;每行是一个数组,长度必须等于 `columns`
- `dtypes` (object?) — 可选
- `formats` (object?) — 可选
### `+table-put` `--styles`
**数组项**(类型 object
- `cell_merges` (array<object>?) — 单元格合并操作数组range 使用 A1 单元格范围merge_type 默认 all each: { merge_type?: enum, range: string }
- `cell_styles` (array<object>?) — 单元格样式操作数组;每项用 A1 单元格 range 指定范围,字段名与 +cells-set-style 对齐 each: { background_color?: string, border_styles?: object, font_color?: string, font_family?: string, font_line?: enum, …共 13 项 }
- `col_sizes` (array<object>?) — 列宽操作数组range 使用列范围如 A:Ctype 为 pixel/standardpixel 需要 size each: { range: string, size?: number, type: enum }
- `name` (string) — 子表名
- `row_sizes` (array<object>?) — 行高操作数组range 使用行范围如 1:3type 为 pixel/standard/autopixel 需要 size each: { range: string, size?: number, type: enum }
## Examples
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`XOR
### `+cells-set` 的拆分与转介绍
"工具选择"段已讲清纯值(`+csv-put`vs 富写入(`+cells-set`)。下表补 CLI 侧的 `+cells-set` **兄弟拆分**,以及不属于本 reference 的**跨 reference 转介绍**——避免 agent 用 `+cells-set` 硬扛所有写入场景。
| 写入场景 | 用这个 | 不要用 |
|---------|--------|--------|
| 只改**已有 cell 的样式**,不动 value/formula | `+cells-set-style` | `+cells-set`(会触发不必要的值写入) |
| 把**单张图片嵌入**到某个 cell | `+cells-set-image` | `+cells-set`(参数更繁琐) |
| **插行/列 + 写入** 这种多步组合,且要原子 | `+batch-update`(见 lark-sheets-batch-update | 多次独立 `+cells-set`(非原子;插入会扰动后续 range |
| 在**多个不连续 range** 上应用同一组样式 | `+cells-batch-set-style`(见 lark-sheets-batch-update | 多次 `+cells-set-style`(非原子) |
### `+cells-set`
示例:
```bash
# 纯值(数组形态);默认即覆盖非空 cell无需显式传 --allow-overwrite
lark-cli sheets +cells-set --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --range "A1:B2" \
--cells '[[{"value":"name"},{"value":"score"}],[{"value":"alice"},{"value":95}]]'
# 富 cell公式 + 样式cells 是二维矩阵每元素一个 cell schema
lark-cli sheets +cells-set --spreadsheet-token shtXXX --sheet-id "$SID" \
--range "C2:C10" --cells @rich-cells.json
```
`--cells` 富格式见 `## Schemas`cells 元素含 value / formula / cell_styles / border_styles / data_validation / multiple_values / note / rich_text值 / 公式 / 样式 / 批注 / 嵌入图片可同一次写入混合提交。
> 中间想跳过的 cell 用空对象 `{}` 占位(底层语义为"保留原值不变"`--cells` 维度仍须与 `--range` 完全一致。例:`--range A1:A5 --cells '[[{"value":1}],[{}],[{}],[{}],[{"value":5}]]'` 只写 A1 和 A5。
>
> 跨多个不连续区域散点写入(如 `D2` + `F7` + `J15`)不属于 `+cells-set` 的能力范围——请用 `+batch-update` 把多次 `+cells-set` 打包成单次原子请求。
### `+cells-set-style`
只改样式,不动 value / formula。10 个 cell_styles 字段拍平为独立 flag边框走 `--border-styles` JSON。
```bash
# 加粗 + 黄底
lark-cli sheets +cells-set-style --url "..." --sheet-name "Sheet1" \
--range "A1:B2" --font-weight bold --background-color "#FFFF00"
# 配套边框
lark-cli sheets +cells-set-style --url "..." --sheet-id "$SID" \
--range "A1:D10" --font-size 12 --horizontal-alignment center \
--border-styles '{"top":{"style":"solid","color":"#000","weight":"thin"},"bottom":{"style":"solid","color":"#000","weight":"thin"}}'
```
### `+cells-set-image`
把单张图片嵌入 cell必须单 cell 范围):
```bash
lark-cli sheets +cells-set-image --url "..." --sheet-name "Sheet1" \
--range "A1" --image ./logo.png
```
### `+csv-put`
示例:
```bash
# 内联 CSV
lark-cli sheets +csv-put --url "https://example.feishu.cn/sheets/shtXXX" \
--sheet-name "Sheet1" --start-cell "A1" \
--csv $'name,score\nalice,95\nbob,87'
# 从文件
lark-cli sheets +csv-put --spreadsheet-token shtXXX --sheet-id "$SID" \
--start-cell "A1" --csv @data.csv
```
> `+csv-put` 比 `+cells-set` 短得多——批量灌值或公式时优先用它。需要样式/批注/图片才换 `+cells-set`。
>
> ✅ `=` 开头的单元格会被当作公式计算(不是字面量文本):
>
> ```bash
> lark-cli sheets +csv-put --url "..." --sheet-name "Sheet1" \
> --start-cell "A1" \
> --csv $'name,score\nalice,=SUM(B2:B10)'
> # ↑ B2 写入公式 =SUM(B2:B10),读回 formula 保留、value 为计算结果。
> # 反过来:无法用 +csv-put 写「= 开头的字面量文本」(会被当公式);样式/批注/图片仍用 +cells-set。
> ```
>
> ⚠️ **公式内部含逗号 / 引号必须 RFC 4180 转义**CSV 用逗号分隔字段,公式里的逗号(如 `COUNTIF(D5:D22,"及格")` 的参数分隔逗号)会被解析器当成字段分隔符,把一格拆成多格、整块二维结构压扁错位。规则:**含逗号的字段整格用双引号包裹,字段内部的引号再翻倍**
>
> ```bash
> # 从 G4 写一个 2 列 3 行的统计块;=COUNTIF 含逗号 + 内部引号,必须转义
> lark-cli sheets +csv-put --url "..." --sheet-name "Sheet1" \
> --start-cell "G4" \
> --csv $'统计项,结果\n成绩总和,=SUM(C5:C22)\n及格人数,"=COUNTIF(D5:D22,""及格"")"'
> # ↑ "=COUNTIF(D5:D22,""及格"")":外层双引号包裹整格,内部 "及格" 的引号翻倍成 ""及格""。
> # 裸写 =COUNTIF(D5:D22,"及格") 会被 CSV 按逗号拆成两格、写入区域从 G4:H6 错位成 G4:K4。
> ```
>
> 💡 **含逗号 / 引号 / 换行的公式优先用 `+cells-set`JSON 二维数组)写入**——`cells[r][c].formula` 字段直接放公式串,没有 CSV 转义负担,从根上杜绝拆列错位。`+table-put` 的 typed 协议只有 `columns / data / dtypes / formats` 四件套、没有 `formula` 字段,公式写入用 `+cells-set` 或 `+csv-put`。准备给 `+csv-put` 的公式加逗号时,先考虑换 `+cells-set`
>
> ```bash
> # 同样的统计块,结构化写入无需任何转义
> lark-cli sheets +cells-set --url "..." --sheet-name "Sheet1" --range "G4:H6" \
> --cells '[[{"value":"统计项"},{"value":"结果"}],[{"value":"成绩总和"},{"formula":"=SUM(C5:C22)"}],[{"value":"及格人数"},{"formula":"=COUNTIF(D5:D22,\"及格\")"}]]'
> ```
> **定位 + 写入边界(关键,避免误覆盖)**
> - 定位用 `--start-cell`(锚点 = 左上角单元格);也接受 `--range` 别名(与 `+csv-get` / `+cells-set` 一致,传区间会自动取左上角)。
> - ⚠️ `--start-cell` / `--range` **只定左上角、不限制写入大小**CSV 从锚点按自身行列数 auto-expand 铺开。给一个"小 range"**不会**截断数据——超出部分照写,且默认覆盖。这与 `+cells-set --range`(精确矩形、`--cells` 必须与 range 同维)语义相反,别把那套心智搬过来。
> - dry-run 与成功响应都回显 `writes_range`(实际落区,如 `B2:D4`**写前先 `--dry-run` 看一眼落区**,确认不会盖到相邻数据。
> - 要保护非空 cell`--allow-overwrite=false`(落区内出现非空 cell 即报错)。
### `+table-put`DataFrame → 飞书,类型保真写入)
把结构化数据DataFrame、list of dict、Counter类型保真写入**已有**表(写入语义同 `+cells-set`)。协议形状**对齐 pandas `to_json(orient="split")`**`columns:[列名]` + `data:[[行...]]`,可选 `dtypes:{列名:pandas_dtype}` 决定每列类型number 保精度、date 落真日期),可选 `formats:{列名:number_format}` 覆盖显示格式(千分位 / 百分比 / 自定义日期。dtypes 缺失时整张表按 string 写入(带 `@` 文本格式,邮编 / 订单号等含前导零的 id 保真)。
只写入**已有**表(`--url` / `--spreadsheet-token` 二选一必填),不新建工作簿——**要新建表格直接用 `+workbook-create --sheets`**(同协议、一步建表 + 类型保真写入,详见 workbook reference。读回用镜像命令 `+table-get`(见 read-data reference输出与 `--sheets` 同构、可 round-trip。
```bash
# sheet 按 name 匹配、缺则新建;多 DataFrame 经 stdin 一次写多 sheet
python export.py | lark-cli sheets +table-put --url "<表URL>" --sheets -
# 某 sheet 带 "mode":"append" 追加到已有数据末尾、默认不重复表头
lark-cli sheets +table-put --spreadsheet-token "<token>" --sheets @payload.json
```
每个 sheet 还可带 `"allow_overwrite": false`(遇非空拒写、保护原数据)、`"header": false`(只写数据不写表头)。完整字段跑 `+table-put --print-schema --flag-name sheets`
#### DataFrame → 协议(用 `df_to_sheet` helper
pandas 的 `df.to_json(orient="split", date_format="iso")` 一步完成所有清洗NaN→null、Timestamp→ISO 字符串、numpy 标量→原生数字),把 dtypes 拼上即可。本 skill 把这段 5 行 helper 打包成可 import 的 [`scripts/sheets_df.py`](../scripts/sheets_df.py)(含 `df_to_sheet``sheet_to_df`,写入 / 读回成对):
```python
from sheets_df import df_to_sheet
# 单 sheet显式 format 覆盖默认显示)
payload = {"sheets": [df_to_sheet(df, "销售", {"营收": "#,##0.00", "毛利率": "0.0%"})]}
# 多 sheet——helper 让每个 sheet 一行,不再重复 boilerplate
payload = {"sheets": [df_to_sheet(df1, "销售"),
df_to_sheet(df2, "成本"),
df_to_sheet(df3, "利润")]}
```
> **CSV-shaped 全文本数据**(不需要类型保真、含前导零的 id 也要保留)省掉 dtypes 即可inline 一行写完,不必走 helper注意保留 `date_format="iso"`,否则 datetime 列会被序列化成 epoch 毫秒数字CLI 拒绝):
> ```python
> payload = {"sheets": [{"name": "原始",
> **json.loads(df.to_json(orient="split", date_format="iso"))}]}
> ```
> **别把 `to_json + json.loads` 换成 `df.to_dict(orient="split")`**:会留 `numpy.int64` 让 `json.dumps` 后续报 "not serializable"——这一步是清洗的关键。
不用 pandas 也行——typed 协议就是纯 JSON。手写场景
```python
# Counter / dict / 手拼数据:直接写 columns + data按需加 dtypes/formats
payload = {"sheets": [{
"name": "渠道",
"columns": ["channel", "count", "rate"],
"data": [["app", 1240, 0.62], ["web", 760, 0.38]],
"dtypes": {"count": "int64", "rate": "float64"},
"formats": {"rate": "0.0%"},
}]}
```
> **dtype 速查**`int64`/`float64`(数值)、`Int64`含空值的整数nullable、`bool`/`boolean`、`datetime64[ns]`date默认 `yyyy-mm-dd`)、`object`string。pandas dtype 字符串原样塞进 dtypes 即可CLI 端按前缀匹配(`int*`/`uint*`/`Int*`/`float*` → number 等)。未识别 dtype 兜底为 string。
#### `--styles`(写入时同时套样式)
`--styles` 在 typed 写入后顺带应用视觉处理,省掉一次 `+cells-set-style` 往返。协议与 `+workbook-create --styles` **完全同构**(详见 workbook reference顶层 `{styles:[...]}`,数组每项对应一个被写入的子表、含 `name`,并按能力拆成四类可选数组——`cell_styles`A1 单元格 range + 扁平样式字段,含 `number_format` / 颜色 / 对齐 / `border_styles`,随内容在同一次写入里一并应用)、`cell_merges``row_sizes``col_sizes`。styles 数组的长度 / 顺序 / name 必须与被写入的子表对应(与 `--sheets.sheets` 一一对应)。
```bash
lark-cli sheets +table-put --url "<表URL>" \
--sheets '{"sheets":[{"name":"明细","columns":["日期","金额"],"dtypes":{"日期":"datetime64[ns]","金额":"float64"},"formats":{"金额":"#,##0.00"},"data":[["2024-01-15",1234.5]]}]}' \
--styles '{"styles":[{"name":"明细",
"cell_styles":[{"range":"A1:B1","font_weight":"bold","background_color":"#f5f5f5","horizontal_alignment":"center"}],
"cell_merges":[{"range":"A1:B1"}],
"col_sizes":[{"range":"A:B","type":"pixel","size":120}]}]}'
```
完整字段跑 `+table-put --print-schema --flag-name styles`
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+cells-set``--cells` 必须能解析为 JSON 二维矩阵且行列数与 `--range` 完全一致;`+cells-set-style` 的样式 flag 至少一个非空(或带 `--border-styles``+cells-set-image``--range` 必须是单 cell起止 cell 相同);`+csv-put``--csv` 必须能按 RFC 4180 解析;`+table-put` 给了 `--styles` 则按子表名 / 顺序 / 数量与 `--sheets.sheets` 对齐校验;防爆参数上限校验。
- `DryRun`:输出目标 range + 推断尺寸 + 是否覆盖非空 cell 警告,零网络副作用。
- `Execute`:写后不自动回读;如需确认,自行调用 `+cells-get --range <写入区域> --include value,formula` 抽样核对。

View File

@ -0,0 +1,32 @@
#!/usr/bin/env python3
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
# SPDX-License-Identifier: MIT
"""DataFrame ↔ Feishu Sheet typed-JSON helpers.
This is the same 7-line snippet the skill docs already inline (see
`lark-sheets-write-cells` "DataFrame → 协议5 行 helper" and
`lark-sheets-read-data` "输出 → DataFrame2 行 helper"), pulled out
so callers can `import` it instead of copy-pasting:
from sheets_df import df_to_sheet, sheet_to_df
Callers run lark-cli themselves; this file is a library, not a CLI.
"""
import json
import pandas as pd
def df_to_sheet(df, name, formats=None):
"""Pack one DataFrame into one entry of a `+table-put --sheets` payload."""
return {
"name": name,
**json.loads(df.to_json(orient="split", date_format="iso")),
"dtypes": df.dtypes.astype(str).to_dict(),
**({"formats": formats} if formats else {}),
}
def sheet_to_df(sheet):
"""Restore one `+table-get` sheet dict into a typed DataFrame."""
return pd.DataFrame(sheet["data"], columns=sheet["columns"]).astype(sheet["dtypes"])