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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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.UploadDriveMediaAllTyped(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 } if !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 (values or formulas: a leading = is evaluated as a formula; no styles / comments; 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 := resolveSpreadsheetTokenExec(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. A cell whose text starts with = is evaluated as a formula; use +cells-set for styles / notes / images.
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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 }, Tips: []string{ "+sheet-create makes an empty sub-sheet. To create a sub-sheet and fill it with typed data and/or styles in one step, use +table-put (missing sheets named in the payload are created automatically) with its --sheets and --styles flags.", }, }
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 := resolveSpreadsheetTokenExec(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 SheetHideGridline = newSheetVisibilityShortcut(
"+sheet-hide-gridline", "Hide gridlines on a sub-sheet.", "hide_gridline",
)
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 := resolveSpreadsheetTokenExec(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 common.ValidationErrorf("--index is required") } if runtime.Int("index") < 0 { return common.ValidationErrorf("--index must be >= 0") } if runtime.Changed("source-index") && runtime.Int("source-index") < 0 { return common.ValidationErrorf("--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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 := resolveSpreadsheetTokenExec(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 SheetShowGridline = newSheetVisibilityShortcut(
"+sheet-show-gridline", "Show gridlines on a sub-sheet.", "show_gridline",
)
SheetShowGridline / SheetHideGridline toggle a sub-sheet's gridline display. Gridline show/hide is the same two-state-via-operation shape as +sheet-hide/+sheet-unhide (no --visible flag), so they reuse newSheetVisibilityShortcut; only the operation enum differs.
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 TableGet = common.Shortcut{ Service: "sheets", Command: "+table-get", Description: "Read sheets back into the typed table protocol (mirror of +table-put); column types are inferred from number_format so the output feeds straight to +table-put or a DataFrame.", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+table-get"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } if strings.TrimSpace(runtime.Str("sheet-id")) != "" && strings.TrimSpace(runtime.Str("sheet-name")) != "" { return common.ValidationErrorf("--sheet-id and --sheet-name are mutually exclusive") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) dry := common.NewDryRunAPI() body, _ := buildToolBody("get_workbook_structure", map[string]interface{}{"excel_id": token}) dry.POST(toolInvokePath(token, ToolKindRead)).Desc("read sub-sheets + grid dimensions via get_workbook_structure").Body(body) rng := strings.TrimSpace(runtime.Str("range")) if rng == "" { rng = "<each sheet's used range (full-grid current_region)>" } input := map[string]interface{}{ "excel_id": token, "ranges": []string{rng}, "include_styles": true, "value_render_option": "raw_value", } sheetSelectorForToolInput(input, strings.TrimSpace(runtime.Str("sheet-id")), strings.TrimSpace(runtime.Str("sheet-name")), ) body, _ = buildToolBody("get_cell_ranges", input) dry.POST(toolInvokePath(token, ToolKindRead)). Desc(fmt.Sprintf("read cells (%s) + styles via get_cell_ranges, then infer column types", rng)). Body(body) return dry }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetTokenExec(runtime) if err != nil { return err } targets, err := tableGetTargets(ctx, runtime, token) if err != nil { return err } noHeader := runtime.Bool("no-header") userRange := strings.TrimSpace(runtime.Str("range")) sheets := make([]interface{}, 0, len(targets)) for _, t := range targets { spec, err := readSheetAsSpec(ctx, runtime, token, t, userRange, noHeader) if err != nil { return err } sheets = append(sheets, spec) } runtime.Out(map[string]interface{}{"sheets": sheets}, nil) return nil }, Tips: []string{ "Output is the same shape +table-put consumes — pipe it back in, or load sheets[].rows into a DataFrame keyed by columns[].name.", "Column types are inferred per column, but only when every non-empty cell agrees; a column mixing types (e.g. numbers + \"暂无\") degrades to string — lossless and round-trips cleanly. Numeric coercion of dirty cells is the caller's job (pandas to_numeric(errors=\"coerce\") on the string column).", }, }
TableGet reads sheets back into the typed table protocol.
var TablePut = common.Shortcut{ Service: "sheets", Command: "+table-put", Description: "Write a typed table (columns with types + rows) into an existing spreadsheet; numbers and dates stay type-faithful.", Risk: "write", Scopes: []string{"sheets:spreadsheet:read", "sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+table-put"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } payload, err := resolveTablePayload(runtime) if err != nil { return err } _, err = parseWorkbookCreateSheetStyles(runtime, payload) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { return tablePutDryRun(runtime) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetTokenExec(runtime) if err != nil { return err } payload, err := resolveTablePayload(runtime) if err != nil { return err } styles, err := parseWorkbookCreateSheetStyles(runtime, payload) if err != nil { return err } return tablePutWrite(ctx, runtime, token, payload, styles) }, Tips: []string{ "Writes into an existing spreadsheet — pass --url or --spreadsheet-token. To create a new workbook first, use +workbook-create, then point --spreadsheet-token here.", "Payload sheets are matched to existing sub-sheets by name (created when absent). Date columns take ISO yyyy-mm-dd strings — converted to real dates (serial + date format).", "--styles applies number formats, colors, merges, and row/col sizes in the same call (same shape as +workbook-create's --styles): one styles item per written sheet, name-matched. Skips the separate +cells-set-style round-trip.", }, }
TablePut is the typed table-put shortcut. It writes into an existing spreadsheet, composing get_workbook_structure / modify_workbook_structure / set_cell_range — no new backend tool, and no workbook creation (use +workbook-create for that, consistent with every other write shortcut).
var WorkbookCreate = common.Shortcut{ Service: "sheets", Command: "+workbook-create", Description: "Create a new spreadsheet, optionally pre-filled with untyped --values or typed --sheets (type-faithful one-step create + write).", 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 common.ValidationErrorf("--title is required") } sheetsGiven := runtime.Changed("sheets") if sheetsGiven && runtime.Str("values") != "" { return common.ValidationErrorf("--values is mutually exclusive with --sheets") } if sheetsGiven { if strings.TrimSpace(runtime.Str("sheets")) == "" { return common.ValidationErrorf("--sheets was given but resolved to empty (empty stdin/file?); pass a typed payload, or drop --sheets to create an empty workbook") } payload, err := parseTablePutPayload(runtime) if err != nil { return err } _, err = parseWorkbookCreateSheetStyles(runtime, payload) return err } sheetStyles, err := parseValuesSheetStyles(runtime) if err != nil { return err } if _, err := buildValuesPayload(runtime, sheetStyles); err != nil { return err } 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) payload, sheetStyles, _ := workbookCreateData(runtime) if payload == nil { if styles := sheetStyles.styleFor(0); styles != nil { appendWorkbookCreateVisualOpsDryRun(dry, "<new-token>", "", valuesSheetName, styles) } return dry } for i := range payload.Sheets { s := &payload.Sheets[i] matrix, _ := buildSheetMatrix(s, headerOn(s)) _, col0, row0, _ := sheetAnchor(s) _ = applyWorkbookCreateStylesToMatrix(matrix, sheetStyles.styleFor(i), col0, row0, fmt.Sprintf("--styles for sheet %q", s.Name)) input := map[string]interface{}{ "excel_id": "<new-token>", "sheet_name": s.Name, "range": tablePutFullRange(s, len(matrix)), "cells": matrix, } wireBody, _ := buildToolBody("set_cell_range", input) dry.POST("/open-apis/sheet_ai/v2/spreadsheets/<new-token>/tools/invoke_write"). Desc(fmt.Sprintf("write sheet %q (%d data rows × %d cols) via set_cell_range", s.Name, len(s.Rows), len(s.Columns))). Body(wireBody) appendWorkbookCreateVisualOpsDryRun(dry, "<new-token>", "", s.Name, sheetStyles.styleFor(i)) } 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} payload, sheetStyles, err := workbookCreateData(runtime) if err != nil { return err } if payload != nil { firstSheetID, err := lookupFirstSheetID(ctx, runtime, token) if err != nil { return workbookCreatedButFillFailed(runtime, token, "resolving its default sheet for the write failed", err) } written, err := writeTypedSheets(ctx, runtime, token, payload, firstSheetID, sheetStyles) if err != nil { return workbookCreatedButFillFailed(runtime, token, "initial fill failed", err) } result["sheets"] = written } else if styles := sheetStyles.styleFor(0); styles != nil { firstSheetID, err := lookupFirstSheetID(ctx, runtime, token) if err != nil { return workbookCreatedButFillFailed(runtime, token, "resolving its default sheet for the write failed", err) } if err := applyWorkbookCreateVisualOps(ctx, runtime, token, firstSheetID, styles); err != nil { return workbookCreatedButFillFailed(runtime, token, "applying visual styles failed", err) } } runtime.Out(result, nil) return nil }, Tips: []string{ "--values is an optional untyped fill (one JSON 2D array). It writes through the same batched set_cell_range path as --sheets; pair it with --styles to set number formats, colors, merges, and row/col sizes. Partial failure leaves the spreadsheet created but empty.", "--sheets writes typed, type-faithful data (dates → real dates, numbers keep precision) in one step — the create + typed write that +table-put can't do on its own. Mutually exclusive with --values; the new workbook's default sheet becomes the first sheet (no empty Sheet1 left behind).", }, }
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 common.ValidationErrorf("--sheet-id is required when --file-extension=csv") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { p, _ := workbookExportParams(runtime) p.OutputDir = strings.TrimSpace(runtime.Str("output-path")) return drive.PlanExportDryRun(runtime, p) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { p, err := workbookExportParams(runtime) if err != nil { return err } if p.Token, err = resolveSpreadsheetTokenExec(runtime); err != nil { return err } applyWorkbookOutputPath(&p, runtime.FileIO(), runtime.Str("output-path")) return drive.RunExport(ctx, runtime, p) }, Tips: []string{ "Polls for a bounded window; if the export is still running it returns a resume reference instead of blocking. Pass --output-path to download the file once ready (omit it to only create the export task and get the file token back).", }, }
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 WorkbookImport = common.Shortcut{ Service: "sheets", Command: "+workbook-import", Description: "Import a local xlsx/xls/csv file as a new spreadsheet (async + poll). Reuses the drive import core with type fixed to sheet.", Risk: "write", Scopes: []string{"docs:document.media:upload", "docs:document:import"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+workbook-import"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { return drive.ValidateImport(workbookImportParams(runtime)) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { return drive.PlanImportDryRun(runtime, workbookImportParams(runtime)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { return drive.RunImport(ctx, runtime, workbookImportParams(runtime)) }, }
WorkbookImport imports a local spreadsheet file as a new Feishu spreadsheet by delegating to the shared drive import core with type fixed to "sheet".
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 := resolveSpreadsheetTokenExec(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_table_io.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. |