From d8cffa94deebae4091f48a24e6bcb03077e88f01 Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Fri, 10 Jul 2026 18:52:25 +0800 Subject: [PATCH] fix(sheets): address remaining review feedback --- shortcuts/sheets/batch_op_contract_test.go | 60 +++++++++++++++++ shortcuts/sheets/batch_op_dispatch.go | 3 + shortcuts/sheets/data/flag-defs.json | 16 ++--- shortcuts/sheets/flag_defs_gen.go | 12 ++-- shortcuts/sheets/flag_view.go | 67 +++++++++++++++---- shortcuts/sheets/lark_sheet_changeset_test.go | 43 ++++++------ .../sheets/lark_sheet_revision_get_test.go | 16 +++-- skills/lark-sheets/SKILL.md | 2 +- .../references/lark-sheets-batch-update.md | 6 +- .../references/lark-sheets-float-image.md | 6 +- 10 files changed, 169 insertions(+), 62 deletions(-) diff --git a/shortcuts/sheets/batch_op_contract_test.go b/shortcuts/sheets/batch_op_contract_test.go index e32397e3f..d4382fe80 100644 --- a/shortcuts/sheets/batch_op_contract_test.go +++ b/shortcuts/sheets/batch_op_contract_test.go @@ -915,3 +915,63 @@ func TestBatchOp_RequiredFlagParity(t *testing.T) { }) } } + +func TestBatchOp_EnumParity(t *testing.T) { + t.Parallel() + + t.Run("canonical casing is normalized before translation", func(t *testing.T) { + t.Parallel() + got, err := translateBatchOp(map[string]interface{}{ + "shortcut": "+cells-clear", + "input": map[string]interface{}{ + "sheet-id": "sh1", + "range": "A1:B2", + "scope": "FORMATS", + }, + }, testToken, 0) + if err != nil { + t.Fatalf("translateBatchOp: %v", err) + } + input, _ := got["input"].(map[string]interface{}) + if input["clear_type"] != "formats" { + t.Fatalf("clear_type = %v, want formats", input["clear_type"]) + } + }) + + tests := []struct { + name string + shortcut string + input map[string]interface{} + want string + }{ + { + name: "invalid clear scope", + shortcut: "+cells-clear", + input: map[string]interface{}{ + "sheet-id": "sh1", "range": "A1:B2", "scope": "formtas", + }, + want: "invalid value \"formtas\" for --scope", + }, + { + name: "invalid copy paste type", + shortcut: "+range-copy", + input: map[string]interface{}{ + "sheet-id": "sh1", "source-range": "A1:B2", "target-range": "D1", "paste-type": "valuez", + }, + want: "invalid value \"valuez\" for --paste-type", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + _, err := translateBatchOp(map[string]interface{}{ + "shortcut": tt.shortcut, + "input": tt.input, + }, testToken, 0) + validationErr := requireValidation(t, err, tt.want) + if validationErr.Param != "--operations" { + t.Errorf("param = %q, want --operations", validationErr.Param) + } + }) + } +} diff --git a/shortcuts/sheets/batch_op_dispatch.go b/shortcuts/sheets/batch_op_dispatch.go index d43916072..7adebad01 100644 --- a/shortcuts/sheets/batch_op_dispatch.go +++ b/shortcuts/sheets/batch_op_dispatch.go @@ -380,6 +380,9 @@ func translateBatchOp(raw interface{}, token string, index int) (map[string]inte if err := fv.validateRawTypes(); err != nil { return nil, sheetsValidationForFlag("operations", "operations[%d] (%s): %v", index, sc, err) } + if err := fv.normalizeAndValidateEnums(); err != nil { + return nil, sheetsValidationForFlag("operations", "operations[%d] (%s): %v", index, sc, err) + } sheetIDFlag, sheetNameFlag := sheetSelectorFlagsForSubOp(sc) sheetID := strings.TrimSpace(fv.Str(sheetIDFlag)) sheetName := strings.TrimSpace(fv.Str(sheetNameFlag)) diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index d32303ceb..c738ef1dc 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -2860,7 +2860,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A1:B2\",\"Sheet2!D1:D10\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), 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 (up to 100 items, e.g. `[\"Sheet1!A1:B2\",\"Sheet2!D1:D10\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id; ranges may target different sheets; the same style is applied to every range", "input": [ "file", "stdin" @@ -3013,7 +3013,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:A100\",\"Sheet1!C2:C100\"]`, prefix written bare without quotes); each item must include a sheet prefix; the prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id", + "desc": "Target ranges as a JSON array (up to 100 items, e.g. `[\"Sheet1!A2:A100\",\"Sheet1!C2:C100\"]`, prefix written bare without quotes); each item must include a sheet prefix; the prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id", "input": [ "file", "stdin" @@ -3137,7 +3137,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:Z1000\",\"Sheet2!A2:Z1000\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), 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 (up to 100 items, e.g. `[\"Sheet1!A2:Z1000\",\"Sheet2!A2:Z1000\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id; ranges may target different sheets; the same scope is cleared from every range", "input": [ "file", "stdin" @@ -4638,7 +4638,7 @@ "kind": "own", "type": "string", "required": "xor", - "desc": "Image reference_id (XOR with `--image-token`); the reference_id returned by the image upload flow" + "desc": "Image URI handle returned by the upload flow (not a sheet object reference_id; XOR with `--image-token`); converted to file_token automatically" }, { "name": "position-row", @@ -4754,15 +4754,15 @@ "name": "image-token", "kind": "own", "type": "string", - "required": "xor", - "desc": "Image file_token (XOR with `--image-uri`). Common source: `image_token` returned by `+float-image-list`" + "required": "optional", + "desc": "Optional image file_token; mutually exclusive with `--image-uri`; omit both to keep the current image. Common source: `image_token` returned by `+float-image-list`" }, { "name": "image-uri", "kind": "own", "type": "string", - "required": "xor", - "desc": "Image reference_id (XOR with `--image-token`); the reference_id returned by the image upload flow" + "required": "optional", + "desc": "Optional image URI handle returned by the upload flow (not a sheet object reference_id); mutually exclusive with `--image-token`; omit both to keep the current image; converted to file_token automatically" }, { "name": "position-row", diff --git a/shortcuts/sheets/flag_defs_gen.go b/shortcuts/sheets/flag_defs_gen.go index 6702ac606..19a822042 100644 --- a/shortcuts/sheets/flag_defs_gen.go +++ b/shortcuts/sheets/flag_defs_gen.go @@ -27,7 +27,7 @@ var flagDefs = map[string]commandDef{ Flags: []flagDef{ {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet URL (XOR with `--spreadsheet-token`)"}, {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"}, - {Name: "ranges", Kind: "own", Type: "string", Required: "required", Desc: "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:Z1000\",\"Sheet2!A2:Z1000\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id; ranges may target different sheets; the same scope is cleared from every range", Input: []string{"file", "stdin"}}, + {Name: "ranges", Kind: "own", Type: "string", Required: "required", Desc: "Target ranges as a JSON array (up to 100 items, e.g. `[\"Sheet1!A2:Z1000\",\"Sheet2!A2:Z1000\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id; ranges may target different sheets; the same scope is cleared from every range", Input: []string{"file", "stdin"}}, {Name: "scope", Kind: "own", Type: "string", Required: "optional", Desc: "Clear scope: `content` (default, values only) / `formats` (formats only) / `all` (values and formats)", Default: "content", Enum: []string{"content", "formats", "all"}}, {Name: "yes", Kind: "system", Type: "bool", Required: "required", Desc: "Confirm destructive write (exit code 10 without this flag); batch clear is irreversible"}, {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, @@ -38,7 +38,7 @@ var flagDefs = map[string]commandDef{ Flags: []flagDef{ {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet URL (XOR with `--spreadsheet-token`)"}, {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"}, - {Name: "ranges", Kind: "own", Type: "string", Required: "required", Desc: "Target ranges as a JSON array (e.g. `[\"Sheet1!A1:B2\",\"Sheet2!D1:D10\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id; ranges may target different sheets; the same style is applied to every range", Input: []string{"file", "stdin"}}, + {Name: "ranges", Kind: "own", Type: "string", Required: "required", Desc: "Target ranges as a JSON array (up to 100 items, e.g. `[\"Sheet1!A1:B2\",\"Sheet2!D1:D10\"]`, prefix written bare without quotes); each prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id; ranges may target different sheets; the same style is applied to every range", Input: []string{"file", "stdin"}}, {Name: "background-color", Kind: "own", Type: "string", Required: "optional", Desc: "Background color (hex, e.g. `#ffffff`)"}, {Name: "font-color", Kind: "own", Type: "string", Required: "optional", Desc: "Font color (hex, e.g. `#000000`)"}, {Name: "font-family", Kind: "own", Type: "string", Required: "optional", Desc: "Font family name (e.g. `Arial`, `Microsoft YaHei`)"}, @@ -475,7 +475,7 @@ var flagDefs = map[string]commandDef{ Flags: []flagDef{ {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet URL (XOR with `--spreadsheet-token`)"}, {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"}, - {Name: "ranges", Kind: "own", Type: "string", Required: "required", Desc: "Target ranges as a JSON array (e.g. `[\"Sheet1!A2:A100\",\"Sheet1!C2:C100\"]`, prefix written bare without quotes); each item must include a sheet prefix; the prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id", Input: []string{"file", "stdin"}}, + {Name: "ranges", Kind: "own", Type: "string", Required: "required", Desc: "Target ranges as a JSON array (up to 100 items, e.g. `[\"Sheet1!A2:A100\",\"Sheet1!C2:C100\"]`, prefix written bare without quotes); each item must include a sheet prefix; the prefix must exactly match the sheet display name (case-sensitive), not the sheet reference_id", Input: []string{"file", "stdin"}}, {Name: "options", Kind: "own", Type: "string", Required: "xor", Desc: "Options as a JSON array, e.g. `[\"opt1\",\"opt2\"]`. Server enforces no item-count cap and no per-item length cap; values containing commas are accepted (they are escape-encoded on the wire). For very large lists prefer `--source-range`.", Input: []string{"file", "stdin"}}, {Name: "colors", Kind: "own", Type: "string", Required: "optional", Desc: "Per-option pill colors, RGB hex array (e.g. `[\"#1FB6C1\",\"#F006C2\"]`). Length may be shorter than the source (`--options` items / `--source-range` cells) — extras cycle through a 10-color palette — but never longer (CLI Validate rejects: `--colors length (N) must not exceed dropdown source size (M)`). **Applies on its own**; ignored when `--highlight=false`.", Input: []string{"file", "stdin"}}, {Name: "multiple", Kind: "own", Type: "bool", Required: "optional", Desc: "Enable multi-select"}, @@ -588,7 +588,7 @@ var flagDefs = map[string]commandDef{ {Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name (XOR with `--sheet-id`)"}, {Name: "image-name", Kind: "own", Type: "string", Required: "required", Desc: "Image name, including extension (e.g. `logo.png`)"}, {Name: "image-token", Kind: "own", Type: "string", Required: "xor", Desc: "Image file_token (XOR with `--image-uri`). Common source: `image_token` returned by `+float-image-list`"}, - {Name: "image-uri", Kind: "own", Type: "string", Required: "xor", Desc: "Image reference_id (XOR with `--image-token`); the reference_id returned by the image upload flow"}, + {Name: "image-uri", Kind: "own", Type: "string", Required: "xor", Desc: "Image URI handle returned by the upload flow (not a sheet object reference_id; XOR with `--image-token`); converted to file_token automatically"}, {Name: "position-row", Kind: "own", Type: "int", Required: "required", Desc: "Row anchor of the image's top-left corner (0-based)"}, {Name: "position-col", Kind: "own", Type: "string", Required: "required", Desc: "Column anchor of the image's top-left corner (column letter, e.g. `A` / `B`)"}, {Name: "size-width", Kind: "own", Type: "int", Required: "required", Desc: "Image width in pixels"}, @@ -632,8 +632,8 @@ var flagDefs = map[string]commandDef{ {Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name (XOR with `--sheet-id`)"}, {Name: "float-image-id", Kind: "own", Type: "string", Required: "required", Desc: "Target float image id"}, {Name: "image-name", Kind: "own", Type: "string", Required: "required", Desc: "Image name, including extension (e.g. `logo.png`)"}, - {Name: "image-token", Kind: "own", Type: "string", Required: "xor", Desc: "Image file_token (XOR with `--image-uri`). Common source: `image_token` returned by `+float-image-list`"}, - {Name: "image-uri", Kind: "own", Type: "string", Required: "xor", Desc: "Image reference_id (XOR with `--image-token`); the reference_id returned by the image upload flow"}, + {Name: "image-token", Kind: "own", Type: "string", Required: "optional", Desc: "Optional image file_token; mutually exclusive with `--image-uri`; omit both to keep the current image. Common source: `image_token` returned by `+float-image-list`"}, + {Name: "image-uri", Kind: "own", Type: "string", Required: "optional", Desc: "Optional image URI handle returned by the upload flow (not a sheet object reference_id); mutually exclusive with `--image-token`; omit both to keep the current image; converted to file_token automatically"}, {Name: "position-row", Kind: "own", Type: "int", Required: "required", Desc: "Row anchor of the image's top-left corner (0-based)"}, {Name: "position-col", Kind: "own", Type: "string", Required: "required", Desc: "Column anchor of the image's top-left corner (column letter, e.g. `A` / `B`)"}, {Name: "size-width", Kind: "own", Type: "int", Required: "required", Desc: "Image width in pixels"}, diff --git a/shortcuts/sheets/flag_view.go b/shortcuts/sheets/flag_view.go index b3caf8263..de1bfaa6d 100644 --- a/shortcuts/sheets/flag_view.go +++ b/shortcuts/sheets/flag_view.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "math" + "slices" "strconv" "strings" ) @@ -118,20 +119,21 @@ func (m mapFlagView) lookup(name string) (interface{}, bool) { // lookupRaw resolves a flag name against the user-supplied input only, trying // the exact key then the hyphen↔underscore variants. func (m mapFlagView) lookupRaw(name string) (interface{}, bool) { - if v, ok := m.raw[name]; ok { - return v, true - } - if alt := strings.ReplaceAll(name, "-", "_"); alt != name { - if v, ok := m.raw[alt]; ok { - return v, true + _, v, ok := m.lookupRawWithKey(name) + return v, ok +} + +func (m mapFlagView) lookupRawWithKey(name string) (string, interface{}, bool) { + for _, key := range []string{ + name, + strings.ReplaceAll(name, "-", "_"), + strings.ReplaceAll(name, "_", "-"), + } { + if v, ok := m.raw[key]; ok { + return key, v, true } } - if alt := strings.ReplaceAll(name, "_", "-"); alt != name { - if v, ok := m.raw[alt]; ok { - return v, true - } - } - return nil, false + return "", nil, false } func (m mapFlagView) Str(name string) string { @@ -299,6 +301,47 @@ func (m mapFlagView) validateRawTypes() error { return nil } +// normalizeAndValidateEnums applies the same flat string-enum contract as the +// standalone cobra path. Canonical casing and known aliases are rewritten in +// place; unknown values are rejected before a translator can silently fall +// back to a different operation. +func (m *mapFlagView) normalizeAndValidateEnums() error { + defs, err := loadFlagDefs() + if err != nil { + return nil //nolint:nilerr // match validateRawTypes: missing embedded metadata must not block the batch + } + spec, ok := defs[m.command] + if !ok { + return nil + } + for _, df := range spec.Flags { + if df.Kind == "system" || df.Type != "string" || len(df.Enum) == 0 { + continue + } + rawKey, raw, changed := m.lookupRawWithKey(df.Name) + if !changed { + continue + } + value, ok := raw.(string) + if !ok { + return fmt.Errorf("--%s must be a string, got %s", df.Name, jsonTypeName(raw)) //nolint:forbidigo // intermediate error; batch dispatcher adds typed operations context + } + if value == "" || slices.Contains(df.Enum, value) { + continue + } + if canonical := canonicalEnumValue(value, df.Enum); canonical != "" { + m.raw[rawKey] = canonical + continue + } + message := fmt.Sprintf("invalid value %q for --%s, allowed: %s", value, df.Name, strings.Join(df.Enum, ", ")) + if match := closestEnumValue(value, df.Enum); match != "" { + message += fmt.Sprintf("; did you mean %q?", match) + } + return fmt.Errorf("%s", message) //nolint:forbidigo // intermediate error; batch dispatcher adds typed operations context + } + return nil +} + // jsonTypeName names the JSON kind of a value decoded by encoding/json, for // type-mismatch error messages. func jsonTypeName(v interface{}) string { diff --git a/shortcuts/sheets/lark_sheet_changeset_test.go b/shortcuts/sheets/lark_sheet_changeset_test.go index 1a8e75a32..ec488245c 100644 --- a/shortcuts/sheets/lark_sheet_changeset_test.go +++ b/shortcuts/sheets/lark_sheet_changeset_test.go @@ -3,10 +3,7 @@ package sheets -import ( - "strings" - "testing" -) +import "testing" // TestChangesetGet_DryRun locks the get_changeset tool input: --end-revision // is only sent when explicitly provided, otherwise the server defaults to the @@ -53,35 +50,37 @@ func TestChangesetGet_Validation(t *testing.T) { t.Parallel() cases := []struct { - name string - args []string - wantSub string + name string + args []string + wantMsg string + wantParam string }{ { - name: "start-revision must be >= 1", - args: []string{"--url", testURL, "--start-revision", "0"}, - wantSub: "start-revision must be >= 1", + name: "start-revision must be >= 1", + args: []string{"--url", testURL, "--start-revision", "0"}, + wantMsg: "start-revision must be >= 1", + wantParam: "--start-revision", }, { - name: "end before start rejected", - args: []string{"--url", testURL, "--start-revision", "100", "--end-revision", "50"}, - wantSub: "end-revision", + name: "end before start rejected", + args: []string{"--url", testURL, "--start-revision", "100", "--end-revision", "50"}, + wantMsg: "end-revision", + wantParam: "--end-revision", }, { - name: "gap over 20 rejected", - args: []string{"--url", testURL, "--start-revision", "1", "--end-revision", "30"}, - wantSub: "version gap exceeds limit", + name: "gap over 20 rejected", + args: []string{"--url", testURL, "--start-revision", "1", "--end-revision", "30"}, + wantMsg: "version gap exceeds limit", + wantParam: "--end-revision", }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { t.Parallel() - stdout, stderr, err := runShortcutCapturingErr(t, ChangesetGet, append(c.args, "--dry-run")) - if err == nil { - t.Fatalf("expected validation error; stdout=%s stderr=%s", stdout, stderr) - } - if !strings.Contains(stdout+stderr+err.Error(), c.wantSub) { - t.Errorf("expected %q; got=%s|%s|%v", c.wantSub, stdout, stderr, err) + _, _, err := runShortcutCapturingErr(t, ChangesetGet, append(c.args, "--dry-run")) + validationErr := requireValidation(t, err, c.wantMsg) + if validationErr.Param != c.wantParam { + t.Errorf("param = %q, want %q", validationErr.Param, c.wantParam) } }) } diff --git a/shortcuts/sheets/lark_sheet_revision_get_test.go b/shortcuts/sheets/lark_sheet_revision_get_test.go index a4413a037..49152016a 100644 --- a/shortcuts/sheets/lark_sheet_revision_get_test.go +++ b/shortcuts/sheets/lark_sheet_revision_get_test.go @@ -3,7 +3,11 @@ package sheets -import "testing" +import ( + "testing" + + "github.com/larksuite/cli/errs" +) func TestRevisionGetProjectRevision(t *testing.T) { t.Parallel() @@ -24,14 +28,12 @@ func TestRevisionGetProjectRevision(t *testing.T) { t.Run("errors when revision is absent", func(t *testing.T) { out := map[string]interface{}{"sheets": []interface{}{}} - if _, err := projectRevision(out); err == nil { - t.Error("expected an error when revision is missing, got nil") - } + _, err := projectRevision(out) + requireProblem(t, err, errs.CategoryInternal, errs.SubtypeInvalidResponse, "revision") }) t.Run("errors on a non-object output", func(t *testing.T) { - if _, err := projectRevision("not-an-object"); err == nil { - t.Error("expected an error for non-object output, got nil") - } + _, err := projectRevision("not-an-object") + requireProblem(t, err, errs.CategoryInternal, errs.SubtypeInvalidResponse, "non-object") }) } diff --git a/skills/lark-sheets/SKILL.md b/skills/lark-sheets/SKILL.md index 9e596125b..3ebaab16d 100644 --- a/skills/lark-sheets/SKILL.md +++ b/skills/lark-sheets/SKILL.md @@ -65,7 +65,7 @@ metadata: | 查找 / 替换文本 | `+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) | — | +| 复核某次(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` | diff --git a/skills/lark-sheets/references/lark-sheets-batch-update.md b/skills/lark-sheets/references/lark-sheets-batch-update.md index 5abb66ee3..1f300ad0a 100644 --- a/skills/lark-sheets/references/lark-sheets-batch-update.md +++ b/skills/lark-sheets/references/lark-sheets-batch-update.md @@ -59,7 +59,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["Sheet1!A1:B2","Sheet2!D1:D10"]`,前缀裸写不加引号);前缀必须与 sheet 真实显示名完全一致(含大小写),不接受 sheet reference_id;支持跨 sheet;所有 range 应用同一组 style | +| `--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`、`微软雅黑`) | @@ -79,7 +79,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组(如 `["Sheet1!A2:A100","Sheet1!C2:C100"]`,前缀裸写不加引号),每项必须带 sheet 前缀;前缀必须与 sheet 真实显示名完全一致(含大小写),不接受 sheet reference_id | +| `--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 | 启用多选 | @@ -100,7 +100,7 @@ _公共:URL/token(无 sheet 定位) · 系统:`--yes`、`--dry-run`_ | Flag | Type | 必填 | 说明 | | --- | --- | --- | --- | -| `--ranges` | string + File + Stdin(简单 JSON) | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["Sheet1!A2:Z1000","Sheet2!A2:Z1000"]`,前缀裸写不加引号);前缀必须与 sheet 真实显示名完全一致(含大小写),不接受 sheet reference_id;支持跨 sheet;对所有 range 执行同一 scope 的清除 | +| `--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 diff --git a/skills/lark-sheets/references/lark-sheets-float-image.md b/skills/lark-sheets/references/lark-sheets-float-image.md index cf0861514..7c8716251 100644 --- a/skills/lark-sheets/references/lark-sheets-float-image.md +++ b/skills/lark-sheets/references/lark-sheets-float-image.md @@ -60,7 +60,7 @@ _公共四件套 · 系统:`--dry-run`_ | --- | --- | --- | --- | | `--image-name` | string | required | 图片名称,含扩展名(如 `logo.png`) | | `--image-token` | string | xor | 图片 file_token(与 `--image-uri` 二选一)。常见来源:`+float-image-list` 返回的 `image_token` | -| `--image-uri` | string | xor | 图片 reference_id(与 `--image-token` 二选一);图片上传链路返回的 reference_id | +| `--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 | 图片宽度(像素) | @@ -78,8 +78,8 @@ _公共四件套 · 系统:`--dry-run`_ | --- | --- | --- | --- | | `--float-image-id` | string | required | 目标图片 id | | `--image-name` | string | required | 图片名称,含扩展名(如 `logo.png`) | -| `--image-token` | string | xor | 图片 file_token(与 `--image-uri` 二选一)。常见来源:`+float-image-list` 返回的 `image_token` | -| `--image-uri` | string | xor | 图片 reference_id(与 `--image-token` 二选一);图片上传链路返回的 reference_id | +| `--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 | 图片宽度(像素) |