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 in one request; fail-fast on the first failing sub-op (already-applied sub-ops are NOT rolled back).", 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) dr := invokeToolDryRun(token, ToolKindWrite, "batch_update", input) if warnings := batchWarnings(runtime); len(warnings) > 0 { dr.Set("warning_message", strings.Join(warnings, "\n")) } return dr }, 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 } for _, w := range batchWarnings(runtime) { fmt.Fprintln(runtime.IO().ErrOut, w) } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", input) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "high-risk-write: preview with --dry-run, get the user's explicit consent, then re-run with --yes appended — do not pass --yes before the user has confirmed (without it the call exits 10 asking for confirmation).", "Execution is fail-fast, NOT transactional: on \"N succeeded, M failed\" the succeeded sub-ops stay applied (no rollback) — fix the failure and resend ONLY the operations from the first failed index onward; resending the whole batch re-applies the succeeded ones. Pass --continue-on-error to keep going past failures instead.", "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 batch request (irreversible; fail-fast, no rollback).", 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 batch request (fail-fast, no rollback).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+cells-batch-set-style"), Tips: []string{ "DEPRECATED: superseded by +styles-put, whose one spec also covers merges, row/col sizes and freeze — prefer it for new work.", `Example: lark-cli sheets +cells-batch-set-style --url <URL> --ranges '["Sheet1!A1:B2","汇总!C1:C9"]' --font-weight bold`, "Every range carries its sheet-NAME prefix (Sheet1!A1:B2, not a sheet_id) — there is no --sheet-id / --sheet-name flag here.", }, 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 } fmt.Fprintln(runtime.IO().ErrOut, "note: +cells-batch-set-style is superseded by +styles-put (one spec covers styles + merges + row/col sizes + freeze); prefer +styles-put for new work") 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 — pass --yes to confirm (exit 10 without it), or preview with --dry-run first; 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 } return emitReadResult(runtime, out) }, }
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"), Tips: []string{ `Example: lark-cli sheets +cells-set --url <URL> --sheet-name Sheet1 --range A1:B1 --cells '[[{"value":"名称"},{"formula":"=SUM(B2:B9)"}]]'`, `--cells is always a 2D array (rows × cells), even for one cell: [[{"value":…}]].`, `Scattered regions (e.g. fixing formulas across ranges/sheets): --writes '[{"sheet_name":…,"range":…,"cells":[[…]]}, …]' — one batch request (fail-fast, no rollback), sheet selector inside each item.`, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if runtime.Changed("writes") { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } _, err = cellsSetWritesOps(runtime, token) return err } return validateViaInput(cellsSetInput)(ctx, runtime) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) if runtime.Changed("writes") { ops, _ := cellsSetWritesOps(runtime, token) return invokeToolDryRun(token, ToolKindWrite, "batch_update", map[string]interface{}{ "excel_id": token, "operations": ops, }) } 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 } if runtime.Changed("writes") { ops, err := cellsSetWritesOps(runtime, token) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", map[string]interface{}{ "excel_id": token, "operations": ops, }) if err != nil { return err } runtime.Out(out, nil) return nil } 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). The plural form --writes ([{sheet_name, range, cells}, …]) fans scattered regions — cross-sheet allowed — into ONE atomic batch_update: eval traces show "fix all broken formulas across ranges/sheets" as the dominant homogeneous scenario still hand-assembled as +batch-update operations arrays.
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.
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"), Tips: []string{ `Example: lark-cli sheets +cells-set-style --url <URL> --sheet-name Sheet1 --range A1:D1 --font-weight bold --background-color "#F0F0F0" --horizontal-alignment center`, `Borders take JSON: --border-styles '{"top":{"style":"solid","weight":"thin","color":"#000000"}}' (sides: top/bottom/left/right).`, }, 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-family, 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 ChangesetGet = common.Shortcut{ Service: "sheets", Command: "+changeset-get", Description: "Fetch the raw changeset (edit actions) between two versions, to review whether an AI edit fulfilled the request.", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+changeset-get"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } _, _, err := changesetRevisions(runtime) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) input, _ := changesetInput(runtime, token) return invokeToolDryRun(token, ToolKindRead, "get_changeset", input) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetTokenExec(runtime) if err != nil { return err } input, err := changesetInput(runtime, token) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindRead, "get_changeset", input) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Pass only --start-revision to diff against the latest version; add --end-revision to bound the range.", "The version gap is capped at 20 revisions (end - start + 1 <= 20).", }, }
ChangesetGet fetches the raw changesets between two spreadsheet versions.
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 in pixels (NOT Excel char units): --range + --width <px> for one uniform width, --widths '{\"A\":100,\"C:E\":120}' for per-column widths in one batch request, or --type standard to reset (--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"), Tips: []string{ "Example: lark-cli sheets +cols-resize --url <URL> --sheet-name Sheet1 --range A:C --width 120", `Different widths per column in one batch request: --widths '{"A":80,"C:E":120}'. Widths are pixels (px ≈ chars × 8 + 16), not Excel character units.`, }, Validate: validateViaResize("column"), DryRun: resizeDryRun("column"), Execute: resizeExecute("column"), }
ColsResize wraps resize_range for column widths. Pass --range + --width <px> for a uniform pixel width, --widths '{"A":100,"C:E":120}' for per-column widths, or --type standard for the default width. Column widths do not support auto-fit — --type does not accept auto.
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 } 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) } return emitReadResult(runtime, out) }, }
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: func(ctx context.Context, runtime *common.RuntimeContext) error { if runtime.Changed("ranges") { if runtime.Changed("range") { return sheetsValidationForFlag("ranges", "--range and --ranges are mutually exclusive; put every range into --ranges") } token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } sheetID, sheetName, err := resolveSheetSelector(runtime) if err != nil { return err } _, err = dimDeleteRangesOps(runtime, token, sheetID, sheetName) return err } return validateDimRangeOp("delete")(ctx, runtime) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) sheetID, sheetName, _ := resolveSheetSelector(runtime) if runtime.Changed("ranges") { ops, _ := dimDeleteRangesOps(runtime, token, sheetID, sheetName) return invokeToolDryRun(token, ToolKindWrite, "batch_update", map[string]interface{}{ "excel_id": token, "operations": ops, }) } 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 } if runtime.Changed("ranges") { ops, err := dimDeleteRangesOps(runtime, token, sheetID, sheetName) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", map[string]interface{}{ "excel_id": token, "operations": ops, }) if err != nil { return err } runtime.Out(out, nil) return nil } 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.", `Scattered ranges: --ranges '["5:5","8:8","11:13"]' deletes them in one batch request (fail-fast, no rollback) — the CLI orders positions descending, so indexes never shift under you.`, }, }
DimDelete deletes rows / columns — irreversible, high-risk-write.
var DimFreeze = common.Shortcut{ Service: "sheets", Command: "+dim-freeze", Description: "Freeze the first N rows and/or columns; this sets the whole freeze state, so an axis you do not name ends up unfrozen.", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+dim-freeze"), Tips: []string{ "Example: lark-cli sheets +dim-freeze --url <URL> --sheet-name Sheet1 --rows 1 --cols 2 (holds the header row and the first 2 columns in one call)", "Freezing is not additive: --dimension row --count 1 followed by --dimension column --count 2 leaves ONLY the columns frozen. Pass --rows/--cols together instead of calling twice", "To unfreeze one axis but keep the other, state the survivor: --rows 0 --cols 2. Bare --count 0 clears both", }, 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) dr := invokeToolDryRun(token, ToolKindWrite, "modify_sheet_structure", input) if note := dimFreezeLegacyNote(runtime); note != "" { dr.Set("warning_message", note) } 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 := dimFreezeInput(runtime, token, sheetID, sheetName) if err != nil { return err } if note := dimFreezeLegacyNote(runtime); note != "" { fmt.Fprintln(runtime.IO().ErrOut, note) } out, err := callTool(ctx, runtime, token, ToolKindWrite, "modify_sheet_structure", input) if err != nil { return err } runtime.Out(out, nil) return nil }, }
DimFreeze sets the sheet's freeze state. Freeze is full-state replacement server-side (verified 07-31 live), so every call states the WHOLE state: --rows/--cols name both axes at once, while the older --dimension/--count pair can only name one and therefore unfreezes the other.
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"), Tips: []string{ "Example: lark-cli sheets +dim-insert --url <URL> --sheet-name Sheet1 --position 3 --count 2 --inherit-style before", "Rows vs columns comes from --position alone: a row number (3) inserts rows, a column letter (C) inserts columns — there is no --dimension flag.", }, 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) dr := invokeToolDryRun(token, ToolKindWrite, "modify_sheet_structure", input) switch { case dimInsertNeedsBeforeStyleWarning(runtime): dr.Set("warning_message", dimInsertBeforeStyleWarning) case dimInsertAnchorShifted(runtime, input): dr.Set("warning_message", fmt.Sprintf( "note: the previewed position is %q, not the %q you passed — this is not an off-by-one. --inherit-style before is emulated by anchoring one row/column earlier and inserting after it, which lands in the same place while copying the PRECEDING style. The row/column still appears at %q.", input["position"], strings.TrimSpace(runtime.Str("position")), strings.TrimSpace(runtime.Str("position")))) } 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 := dimInsertInput(runtime, token, sheetID, sheetName) if err != nil { return err } if dimInsertNeedsBeforeStyleWarning(runtime) { fmt.Fprintln(runtime.IO().ErrOut, dimInsertBeforeStyleWarning) } 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 in one batch request (irreversible; fail-fast, no rollback).", 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 } if _, err := validateDropdownRanges(runtime); err != nil { return err } 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 in one batch request (fail-fast, no rollback).", 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 FormulaVerify = common.Shortcut{ Service: "sheets", Command: "+formula-verify", Description: "Scan formulas / cell errors and return a recalc.py-shaped status report (success / errors_found / partial). Use --ai-only to poll AI-formula compute status only.", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+formula-verify"), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } if err := validateFormulaVerifySheetSelector(runtime); err != nil { return err } return validateFormulaVerifyLimits(runtime) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) return invokeToolDryRun(token, ToolKindRead, "verify_formula", formulaVerifyInput(runtime, 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, "verify_formula", formulaVerifyInput(runtime, token)) if err != nil { return err } runtime.Out(out, nil) if runtime.Bool("exit-on-error") { return formulaVerifyExitOnError(out) } return nil }, }
FormulaVerify wraps verify_formula. Sheet selection is optional (both --sheet-id and --sheet-name are repeatable); when omitted, the tool scans every visible sub-sheet's current_region.
var HistoryList = common.Shortcut{ Service: "sheets", Command: "+history-list", Description: "List a spreadsheet's edit history versions (history_version_id, create_time, action, all_block_revision).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: append(historyLocatorFlags(), common.Flag{Name: "end-version", Type: "int", Desc: "Max version to query (descending pagination). Omit on the first call; pass the previous response's next_end_version on subsequent pages."}, ), 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, "history_list", historyListInput(runtime, 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, "history_list", historyListInput(runtime, token)) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Capture a history_version_id from the result to feed +history-revert.", "For older history, capture next_end_version from the response and pass it as --end-version on the next call (omitted by the server when the earliest page is reached).", }, }
HistoryList wraps the history_list tool: list a spreadsheet's history versions. Each item carries history_version_id / create_time / action / all_block_revision (projected server-side). An empty sheet yields an empty list and exit 0.
Backward pagination: --end-version (optional int) maps to the tool's `end_version` parameter. Omit on the first call to fetch the latest page. On subsequent pages pass the previous response's next_end_version as --end-version. The tool returns next_end_version + has_more only when more history exists; both fields are absent at the earliest page.
var HistoryRevert = common.Shortcut{ Service: "sheets", Command: "+history-revert", Description: "Revert a spreadsheet to a given history version (asynchronous; poll with +history-revert-status).", Risk: "high-risk-write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: historyRevertFlags(), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } _, err := validateHistoryVersionID(runtime) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) versionID := strings.TrimSpace(runtime.Str("history-version-id")) return invokeToolDryRun(token, ToolKindWrite, "history_revert", historyRevertInput(token, versionID)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetTokenExec(runtime) if err != nil { return err } versionID, err := validateHistoryVersionID(runtime) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "history_revert", historyRevertInput(token, versionID)) if err != nil { return err } runtime.Out(out, nil) return nil }, Tips: []string{ "Revert overwrites the current spreadsheet content. Always run with --dry-run first to verify the target spreadsheet and history_version_id.", "Revert is asynchronous — pass the returned id to +history-revert-status to track in-progress / success / failure.", }, }
HistoryRevert wraps the history_revert tool (write): asynchronously revert a spreadsheet to the given history version. --history-version-id is required at the cli surface (cobra MarkFlagRequired); a missing flag fails before Validate runs with cobra's standard "required flag(s)" error (which the dispatcher classifies as a typed *errs.ValidationError, exit 2). We still trim + reject empty / control-char values in Validate to catch the case where cobra accepts --history-version-id with an empty-string value.
var HistoryRevertStatus = common.Shortcut{ Service: "sheets", Command: "+history-revert-status", Description: "Poll the status of a history revert (in-progress / success / failure).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: historyRevertStatusFlags(), Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, err := resolveSpreadsheetToken(runtime); err != nil { return err } _, err := validateTransactionID(runtime) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) txnID := strings.TrimSpace(runtime.Str("transaction-id")) return invokeToolDryRun(token, ToolKindRead, "history_revert_status", historyRevertStatusInput(token, txnID)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetTokenExec(runtime) if err != nil { return err } txnID, err := validateTransactionID(runtime) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindRead, "history_revert_status", historyRevertStatusInput(token, txnID)) if err != nil { return err } runtime.Out(out, nil) return nil }, }
HistoryRevertStatus wraps the history_revert_status tool (read): poll the outcome of a prior +history-revert. The tool output distinguishes in-progress / success / failure and is passed through verbatim.
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 RevisionGet = common.Shortcut{ Service: "sheets", Command: "+revision-get", Description: "Get the spreadsheet's current document revision (version number).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+revision-get"), 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 } rev, err := projectRevision(out) if err != nil { return err } runtime.Out(map[string]interface{}{"revision": rev}, nil) return nil }, Tips: []string{ "The revision is the version anchor for recover / undo; every read and write tool response already carries it.", }, }
─── lark_sheet_revision_get ───────────────────────────────────────────
RevisionGet is a read-only derivative over get_workbook_structure that projects out only the document revision (version number). The backend surfaces `revision` on every read/write tool response, so this shortcut needs no dedicated backend tool — it issues the lightest existing read (no range, just the workbook token) and narrows the payload to the single field callers want.
The revision is the anchor for recover / undo. Callers that have just run a write already have it in that write's response; +revision-get is the explicit, zero-side-effect way to fetch the current value on its own.
var RowsResize = common.Shortcut{ Service: "sheets", Command: "+rows-resize", Description: "Resize rows in pixels: --range + --height <px> for one uniform height, --heights '{\"1\":50,\"2:20\":30,\"21\":\"auto\"}' for per-row heights in one batch request, or --type standard/auto (--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: resizeDryRun("row"), Execute: resizeExecute("row"), }
RowsResize wraps resize_range for row heights. Pass --range + --height <px> for a uniform pixel height, --heights '{"1":50,"2:20":30}' for per-row heights, or --type standard/auto for non-pixel modes.
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"), Tips: []string{ "Example: lark-cli sheets +sheet-copy --url <URL> --sheet-name 数据源 --title 数据源-副本", "--sheet-name / --sheet-id selects the SOURCE sheet; the copy's new name goes in --title.", }, 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 SheetList = common.Shortcut{ Service: "sheets", Command: "+sheet-list", Description: "List a spreadsheet's sub-sheets with their metadata (sheet_id, title, dimensions, freeze, hidden).", Risk: "read", Scopes: []string{"sheets:spreadsheet:read"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Hidden: true, Flags: flagsFor("+sheet-list"), 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 } sheets, err := projectSheets(out) if err != nil { return err } runtime.Out(sheets, nil) return nil }, Tips: []string{ "+workbook-info is the documented command for this: it returns these same sheets alongside the rest of the workbook structure.", }, }
─── lark_sheet_sheet_list ─────────────────────────────────────────────
SheetList is a read-only derivative over get_workbook_structure that projects out only the sub-sheet array. +workbook-info is the documented way to read a workbook's structure; +sheet-list exists because callers reach for that name unprompted — the sheets surface has a whole +sheet-* family (+sheet-create / +sheet-copy / +sheet-delete / +sheet-info / ...), so "list the sheets" spells itself +sheet-list. The miss is not self-correcting either: internal/suggest ranks shared prefixes first, so the "did you mean" hint points at +sheet-create and its siblings, never at +workbook-info.
Hidden on both surfaces on purpose: absent from `sheets --help` (the Hidden field below) and from the lark-sheets skill docs (doc_hidden_shortcuts in sheet-skill-spec's canonical-spec/surfaces/bundle.json). Callers who read either surface are never offered a second name for what +workbook-info already does; the command only ever answers someone who typed it anyway.
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 StylesPut = common.Shortcut{ Service: "sheets", Command: "+styles-put", Description: "Apply one declarative visual spec (styles/merges/row-col sizes/freeze) to existing sheets in one batch request (fail-fast, no rollback).", Risk: "write", Scopes: []string{"sheets:spreadsheet:write_only"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: flagsFor("+styles-put"), Tips: []string{ `Example: lark-cli sheets +styles-put --url <URL> --styles '{"styles":[{"name":"Sheet1","cell_styles":[{"range":"A1:F1","font_weight":"bold"}],"freeze":{"rows":1}}]}'`, "Same --styles vocabulary as +workbook-create / +table-put; one item per target sheet, name = the real sheet name.", "Style stamps are safe to re-run; the whole spec goes out as one batch request — fail-fast, and applied sub-ops are NOT rolled back.", }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetToken(runtime) if err != nil { return err } _, err = stylesPutOperations(runtime, token) return err }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token, _ := resolveSpreadsheetToken(runtime) ops, _ := stylesPutOperations(runtime, token) return invokeToolDryRun(token, ToolKindWrite, "batch_update", map[string]interface{}{ "excel_id": token, "operations": ops, }) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token, err := resolveSpreadsheetTokenExec(runtime) if err != nil { return err } ops, err := stylesPutOperations(runtime, token) if err != nil { return err } out, err := callTool(ctx, runtime, token, ToolKindWrite, "batch_update", map[string]interface{}{ "excel_id": token, "operations": ops, }) if err != nil { return err } runtime.Out(out, nil) return nil }, }
─── +styles-put ──────────────────────────────────────────────────────
Declarative visual spec for EXISTING spreadsheets. Eval attribution showed ~73% of real +batch-update calls were pure formatting finishers (style stamps + merges + resizes + freeze) hand-assembled as imperative operations arrays — the top error surface. +styles-put replaces that with the {styles:[...]} protocol already shared by +workbook-create / +table-put --styles (identical vocabulary, parsed by the same parseWorkbookCreateStyleItem), applied to a live workbook and expanded client-side into ONE atomic batch_update.
Per-sheet expansion order (server behavior verified live: style stamps over merged regions are allowed — the top-left-only restriction applies to value writes, not styles):
cell_merges → cell_styles → row_sizes → col_sizes → freeze
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() rng := strings.TrimSpace(runtime.Str("range")) if rng != "" && (strings.TrimSpace(runtime.Str("sheet-id")) != "" || strings.TrimSpace(runtime.Str("sheet-name")) != "") { } else { 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) } 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", "cell_limit": unboundedReadLimit, } if n, ok := maxCharsInput(runtime); ok { input["max_chars"] = n } 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)) budget := maxCharsBudget(runtime) var unread []string for i, t := range targets { remaining := 0 if budget > 0 { remaining = budget - consumedChars(sheets) if remaining <= 0 { for _, rest := range targets[i:] { unread = append(unread, rest.name) } break } } spec, err := readSheetAsSpec(ctx, runtime, token, t, userRange, noHeader, remaining) if err != nil { return err } sheets = append(sheets, spec) } payload := map[string]interface{}{"sheets": sheets} if len(unread) > 0 { payload["truncated"] = true payload["unread_sheets"] = unread payload["truncation_warning"] = fmt.Sprintf("the %d-char read budget was exhausted before %d sheet(s) were read (%s); re-run per sheet with --sheet-name, or raise --max-chars", budget, len(unread), strings.Join(unread, ", ")) } return emitReadResult(runtime, payload) }, 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 + \"N/A\") 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 } styles, err := parseWorkbookCreateSheetStyles(runtime, payload, true) if err != nil { return err } if err := payload.checkCellBudgetWithStyles(styles); err != nil { return err } return checkStylesAnchors(payload, styles, false) }, 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, true) if err != nil { return err } return tablePutWrite(ctx, runtime, token, payload, styles) }, Tips: []string{ `Example: lark-cli sheets +table-put --url <URL> --sheets '{"sheets":[{"name":"S1","columns":["City","Rev"],"dtypes":{"Rev":"float64"},"data":[["SH",1234.5]]}]}'`, "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 } styles, err := parseWorkbookCreateSheetStyles(runtime, payload, false) if err != nil { return err } if err := payload.checkCellBudgetWithStyles(styles); err != nil { return err } return checkStylesAnchors(payload, styles, true) } sheetStyles, err := parseValuesSheetStyles(runtime) if err != nil { return err } payload, err := buildValuesPayload(runtime, sheetStyles) if err != nil { return err } return checkStylesAnchors(payload, sheetStyles, true) }, 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) matrix, _ = applyWorkbookCreateStylesToMatrix(matrix, sheetStyles.styleFor(i), col0, row0, fmt.Sprintf("--styles for sheet %q", s.Name)) rng := tablePutFullRange(s, len(matrix)) writeCols := len(s.Columns) if len(matrix) > 0 { writeCols = len(matrix[0]) rng = fmt.Sprintf("%s%d:%s%d", columnIndexToLetter(col0), row0+1, columnIndexToLetter(col0+writeCols-1), row0+len(matrix)) } input := map[string]interface{}{ "excel_id": "<new-token>", "sheet_name": s.Name, "range": rng, "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), writeCols)). 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 { params, err := workbookImportParams(runtime) if err != nil { return err } return drive.ValidateImport(params) }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { params, err := workbookImportParams(runtime) if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) } dry := drive.PlanImportDryRun(runtime, params) if note := workbookImportMislabelNote(params); note != "" { dry.Desc(note) } return dry }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { params, err := workbookImportParams(runtime) if err != nil { return err } if note := workbookImportMislabelNote(params); note != "" { fmt.Fprintln(runtime.IO().ErrOut, note) } return drive.RunImport(ctx, runtime, params) }, }
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 InstallUnknownSubcommandHints ¶ added in v1.0.88
InstallUnknownSubcommandHints hooks the sheets group's Args validator, which cobra runs before the group's RunE. That ordering is what keeps this inside sheets: the framework's unknown-subcommand guard installs on RunE and never touches Args, so the two compose and every name this table does not claim still reaches the framework's ranked "did you mean one of: …".
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
- chart_examples.go
- flag_defs.go
- flag_defs_gen.go
- flag_ergonomics.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_changeset.go
- lark_sheet_formula_verify.go
- lark_sheet_history_list.go
- lark_sheet_history_revert.go
- lark_sheet_object_crud.go
- lark_sheet_object_list.go
- lark_sheet_range_operations.go
- lark_sheet_read_data.go
- lark_sheet_revision_get.go
- lark_sheet_search_replace.go
- lark_sheet_sheet_list.go
- lark_sheet_sheet_structure.go
- lark_sheet_styles_put.go
- lark_sheet_table_io.go
- lark_sheet_workbook.go
- lark_sheet_write_cells.go
- range_sheet_prefix.go
- read_output.go
- sheet_ai_api.go
- shortcuts.go
- style_vocab.go
- subcommand_ergonomics.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. |