diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index 907c5dfe6..770e0b351 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -25,6 +25,32 @@ } ] }, + "+get-revision": { + "risk": "read", + "flags": [ + { + "name": "url", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet locator" + }, + { + "name": "spreadsheet-token", + "kind": "public", + "type": "string", + "required": "xor", + "desc": "Spreadsheet locator" + }, + { + "name": "dry-run", + "kind": "system", + "type": "bool", + "required": "optional", + "desc": "" + } + ] + }, "+sheet-create": { "risk": "write", "flags": [ diff --git a/shortcuts/sheets/flag_defs_gen.go b/shortcuts/sheets/flag_defs_gen.go index 2c324f05f..b32e0c771 100644 --- a/shortcuts/sheets/flag_defs_gen.go +++ b/shortcuts/sheets/flag_defs_gen.go @@ -976,4 +976,12 @@ var flagDefs = map[string]commandDef{ {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, }, }, + "+get-revision": { + Risk: "read", + Flags: []flagDef{ + {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet locator"}, + {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet locator"}, + {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, + }, + }, } diff --git a/shortcuts/sheets/lark_sheet_get_revision.go b/shortcuts/sheets/lark_sheet_get_revision.go new file mode 100644 index 000000000..a904f7e64 --- /dev/null +++ b/shortcuts/sheets/lark_sheet_get_revision.go @@ -0,0 +1,83 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import ( + "context" + + "github.com/larksuite/cli/errs" + "github.com/larksuite/cli/shortcuts/common" +) + +// ─── lark_sheet_get_revision ─────────────────────────────────────────── +// +// GetRevision 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; +get-revision is the +// explicit, zero-side-effect way to fetch the current value on its own. +var GetRevision = common.Shortcut{ + Service: "sheets", + Command: "+get-revision", + 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("+get-revision"), + 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.", + }, +} + +// projectRevision narrows a get_workbook_structure response to its `revision` +// field. An absent revision means the backend predates revision injection on +// read responses; surface that as an explicit error rather than emitting a +// silent null. +func projectRevision(out interface{}) (interface{}, error) { + obj, ok := out.(map[string]interface{}) + if !ok { + return nil, errs.NewInternalError(errs.SubtypeInvalidResponse, + "get_workbook_structure returned non-object output") + } + rev, ok := obj["revision"] + if !ok { + return nil, errs.NewInternalError(errs.SubtypeInvalidResponse, + "get_workbook_structure did not return a revision (backend may not support it yet)") + } + return rev, nil +} diff --git a/shortcuts/sheets/lark_sheet_get_revision_test.go b/shortcuts/sheets/lark_sheet_get_revision_test.go new file mode 100644 index 000000000..f36cf22cc --- /dev/null +++ b/shortcuts/sheets/lark_sheet_get_revision_test.go @@ -0,0 +1,37 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import "testing" + +func TestProjectRevision(t *testing.T) { + t.Parallel() + + t.Run("extracts revision from a workbook-structure object", func(t *testing.T) { + out := map[string]interface{}{ + "revision": float64(60), + "sheets": []interface{}{map[string]interface{}{"sheet_id": "Nh34WX"}}, + } + got, err := projectRevision(out) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != float64(60) { + t.Errorf("revision = %v, want 60", got) + } + }) + + t.Run("errors when revision is absent", func(t *testing.T) { + out := map[string]interface{}{"sheets": []interface{}{}} + if _, err := projectRevision(out); err == nil { + t.Error("expected an error when revision is missing, got nil") + } + }) + + t.Run("errors on a non-object output", func(t *testing.T) { + if _, err := projectRevision("not-an-object"); err == nil { + t.Error("expected an error for non-object output, got nil") + } + }) +} diff --git a/shortcuts/sheets/shortcuts.go b/shortcuts/sheets/shortcuts.go index c0e4d9499..4a8621eb2 100644 --- a/shortcuts/sheets/shortcuts.go +++ b/shortcuts/sheets/shortcuts.go @@ -70,6 +70,7 @@ func shortcutList() []common.Shortcut { return []common.Shortcut{ // lark_sheet_workbook WorkbookInfo, + GetRevision, SheetCreate, SheetDelete, SheetRename,