From 12b94746bb53849460d7de12ed82f70148e4162a Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Mon, 25 May 2026 21:08:58 +0800 Subject: [PATCH 1/2] fix(sheets): +dropdown-get accepts --sheet-id/--sheet-name + bare --range Align +dropdown-get with its get_cell_ranges siblings (+cells-get / +csv-get): sheet selection is now via --sheet-id / --sheet-name (XOR) and --range is a bare A1 reference. The previous shape required the sheet prefix inside --range (e.g. "Sheet1!A2:A100") and was the odd one out among the read-data wrappers; callers pasting the sheet-id form straight from the URL hit a misleading "sheet not found, sheetId: , sheetName: " error because the prefix was unconditionally treated as sheet_name. Flag schema + skill reference regenerated from the upstream Lark Base Shortcut-flags table. --- shortcuts/sheets/data/flag-defs.json | 16 +++++++- shortcuts/sheets/lark_sheet_read_data.go | 38 ++++++++++--------- shortcuts/sheets/lark_sheet_read_data_test.go | 37 ++++++++++++++---- .../references/lark-sheets-read-data.md | 4 +- 4 files changed, 67 insertions(+), 28 deletions(-) diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index 979cf5ef7..4e6bde65b 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -1238,12 +1238,26 @@ "required": "xor", "desc": "Spreadsheet token (XOR with `--url`)" }, + { + "name": "sheet-id", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet reference_id (XOR with `--sheet-name`)" + }, + { + "name": "sheet-name", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Sheet name (XOR with `--sheet-id`)" + }, { "name": "range", "kind": "own", "type": "string", "required": "required", - "desc": "Target range (A1 notation; must include the sheet prefix, e.g. `sheet1!A2:A100`)" + "desc": "Target range in A1 notation, e.g. `A2:A100` (no sheet prefix — use `--sheet-id` / `--sheet-name` to select the sheet)" }, { "name": "dry-run", diff --git a/shortcuts/sheets/lark_sheet_read_data.go b/shortcuts/sheets/lark_sheet_read_data.go index 89a4b49db..bffe18be1 100644 --- a/shortcuts/sheets/lark_sheet_read_data.go +++ b/shortcuts/sheets/lark_sheet_read_data.go @@ -215,16 +215,16 @@ func stripRowPrefixFromCsvOutput(out interface{}) interface{} { } // DropdownGet wraps get_cell_ranges scoped to data_validation: read the -// dropdown configuration on a range. The CLI accepts the range in the -// sheet-prefixed form (e.g. "sheet1!A2:A100") for convenience; the -// prefix is split client-side into sheet_name + bare A1 because the -// get_cell_ranges tool wants sheet selector and ranges as separate -// fields (ranges with the "sheet!" prefix gets the empty-sheet_id -// rejection from the server). +// dropdown configuration on a range. Aligned with its sibling +cells-get +// — sheet selection is via --sheet-id / --sheet-name (XOR), and --range +// is a bare A1 reference. The earlier "must include a sheet prefix" +// shape was the odd one out among the get_cell_ranges wrappers and made +// callers treat the prefix as either name or id; folding it into the +// canonical --sheet-id selector removes that ambiguity. var DropdownGet = common.Shortcut{ Service: "sheets", Command: "+dropdown-get", - Description: "Read the dropdown / data-validation configuration on a sheet-prefixed range.", + Description: "Read the dropdown / data-validation configuration on a range.", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, @@ -234,24 +234,29 @@ var DropdownGet = common.Shortcut{ if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } + if _, _, err := resolveSheetSelector(runtime); err != nil { + return err + } if strings.TrimSpace(runtime.Str("range")) == "" { return common.FlagErrorf("--range is required") } - if !strings.Contains(runtime.Str("range"), "!") { - return common.FlagErrorf("--range must include a sheet prefix (e.g. sheet1!A2:A100)") - } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) - return invokeToolDryRun(token, ToolKindRead, "get_cell_ranges", dropdownGetInput(runtime, token)) + sheetID, sheetName, _ := resolveSheetSelector(runtime) + return invokeToolDryRun(token, ToolKindRead, "get_cell_ranges", dropdownGetInput(runtime, token, sheetID, sheetName)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } - out, err := callTool(ctx, runtime, token, ToolKindRead, "get_cell_ranges", dropdownGetInput(runtime, token)) + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + out, err := callTool(ctx, runtime, token, ToolKindRead, "get_cell_ranges", dropdownGetInput(runtime, token, sheetID, sheetName)) if err != nil { return err } @@ -260,16 +265,13 @@ var DropdownGet = common.Shortcut{ }, } -func dropdownGetInput(runtime *common.RuntimeContext, token string) map[string]interface{} { - // Validate already enforced the "Sheet!range" prefix, so the - // split error path can't be reached here in practice. - sheetName, bareRange, _ := splitSheetPrefixedRange(strings.TrimSpace(runtime.Str("range"))) +func dropdownGetInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) map[string]interface{} { input := map[string]interface{}{ "excel_id": token, - "ranges": []string{bareRange}, + "ranges": []string{strings.TrimSpace(runtime.Str("range"))}, "include_styles": false, "value_render_option": "formatted_value", } - sheetSelectorForToolInput(input, "", sheetName) + sheetSelectorForToolInput(input, sheetID, sheetName) return input } diff --git a/shortcuts/sheets/lark_sheet_read_data_test.go b/shortcuts/sheets/lark_sheet_read_data_test.go index 78233f795..1d1babd2a 100644 --- a/shortcuts/sheets/lark_sheet_read_data_test.go +++ b/shortcuts/sheets/lark_sheet_read_data_test.go @@ -48,14 +48,30 @@ func TestReadDataShortcuts_DryRun(t *testing.T) { }, }, { - name: "+dropdown-get range with sheet prefix only", + // Canonical form: --sheet-id + bare --range. Aligned with + // +cells-get / +csv-get; before the e2e BUG-019 fix this + // shortcut was the odd one out (range-prefix required). + name: "+dropdown-get with --sheet-id", sc: DropdownGet, - args: []string{"--url", testURL, "--range", "sheet1!A2:A100"}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "C2:C6"}, toolName: "get_cell_ranges", wantInput: map[string]interface{}{ "excel_id": testToken, - "sheet_name": "sheet1", - "ranges": []interface{}{"A2:A100"}, + "sheet_id": testSheetID, + "ranges": []interface{}{"C2:C6"}, + "include_styles": false, + "value_render_option": "formatted_value", + }, + }, + { + name: "+dropdown-get with --sheet-name", + sc: DropdownGet, + args: []string{"--url", testURL, "--sheet-name", "Sheet1", "--range", "C2:C6"}, + toolName: "get_cell_ranges", + wantInput: map[string]interface{}{ + "excel_id": testToken, + "sheet_name": "Sheet1", + "ranges": []interface{}{"C2:C6"}, "include_styles": false, "value_render_option": "formatted_value", }, @@ -72,7 +88,12 @@ func TestReadDataShortcuts_DryRun(t *testing.T) { } } -func TestDropdownGet_RequiresSheetPrefix(t *testing.T) { +// TestDropdownGet_RequiresSheetSelector locks the +cells-get-style +// selector contract: at least one of --sheet-id / --sheet-name must be +// supplied. Before BUG-019 fix this shortcut required a "Sheet!A1" +// prefix inside --range instead; the canonical selector pair is what +// every other get_cell_ranges wrapper uses. +func TestDropdownGet_RequiresSheetSelector(t *testing.T) { t.Parallel() stdout, stderr, err := runShortcutCapturingErr(t, DropdownGet, []string{ "--url", testURL, "--range", "A2:A100", "--dry-run", @@ -80,8 +101,9 @@ func TestDropdownGet_RequiresSheetPrefix(t *testing.T) { if err == nil { t.Fatalf("expected validation error; stdout=%s stderr=%s", stdout, stderr) } - if !strings.Contains(stdout+stderr+err.Error(), "must include a sheet prefix") { - t.Errorf("expected sheet-prefix guard; got=%s|%s|%v", stdout, stderr, err) + combined := stdout + stderr + err.Error() + if !strings.Contains(combined, "sheet-id") && !strings.Contains(combined, "sheet-name") { + t.Errorf("expected --sheet-id/--sheet-name guard; got=%s|%s|%v", stdout, stderr, err) } } @@ -96,6 +118,7 @@ func TestReadData_RequiresRange(t *testing.T) { }{ {"+cells-get", CellsGet}, {"+csv-get", CsvGet}, + {"+dropdown-get", DropdownGet}, } for _, c := range cases { c := c diff --git a/skills/lark-sheets/references/lark-sheets-read-data.md b/skills/lark-sheets/references/lark-sheets-read-data.md index e7cb0a5e4..a3fd6ce97 100644 --- a/skills/lark-sheets/references/lark-sheets-read-data.md +++ b/skills/lark-sheets/references/lark-sheets-read-data.md @@ -97,11 +97,11 @@ _公共四件套 · 系统:`--dry-run`_ ### `+dropdown-get` -_公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_ +_公共四件套 · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--range` | string | required | 目标范围(A1 格式,必须带 sheet 前缀,如 `sheet1!A2:A100`) | +| `--range` | string | required | A1 范围,如 `A2:A100`(不带 sheet 前缀;用 `--sheet-id` / `--sheet-name` 指定 sheet) | ### `+csv-get` From 9c447e735b40a081b58b12930e3b09fdc64d0d6d Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Mon, 25 May 2026 21:24:30 +0800 Subject: [PATCH 2/2] fix(sheets): drop Sheet1! prefix from +cells-get / +csv-get / +csv-put flag examples Server tools-schema.json for get_cell_ranges, get_range_as_csv and set_range_from_csv does not accept a sheet prefix on --range / --start-cell; the sheet is selected via --sheet-id / --sheet-name. +csv-put --start-cell also now states it must be a single cell (no range notation). Synced from spec repo. --- shortcuts/sheets/data/flag-defs.json | 6 +++--- skills/lark-sheets/references/lark-sheets-read-data.md | 4 ++-- skills/lark-sheets/references/lark-sheets-write-cells.md | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index 4e6bde65b..7e1d95d75 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -1180,7 +1180,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "A1 range, e.g. `Sheet1!A1:F10`" + "desc": "A1 range, e.g. `A1:F10` (no sheet prefix — use `--sheet-id` / `--sheet-name` to select the sheet)" }, { "name": "include", @@ -1304,7 +1304,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "A1 range, e.g. `Sheet1!A1:F30`" + "desc": "A1 range, e.g. `A1:F30` (no sheet prefix — use `--sheet-id` / `--sheet-name` to select the sheet)" }, { "name": "value-render-option", @@ -1965,7 +1965,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Top-left A1 anchor (e.g. `Sheet1!A1`); the bottom-right is inferred from CSV row/column counts", + "desc": "Top-left A1 anchor (e.g. `A1`, `B5`; no sheet prefix — use `--sheet-id` / `--sheet-name` to select the sheet); must be a single cell, range notation not accepted; the bottom-right is inferred from CSV row/column counts", "default": "A1" }, { diff --git a/skills/lark-sheets/references/lark-sheets-read-data.md b/skills/lark-sheets/references/lark-sheets-read-data.md index a3fd6ce97..fa1e32de8 100644 --- a/skills/lark-sheets/references/lark-sheets-read-data.md +++ b/skills/lark-sheets/references/lark-sheets-read-data.md @@ -90,7 +90,7 @@ _公共四件套 · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--range` | string | required | A1 范围,如 `Sheet1!A1:F10` | +| `--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 | 防爆,默认 200000(隐藏 flag:不在 `--help` 列出,但可正常传入) | | `--skip-hidden` | bool | optional | 跳过隐藏行列,默认 `false` | @@ -109,7 +109,7 @@ _公共四件套 · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--range` | string | required | A1 范围,如 `Sheet1!A1:F30` | +| `--range` | string | required | A1 范围,如 `A1:F30`(不带 sheet 前缀;用 `--sheet-id` / `--sheet-name` 指定 sheet) | | `--value-render-option` | string | optional | 单元格取值模式(可选值:`formatted_value` / `raw_value` / `formula`)(默认 `formatted_value`) | | `--max-chars` | int | optional | 防爆,默认 200000(隐藏 flag:不在 `--help` 列出,但可正常传入) | | `--include-row-prefix` | bool | optional | 是否在每行前加 `[row=N]` 前缀,默认 `true` | diff --git a/skills/lark-sheets/references/lark-sheets-write-cells.md b/skills/lark-sheets/references/lark-sheets-write-cells.md index 7aa0f99c9..ab8d41af3 100644 --- a/skills/lark-sheets/references/lark-sheets-write-cells.md +++ b/skills/lark-sheets/references/lark-sheets-write-cells.md @@ -286,7 +286,7 @@ _公共四件套 · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--start-cell` | string | required | 目标区域起点 A1(如 `Sheet1!A1`);终点按 CSV 实际行列数自动推断 | +| `--start-cell` | string | required | 目标区域起点 A1(如 `A1`、`B5`,不带 sheet 前缀;用 `--sheet-id` / `--sheet-name` 指定 sheet);必须是单个单元格,不接受范围写法;终点按 CSV 实际行列数自动推断 | | `--csv` | string + File + Stdin(非 JSON 文本) | required | RFC 4180 CSV 文本;只写纯值,不带公式/样式/批注 | | `--allow-overwrite` | bool | optional | 允许覆盖(默认 true);设为 false 时若目标非空报错 |