Documentation
¶
Overview ¶
Package sheets contains lark-sheets shortcuts aligned with the sheet-skill-spec canonical layout. Each shortcut wraps a single sheet-ai-skills tool behind the One-OpenAPI endpoint (sheet_ai/v2/.../tools/invoke_{read,write}).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var BatchUpdate = common.Shortcut{ Service: "sheets", Command: "+batch-update", Description: "Execute a batch of write shortcuts as a single atomic request (rolls back on failure by default).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+batch-update"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } if _, err := batchUpdateInput(runtime, token); err != nil { return err } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) input, _ := batchUpdateInput(runtime, token) return invokeToolDryRun(token, ToolKindWrite, "batch_update", input) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } input, err := batchUpdateInput(runtime, token) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", input) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Default is strict transaction — any sub-tool failure rolls the whole batch back. Pass --continue-on-error to keep partial successes.", "Each sub-op is {shortcut, input}. Do NOT pass input.operation (implied by shortcut name) or input.excel_id / input.url (set at the +batch-update top level).", }, }
BatchUpdate accepts a CLI-shape operations array (each item {shortcut, input}); on Validate / DryRun / Execute we translate each sub-op via batchOpDispatch (see batch_op_dispatch.go) into the MCP {tool_name, input(+operation)} form before calling the underlying batch_update tool.
var CellsBatchClear = common.Shortcut{ Service: "sheets", Command: "+cells-batch-clear", Description: "Clear content/formats across many sheet-prefixed ranges in one atomic batch (irreversible).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-batch-clear"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } if _, err := validateDropdownRanges(runtime); err != nil { return err } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) input, _ := cellsBatchClearInput(runtime, token) return invokeToolDryRun(token, ToolKindWrite, "batch_update", input) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } input, err := cellsBatchClearInput(runtime, token) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", input) if err != nil { return annotateEmbeddedBlockClearErr(err) } runtime.Out(out, nil) return nil }, Tips: []string{ "high-risk-write — always preview with --dry-run; clear is not undoable.", "Every --ranges item must carry a sheet prefix (e.g. \"Sheet1!A1:A10\"); all ranges are cleared with the same --scope.", "Can't delete an embedded pivot/chart by clearing cells — remove the object itself with +pivot-delete / +chart-delete.", }, }
CellsBatchClear clears content / formats / both across many sheet-prefixed ranges in one atomic batch. --ranges is a JSON array of sheet-prefixed A1 strings; --scope reuses the +cells-clear vocabulary (content / formats / all). CLI fans each range into a separate clear_cell_range op inside one batch_update. high-risk-write because clear is irreversible.
var CellsBatchSetStyle = common.Shortcut{ Service: "sheets", Command: "+cells-batch-set-style", 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: flagsFor("+cells-batch-set-style"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { 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) input, _ := cellsBatchSetStyleInput(runtime, token) return invokeToolDryRun(token, ToolKindWrite, "batch_update", input) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } input, err := cellsBatchSetStyleInput(runtime, token) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
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 CellsClear = common.Shortcut{ Service: "sheets", Command: "+cells-clear", Description: "Clear cell content, formats, or both within a range (irreversible).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-clear"), Validate: validateViaInput(cellsClearInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := cellsClearInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "clear_cell_range", 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 := cellsClearInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "clear_cell_range", input) if err != nil { return annotateEmbeddedBlockClearErr(err) } runtime.Out(out, nil) return nil }, Tips: []string{ "high-risk-write — always preview with --dry-run; clear is not undoable.", "Can't delete an embedded pivot/chart by clearing cells — remove the object itself with +pivot-delete / +chart-delete.", }, }
CellsClear wraps clear_cell_range.
CLI's --scope vocabulary (content / formats / all) is normalized to the tool's clear_type vocabulary (contents / formats / all) — the spec's singular/plural mismatch is intentionally absorbed here.
var CellsGet = common.Shortcut{ Service: "sheets", Command: "+cells-get", Description: "Read one or more cell ranges with values, formulas, and optional styles / comments / data validation.", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-get"), 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 strings.TrimSpace(runtime.Str("range")) == "" { return sheetsValidationForFlag("range", "--range is required") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) return invokeToolDryRun(token, ToolKindRead, "get_cell_ranges", cellsGetInput(runtime, token, sheetID, sheetName)) }, 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 } out, err := callTool(ctx, runtime, token, ToolKindRead, "get_cell_ranges", cellsGetInput(runtime, token, sheetID, sheetName)) if err != nil { return err } runtime.Out(out, nil) return nil }, }
CellsGet wraps get_cell_ranges: read multiple A1 ranges and return per-cell values, formulas, styles, and other metadata as requested via --include.
var CellsMerge = newMergeShortcut( "+cells-merge", "Merge cells in a range.", "merge", true, )
CellsMerge / CellsUnmerge share the merge_cells tool, dispatched by the `operation` enum. --merge-type applies to merge only and maps to tool field merge_type (`all` / `rows` / `columns`).
var CellsReplace = common.Shortcut{ Service: "sheets", Command: "+cells-replace", Description: "Find and replace text in a spreadsheet (case / regex / whole-cell / formula-text controls).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-replace"), Validate: validateViaInput(replaceInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := replaceInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "replace_data", 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 := replaceInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "replace_data", input) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Always preview with --dry-run before running — replace can mutate every matching cell across the sheet.", }, }
CellsReplace wraps replace_data: find and replace text across a spreadsheet, with the same option controls as +cells-search.
var CellsSearch = common.Shortcut{ Service: "sheets", Command: "+cells-search", Description: "Find cells matching --find in a spreadsheet (case / regex / whole-cell / formula-text controls).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-search"), 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 strings.TrimSpace(runtime.Str("find")) == "" { return sheetsValidationForFlag("find", "--find is required") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) return invokeToolDryRun(token, ToolKindRead, "search_data", searchInput(runtime, token, sheetID, sheetName)) }, 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 } out, err := callTool(ctx, runtime, token, ToolKindRead, "search_data", searchInput(runtime, token, sheetID, sheetName)) if err != nil { return err } runtime.Out(out, nil) return nil }, }
CellsSearch wraps search_data: find cell coordinates matching --find, with optional case / regex / whole-cell / formula-text controls.
var CellsSet = common.Shortcut{ Service: "sheets", Command: "+cells-set", Description: "Write values / formulas / styles / comments / data validation / embed-image to a cell range.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-set"), Validate: validateViaInput(cellsSetInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := cellsSetInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "set_cell_range", 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 := cellsSetInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "set_cell_range", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
CellsSet wraps set_cell_range: caller provides the cells matrix via --cells (JSON), with an optional --copy-to-range to replicate the written block across a larger area (formula refs auto-shift).
var CellsSetImage = common.Shortcut{ Service: "sheets", Command: "+cells-set-image", Description: "Embed a local image into a single cell (uploads via drive, then set_cell_range with rich_text embed-image).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only", "drive:file:upload"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-set-image"), 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 } r := strings.TrimSpace(runtime.Str("range")) if r == "" { return sheetsValidationForFlag("range", "--range is required") } rows, cols, err := rangeDimensions(r) if err != nil { return sheetsValidationForFlag("range", "--range %q: %v", r, err) } if rows != 1 || cols != 1 { return sheetsValidationForFlag("range", "--range %q must be exactly one cell (got %d×%d)", r, rows, cols) } imgPath := strings.TrimSpace(runtime.Str("image")) if imgPath == "" { return sheetsValidationForFlag("image", "--image is required") } if _, err := validate.SafeLocalFlagPath("--image", imgPath); err != nil { return errs.NewValidationError(errs.SubtypeInvalidArgument, "%s", err). WithParam("--image"). WithCause(err) } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) imgPath := strings.TrimSpace(runtime.Str("image")) fileName := strings.TrimSpace(runtime.Str("name")) if fileName == "" { fileName = filepath.Base(imgPath) } setCellBody, _ := buildToolBody("set_cell_range", map[string]interface{}{ "excel_id": token, "range": strings.TrimSpace(runtime.Str("range")), "sheet_id": sheetSelectorPlaceholder(sheetID, sheetName), "cells": [][]interface{}{{map[string]interface{}{ "rich_text": []map[string]interface{}{{ "type": "embed-image", "text": "", "image_token": "<file_token>", "image_width": "<image_width>", "image_height": "<image_height>", }}, }}}, }) return common.NewDryRunAPI(). POST("/open-apis/drive/v1/medias/upload_all"). Desc("upload local image to drive (parent_type=sheet_image)"). Body(map[string]interface{}{ "file_name": fileName, "parent_type": "sheet_image", "parent_node": token, "size": "<file_size>", "file": "@" + imgPath, }). POST(toolInvokePath(token, ToolKindWrite)). Desc("embed file_token into the cell via set_cell_range"). Body(setCellBody) }, 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 } imgPath := strings.TrimSpace(runtime.Str("image")) fileName := strings.TrimSpace(runtime.Str("name")) if fileName == "" { fileName = filepath.Base(imgPath) } info, err := runtime.FileIO().Stat(imgPath) if err != nil { return sheetsInputStatError("image", err) } imgFile, err := runtime.FileIO().Open(imgPath) if err != nil { return sheetsInputStatError("image", err) } imgCfg, _, err := image.DecodeConfig(imgFile) imgFile.Close() if err != nil { return errs.NewValidationError(errs.SubtypeInvalidArgument, "decode image dimensions: %s", err). WithParam("--image"). WithCause(err) } fileToken, err := common.UploadDriveMediaAll(runtime, common.DriveMediaUploadAllConfig{ FilePath: imgPath, FileName: fileName, FileSize: info.Size(), ParentType: "sheet_image", ParentNode: &token, }) if err != nil { return err } setCellInput := map[string]interface{}{ "excel_id": token, "range": strings.TrimSpace(runtime.Str("range")), "cells": [][]interface{}{{map[string]interface{}{ "rich_text": []map[string]interface{}{{ "type": "embed-image", "text": "", "image_token": fileToken, "image_width": imgCfg.Width, "image_height": imgCfg.Height, }}, }}}, } sheetSelectorForToolInput(setCellInput, sheetID, sheetName) setCellOut, err := callTool(ctx, runtime, token, ToolKindWrite, "set_cell_range", setCellInput) if err != nil { return wrapCellsSetImageWriteError(err, fileToken) } runtime.Out(map[string]interface{}{ "file_token": fileToken, "file_name": fileName, "set_cell_range": setCellOut, }, nil) return nil }, Tips: []string{ "--range must be a single cell. The uploaded image becomes a cell-internal embed; use +float-image-create for floating images.", }, }
CellsSetImage uploads a local image to drive (parent_type=sheet_image, parent_node=spreadsheet token) and then writes a rich_text embed-image into the target single-cell range via the set_cell_range tool.
var CellsSetStyle = common.Shortcut{ Service: "sheets", Command: "+cells-set-style", 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: flagsFor("+cells-set-style"), Validate: validateViaInput(cellsSetStyleInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := cellsSetStyleInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "set_cell_range", 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 := cellsSetStyleInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "set_cell_range", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
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 CellsUnmerge = newMergeShortcut( "+cells-unmerge", "Unmerge cells in a range.", "unmerge", false, )
var ChartCreate = newObjectCreateShortcut(chartSpec)
var ChartDelete = newObjectDeleteShortcut(chartSpec)
var ChartList = newObjectListShortcut(objectListSpec{
command: "+chart-list",
description: "List charts on a sheet, optionally filtered to a single chart_id.",
toolName: "get_chart_objects",
filterFlag: "chart-id",
filterField: "chart_id",
})
ChartList — list charts on a sheet (optionally filtered to one chart_id).
var ChartUpdate = newObjectUpdateShortcut(chartSpec)
var ColsResize = common.Shortcut{ Service: "sheets", Command: "+cols-resize", Description: "Resize columns by pixel / standard (--type pixel needs --size; --range is column letters like \"A:E\" or \"C\"; no auto for cols).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cols-resize"), Validate: validateViaResize("column"), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := resizeInput(runtime, token, sheetID, sheetName, "column") return invokeToolDryRun(token, ToolKindWrite, "resize_range", 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 := resizeInput(runtime, token, sheetID, sheetName, "column") if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "resize_range", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
ColsResize wraps resize_range for column widths. Column widths do not support auto-fit — --type only accepts pixel / standard.
var CondFormatCreate = newObjectCreateShortcut(condFormatSpec)
var CondFormatDelete = newObjectDeleteShortcut(condFormatSpec)
var CondFormatList = newObjectListShortcut(objectListSpec{
command: "+cond-format-list",
description: "List conditional format rules on a sheet, optionally filtered to a single rule.",
toolName: "get_conditional_format_objects",
filterFlag: "rule-id",
filterField: "conditional_format_id",
})
CondFormatList — list conditional format rules. CLI's --rule-id maps to the tool's conditional_format_id (CLI uses the shorter common term).
var CondFormatUpdate = newObjectUpdateShortcut(condFormatSpec)
var CsvGet = common.Shortcut{ Service: "sheets", Command: "+csv-get", Description: "Read a range as CSV (with [row=N] line prefix by default).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+csv-get"), 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 strings.TrimSpace(runtime.Str("range")) == "" { return sheetsValidationForFlag("range", "--range is required") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) return invokeToolDryRun(token, ToolKindRead, "get_range_as_csv", csvGetInput(runtime, token, sheetID, sheetName)) }, 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 } out, err := callTool(ctx, runtime, token, ToolKindRead, "get_range_as_csv", csvGetInput(runtime, token, sheetID, sheetName)) if err != nil { return err } switch { case runtime.Bool("rows-json"): out = assembleRowsJSON(out, strings.TrimSpace(runtime.Str("range"))) case !runtime.Bool("include-row-prefix"): out = stripRowPrefixFromCsvOutput(out) } runtime.Out(out, nil) return nil }, }
CsvGet wraps get_range_as_csv: pull one range as RFC 4180 CSV with optional [row=N] line prefix for easy row-number lookup.
var CsvPut = common.Shortcut{ Service: "sheets", Command: "+csv-put", Description: "Paste RFC-4180 CSV into a sheet at --start-cell (plain values only, auto-expands sheet if needed).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+csv-put"), PostMount: func(cmd *cobra.Command) { if fl := cmd.Flags().Lookup("start-cell"); fl != nil { delete(fl.Annotations, cobra.BashCompOneRequiredFlag) } cmd.MarkFlagsOneRequired("start-cell", "range") cmd.MarkFlagsMutuallyExclusive("start-cell", "range") }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if err := guardCSVValueIsNotFilePath(runtime); err != nil { return err } return validateViaInput(csvPutInput)(ctx, runtime) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := csvPutInput(runtime, token, sheetID, sheetName) dr := invokeToolDryRun(token, ToolKindWrite, "set_range_from_csv", input) if rng, ok := csvPutWriteRangeFromInput(input); ok { dr = dr.Set("writes_range", rng) } return dr }, 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 := csvPutInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "set_range_from_csv", input) if err != nil { return err } if rng, ok := csvPutWriteRangeFromInput(input); ok { if m, isMap := out.(map[string]interface{}); isMap { m["writes_range"] = rng } } runtime.Out(out, nil) return nil }, }
CsvPut wraps set_range_from_csv: dump a CSV blob into a sheet, only writing plain values. Use +cells-set for anything richer (formula / style / note).
var DimDelete = common.Shortcut{ Service: "sheets", Command: "+dim-delete", Description: "Delete rows or columns (irreversible).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dim-delete"), Validate: validateDimRangeOp("delete"), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := dimRangeOpInput(runtime, token, sheetID, sheetName, "delete") return invokeToolDryRun(token, ToolKindWrite, "modify_sheet_structure", 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 := dimRangeOpInput(runtime, token, sheetID, sheetName, "delete") if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_sheet_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Row/column deletion is irreversible. Always preview with --dry-run first.", }, }
DimDelete deletes rows / columns — irreversible, high-risk-write.
var DimFreeze = common.Shortcut{ Service: "sheets", Command: "+dim-freeze", Description: "Freeze the first N rows or columns; --count 0 unfreezes the chosen dimension.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dim-freeze"), Validate: validateViaInput(dimFreezeInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := dimFreezeInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "modify_sheet_structure", 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 := dimFreezeInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_sheet_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
DimFreeze freezes the first N rows or columns; --count 0 unfreezes that dimension.
var DimGroup = newDimGroupShortcut(
"+dim-group", "Group rows or columns into an outline (collapsible).", "group",
)
DimGroup / DimUngroup manage row/column outline groups.
var DimHide = newDimRangeOpShortcut(
"+dim-hide", "Hide rows or columns within a range.", "hide", "write",
)
DimHide / DimUnhide toggle visibility on a row/column range.
var DimInsert = common.Shortcut{ Service: "sheets", Command: "+dim-insert", Description: "Insert blank rows or columns at a given position.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dim-insert"), Validate: validateViaInput(dimInsertInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := dimInsertInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "modify_sheet_structure", 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 := dimInsertInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_sheet_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
DimInsert inserts blank rows / columns and optionally inherits style from the adjacent dimension.
var DimMove = common.Shortcut{ Service: "sheets", Command: "+dim-move", Description: "Move a contiguous block of rows or columns to a new position (re-numbers neighbors).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dim-move"), 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 } _, err := buildDimMovePlan(runtime) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) return common.NewDryRunAPI(). POST(dimMovePath(token, sheetSelectorPlaceholder(sheetID, sheetName))). Body(dimMoveBody(runtime)). Set("spreadsheet_token", token) }, 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 } if sheetID == "" { lookedID, _, err := lookupSheetIndex(ctx, runtime, token, "", sheetName) if err != nil { return err } sheetID = lookedID } data, err := runtime.CallAPITyped("POST", dimMovePath(token, sheetID), nil, dimMoveBody(runtime)) if err != nil { return err } runtime.Out(data, nil) return nil }, }
var DimUngroup = newDimGroupShortcut(
"+dim-ungroup", "Remove a row/column outline group.", "ungroup",
)
var DimUnhide = newDimRangeOpShortcut(
"+dim-unhide", "Unhide rows or columns within a range.", "unhide", "write",
)
var DropdownDelete = common.Shortcut{ Service: "sheets", Command: "+dropdown-delete", Description: "Clear dropdowns from many sheet-prefixed ranges atomically (irreversible).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dropdown-delete"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } ranges, err := validateDropdownRanges(runtime) if err != nil { return err } if len(ranges) > 100 { return sheetsValidationForFlag("ranges", "--ranges accepts at most 100 entries; got %d", len(ranges)) } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) input, _ := dropdownBatchInput(runtime, token, true) return invokeToolDryRun(token, ToolKindWrite, "batch_update", input) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } input, err := dropdownBatchInput(runtime, token, true) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
DropdownDelete clears data_validation across many ranges atomically.
var DropdownGet = common.Shortcut{ Service: "sheets", Command: "+dropdown-get", Description: "Read the dropdown / data-validation configuration on a range.", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dropdown-get"), 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 strings.TrimSpace(runtime.Str("range")) == "" { return sheetsValidationForFlag("range", "--range is required") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) return invokeToolDryRun(token, ToolKindRead, "get_cell_ranges", dropdownGetInput(runtime, token, sheetID, sheetName)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } sheetID, sheetName, err := resolveSheetSelector(runtime) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindRead, "get_cell_ranges", dropdownGetInput(runtime, token, sheetID, sheetName)) if err != nil { return err } runtime.Out(out, nil) return nil }, }
DropdownGet wraps get_cell_ranges scoped to data_validation: read the dropdown configuration on a range. Aligned with its sibling +cells-get — sheet selection is via --sheet-id / --sheet-name (XOR), and --range is a bare A1 reference. The earlier "must include a sheet prefix" shape was the odd one out among the get_cell_ranges wrappers and made callers treat the prefix as either name or id; folding it into the canonical --sheet-id selector removes that ambiguity.
var DropdownSet = common.Shortcut{ Service: "sheets", Command: "+dropdown-set", Description: "Attach a dropdown / data-validation list to every cell in --range.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dropdown-set"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if err := validateViaInput(dropdownSetInput)(ctx, runtime); err != nil { return err } warnDropdownSourceRangeHighlight(runtime) return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := dropdownSetInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "set_cell_range", 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 := dropdownSetInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "set_cell_range", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
DropdownSet places a single dropdown on one range.
var DropdownUpdate = common.Shortcut{ Service: "sheets", Command: "+dropdown-update", Description: "Install or replace one dropdown across many sheet-prefixed ranges atomically.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dropdown-update"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } if _, err := validateDropdownRanges(runtime); err != nil { return err } if _, err := validateDropdownSourceOrOptions(runtime); err != nil { return err } warnDropdownSourceRangeHighlight(runtime) return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) input, _ := dropdownBatchInput(runtime, token, false) return invokeToolDryRun(token, ToolKindWrite, "batch_update", input) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } input, err := dropdownBatchInput(runtime, token, false) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
DropdownUpdate installs/replaces a single dropdown on many ranges in one atomic batch. Sheet ids come from the per-range sheet prefix.
var FilterCreate = common.Shortcut{ Service: "sheets", Command: "+filter-create", Description: "Create a sheet-level filter (one per sheet).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+filter-create"), Validate: validateViaInput(filterCreateInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := filterCreateInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "manage_filter_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 := filterCreateInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_filter_object", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
FilterCreate creates a sheet-level filter. --range covers the data (header inclusive). --data is optional — empty filter is valid.
var FilterDelete = common.Shortcut{ Service: "sheets", Command: "+filter-delete", Description: "Remove the sheet-level filter (irreversible).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+filter-delete"), Validate: validateViaInput(filterDeleteInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := filterDeleteInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "manage_filter_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 := filterDeleteInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_filter_object", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
FilterDelete drops the sheet-level filter entirely. high-risk-write.
var FilterList = newObjectListShortcut(objectListSpec{
command: "+filter-list",
description: "List active sheet-level filters across the workbook (or one sheet).",
toolName: "get_filter_objects",
})
FilterList — list active sheet-level filters. No id filter because each sheet carries at most one filter.
var FilterUpdate = common.Shortcut{ Service: "sheets", Command: "+filter-update", Description: "Update the sheet-level filter (overwrite rules + range).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+filter-update"), Validate: validateViaInput(filterUpdateInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := filterUpdateInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "manage_filter_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 := filterUpdateInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_filter_object", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
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 FilterViewCreate = newObjectCreateShortcut(filterViewSpec)
var FilterViewDelete = newObjectDeleteShortcut(filterViewSpec)
var FilterViewList = newObjectListShortcut(objectListSpec{
command: "+filter-view-list",
description: "List filter views on a sheet, optionally filtered to a single view_id.",
toolName: "get_filter_view_objects",
filterFlag: "view-id",
filterField: "view_id",
})
FilterViewList — list filter views on a sheet. `cli-only` skill (not exposed as MCP tool catalog), but the tool itself is dispatched through the same One-OpenAPI endpoint.
var FilterViewUpdate = newObjectUpdateShortcut(filterViewSpec)
var FloatImageCreate = newFloatImageWriteShortcut( "+float-image-create", "Create a floating image (from a local --image path, or an existing --image-token / --image-uri).", "create", false, false, )
var FloatImageDelete = newObjectDeleteShortcut(floatImageDeleteSpec)
var FloatImageList = newObjectListShortcut(objectListSpec{
command: "+float-image-list",
description: "List floating images on a sheet, optionally filtered to a single float_image_id.",
toolName: "get_float_image_objects",
filterFlag: "float-image-id",
filterField: "float_image_id",
})
FloatImageList — list floating images on a sheet (vs. embedded cell-images which live in cell metadata).
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, )
var PivotCreate = newObjectCreateShortcut(pivotSpec)
var PivotDelete = newObjectDeleteShortcut(pivotSpec)
var PivotList = newObjectListShortcut(objectListSpec{
command: "+pivot-list",
description: "List pivot tables on a sheet, optionally filtered to a single pivot_table_id.",
toolName: "get_pivot_table_objects",
filterFlag: "pivot-table-id",
filterField: "pivot_table_id",
})
PivotList — list pivot tables on a sheet.
var PivotUpdate = newObjectUpdateShortcut(pivotSpec)
var RangeCopy = common.Shortcut{ Service: "sheets", Command: "+range-copy", Description: "Copy a range to a new location (--paste-type controls what is copied).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+range-copy"), Validate: validateRangeMoveOrCopy("copy", true), DryRun: transformDryRunFn("copy", true, false), Execute: transformExecuteFn("copy", true, false), }
RangeCopy duplicates a range to a new location with optional paste-type filter (values / formulas / formats / all).
var RangeFill = common.Shortcut{ Service: "sheets", Command: "+range-fill", Description: "Autofill a target range from a source template (copy / linear / growth / date series).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+range-fill"), Validate: validateViaInput(rangeFillInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := rangeFillInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "transform_range", 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 := rangeFillInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "transform_range", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
RangeFill performs autofill from a template range into a target range. --series-type is a 5-value CLI vocabulary; the tool only distinguishes `copyCells` from `fillSeries`. The mapping is documented in fillSeriesToToolType.
var RangeMove = common.Shortcut{ Service: "sheets", Command: "+range-move", Description: "Cut a range and paste it at a new location (optionally cross-sheet).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+range-move"), Validate: validateRangeMoveOrCopy("move", false), DryRun: transformDryRunFn("move", false, false), Execute: transformExecuteFn("move", false, false), }
RangeMove cuts data from --source-range and pastes at --target-range, optionally on another sheet.
var RangeSort = common.Shortcut{ Service: "sheets", Command: "+range-sort", Description: "Sort rows within a range by one or more columns.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+range-sort"), Validate: validateViaInput(rangeSortInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := rangeSortInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "transform_range", 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 := rangeSortInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "transform_range", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
RangeSort sorts rows within a range by one or more columns.
var RowsResize = common.Shortcut{ Service: "sheets", Command: "+rows-resize", Description: "Resize rows by pixel / standard / auto (--type pixel needs --size; --range is 1-based A1 like \"2:10\" or \"5\").", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+rows-resize"), Validate: validateViaResize("row"), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := resizeInput(runtime, token, sheetID, sheetName, "row") return invokeToolDryRun(token, ToolKindWrite, "resize_range", 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 := resizeInput(runtime, token, sheetID, sheetName, "row") if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "resize_range", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
RowsResize wraps resize_range for row heights. --type auto enables auto-fit (rows only); --type pixel requires --size.
var SheetCopy = common.Shortcut{ Service: "sheets", Command: "+sheet-copy", Description: "Duplicate a sub-sheet, optionally renaming and repositioning the copy.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+sheet-copy"), Validate: validateViaInput(sheetCopyInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := sheetCopyInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "modify_workbook_structure", 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 := sheetCopyInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_workbook_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
SheetCopy duplicates a sub-sheet. --title (optional) names the copy; --index (optional) places it.
var SheetCreate = common.Shortcut{ Service: "sheets", Command: "+sheet-create", Description: "Create a new sub-sheet with an optional position and initial dimensions.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+sheet-create"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } _, err = sheetCreateInput(runtime, token) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) input, _ := sheetCreateInput(runtime, token) return invokeToolDryRun(token, ToolKindWrite, "modify_workbook_structure", input) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } input, err := sheetCreateInput(runtime, token) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_workbook_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
SheetCreate creates a new sub-sheet. --title is the new sheet's name; --index inserts at a specific position (omitted → appended). Default dimensions match the canonical schema (rows=100, cols=26 when omitted — tool's defaults differ but CLI surface stays predictable).
var SheetDelete = common.Shortcut{ Service: "sheets", Command: "+sheet-delete", Description: "Delete a sub-sheet (irreversible).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+sheet-delete"), Validate: validateViaInput(sheetDeleteInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := sheetDeleteInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "modify_workbook_structure", 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 := sheetDeleteInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_workbook_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Sheet deletion is irreversible. Always run with --dry-run first to verify the target sheet_id/sheet_name.", }, }
SheetDelete deletes a sub-sheet. high-risk-write — framework rejects without --yes. Always preview with --dry-run first to confirm the target.
var SheetHide = newSheetVisibilityShortcut(
"+sheet-hide", "Hide a sub-sheet from the tabs bar.", "hide",
)
SheetHide / SheetUnhide toggle visibility. Visible bool semantics live in the operation enum so callers don't need a --visible flag.
var SheetInfo = common.Shortcut{ Service: "sheets", Command: "+sheet-info", Description: "Get a sub-sheet's layout metadata: row heights, column widths, hidden rows/cols, merges, groups, freeze.", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+sheet-info"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } _, _, err := resolveSheetSelector(runtime) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) return invokeToolDryRun(token, ToolKindRead, "get_sheet_structure", sheetInfoInput(runtime, token, sheetID, sheetName)) }, 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 } out, err := callTool(ctx, runtime, token, ToolKindRead, "get_sheet_structure", sheetInfoInput(runtime, token, sheetID, sheetName)) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Frozen rows / columns are top-level fields and are returned regardless of --include.", }, }
SheetInfo wraps get_sheet_structure: row heights, column widths, hidden rows/cols, merged cells, row/column groups, and freeze counts for one sub-sheet (optionally limited to a range).
var SheetMove = common.Shortcut{ Service: "sheets", Command: "+sheet-move", Description: "Move a sub-sheet to a new position.", Risk: "write", Scopes: []string{"sheets:spreadsheet:read", "sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+sheet-move"), 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 !runtime.Changed("index") { return sheetsValidationForFlag("index", "--index is required") } if runtime.Int("index") < 0 { return sheetsValidationForFlag("index", "--index must be >= 0") } if runtime.Changed("source-index") && runtime.Int("source-index") < 0 { return sheetsValidationForFlag("source-index", "--source-index must be >= 0") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input := map[string]interface{}{ "excel_id": token, "operation": "move", "sheet_id": sheetSelectorPlaceholder(sheetID, sheetName), "target_index": runtime.Int("index"), "source_index": sourceIndexOrPlaceholder(runtime), } return invokeToolDryRun(token, ToolKindWrite, "modify_workbook_structure", 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 } resolvedID := sheetID var sourceIndex int needIDLookup := sheetID == "" needIndexLookup := !runtime.Changed("source-index") if needIDLookup || needIndexLookup { lookedID, lookedIdx, err := lookupSheetIndex(ctx, runtime, token, sheetID, sheetName) if err != nil { return err } resolvedID = lookedID sourceIndex = lookedIdx } if runtime.Changed("source-index") { sourceIndex = runtime.Int("source-index") } input := map[string]interface{}{ "excel_id": token, "operation": "move", "sheet_id": resolvedID, "source_index": sourceIndex, "target_index": runtime.Int("index"), } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_workbook_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Pass --source-index when you already know it to avoid the extra read; otherwise CLI derives it from --sheet-id/--sheet-name.", }, }
SheetMove moves a sub-sheet to a new index. The tool requires sheet_id and source_index in addition to target_index. The CLI accepts:
- --sheet-id / --sheet-name to identify the sheet
- --source-index (optional) for explicit source position
When --source-index is omitted, or when --sheet-name is used instead of --sheet-id, Execute issues a single get_workbook_structure read to derive the missing pieces. DryRun stays network-free: it uses <resolve> placeholders for any field that would need that read.
var SheetRename = common.Shortcut{ Service: "sheets", Command: "+sheet-rename", Description: "Rename a sub-sheet.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+sheet-rename"), Validate: validateViaInput(sheetRenameInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := sheetRenameInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "modify_workbook_structure", 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 := sheetRenameInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_workbook_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
SheetRename renames a sub-sheet via --title (mapped to tool's new_name).
var SheetSetTabColor = common.Shortcut{ Service: "sheets", Command: "+sheet-set-tab-color", Description: "Set or clear the tab color of a sub-sheet.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+sheet-set-tab-color"), Validate: validateViaInput(sheetSetTabColorInput), DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) input, _ := sheetSetTabColorInput(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "modify_workbook_structure", 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 := sheetSetTabColorInput(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_workbook_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
SheetSetTabColor sets the tab color of a sub-sheet. --color "" clears.
var SheetUnhide = newSheetVisibilityShortcut(
"+sheet-unhide", "Restore a hidden sub-sheet.", "unhide",
)
var SparklineCreate = newObjectCreateShortcut(sparklineSpec)
var SparklineDelete = newObjectDeleteShortcut(sparklineSpec)
var SparklineList = newObjectListShortcut(objectListSpec{
command: "+sparkline-list",
description: "List sparkline groups on a sheet, optionally filtered by group_id.",
toolName: "get_sparkline_objects",
filterFlag: "group-id",
filterField: "group_id",
})
SparklineList — list sparkline groups on a sheet. The tool also accepts a per-sparkline id (`sparkline_id`); CLI exposes the higher-level --group-id which is what callers usually care about.
var SparklineUpdate = newObjectUpdateShortcut(sparklineSpec)
var WorkbookCreate = common.Shortcut{ Service: "sheets", Command: "+workbook-create", 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"}, HasFormat: true, Flags: flagsFor("+workbook-create"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if strings.TrimSpace(runtime.Str("title")) == "" { return sheetsValidationForFlag("title", "--title is required") } if runtime.Str("headers") != "" { v, err := parseJSONFlag(runtime, "headers") if err != nil { return err } if _, ok := v.([]interface{}); !ok { return sheetsValidationForFlag("headers", "--headers must be a JSON array") } } if runtime.Str("values") != "" { v, err := parseJSONFlag(runtime, "values") if err != nil { return err } rows, ok := v.([]interface{}) if !ok { return sheetsValidationForFlag("values", "--values must be a JSON 2D array") } for i, r := range rows { if _, ok := r.([]interface{}); !ok { return sheetsValidationForFlag("values", "--values[%d] must be an array", i) } } } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { body := map[string]interface{}{"title": strings.TrimSpace(runtime.Str("title"))} if v := strings.TrimSpace(runtime.Str("folder-token")); v != "" { body["folder_token"] = v } dry := common.NewDryRunAPI(). POST("/open-apis/sheets/v3/spreadsheets"). Desc("create spreadsheet"). Body(body) if fill, _ := buildInitialFillInput(runtime); fill != nil { fill["excel_id"] = "<new-token>" fill["sheet_id"] = "<first-sheet-id>" wireBody, _ := buildToolBody("set_cell_range", fill) dry.POST("/open-apis/sheet_ai/v2/spreadsheets/<new-token>/tools/invoke_write"). Desc("fill headers + data via set_cell_range (sheet_id resolved after create)"). Body(wireBody) } return dry }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { body := map[string]interface{}{"title": strings.TrimSpace(runtime.Str("title"))} if v := strings.TrimSpace(runtime.Str("folder-token")); v != "" { body["folder_token"] = v } data, err := runtime.CallAPITyped("POST", "/open-apis/sheets/v3/spreadsheets", nil, body) if err != nil { return err } ss := common.GetMap(data, "spreadsheet") token := common.GetString(ss, "spreadsheet_token") if token == "" { token = common.GetString(ss, "token") } if token == "" { return errs.NewInternalError(errs.SubtypeInvalidResponse, "spreadsheet created but token missing in response") } result := map[string]interface{}{"spreadsheet": ss} fill, err := buildInitialFillInput(runtime) if err != nil { return err } if fill != nil { fill["excel_id"] = token firstSheetID, err := lookupFirstSheetID(ctx, runtime, token) if err != nil { return workbookCreatedButFillFailed(token, "resolving its first sheet for initial fill failed", err) } fill["sheet_id"] = firstSheetID fillOut, err := callTool(ctx, runtime, token, ToolKindWrite, "set_cell_range", fill) if err != nil { return workbookCreatedButFillFailed(token, "initial fill failed", err) } result["initial_fill"] = fillOut } runtime.Out(result, nil) return nil }, Tips: []string{ "--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.", }, }
WorkbookCreate creates a brand-new spreadsheet in the user's drive (optionally inside --folder-token) and can pre-fill the first row of headers and an initial data block.
var WorkbookExport = common.Shortcut{ Service: "sheets", Command: "+workbook-export", Description: "Export a spreadsheet to xlsx or a single sheet to csv (async + poll + optional download).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read", "docs:document:export", "drive:drive.metadata:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+workbook-export"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } ext := runtime.Str("file-extension") if ext == "" { ext = "xlsx" } if ext == "csv" && strings.TrimSpace(runtime.Str("sheet-id")) == "" { return sheetsValidationForFlag("sheet-id", "--sheet-id is required when --file-extension=csv") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) ext := runtime.Str("file-extension") if ext == "" { ext = "xlsx" } body := map[string]interface{}{ "token": token, "type": "sheet", "file_extension": ext, } if sid := strings.TrimSpace(runtime.Str("sheet-id")); sid != "" { body["sub_id"] = sid } dry := common.NewDryRunAPI(). POST("/open-apis/drive/v1/export_tasks"). Desc("create export task"). Body(body). GET("/open-apis/drive/v1/export_tasks/<ticket>"). Desc("poll task status"). Params(map[string]interface{}{"token": token}) if strings.TrimSpace(runtime.Str("output-path")) != "" { dry.GET("/open-apis/drive/v1/export_tasks/file/<file_token>/download"). Desc("download exported file") } return dry }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } ext := runtime.Str("file-extension") if ext == "" { ext = "xlsx" } body := map[string]interface{}{ "token": token, "type": "sheet", "file_extension": ext, } if sid := strings.TrimSpace(runtime.Str("sheet-id")); sid != "" { body["sub_id"] = sid } taskData, err := runtime.CallAPITyped("POST", "/open-apis/drive/v1/export_tasks", nil, body) if err != nil { return err } ticket := common.GetString(taskData, "ticket") if ticket == "" { return errs.NewInternalError(errs.SubtypeInvalidResponse, "export task created but ticket missing") } result := map[string]interface{}{ "ticket": ticket, "file_extension": ext, } // Poll up to ~30s for completion. var fileToken, fileName string for attempt := 0; attempt < 15; attempt++ { status, err := pollExportTask(runtime, token, ticket) if err != nil { return err } switch status.JobStatus { case 0: fileToken = status.FileToken fileName = status.FileName result["file_token"] = fileToken result["file_name"] = fileName result["file_size"] = status.FileSize attempt = 999 case 1, 2: time.Sleep(2 * time.Second) continue default: if status.JobErrorMsg != "" { return errs.NewAPIError(errs.SubtypeServerError, "export task %s failed: %s", ticket, status.JobErrorMsg) } return errs.NewAPIError(errs.SubtypeServerError, "export task %s failed with job_status=%d", ticket, status.JobStatus) } } if fileToken == "" { result["status"] = "polling_timeout" runtime.Out(result, nil) return nil } outPath := strings.TrimSpace(runtime.Str("output-path")) if outPath == "" { runtime.Out(result, nil) return nil } saved, err := downloadExportFile(ctx, runtime, fileToken, outPath, fileName) if err != nil { return err } result["saved_path"] = saved runtime.Out(result, nil) return nil }, Tips: []string{ "Polls up to ~30s (15 × 2s). For very large workbooks rerun and pass --output-path to capture the file once status flips to success.", }, }
WorkbookExport drives the three-step export flow: create task → poll → optionally download. CSV mode requires --sheet-id (the API exports one sheet at a time as csv).
var WorkbookInfo = common.Shortcut{ Service: "sheets", Command: "+workbook-info", Description: "List sub-sheets of a spreadsheet with metadata (sheet_id, title, dimensions, freeze, hidden).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+workbook-info"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { _, err := resolveSpreadsheetToken(runtime) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) return invokeToolDryRun(token, ToolKindRead, "get_workbook_structure", map[string]interface{}{ "excel_id": token, }) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindRead, "get_workbook_structure", map[string]interface{}{ "excel_id": token, }) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "First step for every sheets task — capture sheet_id from the result before doing any sheet-level operation.", }, }
WorkbookInfo wraps get_workbook_structure: list a workbook's sub-sheets with their metadata (sheet_id, title, dimensions, freeze rows and cols, index, hidden). First step for every sheets task — downstream sheet-level operations all depend on the sheet_id returned here.
Functions ¶
func Shortcuts ¶
Shortcuts returns all lark-sheets shortcuts. The list is grouped by canonical skill to mirror the sheet-skill-spec layout (lark_sheet_workbook → lark_sheet_float_image).
Any shortcut whose command is registered in data/flag-schemas.json gets a PrintFlagSchema closure attached, so the framework can serve `--print-schema --flag-name <name>` locally.
Types ¶
Source Files
¶
- batch_op_dispatch.go
- flag_defs.go
- flag_defs_gen.go
- flag_schema.go
- flag_schema_validate.go
- flag_schemas_gen.go
- flag_view.go
- generate.go
- helpers.go
- lark_sheet_batch_update.go
- lark_sheet_object_crud.go
- lark_sheet_object_list.go
- lark_sheet_range_operations.go
- lark_sheet_read_data.go
- lark_sheet_search_replace.go
- lark_sheet_sheet_structure.go
- lark_sheet_workbook.go
- lark_sheet_write_cells.go
- sheet_ai_api.go
- shortcuts.go
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
gen
command
Command gen regenerates flag_defs_gen.go and flag_schemas_gen.go from the data/*.json spec artifacts, so command startup pays no JSON unmarshal.
|
Command gen regenerates flag_defs_gen.go and flag_schemas_gen.go from the data/*.json spec artifacts, so command startup pays no JSON unmarshal. |