diff --git a/shortcuts/sheets/execute_paths_test.go b/shortcuts/sheets/execute_paths_test.go index 21957527b..c36c2fc41 100644 --- a/shortcuts/sheets/execute_paths_test.go +++ b/shortcuts/sheets/execute_paths_test.go @@ -116,7 +116,7 @@ func TestExecute_CellsSet(t *testing.T) { out, err := runShortcutWithStubs(t, CellsSet, []string{ "--url", testURL, "--sheet-id", testSheetID, "--range", "A1:B1", - "--data", `{"cells":[[{"value":"x"},{"value":"y"}]]}`, + "--cells", `{"cells":[[{"value":"x"},{"value":"y"}]]}`, }, stub) if err != nil { t.Fatalf("execute failed: %v\nout=%s", err, out) @@ -217,7 +217,7 @@ func TestExecute_FilterCreate(t *testing.T) { out, err := runShortcutWithStubs(t, FilterCreate, []string{ "--url", testURL, "--sheet-id", testSheetID, "--range", "A1:F100", - "--data", `{"conditions":[{"col":"B","filter_type":"multiValue","expected":["x"]}]}`, + "--properties", `{"rules":[{"col":"B","filter_type":"multiValue","expected":["x"]}]}`, }, stub) if err != nil { t.Fatalf("execute failed: %v\nout=%s", err, out) @@ -228,8 +228,8 @@ func TestExecute_FilterCreate(t *testing.T) { if props["range"] != "A1:F100" { t.Errorf("properties.range = %v", props["range"]) } - if props["conditions"] == nil { - t.Errorf("conditions missing: %#v", props) + if props["rules"] == nil { + t.Errorf("rules missing: %#v", props) } } @@ -240,7 +240,7 @@ func TestExecute_BatchUpdate_Raw(t *testing.T) { stub := toolOutputStub(testToken, "write", `{"results":[{"ok":true}]}`) _, err := runShortcutWithStubs(t, BatchUpdate, []string{ "--url", testURL, - "--data", `{"operations":[{"tool":"set_cell_range","params":{"excel_id":"shtcnTestTOK","range":"A1","cells":[[{"value":1}]]}}]}`, + "--operations", `[{"tool":"set_cell_range","params":{"excel_id":"shtcnTestTOK","range":"A1","cells":[[{"value":1}]]}}]`, "--continue-on-error", "--yes", }, stub) @@ -276,7 +276,7 @@ func TestExecute_WorkbookCreate(t *testing.T) { out, err := runShortcutWithStubs(t, WorkbookCreate, []string{ "--title", "Sales", "--headers", `["Name","Score"]`, - "--data", `[["alice",95]]`, + "--values", `[["alice",95]]`, }, create, fill) if err != nil { t.Fatalf("execute failed: %v\nout=%s", err, out) @@ -324,7 +324,7 @@ func TestExecute_ChartCreate(t *testing.T) { stub := toolOutputStub(testToken, "write", `{"chart_id":"chartNEW"}`) out, err := runShortcutWithStubs(t, ChartCreate, []string{ "--url", testURL, "--sheet-id", testSheetID, - "--data", `{"type":"line"}`, + "--properties", `{"type":"line"}`, }, stub) if err != nil { t.Fatalf("execute failed: %v", err) diff --git a/shortcuts/sheets/helpers.go b/shortcuts/sheets/helpers.go index 51874f4d6..931f3eb5c 100644 --- a/shortcuts/sheets/helpers.go +++ b/shortcuts/sheets/helpers.go @@ -166,3 +166,101 @@ func requireJSONArray(runtime *common.RuntimeContext, name string) ([]interface{ } return a, nil } + +// ─── style flags (shared by +cells-set-style and +cells-batch-set-style) ─ + +var ( + fontStyleEnum = []string{"normal", "italic"} + fontWeightEnum = []string{"normal", "bold"} + fontLineEnum = []string{"none", "underline", "line-through"} + hAlignEnum = []string{"left", "center", "right"} + vAlignEnum = []string{"top", "middle", "bottom"} + wordWrapEnum = []string{"overflow", "auto-wrap", "word-clip"} +) + +// styleFlatFlags returns the 11 flat style flags + --border-styles that both +// +cells-set-style and +cells-batch-set-style expose. Keeping them in one +// place stops the two shortcuts from drifting apart. +func styleFlatFlags() []common.Flag { + return []common.Flag{ + {Name: "background-color", Desc: "hex background color (e.g. #ffffff)"}, + {Name: "font-color", Desc: "hex font color (e.g. #000000)"}, + {Name: "font-size", Type: "int", Desc: "font size in pixels (e.g. 10, 12, 14)"}, + {Name: "font-style", Enum: fontStyleEnum, Desc: "normal / italic"}, + {Name: "font-weight", Enum: fontWeightEnum, Desc: "normal / bold"}, + {Name: "font-line", Enum: fontLineEnum, Desc: "none / underline / line-through"}, + {Name: "horizontal-alignment", Enum: hAlignEnum, Desc: "left / center / right"}, + {Name: "vertical-alignment", Enum: vAlignEnum, Desc: "top / middle / bottom"}, + {Name: "word-wrap", Enum: wordWrapEnum, Desc: "overflow (default) / auto-wrap / word-clip"}, + {Name: "number-format", Desc: "number format string (e.g. @, 0.00, $#,##0.00, mm/dd/yyyy)"}, + {Name: "border-styles", Input: []string{common.File, common.Stdin}, + Desc: "border JSON: { top, bottom, left, right } each = { color, style, weight }"}, + } +} + +// buildCellStyleFromFlags reads the 11 flat style flags and returns the +// cell_styles map expected by set_cell_range. Skips any flag the user +// didn't set so partial styles work. +func buildCellStyleFromFlags(runtime *common.RuntimeContext) map[string]interface{} { + style := map[string]interface{}{} + if v := runtime.Str("background-color"); v != "" { + style["background_color"] = v + } + if v := runtime.Str("font-color"); v != "" { + style["font_color"] = v + } + if runtime.Changed("font-size") && runtime.Int("font-size") > 0 { + style["font_size"] = runtime.Int("font-size") + } + if v := runtime.Str("font-style"); v != "" { + style["font_style"] = v + } + if v := runtime.Str("font-weight"); v != "" { + style["font_weight"] = v + } + if v := runtime.Str("font-line"); v != "" { + style["font_line"] = v + } + if v := runtime.Str("horizontal-alignment"); v != "" { + style["horizontal_alignment"] = v + } + if v := runtime.Str("vertical-alignment"); v != "" { + style["vertical_alignment"] = v + } + if v := runtime.Str("word-wrap"); v != "" { + style["word_wrap"] = v + } + if v := runtime.Str("number-format"); v != "" { + style["number_format"] = v + } + return style +} + +// borderStylesFromFlag parses --border-styles as a JSON object (top/bottom/ +// left/right with style sub-objects). Returns nil when the flag is empty. +func borderStylesFromFlag(runtime *common.RuntimeContext) (map[string]interface{}, error) { + if runtime.Str("border-styles") == "" { + return nil, nil + } + v, err := parseJSONFlag(runtime, "border-styles") + if err != nil { + return nil, err + } + m, ok := v.(map[string]interface{}) + if !ok { + return nil, common.FlagErrorf("--border-styles must be a JSON object") + } + return m, nil +} + +// requireAnyStyleFlag ensures at least one style-defining flag (style or +// border) is set — otherwise the request would do nothing. +func requireAnyStyleFlag(runtime *common.RuntimeContext) error { + if len(buildCellStyleFromFlags(runtime)) > 0 { + return nil + } + if runtime.Str("border-styles") != "" { + return nil + } + return common.FlagErrorf("at least one style flag is required (e.g. --background-color, --font-weight, --border-styles)") +} diff --git a/shortcuts/sheets/lark_sheet_batch_update.go b/shortcuts/sheets/lark_sheet_batch_update.go index 97c991d4b..4d7da18fc 100644 --- a/shortcuts/sheets/lark_sheet_batch_update.go +++ b/shortcuts/sheets/lark_sheet_batch_update.go @@ -41,21 +41,20 @@ var BatchUpdate = common.Shortcut{ AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: append(publicTokenFlags(), - common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true, - Desc: "batch payload JSON: { operations: [{tool, params}, ...] }"}, + common.Flag{Name: "operations", Input: []string{common.File, common.Stdin}, Required: true, + Desc: "operations JSON array: [{tool, params}, ...] (or an envelope object with operations / continue_on_error)"}, common.Flag{Name: "continue-on-error", Type: "bool", Desc: "flip the default strict transaction off; partial success is kept on disk"}, ), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } - body, err := requireJSONObject(runtime, "data") + ops, err := parseBatchOperationsFlag(runtime) if err != nil { return err } - ops, ok := body["operations"].([]interface{}) - if !ok || len(ops) == 0 { - return common.FlagErrorf("--data.operations must be a non-empty JSON array") + if len(ops) == 0 { + return common.FlagErrorf("--operations must be a non-empty JSON array") } return nil }, @@ -86,45 +85,83 @@ var BatchUpdate = common.Shortcut{ } func batchUpdateRawInput(runtime *common.RuntimeContext, token string) (map[string]interface{}, error) { - body, err := requireJSONObject(runtime, "data") + ops, err := parseBatchOperationsFlag(runtime) if err != nil { return nil, err } - ops, _ := body["operations"].([]interface{}) input := map[string]interface{}{ "excel_id": token, "operations": ops, } if runtime.Bool("continue-on-error") { input["continue_on_error"] = true - } else if v, ok := body["continue_on_error"].(bool); ok && v { - // honor an inline override from --data when the flag is unset - input["continue_on_error"] = true + } else if envelope, _ := parseJSONFlag(runtime, "operations"); envelope != nil { + // Honor an inline override when --operations is an envelope object + // rather than a bare operations array. + if m, ok := envelope.(map[string]interface{}); ok { + if v, ok := m["continue_on_error"].(bool); ok && v { + input["continue_on_error"] = true + } + } } return input, nil } -// CellsBatchSetStyle stamps one style block across many ranges atomically. -// --data is an array of {ranges: [...], style: {...}} entries; CLI flattens -// each (entry × range) pair into a set_cell_range operation in the batch. +// parseBatchOperationsFlag accepts --operations as either a JSON array (the +// operations list directly) or an envelope object { operations, continue_on_error } +// for back-compat with the legacy --data shape. Returns the operations array. +func parseBatchOperationsFlag(runtime *common.RuntimeContext) ([]interface{}, error) { + v, err := parseJSONFlag(runtime, "operations") + if err != nil { + return nil, err + } + if v == nil { + return nil, common.FlagErrorf("--operations is required") + } + if arr, ok := v.([]interface{}); ok { + return arr, nil + } + if m, ok := v.(map[string]interface{}); ok { + if ops, ok := m["operations"].([]interface{}); ok { + return ops, nil + } + } + return nil, common.FlagErrorf("--operations must be a JSON array (or { operations: [...] } envelope)") +} + +// CellsBatchSetStyle stamps one style block across many sheet-prefixed +// ranges atomically. --ranges is a JSON array of sheet-prefixed A1 +// strings; the style is composed from the same flat flags as +// +cells-set-style. CLI fans each range into a separate set_cell_range +// op inside one batch_update. var CellsBatchSetStyle = common.Shortcut{ Service: "sheets", Command: "+cells-batch-set-style", - Description: "Apply styles to many sheet-prefixed ranges in one atomic batch.", + Description: "Apply one style block to many sheet-prefixed ranges in one atomic batch.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, - Flags: append(publicTokenFlags(), - common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true, - Desc: "JSON array: [{ranges: [\"sheet1!A1:B2\", ...], style: {...}}, ...] (each range must carry a sheet prefix)"}, + Flags: append( + append(publicTokenFlags(), + common.Flag{Name: "ranges", Input: []string{common.File, common.Stdin}, Required: true, + Desc: "JSON array of sheet-prefixed A1 ranges (e.g. [\"sheet1!A1:B2\", \"sheet1!D1:E2\"])"}), + styleFlatFlags()..., ), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } - _, err := batchStyleEntries(runtime) - return err + if _, err := validateDropdownRanges(runtime); err != nil { + return err + } + if err := requireAnyStyleFlag(runtime); err != nil { + return err + } + if _, err := borderStylesFromFlag(runtime); err != nil { + return err + } + return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) @@ -149,83 +186,43 @@ var CellsBatchSetStyle = common.Shortcut{ }, } -// batchStyleEntries validates --data is the expected array shape. -func batchStyleEntries(runtime *common.RuntimeContext) ([]map[string]interface{}, error) { - raw, err := requireJSONArray(runtime, "data") - if err != nil { - return nil, err - } - if len(raw) == 0 { - return nil, common.FlagErrorf("--data must contain at least one entry") - } - out := make([]map[string]interface{}, 0, len(raw)) - for i, v := range raw { - entry, ok := v.(map[string]interface{}) - if !ok { - return nil, common.FlagErrorf("--data[%d] must be an object", i) - } - rangesRaw, ok := entry["ranges"].([]interface{}) - if !ok || len(rangesRaw) == 0 { - return nil, common.FlagErrorf("--data[%d].ranges must be a non-empty array", i) - } - for j, r := range rangesRaw { - s, ok := r.(string) - if !ok || !strings.Contains(s, "!") { - return nil, common.FlagErrorf("--data[%d].ranges[%d] must be a sheet-prefixed string", i, j) - } - } - if _, ok := entry["style"].(map[string]interface{}); !ok { - return nil, common.FlagErrorf("--data[%d].style must be a JSON object", i) - } - out = append(out, entry) - } - return out, nil -} - func cellsBatchSetStyleInput(runtime *common.RuntimeContext, token string) (map[string]interface{}, error) { - entries, err := batchStyleEntries(runtime) + ranges, err := validateDropdownRanges(runtime) if err != nil { return nil, err } + cellStyle := buildCellStyleFromFlags(runtime) + borderStyles, err := borderStylesFromFlag(runtime) + if err != nil { + return nil, err + } + prototype := map[string]interface{}{} + if len(cellStyle) > 0 { + prototype["cell_styles"] = cellStyle + } + if borderStyles != nil { + prototype["border_styles"] = borderStyles + } var ops []interface{} - for _, entry := range entries { - style := entry["style"].(map[string]interface{}) - // Split border_styles out into its sibling field per set_cell_range's contract. - cellStyle := map[string]interface{}{} - var borderStyles interface{} - for k, v := range style { - if k == "border_styles" { - borderStyles = v - continue - } - cellStyle[k] = v + for _, rng := range ranges { + sheet, sub, err := splitSheetPrefixedRange(rng) + if err != nil { + return nil, err } - ranges, _ := entry["ranges"].([]interface{}) - for _, r := range ranges { - rng := r.(string) - sheet, sub, err := splitSheetPrefixedRange(rng) - if err != nil { - return nil, err - } - rows, cols, err := rangeDimensions(sub) - if err != nil { - return nil, common.FlagErrorf("range %q: %v", rng, err) - } - proto := map[string]interface{}{"cell_styles": cellStyle} - if borderStyles != nil { - proto["border_styles"] = borderStyles - } - cells := fillCellsMatrix(rows, cols, proto) - ops = append(ops, map[string]interface{}{ - "tool": "set_cell_range", - "params": map[string]interface{}{ - "excel_id": token, - "sheet_name": sheet, - "range": sub, - "cells": cells, - }, - }) + rows, cols, err := rangeDimensions(sub) + if err != nil { + return nil, common.FlagErrorf("range %q: %v", rng, err) } + cells := fillCellsMatrix(rows, cols, prototype) + ops = append(ops, map[string]interface{}{ + "tool": "set_cell_range", + "params": map[string]interface{}{ + "excel_id": token, + "sheet_name": sheet, + "range": sub, + "cells": cells, + }, + }) } return map[string]interface{}{ "excel_id": token, diff --git a/shortcuts/sheets/lark_sheet_batch_update_test.go b/shortcuts/sheets/lark_sheet_batch_update_test.go index 2ae045b09..c74e3270f 100644 --- a/shortcuts/sheets/lark_sheet_batch_update_test.go +++ b/shortcuts/sheets/lark_sheet_batch_update_test.go @@ -17,7 +17,7 @@ func TestBatchUpdate_RawPassthrough(t *testing.T) { body := parseDryRunBody(t, BatchUpdate, []string{ "--url", testURL, - "--data", `{"operations":[{"tool":"set_cell_range","params":{"excel_id":"shtcnTOK","sheet_id":"sh1","range":"A1","cells":[[{"value":42}]]}}]}`, + "--operations", `[{"tool":"set_cell_range","params":{"excel_id":"shtcnTOK","sheet_id":"sh1","range":"A1","cells":[[{"value":42}]]}}]`, "--continue-on-error", "--yes", }) @@ -35,27 +35,28 @@ func TestBatchUpdate_HighRiskWriteRequiresYes(t *testing.T) { t.Parallel() stdout, stderr, err := runShortcutCapturingErr(t, BatchUpdate, []string{ "--url", testURL, - "--data", `{"operations":[{"tool":"set_cell_range","params":{}}]}`, + "--operations", `[{"tool":"set_cell_range","params":{}}]`, }) if err == nil { t.Fatalf("expected confirmation_required; stdout=%s stderr=%s", stdout, stderr) } } -// TestCellsBatchSetStyle_FansOutOps verifies 2 entries × multiple ranges -// produce one set_cell_range op per (entry, range). +// TestCellsBatchSetStyle_FansOutOps verifies multiple ranges produce one +// set_cell_range op each, sharing the same style flags. func TestCellsBatchSetStyle_FansOutOps(t *testing.T) { t.Parallel() body := parseDryRunBody(t, CellsBatchSetStyle, []string{ "--url", testURL, - "--data", `[{"ranges":["sheet1!A1:B2","sheet1!D1:E2"],"style":{"font":{"bold":true}}},{"ranges":["sheet1!A5:A6"],"style":{"backColor":"#ff0"}}]`, + "--ranges", `["sheet1!A1:B2","sheet1!D1:E2","sheet1!A5:A6"]`, + "--font-weight", "bold", + "--background-color", "#ffff00", }) input := decodeToolInput(t, body, "batch_update") ops, _ := input["operations"].([]interface{}) if len(ops) != 3 { - t.Fatalf("operations length = %d, want 3 (2 ranges × entry1 + 1 range × entry2)", len(ops)) + t.Fatalf("operations length = %d, want 3 (one per range)", len(ops)) } - // Every op should target set_cell_range with sheet_name carrying the prefix. for i, raw := range ops { op, _ := raw.(map[string]interface{}) if op["tool"] != "set_cell_range" { @@ -65,6 +66,13 @@ func TestCellsBatchSetStyle_FansOutOps(t *testing.T) { if params["sheet_name"] != "sheet1" { t.Errorf("op[%d].sheet_name = %v, want sheet1", i, params["sheet_name"]) } + cells, _ := params["cells"].([]interface{}) + row, _ := cells[0].([]interface{}) + cell, _ := row[0].(map[string]interface{}) + style, _ := cell["cell_styles"].(map[string]interface{}) + if style["font_weight"] != "bold" || style["background_color"] != "#ffff00" { + t.Errorf("op[%d] cell_styles wrong: %#v", i, style) + } } } @@ -157,13 +165,24 @@ func TestBatchUpdate_ValidationGuards(t *testing.T) { // batch-update with empty operations stdout, stderr, err = runShortcutCapturingErr(t, BatchUpdate, []string{ "--url", testURL, - "--data", `{"operations":[]}`, + "--operations", `[]`, "--yes", "--dry-run", }) if err == nil || !strings.Contains(stdout+stderr+err.Error(), "non-empty JSON array") { t.Errorf("expected empty-operations guard; got=%s|%s|%v", stdout, stderr, err) } + + // dropdown-update with non-array --options (object instead) → array guard + stdout, stderr, err = runShortcutCapturingErr(t, DropdownUpdate, []string{ + "--url", testURL, + "--ranges", `["sheet1!A1:A2"]`, + "--options", `{"not":"array"}`, + "--dry-run", + }) + if err == nil || !strings.Contains(stdout+stderr+err.Error(), "must be a JSON array") { + t.Errorf("expected JSON array guard; got=%s|%s|%v", stdout, stderr, err) + } } // TestSplitSheetPrefixedRange exercises the helper directly. diff --git a/shortcuts/sheets/lark_sheet_object_crud.go b/shortcuts/sheets/lark_sheet_object_crud.go index a0c94222b..d87eba32d 100644 --- a/shortcuts/sheets/lark_sheet_object_crud.go +++ b/shortcuts/sheets/lark_sheet_object_crud.go @@ -37,18 +37,22 @@ type objectCRUDSpec struct { toolName string // e.g. "manage_chart_object" idFlag string // e.g. "chart-id" idField string // e.g. "chart_id" - createDataDesc string // help text for --data on create - updateDataDesc string // help text for --data on update + createDataDesc string // help text for --properties on create + updateDataDesc string // help text for --properties on update createExtraFlags []common.Flag - // createExtraInput, when set, mutates the tool input after the standard - // fields are written. Used by pivot to inject --target-sheet-id / - // --target-position alongside properties. - createExtraInput func(rt *common.RuntimeContext, input map[string]interface{}) + updateExtraFlags []common.Flag + // enhanceCreateInput / enhanceUpdateInput, when set, mutate the tool + // input after the standard fields are written. Used to inject + // shortcut-specific flat flags into the input (typically into the + // properties map). The callback is responsible for navigating to the + // right nesting level. + enhanceCreateInput func(rt *common.RuntimeContext, input map[string]interface{}) + enhanceUpdateInput func(rt *common.RuntimeContext, input map[string]interface{}) } func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { flags := append(publicSheetFlags(), - common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true, Desc: spec.createDataDesc}, + common.Flag{Name: "properties", Input: []string{common.File, common.Stdin}, Required: true, Desc: spec.createDataDesc}, ) flags = append(flags, spec.createExtraFlags...) return common.Shortcut{ @@ -67,7 +71,7 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { if _, _, err := resolveSheetSelector(runtime); err != nil { return err } - _, err := requireJSONObject(runtime, "data") + _, err := requireJSONObject(runtime, "properties") return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { @@ -100,7 +104,7 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut { } func objectCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName string, spec objectCRUDSpec) (map[string]interface{}, error) { - props, err := requireJSONObject(runtime, "data") + props, err := requireJSONObject(runtime, "properties") if err != nil { return nil, err } @@ -110,8 +114,8 @@ func objectCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName "properties": props, } sheetSelectorForToolInput(input, sheetID, sheetName) - if spec.createExtraInput != nil { - spec.createExtraInput(runtime, input) + if spec.enhanceCreateInput != nil { + spec.enhanceCreateInput(runtime, input) } return input, nil } @@ -125,9 +129,10 @@ func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut { }) } flags = append(flags, common.Flag{ - Name: "data", Input: []string{common.File, common.Stdin}, Required: true, + Name: "properties", Input: []string{common.File, common.Stdin}, Required: true, Desc: spec.updateDataDesc, }) + flags = append(flags, spec.updateExtraFlags...) return common.Shortcut{ Service: "sheets", Command: spec.commandPrefix + "-update", @@ -147,7 +152,7 @@ func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut { if spec.idFlag != "" && strings.TrimSpace(runtime.Str(spec.idFlag)) == "" { return common.FlagErrorf("--%s is required", spec.idFlag) } - _, err := requireJSONObject(runtime, "data") + _, err := requireJSONObject(runtime, "properties") return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { @@ -180,7 +185,7 @@ func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut { } func objectUpdateInput(runtime *common.RuntimeContext, token, sheetID, sheetName string, spec objectCRUDSpec) (map[string]interface{}, error) { - props, err := requireJSONObject(runtime, "data") + props, err := requireJSONObject(runtime, "properties") if err != nil { return nil, err } @@ -193,6 +198,9 @@ func objectUpdateInput(runtime *common.RuntimeContext, token, sheetID, sheetName if spec.idFlag != "" { input[spec.idField] = strings.TrimSpace(runtime.Str(spec.idFlag)) } + if spec.enhanceUpdateInput != nil { + spec.enhanceUpdateInput(runtime, input) + } return input, nil } @@ -276,25 +284,38 @@ var ChartCreate = newObjectCreateShortcut(chartSpec) var ChartUpdate = newObjectUpdateShortcut(chartSpec) var ChartDelete = newObjectDeleteShortcut(chartSpec) -// pivot — adds --target-sheet-id / --target-position on create +// pivot — create exposes --target-sheet-id / --target-position (top-level +// of the tool input) plus --source / --range hoisted from properties. var pivotSpec = objectCRUDSpec{ commandPrefix: "+pivot", toolName: "manage_pivot_table_object", idFlag: "pivot-table-id", idField: "pivot_table_id", - createDataDesc: "pivot table properties JSON: { data_range, rows, columns, values, filters, show_row_grand_total, show_col_grand_total }", + createDataDesc: "pivot table properties JSON: { rows, columns, values, filters, show_row_grand_total, ... }; --source / --range cover the common scalar fields", updateDataDesc: "full or partial pivot properties JSON (`+pivot-list --pivot-table-id ` first, then patch)", createExtraFlags: []common.Flag{ {Name: "target-sheet-id", Desc: "destination sheet id for the pivot table; omit to auto-create a fresh sheet (recommended)"}, {Name: "target-position", Default: "A1", Desc: "destination start cell, default A1"}, + {Name: "source", Required: true, Desc: "pivot source range, e.g. Sheet1!A1:D100 (--source overrides any properties.source)"}, + {Name: "range", Desc: "destination top-left A1 cell, e.g. F1 (--range overrides any properties.range)"}, }, - createExtraInput: func(rt *common.RuntimeContext, input map[string]interface{}) { + enhanceCreateInput: func(rt *common.RuntimeContext, input map[string]interface{}) { if v := strings.TrimSpace(rt.Str("target-sheet-id")); v != "" { input["target_sheet_id"] = v } if v := strings.TrimSpace(rt.Str("target-position")); v != "" && v != "A1" { input["target_position"] = v } + props, _ := input["properties"].(map[string]interface{}) + if props == nil { + return + } + if v := strings.TrimSpace(rt.Str("source")); v != "" { + props["source"] = v + } + if v := strings.TrimSpace(rt.Str("range")); v != "" { + props["range"] = v + } }, } var PivotCreate = newObjectCreateShortcut(pivotSpec) @@ -302,14 +323,50 @@ var PivotUpdate = newObjectUpdateShortcut(pivotSpec) var PivotDelete = newObjectDeleteShortcut(pivotSpec) // conditional format — CLI surface uses --rule-id (short), wired to the -// tool's conditional_format_id on the wire. +// tool's conditional_format_id on the wire. --rule-type and --ranges are +// hoisted out of properties (both required, set on every CRUD write). +var condFormatRuleTypeEnum = []string{ + "cellValue", "formula", "duplicate", "unique", + "topBottom", "aboveBelowAverage", "dataBar", "colorScale", + "iconSet", "textContains", "dateOccurring", "blankCell", "errorCell", +} +var condFormatExtraFlags = []common.Flag{ + {Name: "rule-type", Required: true, Enum: condFormatRuleTypeEnum, + Desc: "rule type enum (cellValue / formula / duplicate / ...); merged into properties.rule.type"}, + {Name: "ranges", Input: []string{common.File, common.Stdin}, Required: true, + Desc: "A1 ranges JSON array (e.g. [\"A1:A100\",\"C2:C50\"]); merged into properties.ranges"}, +} +var condFormatEnhance = func(rt *common.RuntimeContext, input map[string]interface{}) { + props, _ := input["properties"].(map[string]interface{}) + if props == nil { + return + } + if ruleType := strings.TrimSpace(rt.Str("rule-type")); ruleType != "" { + rule, _ := props["rule"].(map[string]interface{}) + if rule == nil { + rule = map[string]interface{}{} + } + rule["type"] = ruleType + props["rule"] = rule + } + if rt.Str("ranges") != "" { + if arr, err := requireJSONArray(rt, "ranges"); err == nil { + props["ranges"] = arr + } + } +} + var condFormatSpec = objectCRUDSpec{ - commandPrefix: "+cond-format", - toolName: "manage_conditional_format_object", - idFlag: "rule-id", - idField: "conditional_format_id", - createDataDesc: "rule JSON: { range, rule: { type: cell_value|duplicate|data_bar|color_scale|rank|formula, ... } }", - updateDataDesc: "full or partial rule JSON (`+cond-format-list --rule-id ` first, then patch)", + commandPrefix: "+cond-format", + toolName: "manage_conditional_format_object", + idFlag: "rule-id", + idField: "conditional_format_id", + createDataDesc: "rule JSON: { rule: { operator, value, style, ... }, ... }; --rule-type and --ranges cover the common scalar fields", + updateDataDesc: "full or partial rule JSON (`+cond-format-list --rule-id ` first, then patch); --rule-type and --ranges still required", + createExtraFlags: condFormatExtraFlags, + updateExtraFlags: condFormatExtraFlags, + enhanceCreateInput: condFormatEnhance, + enhanceUpdateInput: condFormatEnhance, } var CondFormatCreate = newObjectCreateShortcut(condFormatSpec) var CondFormatUpdate = newObjectUpdateShortcut(condFormatSpec) @@ -328,28 +385,199 @@ var SparklineCreate = newObjectCreateShortcut(sparklineSpec) var SparklineUpdate = newObjectUpdateShortcut(sparklineSpec) var SparklineDelete = newObjectDeleteShortcut(sparklineSpec) -// float image -var floatImageSpec = objectCRUDSpec{ - commandPrefix: "+float-image", - toolName: "manage_float_image_object", - idFlag: "float-image-id", - idField: "float_image_id", - createDataDesc: "float image JSON: { image_uri, image_name, position:{row,col}, size:{width,height}, offset:{x,y} } — image_uri must be pre-uploaded", - updateDataDesc: "full or partial float image JSON (`+float-image-list --float-image-id ` first, then patch)", +// float image — fully hoisted to 10 flat flags. No --properties flag; +// the tool's properties is composed entirely from the position / size / +// offset / image_token / image_uri / z_index flat flags. + +var floatImageFlatFlags = []common.Flag{ + {Name: "image-name", Required: true, Desc: "image file name with extension (e.g. logo.png)"}, + {Name: "image-token", Desc: "image file_token (XOR --image-uri); commonly returned by +float-image-list"}, + {Name: "image-uri", Desc: "image reference_id (XOR --image-token); upstream-supplied like \"<|image|>:abcdef\""}, + {Name: "position-row", Type: "int", Required: true, Desc: "top-left row index (0-based)"}, + {Name: "position-col", Required: true, Desc: "top-left column letter (e.g. A, B)"}, + {Name: "size-width", Type: "int", Required: true, Desc: "image width in pixels"}, + {Name: "size-height", Type: "int", Required: true, Desc: "image height in pixels"}, + {Name: "offset-row", Type: "int", Desc: "in-cell row offset in pixels (optional)"}, + {Name: "offset-col", Type: "int", Desc: "in-cell column offset in pixels (optional)"}, + {Name: "z-index", Type: "int", Desc: "z-order layer for overlapping images (optional)"}, } -var FloatImageCreate = newObjectCreateShortcut(floatImageSpec) -var FloatImageUpdate = newObjectUpdateShortcut(floatImageSpec) -var FloatImageDelete = newObjectDeleteShortcut(floatImageSpec) + +// floatImageProperties assembles the tool's properties object from the +// 10 flat flags. Caller is responsible for marking required flags via +// cobra Required:true; this function only enforces the image_token XOR +// image_uri pair (one must be set). +func floatImageProperties(runtime *common.RuntimeContext) (map[string]interface{}, error) { + token := strings.TrimSpace(runtime.Str("image-token")) + uri := strings.TrimSpace(runtime.Str("image-uri")) + if token == "" && uri == "" { + return nil, common.FlagErrorf("either --image-token or --image-uri is required") + } + if token != "" && uri != "" { + return nil, common.FlagErrorf("--image-token and --image-uri are mutually exclusive") + } + props := map[string]interface{}{ + "image_name": strings.TrimSpace(runtime.Str("image-name")), + "position": map[string]interface{}{ + "row": runtime.Int("position-row"), + "col": strings.TrimSpace(runtime.Str("position-col")), + }, + "size": map[string]interface{}{ + "width": runtime.Int("size-width"), + "height": runtime.Int("size-height"), + }, + } + if token != "" { + props["image_token"] = token + } else { + props["image_uri"] = uri + } + if runtime.Changed("offset-row") || runtime.Changed("offset-col") { + offset := map[string]interface{}{} + if runtime.Changed("offset-row") { + offset["row_offset"] = runtime.Int("offset-row") + } + if runtime.Changed("offset-col") { + offset["col_offset"] = runtime.Int("offset-col") + } + props["offset"] = offset + } + if runtime.Changed("z-index") { + props["z_index"] = runtime.Int("z-index") + } + return props, nil +} + +func newFloatImageWriteShortcut(command, description, op string, withIDFlag, isHighRisk bool) common.Shortcut { + risk := "write" + if isHighRisk { + risk = "high-risk-write" + } + flags := publicSheetFlags() + if withIDFlag { + flags = append(flags, common.Flag{Name: "float-image-id", Required: true, Desc: "target image reference_id"}) + } + flags = append(flags, floatImageFlatFlags...) + return common.Shortcut{ + Service: "sheets", + Command: command, + Description: description, + Risk: risk, + Scopes: []string{"sheets:spreadsheet:write_only"}, + AuthTypes: []string{"user", "bot"}, + HasFormat: true, + Flags: flags, + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + if _, err := resolveSpreadsheetToken(runtime); err != nil { + return err + } + if _, _, err := resolveSheetSelector(runtime); err != nil { + return err + } + if withIDFlag && strings.TrimSpace(runtime.Str("float-image-id")) == "" { + return common.FlagErrorf("--float-image-id is required") + } + _, err := floatImageProperties(runtime) + return err + }, + DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + token, _ := resolveSpreadsheetToken(runtime) + sheetID, sheetName, _ := resolveSheetSelector(runtime) + input, _ := floatImageWriteInput(runtime, token, sheetID, sheetName, op, withIDFlag) + return invokeToolDryRun(token, ToolKindWrite, "manage_float_image_object", input) + }, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetToken(runtime) + if err != nil { + return err + } + sheetID, sheetName, err := resolveSheetSelector(runtime) + if err != nil { + return err + } + input, err := floatImageWriteInput(runtime, token, sheetID, sheetName, op, withIDFlag) + if err != nil { + return err + } + out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_float_image_object", input) + if err != nil { + return err + } + runtime.Out(out, nil) + return nil + }, + } +} + +func floatImageWriteInput(runtime *common.RuntimeContext, token, sheetID, sheetName, op string, withIDFlag bool) (map[string]interface{}, error) { + props, err := floatImageProperties(runtime) + if err != nil { + return nil, err + } + input := map[string]interface{}{ + "excel_id": token, + "operation": op, + "properties": props, + } + sheetSelectorForToolInput(input, sheetID, sheetName) + if withIDFlag { + input["float_image_id"] = strings.TrimSpace(runtime.Str("float-image-id")) + } + return input, nil +} + +var FloatImageCreate = newFloatImageWriteShortcut( + "+float-image-create", + "Create a floating image (referenced by --image-token or --image-uri).", + "create", false, false, +) +var FloatImageUpdate = newFloatImageWriteShortcut( + "+float-image-update", + "Update an existing floating image (target by --float-image-id; provide the full set of flat flags).", + "update", true, false, +) + +// FloatImageDelete uses the standard CRUD delete factory since it only +// needs --float-image-id + --yes. +var floatImageDeleteSpec = objectCRUDSpec{ + commandPrefix: "+float-image", + toolName: "manage_float_image_object", + idFlag: "float-image-id", + idField: "float_image_id", +} +var FloatImageDelete = newObjectDeleteShortcut(floatImageDeleteSpec) // filter view — cli_status: cli-only but the tool is in mcp-tools.json so // it dispatches via the same One-OpenAPI endpoint as every other shortcut. +// --view-name and --range are hoisted out of properties (optional on both +// create and update; they always win over properties.{view_name, range}). +var filterViewExtraFlags = []common.Flag{ + {Name: "range", Desc: "filter view range (A1 covering the header, e.g. A1:F1000); overrides properties.range"}, + {Name: "view-name", Desc: "view title; create omits → server-generated, update omits → keep current. Overrides properties.view_name"}, +} +var filterViewEnhance = func(rt *common.RuntimeContext, input map[string]interface{}) { + props, _ := input["properties"].(map[string]interface{}) + if props == nil { + return + } + if v := strings.TrimSpace(rt.Str("range")); v != "" { + props["range"] = v + } + if v := strings.TrimSpace(rt.Str("view-name")); v != "" { + props["view_name"] = v + } +} + var filterViewSpec = objectCRUDSpec{ - commandPrefix: "+filter-view", - toolName: "manage_filter_view_object", - idFlag: "view-id", - idField: "view_id", - createDataDesc: "filter view JSON: { view_name, range (required, covers header), rules: [...] }", - updateDataDesc: "partial update JSON: any of { view_name, range, rules }; `+filter-view-list --view-id ` first", + commandPrefix: "+filter-view", + toolName: "manage_filter_view_object", + idFlag: "view-id", + idField: "view_id", + createDataDesc: "filter view JSON: { rules?: [...] , filtered_columns?: [...] }; --range / --view-name cover the scalar fields", + updateDataDesc: "partial update JSON: any of { rules, filtered_columns }; `+filter-view-list --view-id ` first", + createExtraFlags: filterViewExtraFlags, + updateExtraFlags: filterViewExtraFlags, + enhanceCreateInput: filterViewEnhance, + enhanceUpdateInput: filterViewEnhance, } var FilterViewCreate = newObjectCreateShortcut(filterViewSpec) var FilterViewUpdate = newObjectUpdateShortcut(filterViewSpec) @@ -374,8 +602,8 @@ var FilterCreate = common.Shortcut{ HasFormat: true, Flags: append(publicSheetFlags(), common.Flag{Name: "range", Required: true, Desc: "filter range including the header row (e.g. A1:F1000)"}, - common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, - Desc: "optional conditions JSON: { conditions: [{col, filter_type, expected, ...}] }; empty filter when omitted"}, + common.Flag{Name: "properties", Input: []string{common.File, common.Stdin}, + Desc: "optional rules JSON: { rules: [...], filtered_columns?: [...] }; empty filter when omitted"}, ), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { @@ -387,8 +615,8 @@ var FilterCreate = common.Shortcut{ if strings.TrimSpace(runtime.Str("range")) == "" { return common.FlagErrorf("--range is required") } - if runtime.Str("data") != "" { - if _, err := requireJSONObject(runtime, "data"); err != nil { + if runtime.Str("properties") != "" { + if _, err := requireJSONObject(runtime, "properties"); err != nil { return err } } @@ -426,8 +654,8 @@ func filterCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName props := map[string]interface{}{ "range": strings.TrimSpace(runtime.Str("range")), } - if runtime.Str("data") != "" { - extra, err := requireJSONObject(runtime, "data") + if runtime.Str("properties") != "" { + extra, err := requireJSONObject(runtime, "properties") if err != nil { return nil, err } @@ -447,19 +675,21 @@ func filterCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName return input, nil } -// FilterUpdate patches the sheet-level filter — change range or -// add/replace conditions. filter_id is implicit (sheet-scoped). +// FilterUpdate patches the sheet-level filter. --properties carries the +// rules; --range is first-class and overrides any properties.range. +// filter_id is implicit (sheet-scoped). var FilterUpdate = common.Shortcut{ Service: "sheets", Command: "+filter-update", - Description: "Update the sheet-level filter (patch range or conditions).", + Description: "Update the sheet-level filter (overwrite rules + range).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: append(publicSheetFlags(), - common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true, - Desc: "patch JSON: { range?, conditions?: [...] } — read with +filter-list first"}, + common.Flag{Name: "properties", Input: []string{common.File, common.Stdin}, Required: true, + Desc: "patch JSON: { rules: [...], filtered_columns?: [...] } — read with +filter-list first"}, + common.Flag{Name: "range", Required: true, Desc: "filter range A1 (e.g. A1:F1000); overrides properties.range"}, ), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { @@ -468,7 +698,10 @@ var FilterUpdate = common.Shortcut{ if _, _, err := resolveSheetSelector(runtime); err != nil { return err } - _, err := requireJSONObject(runtime, "data") + if strings.TrimSpace(runtime.Str("range")) == "" { + return common.FlagErrorf("--range is required") + } + _, err := requireJSONObject(runtime, "properties") return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { @@ -500,10 +733,12 @@ var FilterUpdate = common.Shortcut{ } func filterUpdateInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) (map[string]interface{}, error) { - props, err := requireJSONObject(runtime, "data") + props, err := requireJSONObject(runtime, "properties") if err != nil { return nil, err } + // --range wins over any properties.range + props["range"] = strings.TrimSpace(runtime.Str("range")) input := map[string]interface{}{ "excel_id": token, "operation": "update", diff --git a/shortcuts/sheets/lark_sheet_object_crud_test.go b/shortcuts/sheets/lark_sheet_object_crud_test.go index 0cbfe721c..933da4a38 100644 --- a/shortcuts/sheets/lark_sheet_object_crud_test.go +++ b/shortcuts/sheets/lark_sheet_object_crud_test.go @@ -29,7 +29,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { { name: "+chart-create", sc: ChartCreate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"type":"line"}`}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--properties", `{"type":"line"}`}, toolName: "manage_chart_object", wantInput: map[string]interface{}{ "excel_id": testToken, @@ -41,7 +41,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { { name: "+chart-update", sc: ChartUpdate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chartXYZ", "--data", `{"type":"bar"}`}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chartXYZ", "--properties", `{"type":"bar"}`}, toolName: "manage_chart_object", wantInput: map[string]interface{}{ "excel_id": testToken, @@ -51,18 +51,30 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { "properties": map[string]interface{}{"type": "bar"}, }, }, - // pivot — has extra create flags + // pivot — has extra create flags incl. required --source { - name: "+pivot-create with target flags", - sc: PivotCreate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"data_range":"Sheet1!A1:F1000"}`, "--target-sheet-id", "sh2", "--target-position", "B5"}, + name: "+pivot-create with target / source / range flags", + sc: PivotCreate, + args: []string{ + "--url", testURL, "--sheet-id", testSheetID, + "--properties", `{"rows":[{"field":"A"}]}`, + "--source", "Sheet1!A1:F1000", + "--range", "F1", + "--target-sheet-id", "sh2", + "--target-position", "B5", + }, toolName: "manage_pivot_table_object", wantInput: map[string]interface{}{ - "excel_id": testToken, - "sheet_id": testSheetID, - "operation": "create", - "target_sheet_id": "sh2", - "target_position": "B5", + "excel_id": testToken, + "sheet_id": testSheetID, + "operation": "create", + "target_sheet_id": "sh2", + "target_position": "B5", + "properties": map[string]interface{}{ + "rows": []interface{}{map[string]interface{}{"field": "A"}}, + "source": "Sheet1!A1:F1000", + "range": "F1", + }, }, }, { @@ -77,23 +89,32 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { "pivot_table_id": "ptA", }, }, - // cond-format — --rule-id rename + // cond-format — --rule-id rename + --rule-type / --ranges hoist { - name: "+cond-format-update id rename", - sc: CondFormatUpdate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--rule-id", "ruleA", "--data", `{"rule":{"type":"cell_value"}}`}, + name: "+cond-format-update id rename + rule-type/ranges", + sc: CondFormatUpdate, + args: []string{ + "--url", testURL, "--sheet-id", testSheetID, + "--rule-id", "ruleA", + "--properties", `{"rule":{"operator":"greater_than","value":100}}`, + "--rule-type", "cellValue", + "--ranges", `["A1:A100"]`, + }, toolName: "manage_conditional_format_object", wantInput: map[string]interface{}{ "excel_id": testToken, "sheet_id": testSheetID, "operation": "update", "conditional_format_id": "ruleA", - "properties": map[string]interface{}{"rule": map[string]interface{}{"type": "cell_value"}}, + "properties": map[string]interface{}{ + "rule": map[string]interface{}{"operator": "greater_than", "value": float64(100), "type": "cellValue"}, + "ranges": []interface{}{"A1:A100"}, + }, }, }, // filter — special, no id flag { - name: "+filter-create without --data sends properties.range only", + name: "+filter-create without --properties sends properties.range only", sc: FilterCreate, args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:F1000"}, toolName: "manage_filter_object", @@ -105,14 +126,14 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { }, }, { - name: "+filter-create with --data merges conditions", + name: "+filter-create with --properties merges rules", sc: FilterCreate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:F1000", "--data", `{"conditions":[{"col":"B"}]}`}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:F1000", "--properties", `{"rules":[{"col":"B"}]}`}, toolName: "manage_filter_object", wantInput: map[string]interface{}{ "properties": map[string]interface{}{ - "range": "A1:F1000", - "conditions": []interface{}{map[string]interface{}{"col": "B"}}, + "range": "A1:F1000", + "rules": []interface{}{map[string]interface{}{"col": "B"}}, }, }, }, @@ -131,7 +152,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { { name: "+filter-view-create", sc: FilterViewCreate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"view_name":"v1","range":"A1:Z100"}`}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--properties", `{"view_name":"v1","range":"A1:Z100"}`}, toolName: "manage_filter_view_object", wantInput: map[string]interface{}{ "excel_id": testToken, @@ -143,7 +164,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { { name: "+filter-view-update with --view-id", sc: FilterViewUpdate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--view-id", "vABC", "--data", `{"view_name":"renamed"}`}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--view-id", "vABC", "--properties", `{"view_name":"renamed"}`}, toolName: "manage_filter_view_object", wantInput: map[string]interface{}{ "view_id": "vABC", @@ -154,7 +175,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { { name: "+sparkline-update --group-id → group_id", sc: SparklineUpdate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--group-id", "grpA", "--data", `{"type":"line"}`}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--group-id", "grpA", "--properties", `{"type":"line"}`}, toolName: "manage_sparkline_object", wantInput: map[string]interface{}{ "group_id": "grpA", @@ -162,15 +183,28 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) { "properties": map[string]interface{}{"type": "line"}, }, }, - // float-image + // float-image — fully hoisted to flat flags { - name: "+float-image-create", - sc: FloatImageCreate, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"image_uri":"u","image_name":"x.png"}`}, + name: "+float-image-create with image-token + position/size", + sc: FloatImageCreate, + args: []string{ + "--url", testURL, "--sheet-id", testSheetID, + "--image-name", "logo.png", + "--image-token", "tok_xyz", + "--position-row", "2", "--position-col", "D", + "--size-width", "300", "--size-height", "200", + }, toolName: "manage_float_image_object", wantInput: map[string]interface{}{ - "operation": "create", - "properties": map[string]interface{}{"image_uri": "u", "image_name": "x.png"}, + "excel_id": testToken, + "sheet_id": testSheetID, + "operation": "create", + "properties": map[string]interface{}{ + "image_name": "logo.png", + "image_token": "tok_xyz", + "position": map[string]interface{}{"row": float64(2), "col": "D"}, + "size": map[string]interface{}{"width": float64(300), "height": float64(200)}, + }, }, }, } diff --git a/shortcuts/sheets/lark_sheet_search_replace.go b/shortcuts/sheets/lark_sheet_search_replace.go index a332b207f..a6d94db98 100644 --- a/shortcuts/sheets/lark_sheet_search_replace.go +++ b/shortcuts/sheets/lark_sheet_search_replace.go @@ -124,7 +124,7 @@ var CellsReplace = common.Shortcut{ HasFormat: true, Flags: append(publicSheetFlags(), common.Flag{Name: "find", Required: true, Desc: "text to find (regex when --regex is set)"}, - common.Flag{Name: "replace", Required: true, Desc: "replacement text (empty string deletes the match)"}, + common.Flag{Name: "replacement", Required: true, Desc: "replacement text (empty string deletes the match)"}, common.Flag{Name: "range", Desc: "optional A1 range to scope the replace"}, common.Flag{Name: "match-case", Type: "bool", Desc: "case-sensitive match"}, common.Flag{Name: "match-entire-cell", Type: "bool", Desc: "match the entire cell content only"}, @@ -141,8 +141,8 @@ var CellsReplace = common.Shortcut{ if strings.TrimSpace(runtime.Str("find")) == "" { return common.FlagErrorf("--find is required") } - if !runtime.Changed("replace") { - return common.FlagErrorf("--replace is required (pass an empty string to delete matches)") + if !runtime.Changed("replacement") { + return common.FlagErrorf("--replacement is required (pass an empty string to delete matches)") } return nil }, @@ -176,7 +176,7 @@ func replaceInput(runtime *common.RuntimeContext, token, sheetID, sheetName stri input := map[string]interface{}{ "excel_id": token, "search_term": runtime.Str("find"), - "replace_term": runtime.Str("replace"), + "replace_term": runtime.Str("replacement"), } sheetSelectorForToolInput(input, sheetID, sheetName) if r := strings.TrimSpace(runtime.Str("range")); r != "" { diff --git a/shortcuts/sheets/lark_sheet_search_replace_test.go b/shortcuts/sheets/lark_sheet_search_replace_test.go index f0e43e20c..e3b4cc068 100644 --- a/shortcuts/sheets/lark_sheet_search_replace_test.go +++ b/shortcuts/sheets/lark_sheet_search_replace_test.go @@ -56,7 +56,7 @@ func TestSearchReplaceShortcuts_DryRun(t *testing.T) { { name: "+cells-replace empty replace deletes match", sc: CellsReplace, - args: []string{"--url", testURL, "--sheet-id", testSheetID, "--find", "foo", "--replace", ""}, + args: []string{"--url", testURL, "--sheet-id", testSheetID, "--find", "foo", "--replacement", ""}, toolName: "replace_data", wantInput: map[string]interface{}{ "excel_id": testToken, diff --git a/shortcuts/sheets/lark_sheet_workbook.go b/shortcuts/sheets/lark_sheet_workbook.go index e545cbb30..cdfe6bcd6 100644 --- a/shortcuts/sheets/lark_sheet_workbook.go +++ b/shortcuts/sheets/lark_sheet_workbook.go @@ -540,7 +540,7 @@ var SheetSetTabColor = common.Shortcut{ var WorkbookCreate = common.Shortcut{ Service: "sheets", Command: "+workbook-create", - Description: "Create a new spreadsheet (optionally pre-filled with --headers and --data).", + Description: "Create a new spreadsheet (optionally pre-filled with --headers and --values).", Risk: "write", Scopes: []string{"sheets:spreadsheet:create", "sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, @@ -549,7 +549,7 @@ var WorkbookCreate = common.Shortcut{ {Name: "title", Required: true, Desc: "spreadsheet title"}, {Name: "folder-token", Desc: "destination folder token; omit to land at the drive root"}, {Name: "headers", Input: []string{common.File, common.Stdin}, Desc: "header row JSON array, e.g. [\"列A\",\"列B\"]"}, - {Name: "data", Input: []string{common.File, common.Stdin}, Desc: "initial data JSON 2D array, e.g. [[\"alice\",95]]"}, + {Name: "values", Input: []string{common.File, common.Stdin}, Desc: "initial data JSON 2D array, e.g. [[\"alice\",95]]"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if strings.TrimSpace(runtime.Str("title")) == "" { @@ -564,18 +564,18 @@ var WorkbookCreate = common.Shortcut{ return common.FlagErrorf("--headers must be a JSON array") } } - if runtime.Str("data") != "" { - v, err := parseJSONFlag(runtime, "data") + if runtime.Str("values") != "" { + v, err := parseJSONFlag(runtime, "values") if err != nil { return err } rows, ok := v.([]interface{}) if !ok { - return common.FlagErrorf("--data must be a JSON 2D array") + return common.FlagErrorf("--values must be a JSON 2D array") } for i, r := range rows { if _, ok := r.([]interface{}); !ok { - return common.FlagErrorf("--data[%d] must be an array", i) + return common.FlagErrorf("--values[%d] must be an array", i) } } } @@ -590,7 +590,7 @@ var WorkbookCreate = common.Shortcut{ POST("/open-apis/sheets/v3/spreadsheets"). Desc("create spreadsheet"). Body(body) - if runtime.Str("headers") != "" || runtime.Str("data") != "" { + if runtime.Str("headers") != "" || runtime.Str("values") != "" { fill, _ := buildInitialFillInput(runtime) wireBody, _ := buildToolBody("set_cell_range", fill) dry.POST("/open-apis/sheet_ai/v2/spreadsheets//tools/invoke_write"). @@ -619,7 +619,7 @@ var WorkbookCreate = common.Shortcut{ result := map[string]interface{}{"spreadsheet": ss} - if runtime.Str("headers") != "" || runtime.Str("data") != "" { + if runtime.Str("headers") != "" || runtime.Str("values") != "" { fill, err := buildInitialFillInput(runtime) if err != nil { return err @@ -637,11 +637,11 @@ var WorkbookCreate = common.Shortcut{ return nil }, Tips: []string{ - "--headers and --data are optional follow-up writes. They use the same set_cell_range tool as +cells-set; partial failure leaves the spreadsheet created but empty.", + "--headers and --values are optional follow-up writes. They use the same set_cell_range tool as +cells-set; partial failure leaves the spreadsheet created but empty.", }, } -// buildInitialFillInput zips --headers + --data into a single set_cell_range +// buildInitialFillInput zips --headers + --values into a single set_cell_range // payload writing to the first sheet starting at A1. func buildInitialFillInput(runtime *common.RuntimeContext) (map[string]interface{}, error) { var rows [][]interface{} @@ -654,8 +654,8 @@ func buildInitialFillInput(runtime *common.RuntimeContext) (map[string]interface } rows = append(rows, row) } - if runtime.Str("data") != "" { - v, _ := parseJSONFlag(runtime, "data") + if runtime.Str("values") != "" { + v, _ := parseJSONFlag(runtime, "values") dataArr, _ := v.([]interface{}) for _, r := range dataArr { cells, _ := r.([]interface{}) diff --git a/shortcuts/sheets/lark_sheet_workbook_test.go b/shortcuts/sheets/lark_sheet_workbook_test.go index 532ddc305..eaa49d16c 100644 --- a/shortcuts/sheets/lark_sheet_workbook_test.go +++ b/shortcuts/sheets/lark_sheet_workbook_test.go @@ -303,7 +303,7 @@ func TestWorkbookCreate_DryRun(t *testing.T) { calls := parseDryRunAPI(t, WorkbookCreate, []string{ "--title", "Sales", "--headers", `["Name","Score"]`, - "--data", `[["alice",95],["bob",88]]`, + "--values", `[["alice",95],["bob",88]]`, }) if len(calls) != 2 { t.Fatalf("api calls = %d, want 2 (create + fill)", len(calls)) @@ -329,7 +329,7 @@ func TestWorkbookCreate_DataValidation(t *testing.T) { want string }{ {"headers not array", []string{"--title", "X", "--headers", `"abc"`}, "must be a JSON array"}, - {"data not 2D", []string{"--title", "X", "--data", `["a","b"]`}, "must be an array"}, + {"values not 2D", []string{"--title", "X", "--values", `["a","b"]`}, "must be an array"}, } for _, tt := range cases { tt := tt diff --git a/shortcuts/sheets/lark_sheet_write_cells.go b/shortcuts/sheets/lark_sheet_write_cells.go index cfba894f4..f9ea30ab1 100644 --- a/shortcuts/sheets/lark_sheet_write_cells.go +++ b/shortcuts/sheets/lark_sheet_write_cells.go @@ -40,7 +40,7 @@ var CellsSet = common.Shortcut{ HasFormat: true, Flags: append(publicSheetFlags(), common.Flag{Name: "range", Required: true, Desc: "target A1 range (e.g. A1:C10); cells dimensions must match"}, - common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true, + common.Flag{Name: "cells", Input: []string{common.File, common.Stdin}, Required: true, Desc: "JSON body: { \"cells\": [[{value|formula|cell_styles|...}, ...]], optional copy_to_range / resize_width / resize_height }"}, common.Flag{Name: "allow-overwrite", Type: "bool", Default: "true", Desc: "allow overwriting non-empty cells (default true)"}, common.Flag{Name: "max-cells", Type: "int", Default: "50000", Hidden: true, Desc: "anti-burst cells write cap"}, @@ -55,12 +55,12 @@ var CellsSet = common.Shortcut{ if strings.TrimSpace(runtime.Str("range")) == "" { return common.FlagErrorf("--range is required") } - body, err := requireJSONObject(runtime, "data") + body, err := requireJSONObject(runtime, "cells") if err != nil { return err } if _, ok := body["cells"]; !ok { - return common.FlagErrorf("--data must include a \"cells\" field") + return common.FlagErrorf("--cells must include a \"cells\" field") } return nil }, @@ -93,7 +93,7 @@ var CellsSet = common.Shortcut{ } func cellsSetInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) (map[string]interface{}, error) { - body, err := requireJSONObject(runtime, "data") + body, err := requireJSONObject(runtime, "cells") if err != nil { return nil, err } @@ -102,7 +102,7 @@ func cellsSetInput(runtime *common.RuntimeContext, token, sheetID, sheetName str "range": strings.TrimSpace(runtime.Str("range")), } sheetSelectorForToolInput(input, sheetID, sheetName) - // --data fields override any of these except the core selectors. + // --cells fields override any of these except the core selectors. for k, v := range body { switch k { case "excel_id", "range", "sheet_id", "sheet_name": @@ -117,21 +117,24 @@ func cellsSetInput(runtime *common.RuntimeContext, token, sheetID, sheetName str return input, nil } -// CellsSetStyle wraps set_cell_range applied to a uniform style: parse -// --style once, fan it out to a (rows × cols) cells matrix, and let -// set_cell_range stamp every cell in the range with that style. +// CellsSetStyle stamps a single style block across every cell in --range. +// Style is composed from a dozen flat flags (background-color, font-color, +// font-size, font-style, font-weight, font-line, horizontal-alignment, +// vertical-alignment, word-wrap, number-format) plus --border-styles for +// the only field that still needs a nested object. At least one flag must +// be set. var CellsSetStyle = common.Shortcut{ Service: "sheets", Command: "+cells-set-style", - Description: "Apply a single style block to every cell in a range (values / formulas untouched).", + Description: "Apply style flags to every cell in a range (values / formulas untouched).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, - Flags: append(publicSheetFlags(), - common.Flag{Name: "range", Required: true, Desc: "target A1 range (e.g. A1:B2)"}, - common.Flag{Name: "style", Input: []string{common.File, common.Stdin}, Required: true, - Desc: "style JSON: { font, backColor, horizontal_alignment, vertical_alignment, ... , optional border_styles }"}, + Flags: append( + append(publicSheetFlags(), + common.Flag{Name: "range", Required: true, Desc: "target A1 range (e.g. A1:B2)"}), + styleFlatFlags()..., ), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { @@ -147,7 +150,10 @@ var CellsSetStyle = common.Shortcut{ if _, _, err := rangeDimensions(r); err != nil { return common.FlagErrorf("--range %q: %v", r, err) } - if _, err := requireJSONObject(runtime, "style"); err != nil { + if err := requireAnyStyleFlag(runtime); err != nil { + return err + } + if _, err := borderStylesFromFlag(runtime); err != nil { return err } return nil @@ -181,31 +187,24 @@ var CellsSetStyle = common.Shortcut{ } func cellsSetStyleInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) (map[string]interface{}, error) { - style, err := requireJSONObject(runtime, "style") - if err != nil { - return nil, err - } rangeStr := strings.TrimSpace(runtime.Str("range")) rows, cols, err := rangeDimensions(rangeStr) if err != nil { return nil, common.FlagErrorf("--range %q: %v", rangeStr, err) } - // Split border_styles out of the style block since the tool models it - // as a sibling field of cell_styles. - cellStyle := map[string]interface{}{} - var borderStyles interface{} - for k, v := range style { - if k == "border_styles" { - borderStyles = v - continue - } - cellStyle[k] = v + cellStyle := buildCellStyleFromFlags(runtime) + borderStyles, err := borderStylesFromFlag(runtime) + if err != nil { + return nil, err } cells := make([][]interface{}, rows) for r := range cells { row := make([]interface{}, cols) for c := range row { - cell := map[string]interface{}{"cell_styles": cellStyle} + cell := map[string]interface{}{} + if len(cellStyle) > 0 { + cell["cell_styles"] = cellStyle + } if borderStyles != nil { cell["border_styles"] = borderStyles } diff --git a/shortcuts/sheets/lark_sheet_write_cells_test.go b/shortcuts/sheets/lark_sheet_write_cells_test.go index a7bdc89fa..00092d27a 100644 --- a/shortcuts/sheets/lark_sheet_write_cells_test.go +++ b/shortcuts/sheets/lark_sheet_write_cells_test.go @@ -26,7 +26,7 @@ func TestWriteCellsShortcuts_DryRun(t *testing.T) { args: []string{ "--url", testURL, "--sheet-id", testSheetID, "--range", "A1:B2", - "--data", `{"cells":[[{"value":1},{"value":2}],[{"value":3},{"value":4}]]}`, + "--cells", `{"cells":[[{"value":1},{"value":2}],[{"value":3},{"value":4}]]}`, }, toolName: "set_cell_range", wantInput: map[string]interface{}{ @@ -42,7 +42,7 @@ func TestWriteCellsShortcuts_DryRun(t *testing.T) { args: []string{ "--url", testURL, "--sheet-id", testSheetID, "--range", "A1", - "--data", `{"cells":[[{"value":1}]]}`, + "--cells", `{"cells":[[{"value":1}]]}`, "--allow-overwrite=false", }, toolName: "set_cell_range", @@ -130,34 +130,50 @@ func TestDropdownSet_CellsShape(t *testing.T) { } } -// TestCellsSetStyle_FanOutsBorderStylesOut confirms the border_styles -// field is split out of --style and placed as a sibling of cell_styles -// per the tool contract. -func TestCellsSetStyle_FanOutsBorderStylesOut(t *testing.T) { +// TestCellsSetStyle_FlatFlags verifies that the 11 flat style flags + +// --border-styles compose into cell_styles + border_styles per cell. +func TestCellsSetStyle_FlatFlags(t *testing.T) { t.Parallel() body := parseDryRunBody(t, CellsSetStyle, []string{ "--url", testURL, "--sheet-id", testSheetID, "--range", "A1:B1", - "--style", `{"font":{"bold":true},"border_styles":{"top":{"style":"thick"}}}`, + "--font-weight", "bold", + "--background-color", "#ffff00", + "--horizontal-alignment", "center", + "--border-styles", `{"top":{"style":"thick"}}`, }) input := decodeToolInput(t, body, "set_cell_range") cells, _ := input["cells"].([]interface{}) row, _ := cells[0].([]interface{}) cell, _ := row[0].(map[string]interface{}) + style, _ := cell["cell_styles"].(map[string]interface{}) + if style["font_weight"] != "bold" || style["background_color"] != "#ffff00" || style["horizontal_alignment"] != "center" { + t.Errorf("cell_styles wrong: %#v", style) + } if cell["border_styles"] == nil { t.Fatalf("border_styles missing on cell: %#v", cell) } - style, _ := cell["cell_styles"].(map[string]interface{}) if _, leaked := style["border_styles"]; leaked { t.Errorf("border_styles leaked into cell_styles: %#v", style) } } +func TestCellsSetStyle_RequiresAtLeastOneFlag(t *testing.T) { + t.Parallel() + stdout, stderr, err := runShortcutCapturingErr(t, CellsSetStyle, []string{ + "--url", testURL, "--sheet-id", testSheetID, + "--range", "A1:B2", "--dry-run", + }) + if err == nil || !strings.Contains(stdout+stderr+err.Error(), "at least one style flag") { + t.Errorf("expected style-flag guard; got=%s|%s|%v", stdout, stderr, err) + } +} + func TestCellsSet_RequiresCellsField(t *testing.T) { t.Parallel() stdout, stderr, err := runShortcutCapturingErr(t, CellsSet, []string{ "--url", testURL, "--sheet-id", testSheetID, - "--range", "A1", "--data", `{"foo":"bar"}`, "--dry-run", + "--range", "A1", "--cells", `{"foo":"bar"}`, "--dry-run", }) if err == nil { t.Fatalf("expected validation error; stdout=%s stderr=%s", stdout, stderr) diff --git a/skills/lark-sheets/references/lark-sheets-batch-update.md b/skills/lark-sheets/references/lark-sheets-batch-update.md index 7e37414cf..3c0eab38d 100644 --- a/skills/lark-sheets/references/lark-sheets-batch-update.md +++ b/skills/lark-sheets/references/lark-sheets-batch-update.md @@ -22,8 +22,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `batch_update` | `+batch-update` | high-risk-write | 批量 | @@ -33,15 +31,13 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+batch-update` | Flag | 分类 | Type | 必填 | 说明 | | --- | --- | --- | --- | --- | | `--url` | 公共 | string | XOR | spreadsheet 定位(与子操作的 sheet 定位独立) | | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet 定位(与子操作的 sheet 定位独立) | -| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"operations":[{"tool":"set_cell_range","params":{...}}, ...]}`;按数组顺序串行执行 | +| `--operations` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"operations":[{"tool":"set_cell_range","params":{...}}, ...]}`;按数组顺序串行执行 | | `--yes` | 系统 | bool | 是 | `high-risk-write`,必须二次确认(不带时退出码 10) | | `--dry-run` | 系统 | bool | 否 | 输出每个子操作的请求模板,零网络副作用 | @@ -51,7 +47,18 @@ | --- | --- | --- | --- | --- | | `--url` | 公共 | string | XOR | spreadsheet URL(与 `--spreadsheet-token` 二选一) | | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | JSON 数组 `[{"ranges":["sheet1!A1:B2"],"style":{...}}]`;每个 ranges 元素必须带 sheet 前缀 | +| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["sheet1!A1:B2","sheet1!D1:D10"]`);所有 range 应用同一组 style | +| `--background-color` | 专有 | string | 否 | 背景颜色(十六进制,如 `#ffffff`) | +| `--font-color` | 专有 | string | 否 | 字体颜色(十六进制,如 `#000000`) | +| `--font-size` | 专有 | number | 否 | 字体大小(px,例:10、12、14) | +| `--font-style` | 专有 | string + Enum | 否 | 字体样式 enum:`normal` / `italic` | +| `--font-weight` | 专有 | string + Enum | 否 | 字重 enum:`normal` / `bold` | +| `--font-line` | 专有 | string + Enum | 否 | 字体线条样式 enum:`none` / `underline` / `line-through` | +| `--horizontal-alignment` | 专有 | string + Enum | 否 | 水平对齐 enum:`left` / `center` / `right` | +| `--vertical-alignment` | 专有 | string + Enum | 否 | 垂直对齐 enum:`top` / `middle` / `bottom` | +| `--word-wrap` | 专有 | string + Enum | 否 | 换行策略 enum:`overflow` / `auto-wrap` / `word-clip`(默认 `overflow`) | +| `--number-format` | 专有 | string | 否 | 数字格式(例:文本 `@`、数字 `0.00`、货币 `$#,##0.00`、日期 `mm/dd/yyyy`) | +| `--border-styles` | 专有 | string + File + Stdin(复合 JSON) | 否 | 边框配置 JSON(结构同 +cells-set-style) | | `--dry-run` | 系统 | bool | 否 | | ### `+dropdown-update` @@ -60,9 +67,9 @@ | --- | --- | --- | --- | --- | | `--url` | 公共 | string | XOR | spreadsheet URL(与 `--spreadsheet-token` 二选一) | | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | -| `--ranges` | 专有 | string + File + Stdin | 是 | 目标范围 JSON 数组(如 `["sheet1!A2:A100"]`),每项必须带 sheet 前缀 | -| `--options` | 专有 | string + File + Stdin | 是 | 选项 JSON 数组 | -| `--colors` | 专有 | string + File + Stdin | 否 | 颜色数组(与 `--options` 等长) | +| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 目标范围 JSON 数组(如 `["sheet1!A2:A100"]`),每项必须带 sheet 前缀 | +| `--options` | 专有 | string + File + Stdin(复合 JSON) | 是 | 选项 JSON 数组 | +| `--colors` | 专有 | string + File + Stdin(简单 JSON) | 否 | 颜色数组(与 `--options` 等长) | | `--multiple` | 专有 | bool | 否 | 启用多选 | | `--highlight` | 专有 | bool | 否 | 选项配色 | | `--dry-run` | 系统 | bool | 否 | | @@ -73,7 +80,7 @@ | --- | --- | --- | --- | --- | | `--url` | 公共 | string | XOR | spreadsheet URL(与 `--spreadsheet-token` 二选一) | | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | -| `--ranges` | 专有 | string + File + Stdin | 是 | 目标范围 JSON 数组(最多 100 个,每项带 sheet 前缀) | +| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 目标范围 JSON 数组(最多 100 个,每项带 sheet 前缀) | | `--yes` | 系统 | bool | 是 | `high-risk-write`,必须二次确认(不带时退出码 10) | | `--dry-run` | 系统 | bool | 否 | | @@ -81,7 +88,7 @@ > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+batch-update` `--data` +### `+batch-update` `--operations` _要批量执行的操作列表,按顺序依次执行_ @@ -89,21 +96,15 @@ _要批量执行的操作列表,按顺序依次执行_ - `input` (object) — 对应工具的入参,结构与单独调用该工具时完全一致 - `tool_name` (string) — 要执行的工具名称,如 "set_cell_range"、"clear_cell_range"、"modify_sheet_structure" 等 -### `+cells-batch-set-style` `--data` +### `+cells-batch-set-style` `--border-styles` -_单元格样式属性,包括字体、颜色、对齐方式和数字格式_ +_单元格边框配置,含 top/bottom/left/right 四个方向,每个方向的结构相同(见 top)_ **顶层字段**: -- `background_color` (string?) — 背景颜色(十六进制,例如 "#ffffff") -- `font_color` (string?) — 字体颜色(十六进制,例如 "#000000") -- `font_line` (enum?) — 字体线条样式 [none / underline / line-through] -- `font_size` (number?) — 字体大小(单位:px/像素,例如 10、12、14) -- `font_style` (enum?) — 字体样式 [normal / italic] -- `font_weight` (enum?) — 字重 [normal / bold] -- `horizontal_alignment` (enum?) — 水平对齐方式 [left / center / right] -- `number_format` (string?) — 数字格式(例如:文本用 "@"、数字用 "0.00"、货币用 "$#,##0.00"、日期用 "mm/dd/yyyy") -- `vertical_alignment` (enum?) — 垂直对齐方式 [top / middle / bottom] -- `word_wrap` (enum?) — 是否自动换行,默认溢出,可选自动换行或裁剪 [overflow / auto-wrap / word-clip] +- `bottom` (object?) { color?: string, style?: enum, weight?: enum } +- `left` (object?) { color?: string, style?: enum, weight?: enum } +- `right` (object?) { color?: string, style?: enum, weight?: enum } +- `top` (object?) { color?: string, style?: enum, weight?: enum } ### `+dropdown-update` `--options` @@ -120,8 +121,6 @@ _数据验证配置_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:`--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(前两者 XOR;`+batch-update` 本身不强制 sheet-id,子操作各自携带)。 ### `+batch-update` diff --git a/skills/lark-sheets/references/lark-sheets-chart.md b/skills/lark-sheets/references/lark-sheets-chart.md index fc68c49b0..ca954b77f 100644 --- a/skills/lark-sheets/references/lark-sheets-chart.md +++ b/skills/lark-sheets/references/lark-sheets-chart.md @@ -96,8 +96,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_chart_objects` | `+chart-list` | read | 对象 | @@ -107,8 +105,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+chart-list` | Flag | 分类 | Type | 必填 | 说明 | @@ -128,7 +124,7 @@ | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | 图表完整配置 JSON(`position` / `data` / `properties` 等),结构嵌套深,统一走 JSON 注入 | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 图表完整配置 JSON(`position` / `data` / `properties` 等),结构嵌套深,统一走 JSON 注入 | | `--dry-run` | 系统 | bool | 否 | 零副作用,输出请求模板 | ### `+chart-update` @@ -140,7 +136,7 @@ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--chart-id` | 专有 | string | 是 | 目标图表 reference_id | -| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的图表配置 JSON(先 `+chart-list` 回读再 patch) | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 完整或足够完整的图表配置 JSON(先 `+chart-list` 回读再 patch) | | `--dry-run` | 系统 | bool | 否 | | ### `+chart-delete` @@ -159,7 +155,7 @@ > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+chart-create` `--data` / `+chart-update` `--data` +### `+chart-create` `--properties` / `+chart-update` `--properties` _创建/更新的图表属性_ @@ -171,8 +167,6 @@ _创建/更新的图表属性_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR 规则同 `+csv-get`)。 ### `+chart-list` diff --git a/skills/lark-sheets/references/lark-sheets-conditional-format.md b/skills/lark-sheets/references/lark-sheets-conditional-format.md index 4739e65d4..7fe8753be 100644 --- a/skills/lark-sheets/references/lark-sheets-conditional-format.md +++ b/skills/lark-sheets/references/lark-sheets-conditional-format.md @@ -72,8 +72,6 @@ manage_conditional_format_object create ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_conditional_format_objects` | `+cond-format-list` | read | 对象 | @@ -83,8 +81,6 @@ manage_conditional_format_object create ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+cond-format-list` | Flag | 分类 | Type | 必填 | 说明 | @@ -104,7 +100,9 @@ manage_conditional_format_object create | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"range":"Sheet1!A2:F1000","rule":{"type":"cell_value","operator":"greater_than","value":100,"style":{...}}}`,type 可选 `cell_value` / `duplicate` / `data_bar` / `color_scale` / `rank` / `formula` | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +cond-format-create / --data: 规则配置 JSON,含 `style`(命中样式,必填)和 `attrs?`(规则参数列表,因 rule_type 不同结构而异)/ `has_ref?`。`rule_type` 和 `ranges` 已拎为独立 flag | +| `--rule-type` | 专有 | string + Enum | 是 | 条件格式规则类型 enum:`cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell`(共 13 项);优先级高于 `--data` | +| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--data` | | `--dry-run` | 系统 | bool | 否 | | ### `+cond-format-update` @@ -116,7 +114,9 @@ manage_conditional_format_object create | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--rule-id` | 专有 | string | 是 | 目标规则 id | -| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的规则配置(先 `+cond-format-list --rule-id ` 回读再 patch) | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +cond-format-update / --data: 同 +cond-format-create;update 是整组覆盖式 | +| `--rule-type` | 专有 | string + Enum | 是 | 条件格式规则类型 enum:`cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell`(共 13 项);优先级高于 `--data` | +| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--data` | | `--dry-run` | 系统 | bool | 否 | | ### `+cond-format-delete` @@ -135,7 +135,7 @@ manage_conditional_format_object create > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+cond-format-create` `--data` / `+cond-format-update` `--data` +### `+cond-format-create` `--properties` / `+cond-format-update` `--properties` _创建/更新的条件格式属性_ @@ -148,8 +148,6 @@ _创建/更新的条件格式属性_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。 ### `+cond-format-list` diff --git a/skills/lark-sheets/references/lark-sheets-filter-view.md b/skills/lark-sheets/references/lark-sheets-filter-view.md index 32701c91f..6f1dce115 100644 --- a/skills/lark-sheets/references/lark-sheets-filter-view.md +++ b/skills/lark-sheets/references/lark-sheets-filter-view.md @@ -25,8 +25,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_filter_view_objects` | `+filter-view-list` | read | 对象 | @@ -36,8 +34,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+filter-view-list` | Flag | 分类 | Type | 必填 | 说明 | @@ -57,7 +53,9 @@ | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | 视图配置 JSON:`{"view_name":"...","range":"A1:Z100","rules":[...]}`;省略 view_id 表示 create;range 必填且必须覆盖表头行 | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +filter-view-create / --data: 视图规则 JSON,含 `rules?`(列级筛选规则数组)和 `filtered_columns?`。`range` 和 `view_name` 已拎为独立 flag | +| `--range` | 专有 | string | 是 | 筛选作用的单元格范围(A1 表示法,如 `A1:F1000`);优先级高于 `--data` 中同名字段(create 必填,必须覆盖表头行) | +| `--view-name` | 专有 | string | 否 | 视图名称;create 不传时系统自动分配,update 不传时保留原名。优先级高于 `--data` 中同名字段 | | `--dry-run` | 系统 | bool | 否 | | ### `+filter-view-update` @@ -69,7 +67,9 @@ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--view-id` | 专有 | string | 是 | 目标视图 reference_id | -| `--data` | 专有 | string + File + Stdin | 是 | 部分更新 JSON:含 view_name / range / rules 之一即可;先 +filter-view-list 回读再 patch | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +filter-view-update / --data: 视图规则 JSON,含 `rules?` 和 `filtered_columns?`。`range` 和 `view_name` 已拎为独立 flag;至少传 `--data.rules` / `--range` / `--view-name` 之一 | +| `--range` | 专有 | string | 否 | 筛选作用的单元格范围(A1 表示法,如 `A1:F1000`);优先级高于 `--data` 中同名字段(update 时省略表示保留当前 range) | +| `--view-name` | 专有 | string | 否 | 视图名称;create 不传时系统自动分配,update 不传时保留原名。优先级高于 `--data` 中同名字段 | | `--dry-run` | 系统 | bool | 否 | | ### `+filter-view-delete` @@ -88,7 +88,7 @@ > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+filter-view-create` `--data` / `+filter-view-update` `--data` +### `+filter-view-create` `--properties` / `+filter-view-update` `--properties` _create / update 的视图属性_ @@ -100,8 +100,6 @@ _create / update 的视图属性_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - > ⚠️ 本 skill 是 **CLI 独有**(meta `surface: cli-only`);`generate_mcp` 跳过,不会进 sheet-ai-skills SKILL 集。AI/MCP 侧暂不暴露筛选视图能力。 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`view_id` 是 10 位随机字符串,每个 sheet 可有多个视图。 diff --git a/skills/lark-sheets/references/lark-sheets-filter.md b/skills/lark-sheets/references/lark-sheets-filter.md index 6232cf4ba..35e9198c1 100644 --- a/skills/lark-sheets/references/lark-sheets-filter.md +++ b/skills/lark-sheets/references/lark-sheets-filter.md @@ -29,8 +29,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_filter_objects` | `+filter-list` | read | 对象 | @@ -40,8 +38,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+filter-list` | Flag | 分类 | Type | 必填 | 说明 | @@ -60,8 +56,8 @@ | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--range` | 专有 | string | 是 | 筛选范围,含表头行(如 `A1:F1000`) | -| `--data` | 专有 | string + File + Stdin | 否 | JSON:`{"conditions":[{"col":"B","filter_type":"multiValue","expected":["北京","上海"]}]}`;省略则只建空筛选 | +| `--range` | 专有 | string | 是 | 筛选范围(A1 表示法,含表头行,如 `A1:F1000`);不要重复写入 --data 中的 range 字段 | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 否 | +filter-create / --data: 筛选规则 JSON,含 `rules`(列级筛选规则数组,必填)和 `filtered_columns?`(激活列索引提示)。`range` 是独立 flag(不要再放此 JSON 里) | | `--dry-run` | 系统 | bool | 否 | | ### `+filter-update` @@ -72,7 +68,8 @@ | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | JSON:可改 `range` 或追加 / 替换 `conditions[]`;先 `+filter-list` 回读再 patch | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +filter-update / --data: 筛选规则 JSON,含 `rules` 和 `filtered_columns?`;update 是整组覆盖式(传空 `rules: []` 清空)。`range` 已拎为独立 flag | +| `--range` | 专有 | string | 是 | 筛选作用的单元格范围(A1 表示法,如 `A1:F1000`);优先级高于 `--data` 中同名字段 | | `--dry-run` | 系统 | bool | 否 | | ### `+filter-delete` @@ -90,7 +87,7 @@ > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+filter-create` `--data` / `+filter-update` `--data` +### `+filter-create` `--properties` / `+filter-update` `--properties` _创建/更新的筛选器属性_ @@ -101,8 +98,6 @@ _创建/更新的筛选器属性_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`filter_id` 等同于 `sheet_id`(每个工作表至多一个筛选器)。 ### `+filter-list` diff --git a/skills/lark-sheets/references/lark-sheets-float-image.md b/skills/lark-sheets/references/lark-sheets-float-image.md index a4f6e219f..9a77c13b3 100644 --- a/skills/lark-sheets/references/lark-sheets-float-image.md +++ b/skills/lark-sheets/references/lark-sheets-float-image.md @@ -36,8 +36,6 @@ reference_id 的映射规则: ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_float_image_objects` | `+float-image-list` | read | 对象 | @@ -47,8 +45,6 @@ reference_id 的映射规则: ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+float-image-list` | Flag | 分类 | Type | 必填 | 说明 | @@ -68,7 +64,16 @@ reference_id 的映射规则: | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"image_uri":"...","image_name":"foo.png","position":{"row":2,"col":"D"},"size":{"width":300,"height":200},"offset":{"x":0,"y":0}}` | +| `--image-name` | 专有 | string | 是 | 图片名称,含拓展名(如 `logo.png`) | +| `--image-token` | 专有 | string | XOR | 图片 file_token(与 `--image-uri` 二选一)。常见来源:`+float-image-list` 返回的 `image_token` | +| `--image-uri` | 专有 | string | XOR | 图片 reference_id(与 `--image-token` 二选一);形如 `<\|image\|>:abcdef` 这种带前缀的字符串,从上游 SKILL.md 的素材引用约定取 | +| `--position-row` | 专有 | int | 是 | 图片左上角所在行(0-based) | +| `--position-col` | 专有 | string | 是 | 图片左上角所在列(列字母,如 `A` / `B`) | +| `--size-width` | 专有 | int | 是 | 图片宽度(像素) | +| `--size-height` | 专有 | int | 是 | 图片高度(像素) | +| `--offset-row` | 专有 | int | 否 | 在 position 基础上的行内偏移(像素) | +| `--offset-col` | 专有 | int | 否 | 在 position 基础上的列内偏移(像素) | +| `--z-index` | 专有 | int | 否 | 图片 Z 轴层级,控制重叠顺序 | | `--dry-run` | 系统 | bool | 否 | | ### `+float-image-update` @@ -80,7 +85,16 @@ reference_id 的映射规则: | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--float-image-id` | 专有 | string | 是 | 目标图片 id | -| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的配置 JSON(先 `+float-image-list --float-image-id ` 回读再 patch) | +| `--image-name` | 专有 | string | 是 | 图片名称,含拓展名(如 `logo.png`) | +| `--image-token` | 专有 | string | XOR | 图片 file_token(与 `--image-uri` 二选一)。常见来源:`+float-image-list` 返回的 `image_token` | +| `--image-uri` | 专有 | string | XOR | 图片 reference_id(与 `--image-token` 二选一);形如 `<\|image\|>:abcdef` 这种带前缀的字符串,从上游 SKILL.md 的素材引用约定取 | +| `--position-row` | 专有 | int | 是 | 图片左上角所在行(0-based) | +| `--position-col` | 专有 | string | 是 | 图片左上角所在列(列字母,如 `A` / `B`) | +| `--size-width` | 专有 | int | 是 | 图片宽度(像素) | +| `--size-height` | 专有 | int | 是 | 图片高度(像素) | +| `--offset-row` | 专有 | int | 否 | 在 position 基础上的行内偏移(像素) | +| `--offset-col` | 专有 | int | 否 | 在 position 基础上的列内偏移(像素) | +| `--z-index` | 专有 | int | 否 | 图片 Z 轴层级,控制重叠顺序 | | `--dry-run` | 系统 | bool | 否 | | ### `+float-image-delete` @@ -95,27 +109,8 @@ reference_id 的映射规则: | `--yes` | 系统 | bool | 是 | `high-risk-write`,删除不可逆 | | `--dry-run` | 系统 | bool | 否 | | -## Schemas - -> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 - -### `+float-image-create` `--data` / `+float-image-update` `--data` - -_创建/更新的浮动图片属性_ - -**顶层字段**: -- `image_name` (string) — 图片名称,含拓展名,create 时必填 -- `image_token` (string?) — 图片 fileToken(与 image_uri 二选一) -- `image_uri` (string?) — 图片的 reference_id(与 image_token 二选一) -- `offset` (object?) — 可选 { col_offset?: number, row_offset?: number } -- `position` (object) — 必填 { col: string, row: number } -- `size` (object) — 必填 { height: number, width: number } -- `z_index` (number?) — 可选 - ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。浮动图片是 sheet 级对象——和单元格内嵌图片不同(后者走 `+cells-set`)。 ### `+float-image-list` diff --git a/skills/lark-sheets/references/lark-sheets-pivot-table.md b/skills/lark-sheets/references/lark-sheets-pivot-table.md index 6c5f11866..3b317e471 100644 --- a/skills/lark-sheets/references/lark-sheets-pivot-table.md +++ b/skills/lark-sheets/references/lark-sheets-pivot-table.md @@ -38,8 +38,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_pivot_table_objects` | `+pivot-list` | read | 对象 | @@ -49,8 +47,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+pivot-list` | Flag | 分类 | Type | 必填 | 说明 | @@ -70,9 +66,11 @@ | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"data_range":"Sheet1!A1:F1000","rows":[...],"columns":[...],"values":[...],"filters":[...],"show_row_grand_total":true,"show_col_grand_total":true}` | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"data_range":"Sheet1!A1:F1000","rows":[...],"columns":[...],"values":[...],"filters":[...],"show_row_grand_total":true,"show_col_grand_total":true}` | | `--target-sheet-id` | 专有 | string | 否 | 透视表落点子表 id;省略时自动新建子表(推荐) | | `--target-position` | 专有 | string | 否 | 落点起始 cell(如 `A1`),默认 `A1` | +| `--source` | 专有 | string | 是 | 透视表源数据区域(A1 表示法,格式 `SheetName!StartCell:EndCell`,如 `Sheet1!A1:D100`) | +| `--range` | 专有 | string | 否 | 透视表放置位置(左上角 A1 单值,如 `F1`);省略时放在新建子表的左上角 | | `--dry-run` | 系统 | bool | 否 | | ### `+pivot-update` @@ -84,7 +82,7 @@ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--pivot-table-id` | 专有 | string | 是 | 目标透视表 id | -| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的配置(先 `+pivot-list --pivot-table-id ` 回读再 patch) | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 完整或足够完整的配置(先 `+pivot-list --pivot-table-id ` 回读再 patch) | | `--dry-run` | 系统 | bool | 否 | | ### `+pivot-delete` @@ -103,7 +101,7 @@ > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+pivot-create` `--data` / `+pivot-update` `--data` +### `+pivot-create` `--properties` / `+pivot-update` `--properties` _创建/更新的透视表属性_ @@ -124,8 +122,6 @@ _创建/更新的透视表属性_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`+pivot-create` 默认自动新建子表存放透视表产物(推荐)。 ### `+pivot-list` diff --git a/skills/lark-sheets/references/lark-sheets-range-operations.md b/skills/lark-sheets/references/lark-sheets-range-operations.md index 0ffc81bd2..2687470c3 100644 --- a/skills/lark-sheets/references/lark-sheets-range-operations.md +++ b/skills/lark-sheets/references/lark-sheets-range-operations.md @@ -106,8 +106,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `clear_cell_range` | `+cells-clear` | high-risk-write | 单元格 | @@ -121,8 +119,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+cells-clear` | Flag | 分类 | Type | 必填 | 说明 | @@ -225,7 +221,7 @@ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--range` | 专有 | string | 是 | 排序范围(含或不含表头由 `--has-header` 决定) | -| `--sort-keys` | 专有 | string + File + Stdin | 是 | JSON:`[{"col":"B","order":"asc"},{"col":"D","order":"desc"}]` | +| `--sort-keys` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`[{"col":"B","order":"asc"},{"col":"D","order":"desc"}]` | | `--has-header` | 专有 | bool | 否 | 第一行是表头不参与排序,默认 false | | `--dry-run` | 系统 | bool | 否 | | @@ -243,8 +239,6 @@ _排序条件列表(仅 sort 操作)_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - > ⚠️ 本 skill 派生的 7 条 shortcut 跨 3 个分组:`+dim-resize` → 工作表,`+cells-*` → 单元格,`+range-*` → 区域。skill 视角统一在这里讲解。 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。 diff --git a/skills/lark-sheets/references/lark-sheets-read-data.md b/skills/lark-sheets/references/lark-sheets-read-data.md index 0791149a7..65a3ff0e3 100644 --- a/skills/lark-sheets/references/lark-sheets-read-data.md +++ b/skills/lark-sheets/references/lark-sheets-read-data.md @@ -76,8 +76,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `export_sheet_to_sandbox` | _Sheet Tool 独有,CLI 不实现_ | — | — | @@ -87,8 +85,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+cells-get` | Flag | 分类 | Type | 必填 | 说明 | @@ -131,8 +127,6 @@ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - ### `+csv-get` 公共四件套:`--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(前两者 XOR,后两者 XOR)。 diff --git a/skills/lark-sheets/references/lark-sheets-search-replace.md b/skills/lark-sheets/references/lark-sheets-search-replace.md index 84bf056ed..30a8eab10 100644 --- a/skills/lark-sheets/references/lark-sheets-search-replace.md +++ b/skills/lark-sheets/references/lark-sheets-search-replace.md @@ -24,8 +24,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `search_data` | `+cells-search` | read | 单元格 | @@ -33,8 +31,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+cells-search` | Flag | 分类 | Type | 必填 | 说明 | @@ -71,8 +67,6 @@ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR 规则)。 ### `+cells-search` diff --git a/skills/lark-sheets/references/lark-sheets-sheet-structure.md b/skills/lark-sheets/references/lark-sheets-sheet-structure.md index 85a25ffdd..c1e83e0b0 100644 --- a/skills/lark-sheets/references/lark-sheets-sheet-structure.md +++ b/skills/lark-sheets/references/lark-sheets-sheet-structure.md @@ -37,8 +37,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_sheet_structure` | `+sheet-info` | read | 工作表 | @@ -53,8 +51,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+sheet-info` | Flag | 分类 | Type | 必填 | 说明 | @@ -176,8 +172,6 @@ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。 ### `+sheet-info` diff --git a/skills/lark-sheets/references/lark-sheets-sparkline.md b/skills/lark-sheets/references/lark-sheets-sparkline.md index c856a5f8e..da3f7e5c9 100644 --- a/skills/lark-sheets/references/lark-sheets-sparkline.md +++ b/skills/lark-sheets/references/lark-sheets-sparkline.md @@ -22,8 +22,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_sparkline_objects` | `+sparkline-list` | read | 对象 | @@ -33,8 +31,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+sparkline-list` | Flag | 分类 | Type | 必填 | 说明 | @@ -54,7 +50,7 @@ | `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) | | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | -| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"type":"line\\ | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"type":"line\|column\|winLoss","data_range":"A2:F10","target_range":"G2:G10","style":{...},"special_points":{...}}`;type 三种 enum;data_range 与 target_range 行/列数需对齐 | | `--dry-run` | 系统 | bool | 否 | | ### `+sparkline-update` @@ -66,7 +62,7 @@ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--group-id` | 专有 | string | 是 | 目标组 id | -| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的配置(先 `+sparkline-list --group-id ` 回读再 patch) | +| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 完整或足够完整的配置(先 `+sparkline-list --group-id ` 回读再 patch);可改 type / data_range / target_range / style / special_points 等字段 | | `--dry-run` | 系统 | bool | 否 | | ### `+sparkline-delete` @@ -85,7 +81,7 @@ > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+sparkline-create` `--data` / `+sparkline-update` `--data` +### `+sparkline-create` `--properties` / `+sparkline-update` `--properties` _创建/更新/部分删除的迷你图属性_ @@ -95,8 +91,6 @@ _创建/更新/部分删除的迷你图属性_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。迷你图按 `group_id` 管理——一组同形态的迷你图共享类型 / 样式 / 数据源映射。注意:不等同于已禁用的 `SPARKLINE()` 公式函数。 ### `+sparkline-list` diff --git a/skills/lark-sheets/references/lark-sheets-workbook.md b/skills/lark-sheets/references/lark-sheets-workbook.md index 78148673b..a8b518949 100644 --- a/skills/lark-sheets/references/lark-sheets-workbook.md +++ b/skills/lark-sheets/references/lark-sheets-workbook.md @@ -30,8 +30,6 @@ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `get_workbook_structure` | `+workbook-info` | read | 工作簿 | @@ -48,8 +46,6 @@ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+workbook-info` | Flag | 分类 | Type | 必填 | 说明 | @@ -154,8 +150,8 @@ | --- | --- | --- | --- | --- | | `--title` | 专有 | string | 是 | 新 spreadsheet 标题 | | `--folder-token` | 专有 | string | 否 | 目标文件夹 token;省略放根目录 | -| `--headers` | 专有 | string + File + Stdin | 否 | 表头行 JSON 数组:`["列A","列B"]` | -| `--data` | 专有 | string + File + Stdin | 否 | 初始数据 JSON 二维数组:`[["alice",95]]` | +| `--headers` | 专有 | string + File + Stdin(简单 JSON) | 否 | 表头行 JSON 数组:`["列A","列B"]` | +| `--values` | 专有 | string + File + Stdin(简单 JSON) | 否 | 初始数据 JSON 二维数组:`[["alice",95]]` | | `--dry-run` | 系统 | bool | 否 | | ### `+workbook-export` @@ -171,8 +167,6 @@ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`+workbook-info` 只用前两者;`+sheet-*` 系列对单个工作表操作,需 `--sheet-id` 或 `--sheet-name`。 ### `+workbook-info` diff --git a/skills/lark-sheets/references/lark-sheets-write-cells.md b/skills/lark-sheets/references/lark-sheets-write-cells.md index 5bf1e06d1..8120bf88b 100644 --- a/skills/lark-sheets/references/lark-sheets-write-cells.md +++ b/skills/lark-sheets/references/lark-sheets-write-cells.md @@ -167,8 +167,6 @@ set_cell_range — range="A11:H11", cells=[[ ## Shortcuts -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。 - | MCP tool | CLI shortcut | Risk | 分组 | | --- | --- | --- | --- | | `set_cell_range` | `+cells-set` | write | 单元格 | @@ -180,8 +178,6 @@ set_cell_range — range="A11:H11", cells=[[ ## Flags -> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。 - ### `+cells-set` | Flag | 分类 | Type | 必填 | 说明 | @@ -191,7 +187,7 @@ set_cell_range — range="A11:H11", cells=[[ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--range` | 专有 | string | 是 | 写入区域 A1 格式 | -| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"values": [[...], ...]}`;可含 `formula` / `cell_styles` / `comments` / `embed_image` 富信息 | +| `--cells` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"values": [[...], ...]}`;可含 `formula` / `cell_styles` / `comments` / `embed_image` 富信息 | | `--allow-overwrite` | 专有 | bool | 否 | 允许覆盖非空 cell;默认 false 时遇非空 cell 报错 | | `--max-cells` | 专有 | int + Hidden | 否 | 防爆,默认 50000 | | `--dry-run` | 系统 | bool | 否 | | @@ -205,7 +201,17 @@ set_cell_range — range="A11:H11", cells=[[ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--range` | 专有 | string | 是 | 目标范围 A1 格式(如 `A1:B2`) | -| `--style` | 专有 | string + File + Stdin | 是 | 样式 JSON:`{"font":{"bold":true},"backColor":"#fff","border_styles":{...}}`;只改样式,不动 value/formula(底层走 set_cell_range 的 cell_styles + border_styles 字段) | +| `--background-color` | 专有 | string | 否 | 背景颜色(十六进制,如 `#ffffff`) | +| `--font-color` | 专有 | string | 否 | 字体颜色(十六进制,如 `#000000`) | +| `--font-size` | 专有 | number | 否 | 字体大小(px,例:10、12、14) | +| `--font-style` | 专有 | string + Enum | 否 | 字体样式 enum:`normal` / `italic` | +| `--font-weight` | 专有 | string + Enum | 否 | 字重 enum:`normal` / `bold` | +| `--font-line` | 专有 | string + Enum | 否 | 字体线条样式 enum:`none` / `underline` / `line-through` | +| `--horizontal-alignment` | 专有 | string + Enum | 否 | 水平对齐 enum:`left` / `center` / `right` | +| `--vertical-alignment` | 专有 | string + Enum | 否 | 垂直对齐 enum:`top` / `middle` / `bottom` | +| `--word-wrap` | 专有 | string + Enum | 否 | 换行策略 enum:`overflow` / `auto-wrap` / `word-clip`(默认 `overflow`) | +| `--number-format` | 专有 | string | 否 | 数字格式(例:文本 `@`、数字 `0.00`、货币 `$#,##0.00`、日期 `mm/dd/yyyy`) | +| `--border-styles` | 专有 | string + File + Stdin(复合 JSON) | 否 | 边框配置 JSON:`{ top: {style,color,weight}, bottom: ..., left: ..., right: ... }`;4 方向结构相同 | | `--dry-run` | 系统 | bool | 否 | | ### `+cells-set-image` @@ -230,8 +236,8 @@ set_cell_range — range="A11:H11", cells=[[ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--range` | 专有 | string | 是 | 目标范围 A1 格式(如 `A2:A100`) | -| `--options` | 专有 | string + File + Stdin | 是 | 选项 JSON 数组 `["opt1","opt2"]`;最多 500 项,每项 ≤100 字符,不含逗号 | -| `--colors` | 专有 | string + File + Stdin | 否 | RGB hex 颜色数组(如 `["#1FB6C1","#F006C2"]`),长度必须与 `--options` 一致 | +| `--options` | 专有 | string + File + Stdin(复合 JSON) | 是 | 选项 JSON 数组 `["opt1","opt2"]`;最多 500 项,每项 ≤100 字符,不含逗号 | +| `--colors` | 专有 | string + File + Stdin(简单 JSON) | 否 | RGB hex 颜色数组(如 `["#1FB6C1","#F006C2"]`),长度必须与 `--options` 一致 | | `--multiple` | 专有 | bool | 否 | 启用多选;默认 `false` | | `--highlight` | 专有 | bool | 否 | 选项配色显示;默认 `false` | | `--dry-run` | 系统 | bool | 否 | | @@ -245,7 +251,7 @@ set_cell_range — range="A11:H11", cells=[[ | `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) | | `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) | | `--range` | 专有 | string | 是 | 目标区域起点 A1(如 `Sheet1!A1`);自动按 CSV 行列数推断终点 | -| `--csv` | 专有 | string + File + Stdin | 是 | RFC 4180 CSV 文本;只写纯值,不带公式/样式/批注 | +| `--csv` | 专有 | string + File + Stdin(非 JSON 文本) | 是 | RFC 4180 CSV 文本;只写纯值,不带公式/样式/批注 | | `--allow-overwrite` | 专有 | bool | 否 | 允许覆盖;默认 false 时若目标非空报错 | | `--dry-run` | 系统 | bool | 否 | | @@ -253,7 +259,7 @@ set_cell_range — range="A11:H11", cells=[[ > 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets --print-schema --flag `(runtime introspection,待落地)。 -### `+cells-set` `--data` +### `+cells-set` `--cells` **顶层字段**: @@ -266,21 +272,15 @@ set_cell_range — range="A11:H11", cells=[[ - `rich_text` (array?) — 富文本内容 each: { attachment_name?: string, attachment_token?: string, attachment_uri?: string, file_size?: number, image_height?: number, …共 17 项 } - `value` (oneOf?) — 静态单元格值(文本、数字、布尔) -### `+cells-set-style` `--style` +### `+cells-set-style` `--border-styles` -_单元格样式属性,包括字体、颜色、对齐方式和数字格式_ +_单元格边框配置,含 top/bottom/left/right 四个方向,每个方向的结构相同(见 top)_ **顶层字段**: -- `background_color` (string?) — 背景颜色(十六进制,例如 "#ffffff") -- `font_color` (string?) — 字体颜色(十六进制,例如 "#000000") -- `font_line` (enum?) — 字体线条样式 [none / underline / line-through] -- `font_size` (number?) — 字体大小(单位:px/像素,例如 10、12、14) -- `font_style` (enum?) — 字体样式 [normal / italic] -- `font_weight` (enum?) — 字重 [normal / bold] -- `horizontal_alignment` (enum?) — 水平对齐方式 [left / center / right] -- `number_format` (string?) — 数字格式(例如:文本用 "@"、数字用 "0.00"、货币用 "$#,##0.00"、日期用 "mm/dd/yyyy") -- `vertical_alignment` (enum?) — 垂直对齐方式 [top / middle / bottom] -- `word_wrap` (enum?) — 是否自动换行,默认溢出,可选自动换行或裁剪 [overflow / auto-wrap / word-clip] +- `bottom` (object?) { color?: string, style?: enum, weight?: enum } +- `left` (object?) { color?: string, style?: enum, weight?: enum } +- `right` (object?) { color?: string, style?: enum, weight?: enum } +- `top` (object?) { color?: string, style?: enum, weight?: enum } ### `+dropdown-set` `--options` @@ -297,8 +297,6 @@ _数据验证配置_ ## Examples -> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。 - 公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。 ### `+cells-set`