diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index 6df7df78a..ec032f10e 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -1,4 +1,37 @@ { + "+recover": { + "risk": "write", + "flags": [ + { + "name": "url", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet URL (XOR with `--spreadsheet-token`)" + }, + { + "name": "spreadsheet-token", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet token (XOR with `--url`)" + }, + { + "name": "to-revision", + "kind": "own", + "type": "int", + "required": "required", + "desc": "Restore the whole spreadsheet to this revision (a revision number returned by a prior write)" + }, + { + "name": "dry-run", + "kind": "system", + "type": "bool", + "required": "optional", + "desc": "" + } + ] + }, "+workbook-info": { "risk": "read", "flags": [ @@ -54,7 +87,7 @@ "kind": "own", "type": "int", "required": "optional", - "desc": "Insert position (0-based); appended to the end when omitted", + "desc": "Insert position; appended to the end when omitted", "default": "-1" }, { @@ -2020,7 +2053,7 @@ "kind": "own", "type": "string", "required": "required", - "desc": "RFC 4180 CSV text; values or formulas (a leading = is evaluated as a formula); no styles / comments / images (use +cells-set for those).", + "desc": "RFC 4180 CSV text; plain values only (no formulas / styles / comments)", "input": [ "file", "stdin" diff --git a/shortcuts/sheets/flag_defs_gen.go b/shortcuts/sheets/flag_defs_gen.go index 6c3f58087..95e44d3c0 100644 --- a/shortcuts/sheets/flag_defs_gen.go +++ b/shortcuts/sheets/flag_defs_gen.go @@ -319,7 +319,7 @@ var flagDefs = map[string]commandDef{ {Name: "sheet-id", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet reference_id (XOR with `--sheet-name`)"}, {Name: "sheet-name", Kind: "public", Type: "string", Required: "xor", Desc: "Sheet name (XOR with `--sheet-id`)"}, {Name: "start-cell", Kind: "own", Type: "string", Required: "required", Desc: "Top-left A1 anchor (e.g. `A1`, `B5`; no sheet prefix — use `--sheet-id` / `--sheet-name` to select the sheet); must be a single cell, range notation not accepted; the bottom-right is inferred from CSV row/column counts", Default: "A1"}, - {Name: "csv", Kind: "own", Type: "string", Required: "required", Desc: "RFC 4180 CSV text; values or formulas (a leading = is evaluated as a formula); no styles / comments / images (use +cells-set for those).", Input: []string{"file", "stdin"}}, + {Name: "csv", Kind: "own", Type: "string", Required: "required", Desc: "RFC 4180 CSV text; plain values only (no formulas / styles / comments)", Input: []string{"file", "stdin"}}, {Name: "allow-overwrite", Kind: "own", Type: "bool", Required: "optional", Desc: "Allow overwriting (default true); set false to error if any target cell is non-empty", Default: "true"}, {Name: "range", Kind: "own", Type: "string", Required: "optional", Desc: "alias for --start-cell (parity with +csv-get / +cells-set, which locate with --range); a range like A1:H17 collapses to its top-left cell", Hidden: true}, {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, @@ -734,6 +734,15 @@ var flagDefs = map[string]commandDef{ {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, }, }, + "+recover": { + Risk: "write", + Flags: []flagDef{ + {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet URL (XOR with `--spreadsheet-token`)"}, + {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet token (XOR with `--url`)"}, + {Name: "to-revision", Kind: "own", Type: "int", Required: "required", Desc: "Restore the whole spreadsheet to this revision (a revision number returned by a prior write)"}, + {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, + }, + }, "+rows-resize": { Risk: "write", Flags: []flagDef{ diff --git a/shortcuts/sheets/lark_sheet_recover.go b/shortcuts/sheets/lark_sheet_recover.go new file mode 100644 index 000000000..24f414c38 --- /dev/null +++ b/shortcuts/sheets/lark_sheet_recover.go @@ -0,0 +1,85 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +// ─── lark_sheet_recover ─────────────────────────────────────────────── +// +// Wraps: +// - recover_to_revision (write) — powers +recover +// +// Rolls the WHOLE spreadsheet back to a past revision (the undo design doc's +// "方案 B"). Unlike +undo — which is precise, per-edit, and scoped to this CLI +// link — +recover is a full-document version restore. The facade gateway +// already owns this capability (the same revert-by-revision path the web +// "history" panel drives): it submits a single RECOVER changeset that reverts +// every sheet to the target revision and produces a new revision. The CLI only +// passes the target revision; all the work stays server-side. +// +// ⚠️ Full-table overwrite: +recover discards EVERY change made after +// --to-revision, including other collaborators' (and the web UI's) edits. Use +// it only on agent scratch spreadsheets, or when a whole-document rollback is +// acceptable. For precise, this-link-only undo, use +undo instead. +var Recover = common.Shortcut{ + Service: "sheets", + Command: "+recover", + Description: "Roll the whole spreadsheet back to a past revision (full-document restore; discards all later edits).", + Risk: "write", + Scopes: []string{"sheets:spreadsheet:write_only"}, + AuthTypes: []string{"user", "bot"}, + HasFormat: true, + Flags: flagsFor("+recover"), + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetToken(runtime) + if err != nil { + return err + } + _, err = recoverInput(runtime, token) + return err + }, + DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + token, _ := resolveSpreadsheetToken(runtime) + input, _ := recoverInput(runtime, token) + return invokeToolDryRun(token, ToolKindWrite, "recover_to_revision", input) + }, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetToken(runtime) + if err != nil { + return err + } + input, err := recoverInput(runtime, token) + if err != nil { + return err + } + out, err := callTool(ctx, runtime, token, ToolKindWrite, "recover_to_revision", input) + if err != nil { + return err + } + runtime.Out(out, nil) + return nil + }, + Tips: []string{ + "+recover is a FULL-DOCUMENT rollback — it discards every edit made after --to-revision, including other collaborators'. For precise, this-link-only undo, use +undo instead.", + "--to-revision takes a revision number returned by a prior write (the `revision` field in the response).", + "Use --dry-run to preview the recover request before running it.", + }, +} + +// recoverInput builds the recover_to_revision tool body. Network-free; shared +// by Validate, DryRun, and Execute. +func recoverInput(runtime flagView, token string) (map[string]interface{}, error) { + rev := runtime.Int("to-revision") + if rev < 1 { + return nil, common.FlagErrorf("--to-revision must be a positive revision number") + } + return map[string]interface{}{ + "excel_id": token, + "to_revision": rev, + }, nil +} diff --git a/shortcuts/sheets/shortcuts.go b/shortcuts/sheets/shortcuts.go index 005d03292..6be213f66 100644 --- a/shortcuts/sheets/shortcuts.go +++ b/shortcuts/sheets/shortcuts.go @@ -61,6 +61,9 @@ func shortcutList() []common.Shortcut { DropdownGet, TableGet, + // lark_sheet_recover + Recover, + // lark_sheet_search_replace CellsSearch, CellsReplace,