sheets

package
v1.0.63 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 1, 2026 License: MIT Imports: 27 Imported by: 0

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

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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`).

View Source
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.

View Source
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.

View Source
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).

View Source
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=" + sheetMediaParentType(token) + ")").
			Body(map[string]interface{}{
				"file_name":   fileName,
				"parent_type": sheetMediaParentType(token),
				"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 := uploadSheetImage(runtime, token, imgPath, fileName, info.Size())
		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.

View Source
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.

View Source
var CellsUnmerge = newMergeShortcut(
	"+cells-unmerge", "Unmerge cells in a range.", "unmerge", false,
)
View Source
var ChartCreate = newObjectCreateShortcut(chartSpec)
View Source
var ChartDelete = newObjectDeleteShortcut(chartSpec)
View Source
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).

View Source
var ChartUpdate = newObjectUpdateShortcut(chartSpec)
View Source
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.

View Source
var CondFormatCreate = newObjectCreateShortcut(condFormatSpec)
View Source
var CondFormatDelete = newObjectDeleteShortcut(condFormatSpec)
View Source
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).

View Source
var CondFormatUpdate = newObjectUpdateShortcut(condFormatSpec)
View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
var DimGroup = newDimGroupShortcut(
	"+dim-group", "Group rows or columns into an outline (collapsible).", "group",
)

DimGroup / DimUngroup manage row/column outline groups.

View Source
var DimHide = newDimRangeOpShortcut(
	"+dim-hide", "Hide rows or columns within a range.", "hide", "write",
)

DimHide / DimUnhide toggle visibility on a row/column range.

View Source
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.

View Source
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
	},
}
View Source
var DimUngroup = newDimGroupShortcut(
	"+dim-ungroup", "Remove a row/column outline group.", "ungroup",
)
View Source
var DimUnhide = newDimRangeOpShortcut(
	"+dim-unhide", "Unhide rows or columns within a range.", "unhide", "write",
)
View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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).

View Source
var FilterViewCreate = newObjectCreateShortcut(filterViewSpec)
View Source
var FilterViewDelete = newObjectDeleteShortcut(filterViewSpec)
View Source
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.

View Source
var FilterViewUpdate = newObjectUpdateShortcut(filterViewSpec)
View Source
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,
)
View Source
var FloatImageDelete = newObjectDeleteShortcut(floatImageDeleteSpec)
View Source
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).

View Source
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,
)
View Source
var PivotCreate = newObjectCreateShortcut(pivotSpec)
View Source
var PivotDelete = newObjectDeleteShortcut(pivotSpec)
View Source
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.

View Source
var PivotUpdate = newObjectUpdateShortcut(pivotSpec)
View Source
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).

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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.

View Source
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).

View Source
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.

View Source
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.

View Source
var SheetHideGridline = newSheetVisibilityShortcut(
	"+sheet-hide-gridline", "Hide gridlines on a sub-sheet.", "hide_gridline",
)
View Source
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).

View Source
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.

View Source
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).

View Source
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.

View Source
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.

View Source
var SheetUnhide = newSheetVisibilityShortcut(
	"+sheet-unhide", "Restore a hidden sub-sheet.", "unhide",
)
View Source
var SparklineCreate = newObjectCreateShortcut(sparklineSpec)
View Source
var SparklineDelete = newObjectDeleteShortcut(sparklineSpec)
View Source
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.

View Source
var SparklineUpdate = newObjectUpdateShortcut(sparklineSpec)
View Source
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.

View Source
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).

View Source
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.

View Source
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).

View Source
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".

View Source
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

func Shortcuts() []common.Shortcut

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

type ToolKind added in v1.0.47

type ToolKind string

ToolKind selects the One-OpenAPI endpoint and its rate-limit bucket.

  • ToolKindRead → POST .../tools/invoke_read (scope sheets:spreadsheet:read, 10 qps)
  • ToolKindWrite → POST .../tools/invoke_write (scope sheets:spreadsheet:write_only, 5 qps)
const (
	ToolKindRead  ToolKind = "read"
	ToolKindWrite ToolKind = "write"
)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL