From 8e84f47d3eabd62a8df22e7450aedea00ee19f99 Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Thu, 28 May 2026 16:34:34 +0800 Subject: [PATCH 1/6] docs(sheets): sync +pivot-create summarize_by lowercase enum values from spec --- .../references/lark-sheets-pivot-table.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/skills/lark-sheets/references/lark-sheets-pivot-table.md b/skills/lark-sheets/references/lark-sheets-pivot-table.md index 28fa8ee51..25bbad56d 100644 --- a/skills/lark-sheets/references/lark-sheets-pivot-table.md +++ b/skills/lark-sheets/references/lark-sheets-pivot-table.md @@ -20,19 +20,19 @@ 创建透视表前先识别用户需求中的分组维度和聚合指标,**不要搞反**: - **rows(行字段)** = 分组维度,即"按什么分组"。例:部门、地区、医生、产品类别 -- **values(值字段)** = 聚合指标,即"统计什么数值"。例:SUM(销售额)、COUNT(订单数) +- **values(值字段)** = 聚合指标,即"统计什么数值"。例:销售额(聚合方式 `sum`)、订单数(聚合方式 `count`) - **columns(列字段)** = 交叉维度(可选),即"再按什么横向展开"。例:月份、性别 | 用户说 | rows | values | columns | |--------|------|--------|---------| -| "按部门统计人数" | 部门 | COUNT(姓名) | — | -| "按医生统计费用和结余" | 主管医生 | SUM(费用), SUM(结余) | — | -| "各部门男女人数" | 部门 | COUNT(姓名) | 性别 | +| "按部门统计人数" | 部门 | 姓名(`summarize_by: "count"`) | — | +| "按医生统计费用和结余" | 主管医生 | 费用(`"sum"`)、结余(`"sum"`) | — | +| "各部门男女人数" | 部门 | 姓名(`"count"`) | 性别 | **常见配置错误(必须注意)**: - **数据源范围必须精确**:透视表的数据源范围必须包含表头行,且精确覆盖全部数据行列。范围过大(包含空行/空列)或过小(遗漏数据列)都会导致透视表结果错误 -- **行列字段选择要匹配用户意图**:用户说"按商品统计金额"→ 行字段=商品,值字段=金额(SUM)。不要把行列字段搞反 -- **聚合类型要匹配**:用户说"统计数量"→ COUNT;"统计总额"→ SUM;"统计平均"→ AVERAGE。默认不要用 COUNT 替代 SUM +- **行列字段选择要匹配用户意图**:用户说"按商品统计金额"→ 行字段=商品,值字段=金额(`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 - **创建后必须验证**:调用 `+pivot-list` 确认透视表结构正确 From 77f86ec2fd650b59d8d571949d09f7f57294971d Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Thu, 28 May 2026 17:38:15 +0800 Subject: [PATCH 2/6] docs(sheets): wrap sheet names in single quotes in A1 examples Synced from spec. Affects 3 reference md (pivot-table / batch-update / write-cells) and 2 generated flag-data JSONs. A1 examples like `Sheet1!A1:D100` now read `'Sheet1'!A1:D100` so models default to single-quoted sheet names. Excel A1 notation requires single quotes for sheet names containing hyphens / spaces / non-ASCII chars; always-quoting is also valid for plain names, so this is the safer default to teach. Affected flags: - +pivot-create --source - +dropdown-update --ranges / --source-range - +dropdown-delete --ranges - +dropdown-set --source-range - +cells-batch-set-style --ranges - +cells-batch-clear --ranges --- shortcuts/sheets/data/flag-defs.json | 14 +++++++------- shortcuts/sheets/data/flag-schemas.json | 8 ++++---- .../references/lark-sheets-batch-update.md | 10 +++++----- .../references/lark-sheets-pivot-table.md | 2 +- .../references/lark-sheets-write-cells.md | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index eb221c4c9..de3b1ddf6 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -1799,7 +1799,7 @@ "kind": "own", "type": "string", "required": "xor", - "desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `Sheet1!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress." + "desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `'Sheet1'!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress." }, { "name": "dry-run", @@ -2527,7 +2527,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A1:B2\",\"Sheet2!D1:D10\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same style is applied to every range", + "desc": "Target ranges as a JSON array (e.g. `[\"'Sheet1'!A1:B2\",\"'Sheet2'!D1:D10\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same style is applied to every range", "input": [ "file", "stdin" @@ -2673,7 +2673,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:A100\",\"Sheet1!C2:C100\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id", + "desc": "Target ranges as a JSON array (e.g. `[\"'Sheet1'!A2:A100\",\"'Sheet1'!C2:C100\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id", "input": [ "file", "stdin" @@ -2720,7 +2720,7 @@ "kind": "own", "type": "string", "required": "xor", - "desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `Sheet1!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress." + "desc": "Source range for listFromRange dropdown (A1 + sheet prefix, e.g. `'Sheet1'!T1:T3`); maps to server `data_validation.range` and auto-sets `data_validation.type='listFromRange'`. XOR with `--options`: pass `--options` for an inline list (type=list), pass this for a range reference (type=listFromRange). `--colors` length rule unchanged (≤ source range cell count); `--highlight` / `--multiple` behave the same. When `--highlight` is on and the source covers more than 2000 cells, the server flags the dropdown as option-error (highlight + large source is an unsupported combo); CLI emits a stderr warning. Pass `--highlight=false` to suppress." }, { "name": "dry-run", @@ -2753,7 +2753,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Target ranges as a JSON array (up to 100 items, e.g. `[\"Sheet1!E2:E6\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id", + "desc": "Target ranges as a JSON array (up to 100 items, e.g. `[\"'Sheet1'!E2:E6\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id", "input": [ "file", "stdin" @@ -2797,7 +2797,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:Z1000\",\"Sheet2!A2:Z1000\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same scope is cleared from every range", + "desc": "Target ranges as a JSON array (e.g. `[\"'Sheet1'!A2:Z1000\",\"'Sheet2'!A2:Z1000\"]`); each item must include a sheet prefix; the prefix must be the sheet display name (e.g. `Sheet1`), not the sheet reference_id; ranges may target different sheets; the same scope is cleared from every range", "input": [ "file", "stdin" @@ -3144,7 +3144,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Pivot table source range (A1 notation; format `SheetName!StartCell:EndCell`, e.g. `Sheet1!A1:D100`)" + "desc": "Pivot table source range (A1 notation; format `'SheetName'!StartCell:EndCell`, e.g. `'Sheet1'!A1:D100`)" }, { "name": "range", diff --git a/shortcuts/sheets/data/flag-schemas.json b/shortcuts/sheets/data/flag-schemas.json index bdcd9abe0..4a909314d 100644 --- a/shortcuts/sheets/data/flag-schemas.json +++ b/shortcuts/sheets/data/flag-schemas.json @@ -3634,7 +3634,7 @@ }, "rules": { "type": "array", - "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 为整组覆盖(PUT 语义):传入的 rules 即更新后的完整规则集,会先清空所有现有列的旧规则再应用,未列出的列其旧规则会被清除(不与旧规则合并);传空数组 [] 即清空所有列的规则。", + "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 时传空数组 [] 表示清空所有现有列的规则。", "items": { "type": "object", "description": "单列筛选规则。", @@ -3879,7 +3879,7 @@ }, "rules": { "type": "array", - "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 为整组覆盖(PUT 语义):传入的 rules 即更新后的完整规则集,会先清空所有现有列的旧规则再应用,未列出的列其旧规则会被清除(不与旧规则合并);传空数组 [] 即清空所有列的规则。", + "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。update 时传空数组 [] 表示清空所有现有列的规则。", "items": { "type": "object", "description": "单列筛选规则。", @@ -4128,7 +4128,7 @@ }, "rules": { "type": "array", - "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。update 同样为整组覆盖(PUT 语义):传入的 rules 整组替换该视图所有列的规则,未列出的列其旧规则会被清除(不与旧规则合并),传空数组 [] 即清空全部。", + "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。", "items": { "type": "object", "description": "单列筛选规则。", @@ -4373,7 +4373,7 @@ }, "rules": { "type": "array", - "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。update 同样为整组覆盖(PUT 语义):传入的 rules 整组替换该视图所有列的规则,未列出的列其旧规则会被清除(不与旧规则合并),传空数组 [] 即清空全部。", + "description": "列级筛选规则列表,每一项对应一个具体列的筛选条件。结构与 manage_filter_object.properties.rules 完全一致。", "items": { "type": "object", "description": "单列筛选规则。", diff --git a/skills/lark-sheets/references/lark-sheets-batch-update.md b/skills/lark-sheets/references/lark-sheets-batch-update.md index c16f5f8a8..7de341f9b 100644 --- a/skills/lark-sheets/references/lark-sheets-batch-update.md +++ b/skills/lark-sheets/references/lark-sheets-batch-update.md @@ -51,7 +51,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["Sheet1!A1:B2","Sheet2!D1:D10"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;所有 range 应用同一组 style | +| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["'Sheet1'!A1:B2","'Sheet2'!D1:D10"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;所有 range 应用同一组 style | | `--background-color` | string | optional | 背景颜色(十六进制,如 `#ffffff`) | | `--font-color` | string | optional | 字体颜色(十六进制,如 `#000000`) | | `--font-size` | float64 | optional | 字体大小(px,例:10、12、14) | @@ -70,12 +70,12 @@ _公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(如 `["Sheet1!A2:A100","Sheet1!C2:C100"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id | +| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(如 `["'Sheet1'!A2:A100","'Sheet1'!C2:C100"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 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 模式的下拉源 range,A1 表示法 + 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`。 | +| `--source-range` | string | xor | listFromRange 模式的下拉源 range,A1 表示法 + 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` @@ -83,7 +83,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--yes`、`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(最多 100 个,如 `["Sheet1!E2:E6"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id | +| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(最多 100 个,如 `["'Sheet1'!E2:E6"]`),每项必须带 sheet 前缀;前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id | ### `+cells-batch-clear` @@ -91,7 +91,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--yes`、`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["Sheet1!A2:Z1000","Sheet2!A2:Z1000"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;对所有 range 执行同一 scope 的清除 | +| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["'Sheet1'!A2:Z1000","'Sheet2'!A2:Z1000"]`);前缀必须是 sheet 显示名(如 `Sheet1`),不接受 sheet reference_id;支持跨 sheet;对所有 range 执行同一 scope 的清除 | | `--scope` | string | optional | 清除范围 enum:`content`(默认,仅清内容)/ `formats`(仅清格式)/ `all`(清内容 + 格式)(可选值:`content` / `formats` / `all`) | ## Schemas diff --git a/skills/lark-sheets/references/lark-sheets-pivot-table.md b/skills/lark-sheets/references/lark-sheets-pivot-table.md index 25bbad56d..3a8f1cafa 100644 --- a/skills/lark-sheets/references/lark-sheets-pivot-table.md +++ b/skills/lark-sheets/references/lark-sheets-pivot-table.md @@ -63,7 +63,7 @@ _公共四件套 · 系统:`--dry-run`_ | --- | --- | --- | --- | | `--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 | 透视表落点子表内的起始 cell(A1 格式,如 `A1`),映射到顶层 `target_position`,默认 `A1`(值为 A1 时不下发)。它与 `--range` 都表达落点但落在不同 wire 字段,避免两者同时给冲突值 | -| `--source` | string | required | 透视表源数据区域(A1 表示法,格式 `SheetName!StartCell:EndCell`,如 `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` diff --git a/skills/lark-sheets/references/lark-sheets-write-cells.md b/skills/lark-sheets/references/lark-sheets-write-cells.md index c03d25950..d19f2febf 100644 --- a/skills/lark-sheets/references/lark-sheets-write-cells.md +++ b/skills/lark-sheets/references/lark-sheets-write-cells.md @@ -290,7 +290,7 @@ _公共四件套 · 系统:`--dry-run`_ | `--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 模式的下拉源 range,A1 表示法 + 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`。 | +| `--source-range` | string | xor | listFromRange 模式的下拉源 range,A1 表示法 + 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` From 0f695b60ece4640fbf0a9a2726657cc2c4ae7593 Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Thu, 28 May 2026 17:51:00 +0800 Subject: [PATCH 3/6] docs(sheets): wrap A1 sheet names in handwritten examples + bash histexpand guide Synced from spec. Affects 4 reference md (chart / pivot-table / sparkline / write-cells) and SKILL.md. In addition to wrapping sheet names in single quotes in all remaining handwritten examples (covers chart refs.value / nameRef, sparkline source, write-cells --source-range, pivot-create narrative), SKILL.md gains a new "Shell quoting for A1 references with !" section. The new section addresses bash history expansion: in interactive bash (e.g., ShellExec sandbox), unescaped `!Word` after `"..."` triggers `bash: !A1: event not found`, dropping the command before lark-cli sees it. The section gives 4 quoting strategies (shell single-quote outer, `set +H` prefix, mixed quoting, sheet-rename fallback) and an anti-pattern list. Affected files: - skills/lark-sheets/SKILL.md (new section) - skills/lark-sheets/references/lark-sheets-chart.md - skills/lark-sheets/references/lark-sheets-pivot-table.md - skills/lark-sheets/references/lark-sheets-sparkline.md - skills/lark-sheets/references/lark-sheets-write-cells.md --- skills/lark-sheets/SKILL.md | 34 ++++++++++++++++++- .../references/lark-sheets-chart.md | 30 ++++++++-------- .../references/lark-sheets-pivot-table.md | 11 ++++-- .../references/lark-sheets-sparkline.md | 8 ++--- .../references/lark-sheets-write-cells.md | 10 +++--- 5 files changed, 66 insertions(+), 27 deletions(-) diff --git a/skills/lark-sheets/SKILL.md b/skills/lark-sheets/SKILL.md index b00c35ad0..290eafd62 100644 --- a/skills/lark-sheets/SKILL.md +++ b/skills/lark-sheets/SKILL.md @@ -81,7 +81,7 @@ metadata: - ⚠️ **`/wiki/` 知识库链接不能直接当表格定位用**:wiki 链接背后可能是电子表格,也可能是文档 / 多维表格等其它类型,`--url` **不会**自动把 wiki token 解析成 spreadsheet token,直接传会失败。必须先把它解析成真实文档 token —— `lark-cli wiki +node-get --node-token ""`,确认返回的 `obj_type` 为 `sheet` 后,取其 `obj_token` 作为 `--spreadsheet-token` 传入(解析细节见 [`../lark-wiki/SKILL.md`](../lark-wiki/SKILL.md))。 - **例外**:`+workbook-create` 是新建一个还不存在的表格,**不接受任何 spreadsheet / sheet 定位 flag**(只有 `--title` / `--folder-token` / `--headers` / `--values`)。 2. **sheet 定位(公共四件套 shortcut 必填)**:`--sheet-id` 与 `--sheet-name` 二选一,**必须给其中之一**。两个都不给 → 校验报错 `specify at least one of --sheet-id or --sheet-name`。 - - ⚠️ **`--range` 里的 `Sheet1!` 前缀不能替代 sheet 定位**:即使写了 `--range "Sheet1!A1:B2"`,仍**必须**额外传 `--sheet-id` 或 `--sheet-name`,否则照样报上面的错。 + - ⚠️ **`--range` 里的 `Sheet1!` 前缀不能替代 sheet 定位**:即使写了 `--range "'Sheet1'!A1:B2"`,仍**必须**额外传 `--sheet-id` 或 `--sheet-name`,否则照样报上面的错。 - **例外**:徽章标为 `_公共:URL/token(无 sheet 定位)…_` 的 shortcut(如 `+workbook-info` / `+workbook-export` / `+batch-update` / `+dropdown-get|update|delete` / `+cells-batch-set-style` / `+cells-batch-clear` / `+sheet-create`)**不接受也不需要** sheet 定位,只给一组 spreadsheet 定位即可。 | Flag | Type | 必填 | 说明 | @@ -127,3 +127,35 @@ lark-cli sheets +cells-set --url "..." --sheet-name "Sheet1" --range "A1:B2" --c ``` **`@file` 接绝对路径会被拒,且被拒后不要照报错提示做。** `@file` 出于安全只接受 cwd 下的相对路径,传 `@/tmp/cells.json` 这类绝对路径或 cwd 之外的路径会被拒。此时报错会建议"先 cd 到目标目录,或改用相对路径"——**两条都不要照做**:cd 过去、或把临时文件写进用户项目目录,都会污染工作目录。正解是改用 stdin(`-- - < 文件`)。 + +## Shell 调用注意事项:A1 reference 含 `!` 的引号选择 + +A1 表示法的 sheet 前缀(`Sheet1!A1`)里那个 `!` 在 interactive bash(豆包 ShellExec / 任何带 history 的 shell)下是**历史展开触发字符**。`!Word` 形式会被 bash 当成"展开最近以 Word 开头的历史命令",如果没有匹配就报: + +``` +bash: !A1: event not found +``` + +——命令根本到不了 lark-cli。**double-quote 内 `!` 仍会触发 histexpand**;只有 single-quote 或显式关闭 history expansion 才能让 `!` 字面通过。 + +**对 agent 的可执行规则**: + +| 场景 | 推荐写法 | 说明 | +|---|---|---| +| 一次性调用,sheet 名无特殊字符 | `--source 'Sheet1!A1:D100'` | shell single-quote 整段包住,A1 内部不带单引号也合法 | +| 一次性调用,sheet 名含 `-` / 空格 / 非 ASCII | `set +H; lark-cli sheets … --source "'Sales-Q1'!A1:D100"` | 先 `set +H` 关 histexpand,然后 double-quote 外层 + A1 内部 single-quote 包裹 sheet 名 | +| 该 shell session 后续会跑大量 lark-cli 命令 | 在 session 第一行执行 `set +H`,后续全程 double-quote 即可 | 一次性配置,减少每条命令的引号心智 | +| 实在写不对 | `+sheet-rename --title ` 先把含特殊字符的 sheet 改成 plain 名 | U046 模型走通的兜底路径,但要权衡是否会破坏其它引用 | + +**反模式**: + +- ❌ `--source "Sheet1!A1:D100"`(double-quote + 未关 histexpand)—— `!A1` 被吃掉,命令不达 lark-cli +- ❌ `--source 'Sales-Q1'\''!A1:D100'`(缺一节 `'\''`)—— shell 把 `Sales-Q1` 当字符串、`!A1` 又触发 histexpand +- ❌ `--source "'Sheet1'!A1"`(未 `set +H` 的 double-quote)—— A1 内部包是对的、但 shell 层的 `!A1` 还会被展开 + +**注意区分两层引号**: + +- **shell 层引号**(bash 的 `"..."` / `'...'`):决定 bash 是否做参数展开、变量替换、history expansion +- **A1 层引号**(A1 reference 字符串里的 `'Sheet name'!A1`):Excel/Sheets 标准,sheet 名含 `-` / 空格 / 非 ASCII 字符时**必须**单引号包裹;plain 名时可选(建议总是包裹以保持一致) + +bash single-quote 不可嵌套,要在 single-quote 中再放 single-quote,用序列 `'\''`(关闭外层 single-quote → 转义一个 single-quote → 重开外层 single-quote);嫌麻烦就用 `set +H` 方案。 diff --git a/skills/lark-sheets/references/lark-sheets-chart.md b/skills/lark-sheets/references/lark-sheets-chart.md index f575cade5..3c242f9ef 100644 --- a/skills/lark-sheets/references/lark-sheets-chart.md +++ b/skills/lark-sheets/references/lark-sheets-chart.md @@ -53,7 +53,7 @@ > **正确做法**: > 1. 在 `data` 下显式设置 `"headerMode": "detached"`; > 2. `refs` **只覆盖该子集的纯数据**,不要向上/向左多带 1 行/列,也不要把全局表头整段并进来(否则会把其它分组的数据混进图); -> 3. **`nameRef` 必填**:给 `dim1.serie.nameRef` 写真正表头中"类别名"那一格的 A1 引用(如 `Sheet2!A1`),给每个 `dim2.series[i].nameRef` 写对应数值列的 A1 引用(如 `Sheet2!C1`、`Sheet2!D1`)。任一缺失会被校验拦下并报 `headerMode=detached requires ... nameRef`; +> 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 开始。 > @@ -173,7 +173,7 @@ lark-cli sheets +chart-create --url "https://example.feishu.cn/sheets/shtXXX" \ "size":{"width":600,"height":400}, "snapshot":{ "data":{ - "refs":[{"value":"Sheet1!A1:B10"}], + "refs":[{"value":"'Sheet1'!A1:B10"}], "dim1":{"serie":{"index":1}}, "dim2":{"series":[{"index":2}]} }, @@ -203,7 +203,7 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet1" --properties '{ }] }}, "data":{ - "refs":[{"value":"Sheet1!A1:B11"}], + "refs":[{"value":"'Sheet1'!A1:B11"}], "dim1":{"serie":{"index":1,"aggregate":true}}, "dim2":{"series":[{"index":2,"aggregateType":"sum"}]} } @@ -225,11 +225,11 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties '{ "data":{ "headerMode":"detached", "direction":"column", - "refs":[{"value":"Sheet2!A11:D17"}], - "dim1":{"serie":{"index":1,"nameRef":"Sheet2!A1"}}, + "refs":[{"value":"'Sheet2'!A11:D17"}], + "dim1":{"serie":{"index":1,"nameRef":"'Sheet2'!A1"}}, "dim2":{"series":[ - {"index":3,"nameRef":"Sheet2!C1"}, - {"index":4,"nameRef":"Sheet2!D1"} + {"index":3,"nameRef":"'Sheet2'!C1"}, + {"index":4,"nameRef":"'Sheet2'!D1"} ]} } } @@ -250,9 +250,9 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties '{ ```jsonc // 错误 1:refs 含全局表头但跨段 —— 多个区域被混进同一张图 -{"data":{"refs":[{"value":"Sheet!A1:E17"}], ... }} // 华东图混进华北 8 行 +{"data":{"refs":[{"value":"'Sheet'!A1:E17"}], ... }} // 华东图混进华北 8 行 // 错误 2:inline + refs 只取数据段、不写 detached/nameRef —— 图例显示成具体数据值 -{"data":{"refs":[{"value":"Sheet!A10:E17"}],"dim1":{"serie":{"index":1}}, ... }} +{"data":{"refs":[{"value":"'Sheet'!A10:E17"}],"dim1":{"serie":{"index":1}}, ... }} ``` ✅ 正确模式:3 张图各自 detached、refs 干净不重叠: @@ -261,15 +261,15 @@ lark-cli sheets +chart-create --url "..." --sheet-name "Sheet2" --properties '{ // 图 1:华北 {"data":{ "headerMode":"detached","direction":"column", - "refs":[{"value":"Sheet!A2:E9"}], - "dim1":{"serie":{"index":1,"nameRef":"Sheet!A1"}}, + "refs":[{"value":"'Sheet'!A2:E9"}], + "dim1":{"serie":{"index":1,"nameRef":"'Sheet'!A1"}}, "dim2":{"series":[ - {"index":3,"nameRef":"Sheet!C1"}, - {"index":4,"nameRef":"Sheet!D1"} + {"index":3,"nameRef":"'Sheet'!C1"}, + {"index":4,"nameRef":"'Sheet'!D1"} ]} }} -// 图 2:华东 —— refs 改 Sheet!A10:E17,其余同上 -// 图 3:华南 —— refs 改 Sheet!A18:E25,其余同上 +// 图 2:华东 —— refs 改 'Sheet'!A10:E17,其余同上 +// 图 3:华南 —— refs 改 'Sheet'!A18:E25,其余同上 ``` > `--properties` JSON 关键字段: diff --git a/skills/lark-sheets/references/lark-sheets-pivot-table.md b/skills/lark-sheets/references/lark-sheets-pivot-table.md index 3a8f1cafa..4b8b524c1 100644 --- a/skills/lark-sheets/references/lark-sheets-pivot-table.md +++ b/skills/lark-sheets/references/lark-sheets-pivot-table.md @@ -121,7 +121,7 @@ lark-cli sheets +pivot-list --url "..." --sheet-id "$SID" > 数据源 `--source` 必须从表头行开始;空行 / 汇总行会被当作数据参与聚合,需提前用 `+csv-get` 确认起止边界。`--source` 和 `--range` 是独立 flag(不要再放 `--properties`);`rows` / `columns` / `values` 等数组字段走 `--properties`。 > > **先理清 `+pivot-create` 上 4 个位置类入参(语义不同,别混)**: -> - `--source`(**必填**):**源数据**区域,须自带 `Sheet!` 前缀(如 `Sheet1!A1:D100`)。源 sheet 的名字在 `--source` 字符串里,**不**通过单独 flag 传。 +> - `--source`(**必填**):**源数据**区域,须自带 `Sheet!` 前缀(如 `'Sheet1'!A1:D100`,sheet 名按 A1 标准单引号包裹)。源 sheet 的名字在 `--source` 字符串里,**不**通过单独 flag 传。 > - `--sheet-id` / `--sheet-name`:**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。**注意:跟其它 shortcut 不同,这里 `--sheet-id` / `--sheet-name` 表达的不是"数据源所在 sheet"而是"产物落点 sheet"**。 > - `--target-position`(可选,A1 表示法,默认 `A1`):落点 sheet 内的起始 cell,映射到顶层 `target_position`。 > - `--range`(可选,A1 单值,仅 create 生效):跟 `--target-position` 表达同一意图但映射到 `properties.range`,**两者不要同时给**。 @@ -136,13 +136,18 @@ lark-cli sheets +pivot-list --url "..." --sheet-id "$SID" ```bash # 策略 1(强烈推荐):不传任何落点 flag → 后端自动新建子表,零覆盖风险 lark-cli sheets +pivot-create --url "..." \ - --source "Sheet1!A1:D100" --properties @pivot.json + --source "'Sheet1'!A1:D100" --properties @pivot.json # 策略 2:落进指定的已有目标子表(注意目标 sheet ≠ 源 sheet,否则要配 --target-position 避开源数据) lark-cli sheets +pivot-create --url "..." \ - --source "Sheet1!A1:D100" --sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json + --source "'Sheet1'!A1:D100" --sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json ``` +> ⚠️ 上面 bash 示例的 `--source` 用了双引号,在 interactive bash(含豆包 ShellExec)下 `!` 会触发 history expansion 报 `bash: !A1: event not found`。**实际跑命令时**用以下任一种写法: +> - **首选**:命令最前面加 `set +H;` 关掉 history expansion,全程 double-quote 不踩坑 +> - **次选**:`--source` 整体改 shell single-quote,但 sheet 名内的 A1 单引号需要 `'\''` 转义:`--source ''\''Sheet1'\''!A1:D100'` +> - **应急**:先 `+sheet-rename --title ` 把含 `-` / 空格的 sheet 名改成 plain 名(U046 模型最终走通的兜底路径) + ### `+pivot-update` > 不允许改 `--source` / `--range`(透视表创建后位置/数据源固定);只能用 `--properties` 改 rows / columns / values / filters 等。先 `+pivot-list --pivot-table-id ` 回读再 patch,避免漏字段。 diff --git a/skills/lark-sheets/references/lark-sheets-sparkline.md b/skills/lark-sheets/references/lark-sheets-sparkline.md index 6c2bea74c..090dbcda5 100644 --- a/skills/lark-sheets/references/lark-sheets-sparkline.md +++ b/skills/lark-sheets/references/lark-sheets-sparkline.md @@ -108,8 +108,8 @@ lark-cli sheets +sparkline-create --url "..." --sheet-id "$SID" --properties @sp { "config": { "line_width": 2 }, "sparklines": [ - {"position": {"row": 1, "col": "F"}, "source": "Sheet1!A2:E2"}, - {"position": {"row": 2, "col": "F"}, "source": "Sheet1!A3:E3"} + {"position": {"row": 1, "col": "F"}, "source": "'Sheet1'!A2:E2"}, + {"position": {"row": 2, "col": "F"}, "source": "'Sheet1'!A3:E3"} ] } ``` @@ -122,8 +122,8 @@ lark-cli sheets +sparkline-create --url "..." --sheet-id "$SID" --properties @sp # 假设 +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_id":"sl_1","source":"'Sheet1'!A2:A20"}, + {"sparkline_id":"sl_2","source":"'Sheet1'!B2:B20"} ] }' ``` diff --git a/skills/lark-sheets/references/lark-sheets-write-cells.md b/skills/lark-sheets/references/lark-sheets-write-cells.md index d19f2febf..3fe4926fd 100644 --- a/skills/lark-sheets/references/lark-sheets-write-cells.md +++ b/skills/lark-sheets/references/lark-sheets-write-cells.md @@ -143,9 +143,9 @@ Step 2: `+cells-set` — range="A2", cells 含 value + cell_styles + border_styl | flag | 选项来源 | 适用场景 | |---|---|---| | `--options '["a","b","c"]'` | 写在命令里的固定列表 | 选项集是常量、不需要事后维护 | -| `--source-range 'Sheet1!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步;想维护一张「枚举值」列后多处引用 | +| `--source-range '''Sheet1''!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步;想维护一张「枚举值」列后多处引用 | -两个 flag **必须传一个、且只能传一个**——同时传或都不传,CLI 会立刻报错。`--source-range` 用 A1 + sheet 前缀写法(如 `Sheet1!T1:T3`),可以指同 sheet 也可以指其它 sheet(如 `Refs!A1:A10`)。 +两个 flag **必须传一个、且只能传一个**——同时传或都不传,CLI 会立刻报错。`--source-range` 用 A1 + sheet 前缀写法(如 `'Sheet1'!T1:T3`,sheet 名按 A1 标准单引号包裹),可以指同 sheet 也可以指其它 sheet(如 `'Refs'!A1:A10`)。 ### 配色:默认即上色,三种意图三条线 @@ -182,16 +182,18 @@ lark-cli sheets +dropdown-set \ --colors '["#bff7d9","#FFE699","#bacefd"]' ``` -**`--source-range` 模式**(先在 `Sheet1!T1:T3` 维护「男/女/保密」三行,再让 `B2:B21` 引用它): +**`--source-range` 模式**(先在 `'Sheet1'!T1:T3` 维护「男/女/保密」三行,再让 `B2:B21` 引用它): ``` lark-cli sheets +dropdown-set \ --url https://... --sheet-id \ --range B2:B21 \ - --source-range 'Sheet1!T1:T3' \ + --source-range ''\''Sheet1'\''!T1:T3' \ --colors '["#cce8ff","#ffd6e7","#e6e6e6"]' ``` +> ⚠️ `--source-range` 的 shell 包法:上面用 `''\''Sheet1'\''!T1:T3'` 是为了在 shell single-quote 外层中嵌入 A1 内部 single-quote(防 bash 历史展开 `!T1`)。等效更简单写法:命令最前加 `set +H;` 关 history expansion,然后 `--source-range "'Sheet1'!T1:T3"`。详见 SKILL.md「Shell 调用注意事项」。 + **纯白下拉**(明确告诉用户"不要彩色"时才用): ``` From 690e74689667db70255ebce642b711f89f49ee5c Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Thu, 28 May 2026 18:03:59 +0800 Subject: [PATCH 4/6] docs(sheets): drop bash histexpand section, fix write-cells table escape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sync from spec, refining the bash-quoting deep-dive added in 0f695b6: - Drop the `## Shell 调用注意事项` section in SKILL.md and the inline `⚠️ bash 引号` callouts in lark-sheets-pivot-table.md and lark-sheets-write-cells.md. The 4-scenario quoting table + anti-pattern list turned out too verbose for the SKILL intro; single-quoted examples in the references are themselves enough nudge. - lark-sheets-write-cells.md L146: fix the table cell escape from the malformed `'''Sheet1''!T1:T3'` (consecutive `''` are no-op empty strings) to `''\''Sheet1'\''!T1:T3'`, matching the bash example at L191 verbatim. Net: 1 insertion, 40 deletions across 3 files. --- skills/lark-sheets/SKILL.md | 32 ------------------- .../references/lark-sheets-pivot-table.md | 5 --- .../references/lark-sheets-write-cells.md | 4 +-- 3 files changed, 1 insertion(+), 40 deletions(-) diff --git a/skills/lark-sheets/SKILL.md b/skills/lark-sheets/SKILL.md index 290eafd62..773a4d9d1 100644 --- a/skills/lark-sheets/SKILL.md +++ b/skills/lark-sheets/SKILL.md @@ -127,35 +127,3 @@ lark-cli sheets +cells-set --url "..." --sheet-name "Sheet1" --range "A1:B2" --c ``` **`@file` 接绝对路径会被拒,且被拒后不要照报错提示做。** `@file` 出于安全只接受 cwd 下的相对路径,传 `@/tmp/cells.json` 这类绝对路径或 cwd 之外的路径会被拒。此时报错会建议"先 cd 到目标目录,或改用相对路径"——**两条都不要照做**:cd 过去、或把临时文件写进用户项目目录,都会污染工作目录。正解是改用 stdin(`-- - < 文件`)。 - -## Shell 调用注意事项:A1 reference 含 `!` 的引号选择 - -A1 表示法的 sheet 前缀(`Sheet1!A1`)里那个 `!` 在 interactive bash(豆包 ShellExec / 任何带 history 的 shell)下是**历史展开触发字符**。`!Word` 形式会被 bash 当成"展开最近以 Word 开头的历史命令",如果没有匹配就报: - -``` -bash: !A1: event not found -``` - -——命令根本到不了 lark-cli。**double-quote 内 `!` 仍会触发 histexpand**;只有 single-quote 或显式关闭 history expansion 才能让 `!` 字面通过。 - -**对 agent 的可执行规则**: - -| 场景 | 推荐写法 | 说明 | -|---|---|---| -| 一次性调用,sheet 名无特殊字符 | `--source 'Sheet1!A1:D100'` | shell single-quote 整段包住,A1 内部不带单引号也合法 | -| 一次性调用,sheet 名含 `-` / 空格 / 非 ASCII | `set +H; lark-cli sheets … --source "'Sales-Q1'!A1:D100"` | 先 `set +H` 关 histexpand,然后 double-quote 外层 + A1 内部 single-quote 包裹 sheet 名 | -| 该 shell session 后续会跑大量 lark-cli 命令 | 在 session 第一行执行 `set +H`,后续全程 double-quote 即可 | 一次性配置,减少每条命令的引号心智 | -| 实在写不对 | `+sheet-rename --title ` 先把含特殊字符的 sheet 改成 plain 名 | U046 模型走通的兜底路径,但要权衡是否会破坏其它引用 | - -**反模式**: - -- ❌ `--source "Sheet1!A1:D100"`(double-quote + 未关 histexpand)—— `!A1` 被吃掉,命令不达 lark-cli -- ❌ `--source 'Sales-Q1'\''!A1:D100'`(缺一节 `'\''`)—— shell 把 `Sales-Q1` 当字符串、`!A1` 又触发 histexpand -- ❌ `--source "'Sheet1'!A1"`(未 `set +H` 的 double-quote)—— A1 内部包是对的、但 shell 层的 `!A1` 还会被展开 - -**注意区分两层引号**: - -- **shell 层引号**(bash 的 `"..."` / `'...'`):决定 bash 是否做参数展开、变量替换、history expansion -- **A1 层引号**(A1 reference 字符串里的 `'Sheet name'!A1`):Excel/Sheets 标准,sheet 名含 `-` / 空格 / 非 ASCII 字符时**必须**单引号包裹;plain 名时可选(建议总是包裹以保持一致) - -bash single-quote 不可嵌套,要在 single-quote 中再放 single-quote,用序列 `'\''`(关闭外层 single-quote → 转义一个 single-quote → 重开外层 single-quote);嫌麻烦就用 `set +H` 方案。 diff --git a/skills/lark-sheets/references/lark-sheets-pivot-table.md b/skills/lark-sheets/references/lark-sheets-pivot-table.md index 4b8b524c1..a13a70577 100644 --- a/skills/lark-sheets/references/lark-sheets-pivot-table.md +++ b/skills/lark-sheets/references/lark-sheets-pivot-table.md @@ -143,11 +143,6 @@ lark-cli sheets +pivot-create --url "..." \ --source "'Sheet1'!A1:D100" --sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json ``` -> ⚠️ 上面 bash 示例的 `--source` 用了双引号,在 interactive bash(含豆包 ShellExec)下 `!` 会触发 history expansion 报 `bash: !A1: event not found`。**实际跑命令时**用以下任一种写法: -> - **首选**:命令最前面加 `set +H;` 关掉 history expansion,全程 double-quote 不踩坑 -> - **次选**:`--source` 整体改 shell single-quote,但 sheet 名内的 A1 单引号需要 `'\''` 转义:`--source ''\''Sheet1'\''!A1:D100'` -> - **应急**:先 `+sheet-rename --title ` 把含 `-` / 空格的 sheet 名改成 plain 名(U046 模型最终走通的兜底路径) - ### `+pivot-update` > 不允许改 `--source` / `--range`(透视表创建后位置/数据源固定);只能用 `--properties` 改 rows / columns / values / filters 等。先 `+pivot-list --pivot-table-id ` 回读再 patch,避免漏字段。 diff --git a/skills/lark-sheets/references/lark-sheets-write-cells.md b/skills/lark-sheets/references/lark-sheets-write-cells.md index 3fe4926fd..a0df4ff00 100644 --- a/skills/lark-sheets/references/lark-sheets-write-cells.md +++ b/skills/lark-sheets/references/lark-sheets-write-cells.md @@ -143,7 +143,7 @@ Step 2: `+cells-set` — range="A2", cells 含 value + cell_styles + border_styl | flag | 选项来源 | 适用场景 | |---|---|---| | `--options '["a","b","c"]'` | 写在命令里的固定列表 | 选项集是常量、不需要事后维护 | -| `--source-range '''Sheet1''!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步;想维护一张「枚举值」列后多处引用 | +| `--source-range ''\''Sheet1'\''!T1:T3'` | 已有单元格里的值 | 选项要跟数据动态同步;想维护一张「枚举值」列后多处引用 | 两个 flag **必须传一个、且只能传一个**——同时传或都不传,CLI 会立刻报错。`--source-range` 用 A1 + sheet 前缀写法(如 `'Sheet1'!T1:T3`,sheet 名按 A1 标准单引号包裹),可以指同 sheet 也可以指其它 sheet(如 `'Refs'!A1:A10`)。 @@ -192,8 +192,6 @@ lark-cli sheets +dropdown-set \ --colors '["#cce8ff","#ffd6e7","#e6e6e6"]' ``` -> ⚠️ `--source-range` 的 shell 包法:上面用 `''\''Sheet1'\''!T1:T3'` 是为了在 shell single-quote 外层中嵌入 A1 内部 single-quote(防 bash 历史展开 `!T1`)。等效更简单写法:命令最前加 `set +H;` 关 history expansion,然后 `--source-range "'Sheet1'!T1:T3"`。详见 SKILL.md「Shell 调用注意事项」。 - **纯白下拉**(明确告诉用户"不要彩色"时才用): ``` From 9c3d30aa009a623d40ba9ff72a423dac431b0b6c Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Thu, 28 May 2026 19:15:38 +0800 Subject: [PATCH 5/6] =?UTF-8?q?feat(sheets):=20rename=20+pivot-create=20sh?= =?UTF-8?q?eet=20selector=20=E2=86=92=20--target-sheet-{id,name}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit +pivot-create's placement selector (where the pivot table lands) is no longer the generic --sheet-id / --sheet-name; it is now --target-sheet-id / --target-sheet-name. The new names mark this as the *output* sheet, distinct from the *data-source* sheet (which lives inside --source as `'Sheet'!Range`). The other +pivot-{list,update,delete} shortcuts keep --sheet-id / --sheet-name (their semantics are "sheet that hosts the existing pivot", same as every other shortcut). Motivation: an LLM agent reading the previous CLI surface saw +pivot-create expose --sheet-id and assumed (as it had to) that it pointed at the data source, like every other shortcut. The new flag name makes the intent unambiguous at the call site, without relying on the agent having read the narrative caveat in the reference doc. Background: evaluation case U046 spent multiple rounds tripping on this exact confusion before working around it with +sheet-rename. Implementation: - objectCRUDSpec gains createSheetIDFlag / createSheetNameFlag (with default-fallback accessors sheetIDFlagOnCreate / sheetNameFlagOnCreate); newObjectCreateShortcut + objectCreateInput consult the spec instead of hard-coded "sheet-id" / "sheet-name". pivotSpec sets target-sheet-*; every other create spec inherits the defaults. - optionalSheetSelector (only used by pivot create) takes the two flag names as parameters so its mutex / control-char errors quote the names the user actually typed (--target-sheet-id, not --sheet-id). - batch_op_dispatch: introduce sheetSelectorFlagsForSubOp(shortcut) → (idFlag, nameFlag) returning target-sheet-* for "+pivot-create" and the defaults otherwise; translateBatchOp uses it so +pivot-create sub-ops in +batch-update accept the same renamed input keys. - Tests: - lark_sheet_object_crud_test.go: pivot-create cases switch args and expected error wording to target-sheet-*; extra assertion that the mutex error quotes the renamed flag (regression guard against flag-name drift between code and error message). - batch_op_contract_test.go: +pivot-create sub-op test uses target-sheet-id / target-sheet-name input keys; the body-vs-standalone contract loop reads the selector via sheetSelectorFlagsForSubOp so every other shortcut keeps using sheet-id / sheet-name. Synced reference docs (skills/lark-sheets/{SKILL.md, references/lark-sheets-pivot-table.md}) mirror the spec's new flag names, narrative, 3-placement-strategy block, and SKILL.md exception bullet that explains why +pivot-create's badge says 无 sheet 定位 yet still has placement selectors (just under different names). flag-defs.json synced from spec picks up the renamed flags + kind=own. All sheets-package tests pass. --- shortcuts/sheets/batch_op_contract_test.go | 19 ++++++--- shortcuts/sheets/batch_op_dispatch.go | 20 ++++++++- shortcuts/sheets/data/flag-defs.json | 28 ++++++------- shortcuts/sheets/helpers.go | 14 +++++-- shortcuts/sheets/lark_sheet_object_crud.go | 42 +++++++++++++++---- .../sheets/lark_sheet_object_crud_test.go | 41 +++++++++++------- skills/lark-sheets/SKILL.md | 1 + .../references/lark-sheets-pivot-table.md | 22 ++++++---- 8 files changed, 129 insertions(+), 58 deletions(-) diff --git a/shortcuts/sheets/batch_op_contract_test.go b/shortcuts/sheets/batch_op_contract_test.go index ba4fa9cf9..1073dd985 100644 --- a/shortcuts/sheets/batch_op_contract_test.go +++ b/shortcuts/sheets/batch_op_contract_test.go @@ -214,8 +214,12 @@ func TestBatchOp_BodyMatchesStandalone(t *testing.T) { { shortcut: "+pivot-create", sc: PivotCreate, - args: []string{"--sheet-id", "sh1", "--properties", `{"rows":[]}`, "--source", "Sheet1!A1:D100"}, - subInput: `{"sheet-id":"sh1","properties":{"rows":[]},"source":"Sheet1!A1:D100"}`, + // +pivot-create renamed --sheet-id / --sheet-name → --target-sheet-id / + // --target-sheet-name to flag the placement-sheet semantics (the data + // source is in --source). Both standalone args and the +batch-update + // sub-op input must use the new names. + args: []string{"--target-sheet-id", "sh1", "--properties", `{"rows":[]}`, "--source", "Sheet1!A1:D100"}, + subInput: `{"target-sheet-id":"sh1","properties":{"rows":[]},"source":"Sheet1!A1:D100"}`, }, { shortcut: "+cond-format-create", @@ -292,10 +296,13 @@ func TestBatchOp_BodyMatchesStandalone(t *testing.T) { t.Fatalf("bad subInput JSON: %v", err) } fv := newMapFlagViewForCommand(tc.shortcut, subInput) - sid := subInput["sheet-id"] - sname := subInput["sheet-name"] - sidStr, _ := sid.(string) - snameStr, _ := sname.(string) + // Match what translateBatchOp does — read the sheet selector + // via the shortcut-specific flag names so +pivot-create + // (target-sheet-id / target-sheet-name) and the rest + // (sheet-id / sheet-name) both resolve correctly. + sidFlag, snameFlag := sheetSelectorFlagsForSubOp(tc.shortcut) + sidStr, _ := subInput[sidFlag].(string) + snameStr, _ := subInput[snameFlag].(string) batchBody, err := mapping.translate(fv, testToken, sidStr, snameStr) if err != nil { t.Fatalf("batch translate failed: %v", err) diff --git a/shortcuts/sheets/batch_op_dispatch.go b/shortcuts/sheets/batch_op_dispatch.go index 672b48d5b..f0b36e64e 100644 --- a/shortcuts/sheets/batch_op_dispatch.go +++ b/shortcuts/sheets/batch_op_dispatch.go @@ -43,6 +43,21 @@ type batchOpMapping struct { translate batchTranslateFn } +// sheetSelectorFlagsForSubOp returns the (id, name) flag names a +batch-update +// sub-op uses to express its placement / context sheet. Defaults are +// `sheet-id` / `sheet-name`; +pivot-create deviates because its create +// shortcut renamed the placement selector to `target-sheet-id` / +// `target-sheet-name` (the data-source sheet is encoded in --source as +// `'SheetName'!Range`, not in a sheet selector flag). Update / delete on +// pivot still use the default names — only the create create-side +// shortcut was renamed. +func sheetSelectorFlagsForSubOp(shortcut string) (string, string) { + if shortcut == "+pivot-create" { + return "target-sheet-id", "target-sheet-name" + } + return "sheet-id", "sheet-name" +} + // objCreateTranslate / objUpdateTranslate / objDeleteTranslate bind an object // CRUD spec to the shared object_crud builders. func objCreateTranslate(spec objectCRUDSpec) batchTranslateFn { @@ -278,8 +293,9 @@ func translateBatchOp(raw interface{}, token string, index int) (map[string]inte } } fv := newMapFlagViewForCommand(sc, input) - sheetID := strings.TrimSpace(fv.Str("sheet-id")) - sheetName := strings.TrimSpace(fv.Str("sheet-name")) + sheetIDFlag, sheetNameFlag := sheetSelectorFlagsForSubOp(sc) + sheetID := strings.TrimSpace(fv.Str(sheetIDFlag)) + sheetName := strings.TrimSpace(fv.Str(sheetNameFlag)) body, err := mapping.translate(fv, token, sheetID, sheetName) if err != nil { return nil, common.FlagErrorf("operations[%d] (%s): %v", index, sc, err) diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index de3b1ddf6..2bec61ca1 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -3106,20 +3106,6 @@ "required": "xor", "desc": "Spreadsheet token (XOR with `--url`)" }, - { - "name": "sheet-id", - "kind": "public", - "type": "string", - "required": "xor", - "desc": "Reference_id of the sub-sheet where the pivot table is located / will be created to (mutually exclusive with --sheet-name; takes priority when both given; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended)" - }, - { - "name": "sheet-name", - "kind": "public", - "type": "string", - "required": "xor", - "desc": "Name of the sub-sheet where the pivot table is located / will be created to (mutually exclusive with --sheet-id; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended)" - }, { "name": "properties", "kind": "own", @@ -3139,6 +3125,20 @@ "desc": "Top-left cell within the target sub-sheet (A1 notation, e.g. `A1`); maps to the top-level `target_position`, default `A1` (not sent when the value is A1). It and `--range` both express placement but map to different wire fields — avoid passing conflicting values for both.", "default": "A1" }, + { + "name": "target-sheet-id", + "kind": "own", + "type": "string", + "required": "xor", + "desc": "Reference_id of the target sub-sheet where the pivot table will be placed (mutually exclusive with `--target-sheet-name`; takes priority when both given; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended). Distinct from the data-source sheet, which lives inside --source as a 'Sheet'-prefixed A1 reference like 'Sheet1'!A1:D100." + }, + { + "name": "target-sheet-name", + "kind": "own", + "type": "string", + "required": "xor", + "desc": "Name of the target sub-sheet where the pivot table will be placed (mutually exclusive with `--target-sheet-id`; when both omitted, a new sub-sheet is auto-created to host the pivot — recommended). Distinct from the data-source sheet, which lives inside --source as a 'Sheet'-prefixed A1 reference like 'Sheet1'!A1:D100." + }, { "name": "source", "kind": "own", diff --git a/shortcuts/sheets/helpers.go b/shortcuts/sheets/helpers.go index 28fcc1d5c..3b8bf1daf 100644 --- a/shortcuts/sheets/helpers.go +++ b/shortcuts/sheets/helpers.go @@ -142,18 +142,24 @@ func requireSheetSelector(sheetID, sheetName string) error { // Used by shortcuts whose backend tool treats sheet_id/sheet_name as the // placement target rather than the operation context (currently only // +pivot-create). Other shortcuts continue to use requireSheetSelector. -func optionalSheetSelector(sheetID, sheetName string) error { +// +// idFlagName / nameFlagName parameterize the flag names quoted back in +// the mutex / control-char errors — +pivot-create exposes the placement +// selector as `--target-sheet-id` / `--target-sheet-name`, not the +// generic `--sheet-id` / `--sheet-name`, and the error wording must +// match what the user actually typed. +func optionalSheetSelector(sheetID, sheetName, idFlagName, nameFlagName string) error { sheetID = strings.TrimSpace(sheetID) sheetName = strings.TrimSpace(sheetName) if sheetID != "" && sheetName != "" { - return common.FlagErrorf("--sheet-id and --sheet-name are mutually exclusive") + return common.FlagErrorf("--%s and --%s are mutually exclusive", idFlagName, nameFlagName) } if sheetID != "" { - if err := validate.RejectControlChars(sheetID, "sheet-id"); err != nil { + if err := validate.RejectControlChars(sheetID, idFlagName); err != nil { return common.FlagErrorf("%v", err) } } else if sheetName != "" { - if err := validate.RejectControlChars(sheetName, "sheet-name"); err != nil { + if err := validate.RejectControlChars(sheetName, nameFlagName); err != nil { return common.FlagErrorf("%v", err) } } diff --git a/shortcuts/sheets/lark_sheet_object_crud.go b/shortcuts/sheets/lark_sheet_object_crud.go index 69ac31dbd..b896729ea 100644 --- a/shortcuts/sheets/lark_sheet_object_crud.go +++ b/shortcuts/sheets/lark_sheet_object_crud.go @@ -62,6 +62,32 @@ type objectCRUDSpec struct { // rejected. Update/delete continue to require an explicit selector. // Today only pivotSpec opts in. allowEmptySheetSelectorOnCreate bool + // createSheetIDFlag / createSheetNameFlag override the default + // `sheet-id` / `sheet-name` flag names on the *create* shortcut and + // its +batch-update sub-op. Used by pivot to expose + // `target-sheet-id` / `target-sheet-name` — the placement target, + // semantically distinct from the data-source sheet (which is encoded + // in --source as `'SheetName'!Range`). Empty = default names. + // Update/delete continue to use `sheet-id` / `sheet-name`. + createSheetIDFlag string + createSheetNameFlag string +} + +// sheetIDFlagOnCreate / sheetNameFlagOnCreate return the cobra flag name +// used to read the placement-sheet selector on this spec's create +// shortcut. Defaults to `sheet-id` / `sheet-name`. +func (s objectCRUDSpec) sheetIDFlagOnCreate() string { + if s.createSheetIDFlag != "" { + return s.createSheetIDFlag + } + return "sheet-id" +} + +func (s objectCRUDSpec) sheetNameFlagOnCreate() string { + if s.createSheetNameFlag != "" { + return s.createSheetNameFlag + } + return "sheet-name" } func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { @@ -80,15 +106,15 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { if err != nil { return err } - sheetID := strings.TrimSpace(runtime.Str("sheet-id")) - sheetName := strings.TrimSpace(runtime.Str("sheet-name")) + sheetID := strings.TrimSpace(runtime.Str(spec.sheetIDFlagOnCreate())) + sheetName := strings.TrimSpace(runtime.Str(spec.sheetNameFlagOnCreate())) _, err = objectCreateInput(runtime, token, sheetID, sheetName, spec) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) - sheetID := strings.TrimSpace(runtime.Str("sheet-id")) - sheetName := strings.TrimSpace(runtime.Str("sheet-name")) + sheetID := strings.TrimSpace(runtime.Str(spec.sheetIDFlagOnCreate())) + sheetName := strings.TrimSpace(runtime.Str(spec.sheetNameFlagOnCreate())) input, _ := objectCreateInput(runtime, token, sheetID, sheetName, spec) return invokeToolDryRun(token, ToolKindWrite, spec.toolName, input) }, @@ -97,8 +123,8 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { if err != nil { return err } - sheetID := strings.TrimSpace(runtime.Str("sheet-id")) - sheetName := strings.TrimSpace(runtime.Str("sheet-name")) + sheetID := strings.TrimSpace(runtime.Str(spec.sheetIDFlagOnCreate())) + sheetName := strings.TrimSpace(runtime.Str(spec.sheetNameFlagOnCreate())) input, err := objectCreateInput(runtime, token, sheetID, sheetName, spec) if err != nil { return err @@ -116,7 +142,7 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { func objectCreateInput(runtime flagView, token, sheetID, sheetName string, spec objectCRUDSpec) (map[string]interface{}, error) { var err error if spec.allowEmptySheetSelectorOnCreate { - err = optionalSheetSelector(sheetID, sheetName) + err = optionalSheetSelector(sheetID, sheetName, spec.sheetIDFlagOnCreate(), spec.sheetNameFlagOnCreate()) } else { err = requireSheetSelector(sheetID, sheetName) } @@ -312,6 +338,8 @@ var pivotSpec = objectCRUDSpec{ idFlag: "pivot-table-id", idField: "pivot_table_id", allowEmptySheetSelectorOnCreate: true, + createSheetIDFlag: "target-sheet-id", + createSheetNameFlag: "target-sheet-name", enhanceCreateInput: func(rt flagView, input map[string]interface{}) { if v := strings.TrimSpace(rt.Str("target-position")); v != "" && v != "A1" { input["target_position"] = v diff --git a/shortcuts/sheets/lark_sheet_object_crud_test.go b/shortcuts/sheets/lark_sheet_object_crud_test.go index aafc6dc87..d7752887a 100644 --- a/shortcuts/sheets/lark_sheet_object_crud_test.go +++ b/shortcuts/sheets/lark_sheet_object_crud_test.go @@ -52,16 +52,20 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { }, }, // pivot — has extra create flags incl. required --source. - // --sheet-id is the placement target (where the pivot lands); - // pivotSpec.allowEmptySheetSelectorOnCreate lets both --sheet-id - // and --sheet-name be omitted so the backend auto-creates a - // sub-sheet — covered separately in the +pivot-create empty- - // selector / mutex tests below. + // --target-sheet-id is the placement target (where the pivot lands); + // the placement selector is renamed from the generic --sheet-id / + // --sheet-name to --target-sheet-id / --target-sheet-name to keep + // it semantically distinct from the data-source sheet (which is + // encoded inside --source as `'SheetName'!Range`). + // pivotSpec.allowEmptySheetSelectorOnCreate lets both target + // selectors be omitted so the backend auto-creates a sub-sheet — + // covered separately in the +pivot-create empty-selector / mutex + // tests below. { name: "+pivot-create with placement / source / range flags", sc: PivotCreate, args: []string{ - "--url", testURL, "--sheet-id", testSheetID, + "--url", testURL, "--target-sheet-id", testSheetID, "--properties", `{"rows":[{"field":"A"}]}`, "--source", "Sheet1!A1:F1000", "--range", "F1", @@ -80,10 +84,10 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { }, }, }, - // +pivot-create accepts both sheet selectors empty — backend + // +pivot-create accepts both target selectors empty — backend // auto-creates a placement sub-sheet. { - name: "+pivot-create empty --sheet-id / --sheet-name omits sheet from input", + name: "+pivot-create empty --target-sheet-id / --target-sheet-name omits sheet from input", sc: PivotCreate, args: []string{ "--url", testURL, @@ -349,9 +353,9 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { } // TestPivotCreate_SheetSelectorSemantics locks in the "at most one" -// semantics for +pivot-create (and only +pivot-create): both --sheet-id -// and --sheet-name may be omitted (backend auto-creates a placement -// sub-sheet), but passing both is rejected. +// semantics for +pivot-create (and only +pivot-create): both +// --target-sheet-id and --target-sheet-name may be omitted (backend +// auto-creates a placement sub-sheet), but passing both is rejected. // // Companion regression — TestObjectCreate_RequiresSheetSelector below — // confirms every other *-create still rejects empty selector. @@ -378,25 +382,30 @@ func TestPivotCreate_SheetSelectorSemantics(t *testing.T) { t.Parallel() _, stderr, err := runShortcutCapturingErr(t, PivotCreate, []string{ "--url", testURL, - "--sheet-id", testSheetID, - "--sheet-name", "Sheet1", + "--target-sheet-id", testSheetID, + "--target-sheet-name", "Sheet1", "--properties", `{"rows":[{"field":"A"}]}`, "--source", "Sheet1!A1:F1000", }) if err == nil { - t.Fatalf("expected CLI to reject both --sheet-id and --sheet-name set; stderr=%s", stderr) + t.Fatalf("expected CLI to reject both --target-sheet-id and --target-sheet-name set; stderr=%s", stderr) } combined := stderr + err.Error() if !strings.Contains(combined, "mutually exclusive") { t.Errorf("expected error to say 'mutually exclusive'; got=%s|%v", stderr, err) } + // 错误信息必须用真实的 flag 名(target-*),否则模型按消息提示去 + // 改 --sheet-id 还是错的。 + if !strings.Contains(combined, "--target-sheet-id") { + t.Errorf("expected error to quote --target-sheet-id flag name; got=%s|%v", stderr, err) + } }) - t.Run("only sheet-id is accepted", func(t *testing.T) { + t.Run("only target-sheet-id is accepted", func(t *testing.T) { t.Parallel() body := parseDryRunBody(t, PivotCreate, []string{ "--url", testURL, - "--sheet-id", testSheetID, + "--target-sheet-id", testSheetID, "--properties", `{"rows":[{"field":"A"}]}`, "--source", "Sheet1!A1:F1000", }) diff --git a/skills/lark-sheets/SKILL.md b/skills/lark-sheets/SKILL.md index 773a4d9d1..75cc3ba01 100644 --- a/skills/lark-sheets/SKILL.md +++ b/skills/lark-sheets/SKILL.md @@ -83,6 +83,7 @@ metadata: 2. **sheet 定位(公共四件套 shortcut 必填)**:`--sheet-id` 与 `--sheet-name` 二选一,**必须给其中之一**。两个都不给 → 校验报错 `specify at least one of --sheet-id or --sheet-name`。 - ⚠️ **`--range` 里的 `Sheet1!` 前缀不能替代 sheet 定位**:即使写了 `--range "'Sheet1'!A1:B2"`,仍**必须**额外传 `--sheet-id` 或 `--sheet-name`,否则照样报上面的错。 - **例外**:徽章标为 `_公共:URL/token(无 sheet 定位)…_` 的 shortcut(如 `+workbook-info` / `+workbook-export` / `+batch-update` / `+dropdown-get|update|delete` / `+cells-batch-set-style` / `+cells-batch-clear` / `+sheet-create`)**不接受也不需要** sheet 定位,只给一组 spreadsheet 定位即可。 + - **例外**:`+pivot-create` 的徽章虽然同样是「无 sheet 定位」,但它**有自己的 sheet 选择器** `--target-sheet-id` / `--target-sheet-name`(**不是** `--sheet-id` / `--sheet-name`)——这是**透视表落点 sheet**(产物放哪张子表),与"数据源 sheet"语义不同(数据源走 `--source` 的 `'SheetName'!Range`,没有独立 flag)。两个 target 都可不传 → 后端自动新建子表存放透视表(推荐);两个都给则报 `--target-sheet-id and --target-sheet-name are mutually exclusive`。详见 `lark-sheets-pivot-table` 的 `+pivot-create` 段。 | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | diff --git a/skills/lark-sheets/references/lark-sheets-pivot-table.md b/skills/lark-sheets/references/lark-sheets-pivot-table.md index a13a70577..56d2610fd 100644 --- a/skills/lark-sheets/references/lark-sheets-pivot-table.md +++ b/skills/lark-sheets/references/lark-sheets-pivot-table.md @@ -57,12 +57,14 @@ _公共四件套 · 系统:`--dry-run`_ ### `+pivot-create` -_公共四件套 · 系统:`--dry-run`_ +_公共: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 | 透视表落点子表内的起始 cell(A1 格式,如 `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 字段,避免两者同时给冲突值 | @@ -108,7 +110,9 @@ _创建/更新的透视表属性_ ## Examples -公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`。其中 `--sheet-id` / `--sheet-name` 在 `+pivot-update` / `+pivot-delete` / `+pivot-list` 上是公共四件套语义(定位透视表所在 sheet,XOR 必传一个);但在 **`+pivot-create` 上是透视表的"落点"语义**——两个都不传时后端自动新建子表存放产物(强烈推荐,绝不碰源数据)。 +公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`,其中 `--sheet-id` / `--sheet-name` 在 `+pivot-update` / `+pivot-delete` / `+pivot-list` 上是公共四件套语义(定位透视表所在 sheet,XOR 必传一个)。 + +**`+pivot-create` 例外**:placement 选择器改名为 `--target-sheet-id` / `--target-sheet-name`(不再叫 `--sheet-id` / `--sheet-name`),明示这是"产物落点 sheet"而非"数据源 sheet"。两个都不传时后端自动新建子表存放产物(强烈推荐,绝不碰源数据)。数据源 sheet 写在 `--source` 的 `'SheetName'!Range` 里,不靠任何 sheet 选择器 flag 表达。 ### `+pivot-list` @@ -122,16 +126,16 @@ lark-cli sheets +pivot-list --url "..." --sheet-id "$SID" > > **先理清 `+pivot-create` 上 4 个位置类入参(语义不同,别混)**: > - `--source`(**必填**):**源数据**区域,须自带 `Sheet!` 前缀(如 `'Sheet1'!A1:D100`,sheet 名按 A1 标准单引号包裹)。源 sheet 的名字在 `--source` 字符串里,**不**通过单独 flag 传。 -> - `--sheet-id` / `--sheet-name`:**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。**注意:跟其它 shortcut 不同,这里 `--sheet-id` / `--sheet-name` 表达的不是"数据源所在 sheet"而是"产物落点 sheet"**。 +> - `--target-sheet-id` / `--target-sheet-name`:**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。**与其它 shortcut 的 `--sheet-id` / `--sheet-name` 区分**:那是"数据源 / 操作目标 sheet",这里是"产物落点 sheet"——CLI 用不同 flag 名把这两类语义分开,避免误传。 > - `--target-position`(可选,A1 表示法,默认 `A1`):落点 sheet 内的起始 cell,映射到顶层 `target_position`。 > - `--range`(可选,A1 单值,仅 create 生效):跟 `--target-position` 表达同一意图但映射到 `properties.range`,**两者不要同时给**。 > > **落点 3 种策略(互斥,选其一)**: -> 1. **默认(强烈推荐)**:`--sheet-id` / `--sheet-name` / `--target-position` / `--range` **全都不传** → 服务端**自动新建子表**存放产物,绝不碰任何已有数据。 -> 2. **放进指定的已有子表**:传 `--sheet-id <落点子表 id>`(或 `--sheet-name`),可选 `--target-position <子表内起点 cell>`。⚠️ **若落点子表就是源数据所在的 sheet**,必须配 `--target-position` 或 `--range` 指向源数据范围**之外**的位置,否则产物默认从 A1 起会盖在源数据上。 -> 3. **`--range`**:跟策略 2 等价(同样需要 `--sheet-id` / `--sheet-name` 指定落点子表,不然落到自动新建子表),只是用 `properties.range` 那条 wire 路径表达位置。同样的覆盖风险,同样需要避开源数据范围。 +> 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(默认新建子表)即可,零覆盖风险,无需任何 `--sheet-*` / `--range` / `--target-*` flag。 +> 一般用策略 1(默认新建子表)即可,零覆盖风险,无需任何 `--target-*` / `--range` flag。 ```bash # 策略 1(强烈推荐):不传任何落点 flag → 后端自动新建子表,零覆盖风险 @@ -140,7 +144,7 @@ lark-cli sheets +pivot-create --url "..." \ # 策略 2:落进指定的已有目标子表(注意目标 sheet ≠ 源 sheet,否则要配 --target-position 避开源数据) lark-cli sheets +pivot-create --url "..." \ - --source "'Sheet1'!A1:D100" --sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json + --source "'Sheet1'!A1:D100" --target-sheet-id "$DEST_SID" --target-position "A1" --properties @pivot.json ``` ### `+pivot-update` @@ -155,7 +159,7 @@ lark-cli sheets +pivot-delete --url "..." --sheet-id "$SID" --pivot-table-id "$P ### Validate / DryRun / Execute 约束 -- `Validate`:`--url` / `--spreadsheet-token` XOR 必填;`+pivot-{update,delete,list}` 的 `--sheet-id` / `--sheet-name` XOR 必填一个,`+pivot-create` 例外(两个都可空,触发 backend auto-create 子表);`+pivot-create` 的 `--source` 必填且必须含表头行;`--properties` 中 `rows` / `columns` / `values` 至少非空之一;`+pivot-delete` 强制 `--yes` 或 `--dry-run`。 +- `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 ` 并用 `+csv-get` 抽样读透视产物核对输出尺寸 + 总计行位置。 From dce617eab299ff6d1d51195420638b96ac6a35e3 Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Thu, 28 May 2026 19:23:42 +0800 Subject: [PATCH 6/6] docs(sheets): strip migration-history language from pivot reference / SKILL Synced from spec. Removes "renamed from / no longer called / not --sheet-id" style migration-history language that snuck into the previous sync. Reference and SKILL now describe the current flag names directly without referencing the old names. --- skills/lark-sheets/SKILL.md | 3 +-- skills/lark-sheets/references/lark-sheets-pivot-table.md | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/skills/lark-sheets/SKILL.md b/skills/lark-sheets/SKILL.md index 75cc3ba01..0d3692d36 100644 --- a/skills/lark-sheets/SKILL.md +++ b/skills/lark-sheets/SKILL.md @@ -82,8 +82,7 @@ metadata: - **例外**:`+workbook-create` 是新建一个还不存在的表格,**不接受任何 spreadsheet / sheet 定位 flag**(只有 `--title` / `--folder-token` / `--headers` / `--values`)。 2. **sheet 定位(公共四件套 shortcut 必填)**:`--sheet-id` 与 `--sheet-name` 二选一,**必须给其中之一**。两个都不给 → 校验报错 `specify at least one of --sheet-id or --sheet-name`。 - ⚠️ **`--range` 里的 `Sheet1!` 前缀不能替代 sheet 定位**:即使写了 `--range "'Sheet1'!A1:B2"`,仍**必须**额外传 `--sheet-id` 或 `--sheet-name`,否则照样报上面的错。 - - **例外**:徽章标为 `_公共:URL/token(无 sheet 定位)…_` 的 shortcut(如 `+workbook-info` / `+workbook-export` / `+batch-update` / `+dropdown-get|update|delete` / `+cells-batch-set-style` / `+cells-batch-clear` / `+sheet-create`)**不接受也不需要** sheet 定位,只给一组 spreadsheet 定位即可。 - - **例外**:`+pivot-create` 的徽章虽然同样是「无 sheet 定位」,但它**有自己的 sheet 选择器** `--target-sheet-id` / `--target-sheet-name`(**不是** `--sheet-id` / `--sheet-name`)——这是**透视表落点 sheet**(产物放哪张子表),与"数据源 sheet"语义不同(数据源走 `--source` 的 `'SheetName'!Range`,没有独立 flag)。两个 target 都可不传 → 后端自动新建子表存放透视表(推荐);两个都给则报 `--target-sheet-id and --target-sheet-name are mutually exclusive`。详见 `lark-sheets-pivot-table` 的 `+pivot-create` 段。 + - **例外**:徽章标为 `_公共:URL/token(无 sheet 定位)…_` 的 shortcut(如 `+workbook-info` / `+workbook-export` / `+batch-update` / `+dropdown-get|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 | 必填 | 说明 | | --- | --- | --- | --- | diff --git a/skills/lark-sheets/references/lark-sheets-pivot-table.md b/skills/lark-sheets/references/lark-sheets-pivot-table.md index 56d2610fd..dad7a1bb5 100644 --- a/skills/lark-sheets/references/lark-sheets-pivot-table.md +++ b/skills/lark-sheets/references/lark-sheets-pivot-table.md @@ -112,7 +112,7 @@ _创建/更新的透视表属性_ 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`,其中 `--sheet-id` / `--sheet-name` 在 `+pivot-update` / `+pivot-delete` / `+pivot-list` 上是公共四件套语义(定位透视表所在 sheet,XOR 必传一个)。 -**`+pivot-create` 例外**:placement 选择器改名为 `--target-sheet-id` / `--target-sheet-name`(不再叫 `--sheet-id` / `--sheet-name`),明示这是"产物落点 sheet"而非"数据源 sheet"。两个都不传时后端自动新建子表存放产物(强烈推荐,绝不碰源数据)。数据源 sheet 写在 `--source` 的 `'SheetName'!Range` 里,不靠任何 sheet 选择器 flag 表达。 +**`+pivot-create` 例外**:placement 选择器用 `--target-sheet-id` / `--target-sheet-name`(XOR,两个都不传时后端自动新建子表存放产物,强烈推荐,绝不碰源数据)。数据源 sheet 写在 `--source` 的 `'SheetName'!Range` 里,不靠 sheet 选择器 flag。 ### `+pivot-list` @@ -126,7 +126,7 @@ lark-cli sheets +pivot-list --url "..." --sheet-id "$SID" > > **先理清 `+pivot-create` 上 4 个位置类入参(语义不同,别混)**: > - `--source`(**必填**):**源数据**区域,须自带 `Sheet!` 前缀(如 `'Sheet1'!A1:D100`,sheet 名按 A1 标准单引号包裹)。源 sheet 的名字在 `--source` 字符串里,**不**通过单独 flag 传。 -> - `--target-sheet-id` / `--target-sheet-name`:**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。**与其它 shortcut 的 `--sheet-id` / `--sheet-name` 区分**:那是"数据源 / 操作目标 sheet",这里是"产物落点 sheet"——CLI 用不同 flag 名把这两类语义分开,避免误传。 +> - `--target-sheet-id` / `--target-sheet-name`:**透视表的落点 sheet**(即产物放哪张子表)。两个互斥(最多传一个),都不传时后端自动新建子表存放产物(强烈推荐)。 > - `--target-position`(可选,A1 表示法,默认 `A1`):落点 sheet 内的起始 cell,映射到顶层 `target_position`。 > - `--range`(可选,A1 单值,仅 create 生效):跟 `--target-position` 表达同一意图但映射到 `properties.range`,**两者不要同时给**。 >