mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
refactor(sheets): sync shortcut flags with sheet-skill-spec v0.5.0
Upstream hoisted a batch of high-frequency scalar fields out of --data
into independent flags and renamed several composite-JSON flags to
match their semantic content. CLI catches up.
Renames (drop-in, same payload semantics):
- +cells-replace --replace → --replacement
- +cells-set --data → --cells
- +workbook-create --data → --values
- +batch-update --data → --operations (now a bare array;
still accepts the envelope form for
back-compat with continue_on_error)
Flat-flag hoists out of --style / --data:
- +cells-set-style / +cells-batch-set-style
--style JSON drops; replaced by 11 flat style flags
(--background-color / --font-color / --font-size / --font-style /
--font-weight / --font-line / --horizontal-alignment /
--vertical-alignment / --word-wrap / --number-format) plus
--border-styles for the one field that's still nested. Both
shortcuts share styleFlatFlags() + buildCellStyleFromFlags().
- +cells-batch-set-style also drops the [{ranges, style}] array shape
in favor of one --ranges + the same flat style flags applied to
all of them.
Object CRUD --data → --properties everywhere (chart / pivot / cond-format
/ filter / filter-view / sparkline / float-image). Per-skill scalar
hoists merged into properties via an enhanceCreate/UpdateInput callback:
- +pivot-create adds --source (required), --range
(and continues to expose --target-sheet-id /
--target-position at top level)
- +cond-format-{create,update}
adds --rule-type (enum) + --ranges (JSON array);
merged into properties.rule.type and
properties.ranges respectively
- +filter-view-{create,update}
adds --view-name and --range; both override
their properties.* counterparts
- +filter-update adds first-class --range (was buried in --data)
Float-image is fully hoisted — no --properties flag at all. Ten flat
flags (--image-name / --image-token | --image-uri / --position-row /
--position-col / --size-width / --size-height / --offset-row /
--offset-col / --z-index) compose the properties block. Implemented as
its own factory (newFloatImageWriteShortcut) since it diverges from the
shared CRUD spec.
Tests track every flag renamed and add explicit cases for the new flag
combos. go test -race -cover stays at 60.3 %.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -116,7 +116,7 @@ func TestExecute_CellsSet(t *testing.T) {
|
||||
out, err := runShortcutWithStubs(t, CellsSet, []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--range", "A1:B1",
|
||||
"--data", `{"cells":[[{"value":"x"},{"value":"y"}]]}`,
|
||||
"--cells", `{"cells":[[{"value":"x"},{"value":"y"}]]}`,
|
||||
}, stub)
|
||||
if err != nil {
|
||||
t.Fatalf("execute failed: %v\nout=%s", err, out)
|
||||
@@ -217,7 +217,7 @@ func TestExecute_FilterCreate(t *testing.T) {
|
||||
out, err := runShortcutWithStubs(t, FilterCreate, []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--range", "A1:F100",
|
||||
"--data", `{"conditions":[{"col":"B","filter_type":"multiValue","expected":["x"]}]}`,
|
||||
"--properties", `{"rules":[{"col":"B","filter_type":"multiValue","expected":["x"]}]}`,
|
||||
}, stub)
|
||||
if err != nil {
|
||||
t.Fatalf("execute failed: %v\nout=%s", err, out)
|
||||
@@ -228,8 +228,8 @@ func TestExecute_FilterCreate(t *testing.T) {
|
||||
if props["range"] != "A1:F100" {
|
||||
t.Errorf("properties.range = %v", props["range"])
|
||||
}
|
||||
if props["conditions"] == nil {
|
||||
t.Errorf("conditions missing: %#v", props)
|
||||
if props["rules"] == nil {
|
||||
t.Errorf("rules missing: %#v", props)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ func TestExecute_BatchUpdate_Raw(t *testing.T) {
|
||||
stub := toolOutputStub(testToken, "write", `{"results":[{"ok":true}]}`)
|
||||
_, err := runShortcutWithStubs(t, BatchUpdate, []string{
|
||||
"--url", testURL,
|
||||
"--data", `{"operations":[{"tool":"set_cell_range","params":{"excel_id":"shtcnTestTOK","range":"A1","cells":[[{"value":1}]]}}]}`,
|
||||
"--operations", `[{"tool":"set_cell_range","params":{"excel_id":"shtcnTestTOK","range":"A1","cells":[[{"value":1}]]}}]`,
|
||||
"--continue-on-error",
|
||||
"--yes",
|
||||
}, stub)
|
||||
@@ -276,7 +276,7 @@ func TestExecute_WorkbookCreate(t *testing.T) {
|
||||
out, err := runShortcutWithStubs(t, WorkbookCreate, []string{
|
||||
"--title", "Sales",
|
||||
"--headers", `["Name","Score"]`,
|
||||
"--data", `[["alice",95]]`,
|
||||
"--values", `[["alice",95]]`,
|
||||
}, create, fill)
|
||||
if err != nil {
|
||||
t.Fatalf("execute failed: %v\nout=%s", err, out)
|
||||
@@ -324,7 +324,7 @@ func TestExecute_ChartCreate(t *testing.T) {
|
||||
stub := toolOutputStub(testToken, "write", `{"chart_id":"chartNEW"}`)
|
||||
out, err := runShortcutWithStubs(t, ChartCreate, []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--data", `{"type":"line"}`,
|
||||
"--properties", `{"type":"line"}`,
|
||||
}, stub)
|
||||
if err != nil {
|
||||
t.Fatalf("execute failed: %v", err)
|
||||
|
||||
@@ -166,3 +166,101 @@ func requireJSONArray(runtime *common.RuntimeContext, name string) ([]interface{
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// ─── style flags (shared by +cells-set-style and +cells-batch-set-style) ─
|
||||
|
||||
var (
|
||||
fontStyleEnum = []string{"normal", "italic"}
|
||||
fontWeightEnum = []string{"normal", "bold"}
|
||||
fontLineEnum = []string{"none", "underline", "line-through"}
|
||||
hAlignEnum = []string{"left", "center", "right"}
|
||||
vAlignEnum = []string{"top", "middle", "bottom"}
|
||||
wordWrapEnum = []string{"overflow", "auto-wrap", "word-clip"}
|
||||
)
|
||||
|
||||
// styleFlatFlags returns the 11 flat style flags + --border-styles that both
|
||||
// +cells-set-style and +cells-batch-set-style expose. Keeping them in one
|
||||
// place stops the two shortcuts from drifting apart.
|
||||
func styleFlatFlags() []common.Flag {
|
||||
return []common.Flag{
|
||||
{Name: "background-color", Desc: "hex background color (e.g. #ffffff)"},
|
||||
{Name: "font-color", Desc: "hex font color (e.g. #000000)"},
|
||||
{Name: "font-size", Type: "int", Desc: "font size in pixels (e.g. 10, 12, 14)"},
|
||||
{Name: "font-style", Enum: fontStyleEnum, Desc: "normal / italic"},
|
||||
{Name: "font-weight", Enum: fontWeightEnum, Desc: "normal / bold"},
|
||||
{Name: "font-line", Enum: fontLineEnum, Desc: "none / underline / line-through"},
|
||||
{Name: "horizontal-alignment", Enum: hAlignEnum, Desc: "left / center / right"},
|
||||
{Name: "vertical-alignment", Enum: vAlignEnum, Desc: "top / middle / bottom"},
|
||||
{Name: "word-wrap", Enum: wordWrapEnum, Desc: "overflow (default) / auto-wrap / word-clip"},
|
||||
{Name: "number-format", Desc: "number format string (e.g. @, 0.00, $#,##0.00, mm/dd/yyyy)"},
|
||||
{Name: "border-styles", Input: []string{common.File, common.Stdin},
|
||||
Desc: "border JSON: { top, bottom, left, right } each = { color, style, weight }"},
|
||||
}
|
||||
}
|
||||
|
||||
// buildCellStyleFromFlags reads the 11 flat style flags and returns the
|
||||
// cell_styles map expected by set_cell_range. Skips any flag the user
|
||||
// didn't set so partial styles work.
|
||||
func buildCellStyleFromFlags(runtime *common.RuntimeContext) map[string]interface{} {
|
||||
style := map[string]interface{}{}
|
||||
if v := runtime.Str("background-color"); v != "" {
|
||||
style["background_color"] = v
|
||||
}
|
||||
if v := runtime.Str("font-color"); v != "" {
|
||||
style["font_color"] = v
|
||||
}
|
||||
if runtime.Changed("font-size") && runtime.Int("font-size") > 0 {
|
||||
style["font_size"] = runtime.Int("font-size")
|
||||
}
|
||||
if v := runtime.Str("font-style"); v != "" {
|
||||
style["font_style"] = v
|
||||
}
|
||||
if v := runtime.Str("font-weight"); v != "" {
|
||||
style["font_weight"] = v
|
||||
}
|
||||
if v := runtime.Str("font-line"); v != "" {
|
||||
style["font_line"] = v
|
||||
}
|
||||
if v := runtime.Str("horizontal-alignment"); v != "" {
|
||||
style["horizontal_alignment"] = v
|
||||
}
|
||||
if v := runtime.Str("vertical-alignment"); v != "" {
|
||||
style["vertical_alignment"] = v
|
||||
}
|
||||
if v := runtime.Str("word-wrap"); v != "" {
|
||||
style["word_wrap"] = v
|
||||
}
|
||||
if v := runtime.Str("number-format"); v != "" {
|
||||
style["number_format"] = v
|
||||
}
|
||||
return style
|
||||
}
|
||||
|
||||
// borderStylesFromFlag parses --border-styles as a JSON object (top/bottom/
|
||||
// left/right with style sub-objects). Returns nil when the flag is empty.
|
||||
func borderStylesFromFlag(runtime *common.RuntimeContext) (map[string]interface{}, error) {
|
||||
if runtime.Str("border-styles") == "" {
|
||||
return nil, nil
|
||||
}
|
||||
v, err := parseJSONFlag(runtime, "border-styles")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, common.FlagErrorf("--border-styles must be a JSON object")
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// requireAnyStyleFlag ensures at least one style-defining flag (style or
|
||||
// border) is set — otherwise the request would do nothing.
|
||||
func requireAnyStyleFlag(runtime *common.RuntimeContext) error {
|
||||
if len(buildCellStyleFromFlags(runtime)) > 0 {
|
||||
return nil
|
||||
}
|
||||
if runtime.Str("border-styles") != "" {
|
||||
return nil
|
||||
}
|
||||
return common.FlagErrorf("at least one style flag is required (e.g. --background-color, --font-weight, --border-styles)")
|
||||
}
|
||||
|
||||
@@ -41,21 +41,20 @@ var BatchUpdate = common.Shortcut{
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: append(publicTokenFlags(),
|
||||
common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "batch payload JSON: { operations: [{tool, params}, ...] }"},
|
||||
common.Flag{Name: "operations", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "operations JSON array: [{tool, params}, ...] (or an envelope object with operations / continue_on_error)"},
|
||||
common.Flag{Name: "continue-on-error", Type: "bool", Desc: "flip the default strict transaction off; partial success is kept on disk"},
|
||||
),
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
if _, err := resolveSpreadsheetToken(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
body, err := requireJSONObject(runtime, "data")
|
||||
ops, err := parseBatchOperationsFlag(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ops, ok := body["operations"].([]interface{})
|
||||
if !ok || len(ops) == 0 {
|
||||
return common.FlagErrorf("--data.operations must be a non-empty JSON array")
|
||||
if len(ops) == 0 {
|
||||
return common.FlagErrorf("--operations must be a non-empty JSON array")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -86,45 +85,83 @@ var BatchUpdate = common.Shortcut{
|
||||
}
|
||||
|
||||
func batchUpdateRawInput(runtime *common.RuntimeContext, token string) (map[string]interface{}, error) {
|
||||
body, err := requireJSONObject(runtime, "data")
|
||||
ops, err := parseBatchOperationsFlag(runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ops, _ := body["operations"].([]interface{})
|
||||
input := map[string]interface{}{
|
||||
"excel_id": token,
|
||||
"operations": ops,
|
||||
}
|
||||
if runtime.Bool("continue-on-error") {
|
||||
input["continue_on_error"] = true
|
||||
} else if v, ok := body["continue_on_error"].(bool); ok && v {
|
||||
// honor an inline override from --data when the flag is unset
|
||||
input["continue_on_error"] = true
|
||||
} else if envelope, _ := parseJSONFlag(runtime, "operations"); envelope != nil {
|
||||
// Honor an inline override when --operations is an envelope object
|
||||
// rather than a bare operations array.
|
||||
if m, ok := envelope.(map[string]interface{}); ok {
|
||||
if v, ok := m["continue_on_error"].(bool); ok && v {
|
||||
input["continue_on_error"] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// CellsBatchSetStyle stamps one style block across many ranges atomically.
|
||||
// --data is an array of {ranges: [...], style: {...}} entries; CLI flattens
|
||||
// each (entry × range) pair into a set_cell_range operation in the batch.
|
||||
// parseBatchOperationsFlag accepts --operations as either a JSON array (the
|
||||
// operations list directly) or an envelope object { operations, continue_on_error }
|
||||
// for back-compat with the legacy --data shape. Returns the operations array.
|
||||
func parseBatchOperationsFlag(runtime *common.RuntimeContext) ([]interface{}, error) {
|
||||
v, err := parseJSONFlag(runtime, "operations")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if v == nil {
|
||||
return nil, common.FlagErrorf("--operations is required")
|
||||
}
|
||||
if arr, ok := v.([]interface{}); ok {
|
||||
return arr, nil
|
||||
}
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
if ops, ok := m["operations"].([]interface{}); ok {
|
||||
return ops, nil
|
||||
}
|
||||
}
|
||||
return nil, common.FlagErrorf("--operations must be a JSON array (or { operations: [...] } envelope)")
|
||||
}
|
||||
|
||||
// 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 CellsBatchSetStyle = common.Shortcut{
|
||||
Service: "sheets",
|
||||
Command: "+cells-batch-set-style",
|
||||
Description: "Apply styles to many sheet-prefixed ranges in one atomic batch.",
|
||||
Description: "Apply one style block to many sheet-prefixed ranges in one atomic batch.",
|
||||
Risk: "write",
|
||||
Scopes: []string{"sheets:spreadsheet:write_only"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: append(publicTokenFlags(),
|
||||
common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "JSON array: [{ranges: [\"sheet1!A1:B2\", ...], style: {...}}, ...] (each range must carry a sheet prefix)"},
|
||||
Flags: append(
|
||||
append(publicTokenFlags(),
|
||||
common.Flag{Name: "ranges", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "JSON array of sheet-prefixed A1 ranges (e.g. [\"sheet1!A1:B2\", \"sheet1!D1:E2\"])"}),
|
||||
styleFlatFlags()...,
|
||||
),
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
if _, err := resolveSpreadsheetToken(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := batchStyleEntries(runtime)
|
||||
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)
|
||||
@@ -149,83 +186,43 @@ var CellsBatchSetStyle = common.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
// batchStyleEntries validates --data is the expected array shape.
|
||||
func batchStyleEntries(runtime *common.RuntimeContext) ([]map[string]interface{}, error) {
|
||||
raw, err := requireJSONArray(runtime, "data")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(raw) == 0 {
|
||||
return nil, common.FlagErrorf("--data must contain at least one entry")
|
||||
}
|
||||
out := make([]map[string]interface{}, 0, len(raw))
|
||||
for i, v := range raw {
|
||||
entry, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, common.FlagErrorf("--data[%d] must be an object", i)
|
||||
}
|
||||
rangesRaw, ok := entry["ranges"].([]interface{})
|
||||
if !ok || len(rangesRaw) == 0 {
|
||||
return nil, common.FlagErrorf("--data[%d].ranges must be a non-empty array", i)
|
||||
}
|
||||
for j, r := range rangesRaw {
|
||||
s, ok := r.(string)
|
||||
if !ok || !strings.Contains(s, "!") {
|
||||
return nil, common.FlagErrorf("--data[%d].ranges[%d] must be a sheet-prefixed string", i, j)
|
||||
}
|
||||
}
|
||||
if _, ok := entry["style"].(map[string]interface{}); !ok {
|
||||
return nil, common.FlagErrorf("--data[%d].style must be a JSON object", i)
|
||||
}
|
||||
out = append(out, entry)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func cellsBatchSetStyleInput(runtime *common.RuntimeContext, token string) (map[string]interface{}, error) {
|
||||
entries, err := batchStyleEntries(runtime)
|
||||
ranges, err := validateDropdownRanges(runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cellStyle := buildCellStyleFromFlags(runtime)
|
||||
borderStyles, err := borderStylesFromFlag(runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
prototype := map[string]interface{}{}
|
||||
if len(cellStyle) > 0 {
|
||||
prototype["cell_styles"] = cellStyle
|
||||
}
|
||||
if borderStyles != nil {
|
||||
prototype["border_styles"] = borderStyles
|
||||
}
|
||||
var ops []interface{}
|
||||
for _, entry := range entries {
|
||||
style := entry["style"].(map[string]interface{})
|
||||
// Split border_styles out into its sibling field per set_cell_range's contract.
|
||||
cellStyle := map[string]interface{}{}
|
||||
var borderStyles interface{}
|
||||
for k, v := range style {
|
||||
if k == "border_styles" {
|
||||
borderStyles = v
|
||||
continue
|
||||
}
|
||||
cellStyle[k] = v
|
||||
for _, rng := range ranges {
|
||||
sheet, sub, err := splitSheetPrefixedRange(rng)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ranges, _ := entry["ranges"].([]interface{})
|
||||
for _, r := range ranges {
|
||||
rng := r.(string)
|
||||
sheet, sub, err := splitSheetPrefixedRange(rng)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, cols, err := rangeDimensions(sub)
|
||||
if err != nil {
|
||||
return nil, common.FlagErrorf("range %q: %v", rng, err)
|
||||
}
|
||||
proto := map[string]interface{}{"cell_styles": cellStyle}
|
||||
if borderStyles != nil {
|
||||
proto["border_styles"] = borderStyles
|
||||
}
|
||||
cells := fillCellsMatrix(rows, cols, proto)
|
||||
ops = append(ops, map[string]interface{}{
|
||||
"tool": "set_cell_range",
|
||||
"params": map[string]interface{}{
|
||||
"excel_id": token,
|
||||
"sheet_name": sheet,
|
||||
"range": sub,
|
||||
"cells": cells,
|
||||
},
|
||||
})
|
||||
rows, cols, err := rangeDimensions(sub)
|
||||
if err != nil {
|
||||
return nil, common.FlagErrorf("range %q: %v", rng, err)
|
||||
}
|
||||
cells := fillCellsMatrix(rows, cols, prototype)
|
||||
ops = append(ops, map[string]interface{}{
|
||||
"tool": "set_cell_range",
|
||||
"params": map[string]interface{}{
|
||||
"excel_id": token,
|
||||
"sheet_name": sheet,
|
||||
"range": sub,
|
||||
"cells": cells,
|
||||
},
|
||||
})
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"excel_id": token,
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestBatchUpdate_RawPassthrough(t *testing.T) {
|
||||
|
||||
body := parseDryRunBody(t, BatchUpdate, []string{
|
||||
"--url", testURL,
|
||||
"--data", `{"operations":[{"tool":"set_cell_range","params":{"excel_id":"shtcnTOK","sheet_id":"sh1","range":"A1","cells":[[{"value":42}]]}}]}`,
|
||||
"--operations", `[{"tool":"set_cell_range","params":{"excel_id":"shtcnTOK","sheet_id":"sh1","range":"A1","cells":[[{"value":42}]]}}]`,
|
||||
"--continue-on-error",
|
||||
"--yes",
|
||||
})
|
||||
@@ -35,27 +35,28 @@ func TestBatchUpdate_HighRiskWriteRequiresYes(t *testing.T) {
|
||||
t.Parallel()
|
||||
stdout, stderr, err := runShortcutCapturingErr(t, BatchUpdate, []string{
|
||||
"--url", testURL,
|
||||
"--data", `{"operations":[{"tool":"set_cell_range","params":{}}]}`,
|
||||
"--operations", `[{"tool":"set_cell_range","params":{}}]`,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected confirmation_required; stdout=%s stderr=%s", stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// TestCellsBatchSetStyle_FansOutOps verifies 2 entries × multiple ranges
|
||||
// produce one set_cell_range op per (entry, range).
|
||||
// TestCellsBatchSetStyle_FansOutOps verifies multiple ranges produce one
|
||||
// set_cell_range op each, sharing the same style flags.
|
||||
func TestCellsBatchSetStyle_FansOutOps(t *testing.T) {
|
||||
t.Parallel()
|
||||
body := parseDryRunBody(t, CellsBatchSetStyle, []string{
|
||||
"--url", testURL,
|
||||
"--data", `[{"ranges":["sheet1!A1:B2","sheet1!D1:E2"],"style":{"font":{"bold":true}}},{"ranges":["sheet1!A5:A6"],"style":{"backColor":"#ff0"}}]`,
|
||||
"--ranges", `["sheet1!A1:B2","sheet1!D1:E2","sheet1!A5:A6"]`,
|
||||
"--font-weight", "bold",
|
||||
"--background-color", "#ffff00",
|
||||
})
|
||||
input := decodeToolInput(t, body, "batch_update")
|
||||
ops, _ := input["operations"].([]interface{})
|
||||
if len(ops) != 3 {
|
||||
t.Fatalf("operations length = %d, want 3 (2 ranges × entry1 + 1 range × entry2)", len(ops))
|
||||
t.Fatalf("operations length = %d, want 3 (one per range)", len(ops))
|
||||
}
|
||||
// Every op should target set_cell_range with sheet_name carrying the prefix.
|
||||
for i, raw := range ops {
|
||||
op, _ := raw.(map[string]interface{})
|
||||
if op["tool"] != "set_cell_range" {
|
||||
@@ -65,6 +66,13 @@ func TestCellsBatchSetStyle_FansOutOps(t *testing.T) {
|
||||
if params["sheet_name"] != "sheet1" {
|
||||
t.Errorf("op[%d].sheet_name = %v, want sheet1", i, params["sheet_name"])
|
||||
}
|
||||
cells, _ := params["cells"].([]interface{})
|
||||
row, _ := cells[0].([]interface{})
|
||||
cell, _ := row[0].(map[string]interface{})
|
||||
style, _ := cell["cell_styles"].(map[string]interface{})
|
||||
if style["font_weight"] != "bold" || style["background_color"] != "#ffff00" {
|
||||
t.Errorf("op[%d] cell_styles wrong: %#v", i, style)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,13 +165,24 @@ func TestBatchUpdate_ValidationGuards(t *testing.T) {
|
||||
// batch-update with empty operations
|
||||
stdout, stderr, err = runShortcutCapturingErr(t, BatchUpdate, []string{
|
||||
"--url", testURL,
|
||||
"--data", `{"operations":[]}`,
|
||||
"--operations", `[]`,
|
||||
"--yes",
|
||||
"--dry-run",
|
||||
})
|
||||
if err == nil || !strings.Contains(stdout+stderr+err.Error(), "non-empty JSON array") {
|
||||
t.Errorf("expected empty-operations guard; got=%s|%s|%v", stdout, stderr, err)
|
||||
}
|
||||
|
||||
// dropdown-update with non-array --options (object instead) → array guard
|
||||
stdout, stderr, err = runShortcutCapturingErr(t, DropdownUpdate, []string{
|
||||
"--url", testURL,
|
||||
"--ranges", `["sheet1!A1:A2"]`,
|
||||
"--options", `{"not":"array"}`,
|
||||
"--dry-run",
|
||||
})
|
||||
if err == nil || !strings.Contains(stdout+stderr+err.Error(), "must be a JSON array") {
|
||||
t.Errorf("expected JSON array guard; got=%s|%s|%v", stdout, stderr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSplitSheetPrefixedRange exercises the helper directly.
|
||||
|
||||
@@ -37,18 +37,22 @@ type objectCRUDSpec struct {
|
||||
toolName string // e.g. "manage_chart_object"
|
||||
idFlag string // e.g. "chart-id"
|
||||
idField string // e.g. "chart_id"
|
||||
createDataDesc string // help text for --data on create
|
||||
updateDataDesc string // help text for --data on update
|
||||
createDataDesc string // help text for --properties on create
|
||||
updateDataDesc string // help text for --properties on update
|
||||
createExtraFlags []common.Flag
|
||||
// createExtraInput, when set, mutates the tool input after the standard
|
||||
// fields are written. Used by pivot to inject --target-sheet-id /
|
||||
// --target-position alongside properties.
|
||||
createExtraInput func(rt *common.RuntimeContext, input map[string]interface{})
|
||||
updateExtraFlags []common.Flag
|
||||
// enhanceCreateInput / enhanceUpdateInput, when set, mutate the tool
|
||||
// input after the standard fields are written. Used to inject
|
||||
// shortcut-specific flat flags into the input (typically into the
|
||||
// properties map). The callback is responsible for navigating to the
|
||||
// right nesting level.
|
||||
enhanceCreateInput func(rt *common.RuntimeContext, input map[string]interface{})
|
||||
enhanceUpdateInput func(rt *common.RuntimeContext, input map[string]interface{})
|
||||
}
|
||||
|
||||
func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
flags := append(publicSheetFlags(),
|
||||
common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true, Desc: spec.createDataDesc},
|
||||
common.Flag{Name: "properties", Input: []string{common.File, common.Stdin}, Required: true, Desc: spec.createDataDesc},
|
||||
)
|
||||
flags = append(flags, spec.createExtraFlags...)
|
||||
return common.Shortcut{
|
||||
@@ -67,7 +71,7 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
if _, _, err := resolveSheetSelector(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := requireJSONObject(runtime, "data")
|
||||
_, err := requireJSONObject(runtime, "properties")
|
||||
return err
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
@@ -100,7 +104,7 @@ func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
}
|
||||
|
||||
func objectCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName string, spec objectCRUDSpec) (map[string]interface{}, error) {
|
||||
props, err := requireJSONObject(runtime, "data")
|
||||
props, err := requireJSONObject(runtime, "properties")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,8 +114,8 @@ func objectCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName
|
||||
"properties": props,
|
||||
}
|
||||
sheetSelectorForToolInput(input, sheetID, sheetName)
|
||||
if spec.createExtraInput != nil {
|
||||
spec.createExtraInput(runtime, input)
|
||||
if spec.enhanceCreateInput != nil {
|
||||
spec.enhanceCreateInput(runtime, input)
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
@@ -125,9 +129,10 @@ func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
})
|
||||
}
|
||||
flags = append(flags, common.Flag{
|
||||
Name: "data", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Name: "properties", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: spec.updateDataDesc,
|
||||
})
|
||||
flags = append(flags, spec.updateExtraFlags...)
|
||||
return common.Shortcut{
|
||||
Service: "sheets",
|
||||
Command: spec.commandPrefix + "-update",
|
||||
@@ -147,7 +152,7 @@ func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
if spec.idFlag != "" && strings.TrimSpace(runtime.Str(spec.idFlag)) == "" {
|
||||
return common.FlagErrorf("--%s is required", spec.idFlag)
|
||||
}
|
||||
_, err := requireJSONObject(runtime, "data")
|
||||
_, err := requireJSONObject(runtime, "properties")
|
||||
return err
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
@@ -180,7 +185,7 @@ func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut {
|
||||
}
|
||||
|
||||
func objectUpdateInput(runtime *common.RuntimeContext, token, sheetID, sheetName string, spec objectCRUDSpec) (map[string]interface{}, error) {
|
||||
props, err := requireJSONObject(runtime, "data")
|
||||
props, err := requireJSONObject(runtime, "properties")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -193,6 +198,9 @@ func objectUpdateInput(runtime *common.RuntimeContext, token, sheetID, sheetName
|
||||
if spec.idFlag != "" {
|
||||
input[spec.idField] = strings.TrimSpace(runtime.Str(spec.idFlag))
|
||||
}
|
||||
if spec.enhanceUpdateInput != nil {
|
||||
spec.enhanceUpdateInput(runtime, input)
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
@@ -276,25 +284,38 @@ var ChartCreate = newObjectCreateShortcut(chartSpec)
|
||||
var ChartUpdate = newObjectUpdateShortcut(chartSpec)
|
||||
var ChartDelete = newObjectDeleteShortcut(chartSpec)
|
||||
|
||||
// pivot — adds --target-sheet-id / --target-position on create
|
||||
// pivot — create exposes --target-sheet-id / --target-position (top-level
|
||||
// of the tool input) plus --source / --range hoisted from properties.
|
||||
var pivotSpec = objectCRUDSpec{
|
||||
commandPrefix: "+pivot",
|
||||
toolName: "manage_pivot_table_object",
|
||||
idFlag: "pivot-table-id",
|
||||
idField: "pivot_table_id",
|
||||
createDataDesc: "pivot table properties JSON: { data_range, rows, columns, values, filters, show_row_grand_total, show_col_grand_total }",
|
||||
createDataDesc: "pivot table properties JSON: { rows, columns, values, filters, show_row_grand_total, ... }; --source / --range cover the common scalar fields",
|
||||
updateDataDesc: "full or partial pivot properties JSON (`+pivot-list --pivot-table-id <id>` first, then patch)",
|
||||
createExtraFlags: []common.Flag{
|
||||
{Name: "target-sheet-id", Desc: "destination sheet id for the pivot table; omit to auto-create a fresh sheet (recommended)"},
|
||||
{Name: "target-position", Default: "A1", Desc: "destination start cell, default A1"},
|
||||
{Name: "source", Required: true, Desc: "pivot source range, e.g. Sheet1!A1:D100 (--source overrides any properties.source)"},
|
||||
{Name: "range", Desc: "destination top-left A1 cell, e.g. F1 (--range overrides any properties.range)"},
|
||||
},
|
||||
createExtraInput: func(rt *common.RuntimeContext, input map[string]interface{}) {
|
||||
enhanceCreateInput: func(rt *common.RuntimeContext, input map[string]interface{}) {
|
||||
if v := strings.TrimSpace(rt.Str("target-sheet-id")); v != "" {
|
||||
input["target_sheet_id"] = v
|
||||
}
|
||||
if v := strings.TrimSpace(rt.Str("target-position")); v != "" && v != "A1" {
|
||||
input["target_position"] = v
|
||||
}
|
||||
props, _ := input["properties"].(map[string]interface{})
|
||||
if props == nil {
|
||||
return
|
||||
}
|
||||
if v := strings.TrimSpace(rt.Str("source")); v != "" {
|
||||
props["source"] = v
|
||||
}
|
||||
if v := strings.TrimSpace(rt.Str("range")); v != "" {
|
||||
props["range"] = v
|
||||
}
|
||||
},
|
||||
}
|
||||
var PivotCreate = newObjectCreateShortcut(pivotSpec)
|
||||
@@ -302,14 +323,50 @@ var PivotUpdate = newObjectUpdateShortcut(pivotSpec)
|
||||
var PivotDelete = newObjectDeleteShortcut(pivotSpec)
|
||||
|
||||
// conditional format — CLI surface uses --rule-id (short), wired to the
|
||||
// tool's conditional_format_id on the wire.
|
||||
// tool's conditional_format_id on the wire. --rule-type and --ranges are
|
||||
// hoisted out of properties (both required, set on every CRUD write).
|
||||
var condFormatRuleTypeEnum = []string{
|
||||
"cellValue", "formula", "duplicate", "unique",
|
||||
"topBottom", "aboveBelowAverage", "dataBar", "colorScale",
|
||||
"iconSet", "textContains", "dateOccurring", "blankCell", "errorCell",
|
||||
}
|
||||
var condFormatExtraFlags = []common.Flag{
|
||||
{Name: "rule-type", Required: true, Enum: condFormatRuleTypeEnum,
|
||||
Desc: "rule type enum (cellValue / formula / duplicate / ...); merged into properties.rule.type"},
|
||||
{Name: "ranges", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "A1 ranges JSON array (e.g. [\"A1:A100\",\"C2:C50\"]); merged into properties.ranges"},
|
||||
}
|
||||
var condFormatEnhance = func(rt *common.RuntimeContext, input map[string]interface{}) {
|
||||
props, _ := input["properties"].(map[string]interface{})
|
||||
if props == nil {
|
||||
return
|
||||
}
|
||||
if ruleType := strings.TrimSpace(rt.Str("rule-type")); ruleType != "" {
|
||||
rule, _ := props["rule"].(map[string]interface{})
|
||||
if rule == nil {
|
||||
rule = map[string]interface{}{}
|
||||
}
|
||||
rule["type"] = ruleType
|
||||
props["rule"] = rule
|
||||
}
|
||||
if rt.Str("ranges") != "" {
|
||||
if arr, err := requireJSONArray(rt, "ranges"); err == nil {
|
||||
props["ranges"] = arr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var condFormatSpec = objectCRUDSpec{
|
||||
commandPrefix: "+cond-format",
|
||||
toolName: "manage_conditional_format_object",
|
||||
idFlag: "rule-id",
|
||||
idField: "conditional_format_id",
|
||||
createDataDesc: "rule JSON: { range, rule: { type: cell_value|duplicate|data_bar|color_scale|rank|formula, ... } }",
|
||||
updateDataDesc: "full or partial rule JSON (`+cond-format-list --rule-id <id>` first, then patch)",
|
||||
commandPrefix: "+cond-format",
|
||||
toolName: "manage_conditional_format_object",
|
||||
idFlag: "rule-id",
|
||||
idField: "conditional_format_id",
|
||||
createDataDesc: "rule JSON: { rule: { operator, value, style, ... }, ... }; --rule-type and --ranges cover the common scalar fields",
|
||||
updateDataDesc: "full or partial rule JSON (`+cond-format-list --rule-id <id>` first, then patch); --rule-type and --ranges still required",
|
||||
createExtraFlags: condFormatExtraFlags,
|
||||
updateExtraFlags: condFormatExtraFlags,
|
||||
enhanceCreateInput: condFormatEnhance,
|
||||
enhanceUpdateInput: condFormatEnhance,
|
||||
}
|
||||
var CondFormatCreate = newObjectCreateShortcut(condFormatSpec)
|
||||
var CondFormatUpdate = newObjectUpdateShortcut(condFormatSpec)
|
||||
@@ -328,28 +385,199 @@ var SparklineCreate = newObjectCreateShortcut(sparklineSpec)
|
||||
var SparklineUpdate = newObjectUpdateShortcut(sparklineSpec)
|
||||
var SparklineDelete = newObjectDeleteShortcut(sparklineSpec)
|
||||
|
||||
// float image
|
||||
var floatImageSpec = objectCRUDSpec{
|
||||
commandPrefix: "+float-image",
|
||||
toolName: "manage_float_image_object",
|
||||
idFlag: "float-image-id",
|
||||
idField: "float_image_id",
|
||||
createDataDesc: "float image JSON: { image_uri, image_name, position:{row,col}, size:{width,height}, offset:{x,y} } — image_uri must be pre-uploaded",
|
||||
updateDataDesc: "full or partial float image JSON (`+float-image-list --float-image-id <id>` first, then patch)",
|
||||
// float image — fully hoisted to 10 flat flags. No --properties flag;
|
||||
// the tool's properties is composed entirely from the position / size /
|
||||
// offset / image_token / image_uri / z_index flat flags.
|
||||
|
||||
var floatImageFlatFlags = []common.Flag{
|
||||
{Name: "image-name", Required: true, Desc: "image file name with extension (e.g. logo.png)"},
|
||||
{Name: "image-token", Desc: "image file_token (XOR --image-uri); commonly returned by +float-image-list"},
|
||||
{Name: "image-uri", Desc: "image reference_id (XOR --image-token); upstream-supplied like \"<|image|>:abcdef\""},
|
||||
{Name: "position-row", Type: "int", Required: true, Desc: "top-left row index (0-based)"},
|
||||
{Name: "position-col", Required: true, Desc: "top-left column letter (e.g. A, B)"},
|
||||
{Name: "size-width", Type: "int", Required: true, Desc: "image width in pixels"},
|
||||
{Name: "size-height", Type: "int", Required: true, Desc: "image height in pixels"},
|
||||
{Name: "offset-row", Type: "int", Desc: "in-cell row offset in pixels (optional)"},
|
||||
{Name: "offset-col", Type: "int", Desc: "in-cell column offset in pixels (optional)"},
|
||||
{Name: "z-index", Type: "int", Desc: "z-order layer for overlapping images (optional)"},
|
||||
}
|
||||
var FloatImageCreate = newObjectCreateShortcut(floatImageSpec)
|
||||
var FloatImageUpdate = newObjectUpdateShortcut(floatImageSpec)
|
||||
var FloatImageDelete = newObjectDeleteShortcut(floatImageSpec)
|
||||
|
||||
// floatImageProperties assembles the tool's properties object from the
|
||||
// 10 flat flags. Caller is responsible for marking required flags via
|
||||
// cobra Required:true; this function only enforces the image_token XOR
|
||||
// image_uri pair (one must be set).
|
||||
func floatImageProperties(runtime *common.RuntimeContext) (map[string]interface{}, error) {
|
||||
token := strings.TrimSpace(runtime.Str("image-token"))
|
||||
uri := strings.TrimSpace(runtime.Str("image-uri"))
|
||||
if token == "" && uri == "" {
|
||||
return nil, common.FlagErrorf("either --image-token or --image-uri is required")
|
||||
}
|
||||
if token != "" && uri != "" {
|
||||
return nil, common.FlagErrorf("--image-token and --image-uri are mutually exclusive")
|
||||
}
|
||||
props := map[string]interface{}{
|
||||
"image_name": strings.TrimSpace(runtime.Str("image-name")),
|
||||
"position": map[string]interface{}{
|
||||
"row": runtime.Int("position-row"),
|
||||
"col": strings.TrimSpace(runtime.Str("position-col")),
|
||||
},
|
||||
"size": map[string]interface{}{
|
||||
"width": runtime.Int("size-width"),
|
||||
"height": runtime.Int("size-height"),
|
||||
},
|
||||
}
|
||||
if token != "" {
|
||||
props["image_token"] = token
|
||||
} else {
|
||||
props["image_uri"] = uri
|
||||
}
|
||||
if runtime.Changed("offset-row") || runtime.Changed("offset-col") {
|
||||
offset := map[string]interface{}{}
|
||||
if runtime.Changed("offset-row") {
|
||||
offset["row_offset"] = runtime.Int("offset-row")
|
||||
}
|
||||
if runtime.Changed("offset-col") {
|
||||
offset["col_offset"] = runtime.Int("offset-col")
|
||||
}
|
||||
props["offset"] = offset
|
||||
}
|
||||
if runtime.Changed("z-index") {
|
||||
props["z_index"] = runtime.Int("z-index")
|
||||
}
|
||||
return props, nil
|
||||
}
|
||||
|
||||
func newFloatImageWriteShortcut(command, description, op string, withIDFlag, isHighRisk bool) common.Shortcut {
|
||||
risk := "write"
|
||||
if isHighRisk {
|
||||
risk = "high-risk-write"
|
||||
}
|
||||
flags := publicSheetFlags()
|
||||
if withIDFlag {
|
||||
flags = append(flags, common.Flag{Name: "float-image-id", Required: true, Desc: "target image reference_id"})
|
||||
}
|
||||
flags = append(flags, floatImageFlatFlags...)
|
||||
return common.Shortcut{
|
||||
Service: "sheets",
|
||||
Command: command,
|
||||
Description: description,
|
||||
Risk: risk,
|
||||
Scopes: []string{"sheets:spreadsheet:write_only"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: flags,
|
||||
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 withIDFlag && strings.TrimSpace(runtime.Str("float-image-id")) == "" {
|
||||
return common.FlagErrorf("--float-image-id is required")
|
||||
}
|
||||
_, err := floatImageProperties(runtime)
|
||||
return err
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
token, _ := resolveSpreadsheetToken(runtime)
|
||||
sheetID, sheetName, _ := resolveSheetSelector(runtime)
|
||||
input, _ := floatImageWriteInput(runtime, token, sheetID, sheetName, op, withIDFlag)
|
||||
return invokeToolDryRun(token, ToolKindWrite, "manage_float_image_object", input)
|
||||
},
|
||||
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
token, err := resolveSpreadsheetToken(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sheetID, sheetName, err := resolveSheetSelector(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
input, err := floatImageWriteInput(runtime, token, sheetID, sheetName, op, withIDFlag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out, err := callTool(ctx, runtime, token, ToolKindWrite, "manage_float_image_object", input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
runtime.Out(out, nil)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func floatImageWriteInput(runtime *common.RuntimeContext, token, sheetID, sheetName, op string, withIDFlag bool) (map[string]interface{}, error) {
|
||||
props, err := floatImageProperties(runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input := map[string]interface{}{
|
||||
"excel_id": token,
|
||||
"operation": op,
|
||||
"properties": props,
|
||||
}
|
||||
sheetSelectorForToolInput(input, sheetID, sheetName)
|
||||
if withIDFlag {
|
||||
input["float_image_id"] = strings.TrimSpace(runtime.Str("float-image-id"))
|
||||
}
|
||||
return input, nil
|
||||
}
|
||||
|
||||
var FloatImageCreate = newFloatImageWriteShortcut(
|
||||
"+float-image-create",
|
||||
"Create a floating image (referenced by --image-token or --image-uri).",
|
||||
"create", false, false,
|
||||
)
|
||||
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,
|
||||
)
|
||||
|
||||
// FloatImageDelete uses the standard CRUD delete factory since it only
|
||||
// needs --float-image-id + --yes.
|
||||
var floatImageDeleteSpec = objectCRUDSpec{
|
||||
commandPrefix: "+float-image",
|
||||
toolName: "manage_float_image_object",
|
||||
idFlag: "float-image-id",
|
||||
idField: "float_image_id",
|
||||
}
|
||||
var FloatImageDelete = newObjectDeleteShortcut(floatImageDeleteSpec)
|
||||
|
||||
// filter view — cli_status: cli-only but the tool is in mcp-tools.json so
|
||||
// it dispatches via the same One-OpenAPI endpoint as every other shortcut.
|
||||
// --view-name and --range are hoisted out of properties (optional on both
|
||||
// create and update; they always win over properties.{view_name, range}).
|
||||
var filterViewExtraFlags = []common.Flag{
|
||||
{Name: "range", Desc: "filter view range (A1 covering the header, e.g. A1:F1000); overrides properties.range"},
|
||||
{Name: "view-name", Desc: "view title; create omits → server-generated, update omits → keep current. Overrides properties.view_name"},
|
||||
}
|
||||
var filterViewEnhance = func(rt *common.RuntimeContext, input map[string]interface{}) {
|
||||
props, _ := input["properties"].(map[string]interface{})
|
||||
if props == nil {
|
||||
return
|
||||
}
|
||||
if v := strings.TrimSpace(rt.Str("range")); v != "" {
|
||||
props["range"] = v
|
||||
}
|
||||
if v := strings.TrimSpace(rt.Str("view-name")); v != "" {
|
||||
props["view_name"] = v
|
||||
}
|
||||
}
|
||||
|
||||
var filterViewSpec = objectCRUDSpec{
|
||||
commandPrefix: "+filter-view",
|
||||
toolName: "manage_filter_view_object",
|
||||
idFlag: "view-id",
|
||||
idField: "view_id",
|
||||
createDataDesc: "filter view JSON: { view_name, range (required, covers header), rules: [...] }",
|
||||
updateDataDesc: "partial update JSON: any of { view_name, range, rules }; `+filter-view-list --view-id <id>` first",
|
||||
commandPrefix: "+filter-view",
|
||||
toolName: "manage_filter_view_object",
|
||||
idFlag: "view-id",
|
||||
idField: "view_id",
|
||||
createDataDesc: "filter view JSON: { rules?: [...] , filtered_columns?: [...] }; --range / --view-name cover the scalar fields",
|
||||
updateDataDesc: "partial update JSON: any of { rules, filtered_columns }; `+filter-view-list --view-id <id>` first",
|
||||
createExtraFlags: filterViewExtraFlags,
|
||||
updateExtraFlags: filterViewExtraFlags,
|
||||
enhanceCreateInput: filterViewEnhance,
|
||||
enhanceUpdateInput: filterViewEnhance,
|
||||
}
|
||||
var FilterViewCreate = newObjectCreateShortcut(filterViewSpec)
|
||||
var FilterViewUpdate = newObjectUpdateShortcut(filterViewSpec)
|
||||
@@ -374,8 +602,8 @@ var FilterCreate = common.Shortcut{
|
||||
HasFormat: true,
|
||||
Flags: append(publicSheetFlags(),
|
||||
common.Flag{Name: "range", Required: true, Desc: "filter range including the header row (e.g. A1:F1000)"},
|
||||
common.Flag{Name: "data", Input: []string{common.File, common.Stdin},
|
||||
Desc: "optional conditions JSON: { conditions: [{col, filter_type, expected, ...}] }; empty filter when omitted"},
|
||||
common.Flag{Name: "properties", Input: []string{common.File, common.Stdin},
|
||||
Desc: "optional rules JSON: { rules: [...], filtered_columns?: [...] }; empty filter when omitted"},
|
||||
),
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
if _, err := resolveSpreadsheetToken(runtime); err != nil {
|
||||
@@ -387,8 +615,8 @@ var FilterCreate = common.Shortcut{
|
||||
if strings.TrimSpace(runtime.Str("range")) == "" {
|
||||
return common.FlagErrorf("--range is required")
|
||||
}
|
||||
if runtime.Str("data") != "" {
|
||||
if _, err := requireJSONObject(runtime, "data"); err != nil {
|
||||
if runtime.Str("properties") != "" {
|
||||
if _, err := requireJSONObject(runtime, "properties"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -426,8 +654,8 @@ func filterCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName
|
||||
props := map[string]interface{}{
|
||||
"range": strings.TrimSpace(runtime.Str("range")),
|
||||
}
|
||||
if runtime.Str("data") != "" {
|
||||
extra, err := requireJSONObject(runtime, "data")
|
||||
if runtime.Str("properties") != "" {
|
||||
extra, err := requireJSONObject(runtime, "properties")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -447,19 +675,21 @@ func filterCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// FilterUpdate patches the sheet-level filter — change range or
|
||||
// add/replace conditions. filter_id is implicit (sheet-scoped).
|
||||
// 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 FilterUpdate = common.Shortcut{
|
||||
Service: "sheets",
|
||||
Command: "+filter-update",
|
||||
Description: "Update the sheet-level filter (patch range or conditions).",
|
||||
Description: "Update the sheet-level filter (overwrite rules + range).",
|
||||
Risk: "write",
|
||||
Scopes: []string{"sheets:spreadsheet:write_only"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: append(publicSheetFlags(),
|
||||
common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "patch JSON: { range?, conditions?: [...] } — read with +filter-list first"},
|
||||
common.Flag{Name: "properties", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "patch JSON: { rules: [...], filtered_columns?: [...] } — read with +filter-list first"},
|
||||
common.Flag{Name: "range", Required: true, Desc: "filter range A1 (e.g. A1:F1000); overrides properties.range"},
|
||||
),
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
if _, err := resolveSpreadsheetToken(runtime); err != nil {
|
||||
@@ -468,7 +698,10 @@ var FilterUpdate = common.Shortcut{
|
||||
if _, _, err := resolveSheetSelector(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err := requireJSONObject(runtime, "data")
|
||||
if strings.TrimSpace(runtime.Str("range")) == "" {
|
||||
return common.FlagErrorf("--range is required")
|
||||
}
|
||||
_, err := requireJSONObject(runtime, "properties")
|
||||
return err
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
@@ -500,10 +733,12 @@ var FilterUpdate = common.Shortcut{
|
||||
}
|
||||
|
||||
func filterUpdateInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) (map[string]interface{}, error) {
|
||||
props, err := requireJSONObject(runtime, "data")
|
||||
props, err := requireJSONObject(runtime, "properties")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// --range wins over any properties.range
|
||||
props["range"] = strings.TrimSpace(runtime.Str("range"))
|
||||
input := map[string]interface{}{
|
||||
"excel_id": token,
|
||||
"operation": "update",
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
{
|
||||
name: "+chart-create",
|
||||
sc: ChartCreate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"type":"line"}`},
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--properties", `{"type":"line"}`},
|
||||
toolName: "manage_chart_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
@@ -41,7 +41,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
{
|
||||
name: "+chart-update",
|
||||
sc: ChartUpdate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chartXYZ", "--data", `{"type":"bar"}`},
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--chart-id", "chartXYZ", "--properties", `{"type":"bar"}`},
|
||||
toolName: "manage_chart_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
@@ -51,18 +51,30 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
"properties": map[string]interface{}{"type": "bar"},
|
||||
},
|
||||
},
|
||||
// pivot — has extra create flags
|
||||
// pivot — has extra create flags incl. required --source
|
||||
{
|
||||
name: "+pivot-create with target flags",
|
||||
sc: PivotCreate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"data_range":"Sheet1!A1:F1000"}`, "--target-sheet-id", "sh2", "--target-position", "B5"},
|
||||
name: "+pivot-create with target / source / range flags",
|
||||
sc: PivotCreate,
|
||||
args: []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--properties", `{"rows":[{"field":"A"}]}`,
|
||||
"--source", "Sheet1!A1:F1000",
|
||||
"--range", "F1",
|
||||
"--target-sheet-id", "sh2",
|
||||
"--target-position", "B5",
|
||||
},
|
||||
toolName: "manage_pivot_table_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
"sheet_id": testSheetID,
|
||||
"operation": "create",
|
||||
"target_sheet_id": "sh2",
|
||||
"target_position": "B5",
|
||||
"excel_id": testToken,
|
||||
"sheet_id": testSheetID,
|
||||
"operation": "create",
|
||||
"target_sheet_id": "sh2",
|
||||
"target_position": "B5",
|
||||
"properties": map[string]interface{}{
|
||||
"rows": []interface{}{map[string]interface{}{"field": "A"}},
|
||||
"source": "Sheet1!A1:F1000",
|
||||
"range": "F1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -77,23 +89,32 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
"pivot_table_id": "ptA",
|
||||
},
|
||||
},
|
||||
// cond-format — --rule-id rename
|
||||
// cond-format — --rule-id rename + --rule-type / --ranges hoist
|
||||
{
|
||||
name: "+cond-format-update id rename",
|
||||
sc: CondFormatUpdate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--rule-id", "ruleA", "--data", `{"rule":{"type":"cell_value"}}`},
|
||||
name: "+cond-format-update id rename + rule-type/ranges",
|
||||
sc: CondFormatUpdate,
|
||||
args: []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--rule-id", "ruleA",
|
||||
"--properties", `{"rule":{"operator":"greater_than","value":100}}`,
|
||||
"--rule-type", "cellValue",
|
||||
"--ranges", `["A1:A100"]`,
|
||||
},
|
||||
toolName: "manage_conditional_format_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
"sheet_id": testSheetID,
|
||||
"operation": "update",
|
||||
"conditional_format_id": "ruleA",
|
||||
"properties": map[string]interface{}{"rule": map[string]interface{}{"type": "cell_value"}},
|
||||
"properties": map[string]interface{}{
|
||||
"rule": map[string]interface{}{"operator": "greater_than", "value": float64(100), "type": "cellValue"},
|
||||
"ranges": []interface{}{"A1:A100"},
|
||||
},
|
||||
},
|
||||
},
|
||||
// filter — special, no id flag
|
||||
{
|
||||
name: "+filter-create without --data sends properties.range only",
|
||||
name: "+filter-create without --properties sends properties.range only",
|
||||
sc: FilterCreate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:F1000"},
|
||||
toolName: "manage_filter_object",
|
||||
@@ -105,14 +126,14 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "+filter-create with --data merges conditions",
|
||||
name: "+filter-create with --properties merges rules",
|
||||
sc: FilterCreate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:F1000", "--data", `{"conditions":[{"col":"B"}]}`},
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:F1000", "--properties", `{"rules":[{"col":"B"}]}`},
|
||||
toolName: "manage_filter_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"properties": map[string]interface{}{
|
||||
"range": "A1:F1000",
|
||||
"conditions": []interface{}{map[string]interface{}{"col": "B"}},
|
||||
"range": "A1:F1000",
|
||||
"rules": []interface{}{map[string]interface{}{"col": "B"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -131,7 +152,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
{
|
||||
name: "+filter-view-create",
|
||||
sc: FilterViewCreate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"view_name":"v1","range":"A1:Z100"}`},
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--properties", `{"view_name":"v1","range":"A1:Z100"}`},
|
||||
toolName: "manage_filter_view_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
@@ -143,7 +164,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
{
|
||||
name: "+filter-view-update with --view-id",
|
||||
sc: FilterViewUpdate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--view-id", "vABC", "--data", `{"view_name":"renamed"}`},
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--view-id", "vABC", "--properties", `{"view_name":"renamed"}`},
|
||||
toolName: "manage_filter_view_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"view_id": "vABC",
|
||||
@@ -154,7 +175,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
{
|
||||
name: "+sparkline-update --group-id → group_id",
|
||||
sc: SparklineUpdate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--group-id", "grpA", "--data", `{"type":"line"}`},
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--group-id", "grpA", "--properties", `{"type":"line"}`},
|
||||
toolName: "manage_sparkline_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"group_id": "grpA",
|
||||
@@ -162,15 +183,28 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
|
||||
"properties": map[string]interface{}{"type": "line"},
|
||||
},
|
||||
},
|
||||
// float-image
|
||||
// float-image — fully hoisted to flat flags
|
||||
{
|
||||
name: "+float-image-create",
|
||||
sc: FloatImageCreate,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--data", `{"image_uri":"u","image_name":"x.png"}`},
|
||||
name: "+float-image-create with image-token + position/size",
|
||||
sc: FloatImageCreate,
|
||||
args: []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--image-name", "logo.png",
|
||||
"--image-token", "tok_xyz",
|
||||
"--position-row", "2", "--position-col", "D",
|
||||
"--size-width", "300", "--size-height", "200",
|
||||
},
|
||||
toolName: "manage_float_image_object",
|
||||
wantInput: map[string]interface{}{
|
||||
"operation": "create",
|
||||
"properties": map[string]interface{}{"image_uri": "u", "image_name": "x.png"},
|
||||
"excel_id": testToken,
|
||||
"sheet_id": testSheetID,
|
||||
"operation": "create",
|
||||
"properties": map[string]interface{}{
|
||||
"image_name": "logo.png",
|
||||
"image_token": "tok_xyz",
|
||||
"position": map[string]interface{}{"row": float64(2), "col": "D"},
|
||||
"size": map[string]interface{}{"width": float64(300), "height": float64(200)},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ var CellsReplace = common.Shortcut{
|
||||
HasFormat: true,
|
||||
Flags: append(publicSheetFlags(),
|
||||
common.Flag{Name: "find", Required: true, Desc: "text to find (regex when --regex is set)"},
|
||||
common.Flag{Name: "replace", Required: true, Desc: "replacement text (empty string deletes the match)"},
|
||||
common.Flag{Name: "replacement", Required: true, Desc: "replacement text (empty string deletes the match)"},
|
||||
common.Flag{Name: "range", Desc: "optional A1 range to scope the replace"},
|
||||
common.Flag{Name: "match-case", Type: "bool", Desc: "case-sensitive match"},
|
||||
common.Flag{Name: "match-entire-cell", Type: "bool", Desc: "match the entire cell content only"},
|
||||
@@ -141,8 +141,8 @@ var CellsReplace = common.Shortcut{
|
||||
if strings.TrimSpace(runtime.Str("find")) == "" {
|
||||
return common.FlagErrorf("--find is required")
|
||||
}
|
||||
if !runtime.Changed("replace") {
|
||||
return common.FlagErrorf("--replace is required (pass an empty string to delete matches)")
|
||||
if !runtime.Changed("replacement") {
|
||||
return common.FlagErrorf("--replacement is required (pass an empty string to delete matches)")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -176,7 +176,7 @@ func replaceInput(runtime *common.RuntimeContext, token, sheetID, sheetName stri
|
||||
input := map[string]interface{}{
|
||||
"excel_id": token,
|
||||
"search_term": runtime.Str("find"),
|
||||
"replace_term": runtime.Str("replace"),
|
||||
"replace_term": runtime.Str("replacement"),
|
||||
}
|
||||
sheetSelectorForToolInput(input, sheetID, sheetName)
|
||||
if r := strings.TrimSpace(runtime.Str("range")); r != "" {
|
||||
|
||||
@@ -56,7 +56,7 @@ func TestSearchReplaceShortcuts_DryRun(t *testing.T) {
|
||||
{
|
||||
name: "+cells-replace empty replace deletes match",
|
||||
sc: CellsReplace,
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--find", "foo", "--replace", ""},
|
||||
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--find", "foo", "--replacement", ""},
|
||||
toolName: "replace_data",
|
||||
wantInput: map[string]interface{}{
|
||||
"excel_id": testToken,
|
||||
|
||||
@@ -540,7 +540,7 @@ var SheetSetTabColor = common.Shortcut{
|
||||
var WorkbookCreate = common.Shortcut{
|
||||
Service: "sheets",
|
||||
Command: "+workbook-create",
|
||||
Description: "Create a new spreadsheet (optionally pre-filled with --headers and --data).",
|
||||
Description: "Create a new spreadsheet (optionally pre-filled with --headers and --values).",
|
||||
Risk: "write",
|
||||
Scopes: []string{"sheets:spreadsheet:create", "sheets:spreadsheet:write_only"},
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
@@ -549,7 +549,7 @@ var WorkbookCreate = common.Shortcut{
|
||||
{Name: "title", Required: true, Desc: "spreadsheet title"},
|
||||
{Name: "folder-token", Desc: "destination folder token; omit to land at the drive root"},
|
||||
{Name: "headers", Input: []string{common.File, common.Stdin}, Desc: "header row JSON array, e.g. [\"列A\",\"列B\"]"},
|
||||
{Name: "data", Input: []string{common.File, common.Stdin}, Desc: "initial data JSON 2D array, e.g. [[\"alice\",95]]"},
|
||||
{Name: "values", Input: []string{common.File, common.Stdin}, Desc: "initial data JSON 2D array, e.g. [[\"alice\",95]]"},
|
||||
},
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
if strings.TrimSpace(runtime.Str("title")) == "" {
|
||||
@@ -564,18 +564,18 @@ var WorkbookCreate = common.Shortcut{
|
||||
return common.FlagErrorf("--headers must be a JSON array")
|
||||
}
|
||||
}
|
||||
if runtime.Str("data") != "" {
|
||||
v, err := parseJSONFlag(runtime, "data")
|
||||
if runtime.Str("values") != "" {
|
||||
v, err := parseJSONFlag(runtime, "values")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rows, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return common.FlagErrorf("--data must be a JSON 2D array")
|
||||
return common.FlagErrorf("--values must be a JSON 2D array")
|
||||
}
|
||||
for i, r := range rows {
|
||||
if _, ok := r.([]interface{}); !ok {
|
||||
return common.FlagErrorf("--data[%d] must be an array", i)
|
||||
return common.FlagErrorf("--values[%d] must be an array", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -590,7 +590,7 @@ var WorkbookCreate = common.Shortcut{
|
||||
POST("/open-apis/sheets/v3/spreadsheets").
|
||||
Desc("create spreadsheet").
|
||||
Body(body)
|
||||
if runtime.Str("headers") != "" || runtime.Str("data") != "" {
|
||||
if runtime.Str("headers") != "" || runtime.Str("values") != "" {
|
||||
fill, _ := buildInitialFillInput(runtime)
|
||||
wireBody, _ := buildToolBody("set_cell_range", fill)
|
||||
dry.POST("/open-apis/sheet_ai/v2/spreadsheets/<new-token>/tools/invoke_write").
|
||||
@@ -619,7 +619,7 @@ var WorkbookCreate = common.Shortcut{
|
||||
|
||||
result := map[string]interface{}{"spreadsheet": ss}
|
||||
|
||||
if runtime.Str("headers") != "" || runtime.Str("data") != "" {
|
||||
if runtime.Str("headers") != "" || runtime.Str("values") != "" {
|
||||
fill, err := buildInitialFillInput(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -637,11 +637,11 @@ var WorkbookCreate = common.Shortcut{
|
||||
return nil
|
||||
},
|
||||
Tips: []string{
|
||||
"--headers and --data are optional follow-up writes. They use the same set_cell_range tool as +cells-set; partial failure leaves the spreadsheet created but empty.",
|
||||
"--headers and --values are optional follow-up writes. They use the same set_cell_range tool as +cells-set; partial failure leaves the spreadsheet created but empty.",
|
||||
},
|
||||
}
|
||||
|
||||
// buildInitialFillInput zips --headers + --data into a single set_cell_range
|
||||
// buildInitialFillInput zips --headers + --values into a single set_cell_range
|
||||
// payload writing to the first sheet starting at A1.
|
||||
func buildInitialFillInput(runtime *common.RuntimeContext) (map[string]interface{}, error) {
|
||||
var rows [][]interface{}
|
||||
@@ -654,8 +654,8 @@ func buildInitialFillInput(runtime *common.RuntimeContext) (map[string]interface
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
if runtime.Str("data") != "" {
|
||||
v, _ := parseJSONFlag(runtime, "data")
|
||||
if runtime.Str("values") != "" {
|
||||
v, _ := parseJSONFlag(runtime, "values")
|
||||
dataArr, _ := v.([]interface{})
|
||||
for _, r := range dataArr {
|
||||
cells, _ := r.([]interface{})
|
||||
|
||||
@@ -303,7 +303,7 @@ func TestWorkbookCreate_DryRun(t *testing.T) {
|
||||
calls := parseDryRunAPI(t, WorkbookCreate, []string{
|
||||
"--title", "Sales",
|
||||
"--headers", `["Name","Score"]`,
|
||||
"--data", `[["alice",95],["bob",88]]`,
|
||||
"--values", `[["alice",95],["bob",88]]`,
|
||||
})
|
||||
if len(calls) != 2 {
|
||||
t.Fatalf("api calls = %d, want 2 (create + fill)", len(calls))
|
||||
@@ -329,7 +329,7 @@ func TestWorkbookCreate_DataValidation(t *testing.T) {
|
||||
want string
|
||||
}{
|
||||
{"headers not array", []string{"--title", "X", "--headers", `"abc"`}, "must be a JSON array"},
|
||||
{"data not 2D", []string{"--title", "X", "--data", `["a","b"]`}, "must be an array"},
|
||||
{"values not 2D", []string{"--title", "X", "--values", `["a","b"]`}, "must be an array"},
|
||||
}
|
||||
for _, tt := range cases {
|
||||
tt := tt
|
||||
|
||||
@@ -40,7 +40,7 @@ var CellsSet = common.Shortcut{
|
||||
HasFormat: true,
|
||||
Flags: append(publicSheetFlags(),
|
||||
common.Flag{Name: "range", Required: true, Desc: "target A1 range (e.g. A1:C10); cells dimensions must match"},
|
||||
common.Flag{Name: "data", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
common.Flag{Name: "cells", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "JSON body: { \"cells\": [[{value|formula|cell_styles|...}, ...]], optional copy_to_range / resize_width / resize_height }"},
|
||||
common.Flag{Name: "allow-overwrite", Type: "bool", Default: "true", Desc: "allow overwriting non-empty cells (default true)"},
|
||||
common.Flag{Name: "max-cells", Type: "int", Default: "50000", Hidden: true, Desc: "anti-burst cells write cap"},
|
||||
@@ -55,12 +55,12 @@ var CellsSet = common.Shortcut{
|
||||
if strings.TrimSpace(runtime.Str("range")) == "" {
|
||||
return common.FlagErrorf("--range is required")
|
||||
}
|
||||
body, err := requireJSONObject(runtime, "data")
|
||||
body, err := requireJSONObject(runtime, "cells")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := body["cells"]; !ok {
|
||||
return common.FlagErrorf("--data must include a \"cells\" field")
|
||||
return common.FlagErrorf("--cells must include a \"cells\" field")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -93,7 +93,7 @@ var CellsSet = common.Shortcut{
|
||||
}
|
||||
|
||||
func cellsSetInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) (map[string]interface{}, error) {
|
||||
body, err := requireJSONObject(runtime, "data")
|
||||
body, err := requireJSONObject(runtime, "cells")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -102,7 +102,7 @@ func cellsSetInput(runtime *common.RuntimeContext, token, sheetID, sheetName str
|
||||
"range": strings.TrimSpace(runtime.Str("range")),
|
||||
}
|
||||
sheetSelectorForToolInput(input, sheetID, sheetName)
|
||||
// --data fields override any of these except the core selectors.
|
||||
// --cells fields override any of these except the core selectors.
|
||||
for k, v := range body {
|
||||
switch k {
|
||||
case "excel_id", "range", "sheet_id", "sheet_name":
|
||||
@@ -117,21 +117,24 @@ func cellsSetInput(runtime *common.RuntimeContext, token, sheetID, sheetName str
|
||||
return input, nil
|
||||
}
|
||||
|
||||
// CellsSetStyle wraps set_cell_range applied to a uniform style: parse
|
||||
// --style once, fan it out to a (rows × cols) cells matrix, and let
|
||||
// set_cell_range stamp every cell in the range with that style.
|
||||
// CellsSetStyle stamps a single style block across every cell in --range.
|
||||
// Style is composed from a dozen flat flags (background-color, font-color,
|
||||
// font-size, font-style, font-weight, font-line, horizontal-alignment,
|
||||
// vertical-alignment, word-wrap, number-format) plus --border-styles for
|
||||
// the only field that still needs a nested object. At least one flag must
|
||||
// be set.
|
||||
var CellsSetStyle = common.Shortcut{
|
||||
Service: "sheets",
|
||||
Command: "+cells-set-style",
|
||||
Description: "Apply a single style block to every cell in a range (values / formulas untouched).",
|
||||
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: append(publicSheetFlags(),
|
||||
common.Flag{Name: "range", Required: true, Desc: "target A1 range (e.g. A1:B2)"},
|
||||
common.Flag{Name: "style", Input: []string{common.File, common.Stdin}, Required: true,
|
||||
Desc: "style JSON: { font, backColor, horizontal_alignment, vertical_alignment, ... , optional border_styles }"},
|
||||
Flags: append(
|
||||
append(publicSheetFlags(),
|
||||
common.Flag{Name: "range", Required: true, Desc: "target A1 range (e.g. A1:B2)"}),
|
||||
styleFlatFlags()...,
|
||||
),
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
if _, err := resolveSpreadsheetToken(runtime); err != nil {
|
||||
@@ -147,7 +150,10 @@ var CellsSetStyle = common.Shortcut{
|
||||
if _, _, err := rangeDimensions(r); err != nil {
|
||||
return common.FlagErrorf("--range %q: %v", r, err)
|
||||
}
|
||||
if _, err := requireJSONObject(runtime, "style"); err != nil {
|
||||
if err := requireAnyStyleFlag(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := borderStylesFromFlag(runtime); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -181,31 +187,24 @@ var CellsSetStyle = common.Shortcut{
|
||||
}
|
||||
|
||||
func cellsSetStyleInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) (map[string]interface{}, error) {
|
||||
style, err := requireJSONObject(runtime, "style")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rangeStr := strings.TrimSpace(runtime.Str("range"))
|
||||
rows, cols, err := rangeDimensions(rangeStr)
|
||||
if err != nil {
|
||||
return nil, common.FlagErrorf("--range %q: %v", rangeStr, err)
|
||||
}
|
||||
// Split border_styles out of the style block since the tool models it
|
||||
// as a sibling field of cell_styles.
|
||||
cellStyle := map[string]interface{}{}
|
||||
var borderStyles interface{}
|
||||
for k, v := range style {
|
||||
if k == "border_styles" {
|
||||
borderStyles = v
|
||||
continue
|
||||
}
|
||||
cellStyle[k] = v
|
||||
cellStyle := buildCellStyleFromFlags(runtime)
|
||||
borderStyles, err := borderStylesFromFlag(runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cells := make([][]interface{}, rows)
|
||||
for r := range cells {
|
||||
row := make([]interface{}, cols)
|
||||
for c := range row {
|
||||
cell := map[string]interface{}{"cell_styles": cellStyle}
|
||||
cell := map[string]interface{}{}
|
||||
if len(cellStyle) > 0 {
|
||||
cell["cell_styles"] = cellStyle
|
||||
}
|
||||
if borderStyles != nil {
|
||||
cell["border_styles"] = borderStyles
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func TestWriteCellsShortcuts_DryRun(t *testing.T) {
|
||||
args: []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--range", "A1:B2",
|
||||
"--data", `{"cells":[[{"value":1},{"value":2}],[{"value":3},{"value":4}]]}`,
|
||||
"--cells", `{"cells":[[{"value":1},{"value":2}],[{"value":3},{"value":4}]]}`,
|
||||
},
|
||||
toolName: "set_cell_range",
|
||||
wantInput: map[string]interface{}{
|
||||
@@ -42,7 +42,7 @@ func TestWriteCellsShortcuts_DryRun(t *testing.T) {
|
||||
args: []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--range", "A1",
|
||||
"--data", `{"cells":[[{"value":1}]]}`,
|
||||
"--cells", `{"cells":[[{"value":1}]]}`,
|
||||
"--allow-overwrite=false",
|
||||
},
|
||||
toolName: "set_cell_range",
|
||||
@@ -130,34 +130,50 @@ func TestDropdownSet_CellsShape(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestCellsSetStyle_FanOutsBorderStylesOut confirms the border_styles
|
||||
// field is split out of --style and placed as a sibling of cell_styles
|
||||
// per the tool contract.
|
||||
func TestCellsSetStyle_FanOutsBorderStylesOut(t *testing.T) {
|
||||
// TestCellsSetStyle_FlatFlags verifies that the 11 flat style flags +
|
||||
// --border-styles compose into cell_styles + border_styles per cell.
|
||||
func TestCellsSetStyle_FlatFlags(t *testing.T) {
|
||||
t.Parallel()
|
||||
body := parseDryRunBody(t, CellsSetStyle, []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--range", "A1:B1",
|
||||
"--style", `{"font":{"bold":true},"border_styles":{"top":{"style":"thick"}}}`,
|
||||
"--font-weight", "bold",
|
||||
"--background-color", "#ffff00",
|
||||
"--horizontal-alignment", "center",
|
||||
"--border-styles", `{"top":{"style":"thick"}}`,
|
||||
})
|
||||
input := decodeToolInput(t, body, "set_cell_range")
|
||||
cells, _ := input["cells"].([]interface{})
|
||||
row, _ := cells[0].([]interface{})
|
||||
cell, _ := row[0].(map[string]interface{})
|
||||
style, _ := cell["cell_styles"].(map[string]interface{})
|
||||
if style["font_weight"] != "bold" || style["background_color"] != "#ffff00" || style["horizontal_alignment"] != "center" {
|
||||
t.Errorf("cell_styles wrong: %#v", style)
|
||||
}
|
||||
if cell["border_styles"] == nil {
|
||||
t.Fatalf("border_styles missing on cell: %#v", cell)
|
||||
}
|
||||
style, _ := cell["cell_styles"].(map[string]interface{})
|
||||
if _, leaked := style["border_styles"]; leaked {
|
||||
t.Errorf("border_styles leaked into cell_styles: %#v", style)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCellsSetStyle_RequiresAtLeastOneFlag(t *testing.T) {
|
||||
t.Parallel()
|
||||
stdout, stderr, err := runShortcutCapturingErr(t, CellsSetStyle, []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--range", "A1:B2", "--dry-run",
|
||||
})
|
||||
if err == nil || !strings.Contains(stdout+stderr+err.Error(), "at least one style flag") {
|
||||
t.Errorf("expected style-flag guard; got=%s|%s|%v", stdout, stderr, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCellsSet_RequiresCellsField(t *testing.T) {
|
||||
t.Parallel()
|
||||
stdout, stderr, err := runShortcutCapturingErr(t, CellsSet, []string{
|
||||
"--url", testURL, "--sheet-id", testSheetID,
|
||||
"--range", "A1", "--data", `{"foo":"bar"}`, "--dry-run",
|
||||
"--range", "A1", "--cells", `{"foo":"bar"}`, "--dry-run",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatalf("expected validation error; stdout=%s stderr=%s", stdout, stderr)
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `batch_update` | `+batch-update` | high-risk-write | 批量 |
|
||||
@@ -33,15 +31,13 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+batch-update`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `--url` | 公共 | string | XOR | spreadsheet 定位(与子操作的 sheet 定位独立) |
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet 定位(与子操作的 sheet 定位独立) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"operations":[{"tool":"set_cell_range","params":{...}}, ...]}`;按数组顺序串行执行 |
|
||||
| `--operations` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"operations":[{"tool":"set_cell_range","params":{...}}, ...]}`;按数组顺序串行执行 |
|
||||
| `--yes` | 系统 | bool | 是 | `high-risk-write`,必须二次确认(不带时退出码 10) |
|
||||
| `--dry-run` | 系统 | bool | 否 | 输出每个子操作的请求模板,零网络副作用 |
|
||||
|
||||
@@ -51,7 +47,18 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `--url` | 公共 | string | XOR | spreadsheet URL(与 `--spreadsheet-token` 二选一) |
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON 数组 `[{"ranges":["sheet1!A1:B2"],"style":{...}}]`;每个 ranges 元素必须带 sheet 前缀 |
|
||||
| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["sheet1!A1:B2","sheet1!D1:D10"]`);所有 range 应用同一组 style |
|
||||
| `--background-color` | 专有 | string | 否 | 背景颜色(十六进制,如 `#ffffff`) |
|
||||
| `--font-color` | 专有 | string | 否 | 字体颜色(十六进制,如 `#000000`) |
|
||||
| `--font-size` | 专有 | number | 否 | 字体大小(px,例:10、12、14) |
|
||||
| `--font-style` | 专有 | string + Enum | 否 | 字体样式 enum:`normal` / `italic` |
|
||||
| `--font-weight` | 专有 | string + Enum | 否 | 字重 enum:`normal` / `bold` |
|
||||
| `--font-line` | 专有 | string + Enum | 否 | 字体线条样式 enum:`none` / `underline` / `line-through` |
|
||||
| `--horizontal-alignment` | 专有 | string + Enum | 否 | 水平对齐 enum:`left` / `center` / `right` |
|
||||
| `--vertical-alignment` | 专有 | string + Enum | 否 | 垂直对齐 enum:`top` / `middle` / `bottom` |
|
||||
| `--word-wrap` | 专有 | string + Enum | 否 | 换行策略 enum:`overflow` / `auto-wrap` / `word-clip`(默认 `overflow`) |
|
||||
| `--number-format` | 专有 | string | 否 | 数字格式(例:文本 `@`、数字 `0.00`、货币 `$#,##0.00`、日期 `mm/dd/yyyy`) |
|
||||
| `--border-styles` | 专有 | string + File + Stdin(复合 JSON) | 否 | 边框配置 JSON(结构同 +cells-set-style) |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+dropdown-update`
|
||||
@@ -60,9 +67,9 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `--url` | 公共 | string | XOR | spreadsheet URL(与 `--spreadsheet-token` 二选一) |
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--ranges` | 专有 | string + File + Stdin | 是 | 目标范围 JSON 数组(如 `["sheet1!A2:A100"]`),每项必须带 sheet 前缀 |
|
||||
| `--options` | 专有 | string + File + Stdin | 是 | 选项 JSON 数组 |
|
||||
| `--colors` | 专有 | string + File + Stdin | 否 | 颜色数组(与 `--options` 等长) |
|
||||
| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 目标范围 JSON 数组(如 `["sheet1!A2:A100"]`),每项必须带 sheet 前缀 |
|
||||
| `--options` | 专有 | string + File + Stdin(复合 JSON) | 是 | 选项 JSON 数组 |
|
||||
| `--colors` | 专有 | string + File + Stdin(简单 JSON) | 否 | 颜色数组(与 `--options` 等长) |
|
||||
| `--multiple` | 专有 | bool | 否 | 启用多选 |
|
||||
| `--highlight` | 专有 | bool | 否 | 选项配色 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
@@ -73,7 +80,7 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `--url` | 公共 | string | XOR | spreadsheet URL(与 `--spreadsheet-token` 二选一) |
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--ranges` | 专有 | string + File + Stdin | 是 | 目标范围 JSON 数组(最多 100 个,每项带 sheet 前缀) |
|
||||
| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 目标范围 JSON 数组(最多 100 个,每项带 sheet 前缀) |
|
||||
| `--yes` | 系统 | bool | 是 | `high-risk-write`,必须二次确认(不带时退出码 10) |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
@@ -81,7 +88,7 @@
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+batch-update` `--data`
|
||||
### `+batch-update` `--operations`
|
||||
|
||||
_要批量执行的操作列表,按顺序依次执行_
|
||||
|
||||
@@ -89,21 +96,15 @@ _要批量执行的操作列表,按顺序依次执行_
|
||||
- `input` (object) — 对应工具的入参,结构与单独调用该工具时完全一致
|
||||
- `tool_name` (string) — 要执行的工具名称,如 "set_cell_range"、"clear_cell_range"、"modify_sheet_structure" 等
|
||||
|
||||
### `+cells-batch-set-style` `--data`
|
||||
### `+cells-batch-set-style` `--border-styles`
|
||||
|
||||
_单元格样式属性,包括字体、颜色、对齐方式和数字格式_
|
||||
_单元格边框配置,含 top/bottom/left/right 四个方向,每个方向的结构相同(见 top)_
|
||||
|
||||
**顶层字段**:
|
||||
- `background_color` (string?) — 背景颜色(十六进制,例如 "#ffffff")
|
||||
- `font_color` (string?) — 字体颜色(十六进制,例如 "#000000")
|
||||
- `font_line` (enum?) — 字体线条样式 [none / underline / line-through]
|
||||
- `font_size` (number?) — 字体大小(单位:px/像素,例如 10、12、14)
|
||||
- `font_style` (enum?) — 字体样式 [normal / italic]
|
||||
- `font_weight` (enum?) — 字重 [normal / bold]
|
||||
- `horizontal_alignment` (enum?) — 水平对齐方式 [left / center / right]
|
||||
- `number_format` (string?) — 数字格式(例如:文本用 "@"、数字用 "0.00"、货币用 "$#,##0.00"、日期用 "mm/dd/yyyy")
|
||||
- `vertical_alignment` (enum?) — 垂直对齐方式 [top / middle / bottom]
|
||||
- `word_wrap` (enum?) — 是否自动换行,默认溢出,可选自动换行或裁剪 [overflow / auto-wrap / word-clip]
|
||||
- `bottom` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
- `left` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
- `right` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
- `top` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
|
||||
### `+dropdown-update` `--options`
|
||||
|
||||
@@ -120,8 +121,6 @@ _数据验证配置_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:`--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(前两者 XOR;`+batch-update` 本身不强制 sheet-id,子操作各自携带)。
|
||||
|
||||
### `+batch-update`
|
||||
|
||||
@@ -96,8 +96,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_chart_objects` | `+chart-list` | read | 对象 |
|
||||
@@ -107,8 +105,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+chart-list`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -128,7 +124,7 @@
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 图表完整配置 JSON(`position` / `data` / `properties` 等),结构嵌套深,统一走 JSON 注入 |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 图表完整配置 JSON(`position` / `data` / `properties` 等),结构嵌套深,统一走 JSON 注入 |
|
||||
| `--dry-run` | 系统 | bool | 否 | 零副作用,输出请求模板 |
|
||||
|
||||
### `+chart-update`
|
||||
@@ -140,7 +136,7 @@
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--chart-id` | 专有 | string | 是 | 目标图表 reference_id |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的图表配置 JSON(先 `+chart-list` 回读再 patch) |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 完整或足够完整的图表配置 JSON(先 `+chart-list` 回读再 patch) |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+chart-delete`
|
||||
@@ -159,7 +155,7 @@
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+chart-create` `--data` / `+chart-update` `--data`
|
||||
### `+chart-create` `--properties` / `+chart-update` `--properties`
|
||||
|
||||
_创建/更新的图表属性_
|
||||
|
||||
@@ -171,8 +167,6 @@ _创建/更新的图表属性_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR 规则同 `+csv-get`)。
|
||||
|
||||
### `+chart-list`
|
||||
|
||||
@@ -72,8 +72,6 @@ manage_conditional_format_object create
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_conditional_format_objects` | `+cond-format-list` | read | 对象 |
|
||||
@@ -83,8 +81,6 @@ manage_conditional_format_object create
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+cond-format-list`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -104,7 +100,9 @@ manage_conditional_format_object create
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"range":"Sheet1!A2:F1000","rule":{"type":"cell_value","operator":"greater_than","value":100,"style":{...}}}`,type 可选 `cell_value` / `duplicate` / `data_bar` / `color_scale` / `rank` / `formula` |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +cond-format-create / --data: 规则配置 JSON,含 `style`(命中样式,必填)和 `attrs?`(规则参数列表,因 rule_type 不同结构而异)/ `has_ref?`。`rule_type` 和 `ranges` 已拎为独立 flag |
|
||||
| `--rule-type` | 专有 | string + Enum | 是 | 条件格式规则类型 enum:`cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell`(共 13 项);优先级高于 `--data` |
|
||||
| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--data` |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+cond-format-update`
|
||||
@@ -116,7 +114,9 @@ manage_conditional_format_object create
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--rule-id` | 专有 | string | 是 | 目标规则 id |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的规则配置(先 `+cond-format-list --rule-id <id>` 回读再 patch) |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +cond-format-update / --data: 同 +cond-format-create;update 是整组覆盖式 |
|
||||
| `--rule-type` | 专有 | string + Enum | 是 | 条件格式规则类型 enum:`cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell`(共 13 项);优先级高于 `--data` |
|
||||
| `--ranges` | 专有 | string + File + Stdin(简单 JSON) | 是 | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--data` |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+cond-format-delete`
|
||||
@@ -135,7 +135,7 @@ manage_conditional_format_object create
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+cond-format-create` `--data` / `+cond-format-update` `--data`
|
||||
### `+cond-format-create` `--properties` / `+cond-format-update` `--properties`
|
||||
|
||||
_创建/更新的条件格式属性_
|
||||
|
||||
@@ -148,8 +148,6 @@ _创建/更新的条件格式属性_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。
|
||||
|
||||
### `+cond-format-list`
|
||||
|
||||
@@ -25,8 +25,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_filter_view_objects` | `+filter-view-list` | read | 对象 |
|
||||
@@ -36,8 +34,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+filter-view-list`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -57,7 +53,9 @@
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 视图配置 JSON:`{"view_name":"...","range":"A1:Z100","rules":[...]}`;省略 view_id 表示 create;range 必填且必须覆盖表头行 |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +filter-view-create / --data: 视图规则 JSON,含 `rules?`(列级筛选规则数组)和 `filtered_columns?`。`range` 和 `view_name` 已拎为独立 flag |
|
||||
| `--range` | 专有 | string | 是 | 筛选作用的单元格范围(A1 表示法,如 `A1:F1000`);优先级高于 `--data` 中同名字段(create 必填,必须覆盖表头行) |
|
||||
| `--view-name` | 专有 | string | 否 | 视图名称;create 不传时系统自动分配,update 不传时保留原名。优先级高于 `--data` 中同名字段 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+filter-view-update`
|
||||
@@ -69,7 +67,9 @@
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--view-id` | 专有 | string | 是 | 目标视图 reference_id |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 部分更新 JSON:含 view_name / range / rules 之一即可;先 +filter-view-list 回读再 patch |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +filter-view-update / --data: 视图规则 JSON,含 `rules?` 和 `filtered_columns?`。`range` 和 `view_name` 已拎为独立 flag;至少传 `--data.rules` / `--range` / `--view-name` 之一 |
|
||||
| `--range` | 专有 | string | 否 | 筛选作用的单元格范围(A1 表示法,如 `A1:F1000`);优先级高于 `--data` 中同名字段(update 时省略表示保留当前 range) |
|
||||
| `--view-name` | 专有 | string | 否 | 视图名称;create 不传时系统自动分配,update 不传时保留原名。优先级高于 `--data` 中同名字段 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+filter-view-delete`
|
||||
@@ -88,7 +88,7 @@
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+filter-view-create` `--data` / `+filter-view-update` `--data`
|
||||
### `+filter-view-create` `--properties` / `+filter-view-update` `--properties`
|
||||
|
||||
_create / update 的视图属性_
|
||||
|
||||
@@ -100,8 +100,6 @@ _create / update 的视图属性_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
> ⚠️ 本 skill 是 **CLI 独有**(meta `surface: cli-only`);`generate_mcp` 跳过,不会进 sheet-ai-skills SKILL 集。AI/MCP 侧暂不暴露筛选视图能力。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`view_id` 是 10 位随机字符串,每个 sheet 可有多个视图。
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_filter_objects` | `+filter-list` | read | 对象 |
|
||||
@@ -40,8 +38,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+filter-list`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -60,8 +56,8 @@
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--range` | 专有 | string | 是 | 筛选范围,含表头行(如 `A1:F1000`) |
|
||||
| `--data` | 专有 | string + File + Stdin | 否 | JSON:`{"conditions":[{"col":"B","filter_type":"multiValue","expected":["北京","上海"]}]}`;省略则只建空筛选 |
|
||||
| `--range` | 专有 | string | 是 | 筛选范围(A1 表示法,含表头行,如 `A1:F1000`);不要重复写入 --data 中的 range 字段 |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 否 | +filter-create / --data: 筛选规则 JSON,含 `rules`(列级筛选规则数组,必填)和 `filtered_columns?`(激活列索引提示)。`range` 是独立 flag(不要再放此 JSON 里) |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+filter-update`
|
||||
@@ -72,7 +68,8 @@
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON:可改 `range` 或追加 / 替换 `conditions[]`;先 `+filter-list` 回读再 patch |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | +filter-update / --data: 筛选规则 JSON,含 `rules` 和 `filtered_columns?`;update 是整组覆盖式(传空 `rules: []` 清空)。`range` 已拎为独立 flag |
|
||||
| `--range` | 专有 | string | 是 | 筛选作用的单元格范围(A1 表示法,如 `A1:F1000`);优先级高于 `--data` 中同名字段 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+filter-delete`
|
||||
@@ -90,7 +87,7 @@
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+filter-create` `--data` / `+filter-update` `--data`
|
||||
### `+filter-create` `--properties` / `+filter-update` `--properties`
|
||||
|
||||
_创建/更新的筛选器属性_
|
||||
|
||||
@@ -101,8 +98,6 @@ _创建/更新的筛选器属性_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`filter_id` 等同于 `sheet_id`(每个工作表至多一个筛选器)。
|
||||
|
||||
### `+filter-list`
|
||||
|
||||
@@ -36,8 +36,6 @@ reference_id 的映射规则:
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_float_image_objects` | `+float-image-list` | read | 对象 |
|
||||
@@ -47,8 +45,6 @@ reference_id 的映射规则:
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+float-image-list`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -68,7 +64,16 @@ reference_id 的映射规则:
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"image_uri":"...","image_name":"foo.png","position":{"row":2,"col":"D"},"size":{"width":300,"height":200},"offset":{"x":0,"y":0}}` |
|
||||
| `--image-name` | 专有 | string | 是 | 图片名称,含拓展名(如 `logo.png`) |
|
||||
| `--image-token` | 专有 | string | XOR | 图片 file_token(与 `--image-uri` 二选一)。常见来源:`+float-image-list` 返回的 `image_token` |
|
||||
| `--image-uri` | 专有 | string | XOR | 图片 reference_id(与 `--image-token` 二选一);形如 `<\|image\|>:abcdef` 这种带前缀的字符串,从上游 SKILL.md 的素材引用约定取 |
|
||||
| `--position-row` | 专有 | int | 是 | 图片左上角所在行(0-based) |
|
||||
| `--position-col` | 专有 | string | 是 | 图片左上角所在列(列字母,如 `A` / `B`) |
|
||||
| `--size-width` | 专有 | int | 是 | 图片宽度(像素) |
|
||||
| `--size-height` | 专有 | int | 是 | 图片高度(像素) |
|
||||
| `--offset-row` | 专有 | int | 否 | 在 position 基础上的行内偏移(像素) |
|
||||
| `--offset-col` | 专有 | int | 否 | 在 position 基础上的列内偏移(像素) |
|
||||
| `--z-index` | 专有 | int | 否 | 图片 Z 轴层级,控制重叠顺序 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+float-image-update`
|
||||
@@ -80,7 +85,16 @@ reference_id 的映射规则:
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--float-image-id` | 专有 | string | 是 | 目标图片 id |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的配置 JSON(先 `+float-image-list --float-image-id <id>` 回读再 patch) |
|
||||
| `--image-name` | 专有 | string | 是 | 图片名称,含拓展名(如 `logo.png`) |
|
||||
| `--image-token` | 专有 | string | XOR | 图片 file_token(与 `--image-uri` 二选一)。常见来源:`+float-image-list` 返回的 `image_token` |
|
||||
| `--image-uri` | 专有 | string | XOR | 图片 reference_id(与 `--image-token` 二选一);形如 `<\|image\|>:abcdef` 这种带前缀的字符串,从上游 SKILL.md 的素材引用约定取 |
|
||||
| `--position-row` | 专有 | int | 是 | 图片左上角所在行(0-based) |
|
||||
| `--position-col` | 专有 | string | 是 | 图片左上角所在列(列字母,如 `A` / `B`) |
|
||||
| `--size-width` | 专有 | int | 是 | 图片宽度(像素) |
|
||||
| `--size-height` | 专有 | int | 是 | 图片高度(像素) |
|
||||
| `--offset-row` | 专有 | int | 否 | 在 position 基础上的行内偏移(像素) |
|
||||
| `--offset-col` | 专有 | int | 否 | 在 position 基础上的列内偏移(像素) |
|
||||
| `--z-index` | 专有 | int | 否 | 图片 Z 轴层级,控制重叠顺序 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+float-image-delete`
|
||||
@@ -95,27 +109,8 @@ reference_id 的映射规则:
|
||||
| `--yes` | 系统 | bool | 是 | `high-risk-write`,删除不可逆 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
## Schemas
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+float-image-create` `--data` / `+float-image-update` `--data`
|
||||
|
||||
_创建/更新的浮动图片属性_
|
||||
|
||||
**顶层字段**:
|
||||
- `image_name` (string) — 图片名称,含拓展名,create 时必填
|
||||
- `image_token` (string?) — 图片 fileToken(与 image_uri 二选一)
|
||||
- `image_uri` (string?) — 图片的 reference_id(与 image_token 二选一)
|
||||
- `offset` (object?) — 可选 { col_offset?: number, row_offset?: number }
|
||||
- `position` (object) — 必填 { col: string, row: number }
|
||||
- `size` (object) — 必填 { height: number, width: number }
|
||||
- `z_index` (number?) — 可选
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。浮动图片是 sheet 级对象——和单元格内嵌图片不同(后者走 `+cells-set`)。
|
||||
|
||||
### `+float-image-list`
|
||||
|
||||
@@ -38,8 +38,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_pivot_table_objects` | `+pivot-list` | read | 对象 |
|
||||
@@ -49,8 +47,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+pivot-list`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -70,9 +66,11 @@
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"data_range":"Sheet1!A1:F1000","rows":[...],"columns":[...],"values":[...],"filters":[...],"show_row_grand_total":true,"show_col_grand_total":true}` |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"data_range":"Sheet1!A1:F1000","rows":[...],"columns":[...],"values":[...],"filters":[...],"show_row_grand_total":true,"show_col_grand_total":true}` |
|
||||
| `--target-sheet-id` | 专有 | string | 否 | 透视表落点子表 id;省略时自动新建子表(推荐) |
|
||||
| `--target-position` | 专有 | string | 否 | 落点起始 cell(如 `A1`),默认 `A1` |
|
||||
| `--source` | 专有 | string | 是 | 透视表源数据区域(A1 表示法,格式 `SheetName!StartCell:EndCell`,如 `Sheet1!A1:D100`) |
|
||||
| `--range` | 专有 | string | 否 | 透视表放置位置(左上角 A1 单值,如 `F1`);省略时放在新建子表的左上角 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+pivot-update`
|
||||
@@ -84,7 +82,7 @@
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--pivot-table-id` | 专有 | string | 是 | 目标透视表 id |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的配置(先 `+pivot-list --pivot-table-id <id>` 回读再 patch) |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 完整或足够完整的配置(先 `+pivot-list --pivot-table-id <id>` 回读再 patch) |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+pivot-delete`
|
||||
@@ -103,7 +101,7 @@
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+pivot-create` `--data` / `+pivot-update` `--data`
|
||||
### `+pivot-create` `--properties` / `+pivot-update` `--properties`
|
||||
|
||||
_创建/更新的透视表属性_
|
||||
|
||||
@@ -124,8 +122,6 @@ _创建/更新的透视表属性_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`+pivot-create` 默认自动新建子表存放透视表产物(推荐)。
|
||||
|
||||
### `+pivot-list`
|
||||
|
||||
@@ -106,8 +106,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `clear_cell_range` | `+cells-clear` | high-risk-write | 单元格 |
|
||||
@@ -121,8 +119,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+cells-clear`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -225,7 +221,7 @@
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--range` | 专有 | string | 是 | 排序范围(含或不含表头由 `--has-header` 决定) |
|
||||
| `--sort-keys` | 专有 | string + File + Stdin | 是 | JSON:`[{"col":"B","order":"asc"},{"col":"D","order":"desc"}]` |
|
||||
| `--sort-keys` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`[{"col":"B","order":"asc"},{"col":"D","order":"desc"}]` |
|
||||
| `--has-header` | 专有 | bool | 否 | 第一行是表头不参与排序,默认 false |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
@@ -243,8 +239,6 @@ _排序条件列表(仅 sort 操作)_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
> ⚠️ 本 skill 派生的 7 条 shortcut 跨 3 个分组:`+dim-resize` → 工作表,`+cells-*` → 单元格,`+range-*` → 区域。skill 视角统一在这里讲解。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。
|
||||
|
||||
@@ -76,8 +76,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `export_sheet_to_sandbox` | _Sheet Tool 独有,CLI 不实现_ | — | — |
|
||||
@@ -87,8 +85,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+cells-get`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -131,8 +127,6 @@
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
### `+csv-get`
|
||||
|
||||
公共四件套:`--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(前两者 XOR,后两者 XOR)。
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `search_data` | `+cells-search` | read | 单元格 |
|
||||
@@ -33,8 +31,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+cells-search`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -71,8 +67,6 @@
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR 规则)。
|
||||
|
||||
### `+cells-search`
|
||||
|
||||
@@ -37,8 +37,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_sheet_structure` | `+sheet-info` | read | 工作表 |
|
||||
@@ -53,8 +51,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+sheet-info`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -176,8 +172,6 @@
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。
|
||||
|
||||
### `+sheet-info`
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_sparkline_objects` | `+sparkline-list` | read | 对象 |
|
||||
@@ -33,8 +31,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+sparkline-list`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -54,7 +50,7 @@
|
||||
| `--spreadsheet-token` | 公共 | string | XOR | spreadsheet token(与 `--url` 二选一) |
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"type":"line\\ |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"type":"line\|column\|winLoss","data_range":"A2:F10","target_range":"G2:G10","style":{...},"special_points":{...}}`;type 三种 enum;data_range 与 target_range 行/列数需对齐 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+sparkline-update`
|
||||
@@ -66,7 +62,7 @@
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--group-id` | 专有 | string | 是 | 目标组 id |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | 完整或足够完整的配置(先 `+sparkline-list --group-id <id>` 回读再 patch) |
|
||||
| `--properties` | 专有 | string + File + Stdin(复合 JSON) | 是 | 完整或足够完整的配置(先 `+sparkline-list --group-id <id>` 回读再 patch);可改 type / data_range / target_range / style / special_points 等字段 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+sparkline-delete`
|
||||
@@ -85,7 +81,7 @@
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+sparkline-create` `--data` / `+sparkline-update` `--data`
|
||||
### `+sparkline-create` `--properties` / `+sparkline-update` `--properties`
|
||||
|
||||
_创建/更新/部分删除的迷你图属性_
|
||||
|
||||
@@ -95,8 +91,6 @@ _创建/更新/部分删除的迷你图属性_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。迷你图按 `group_id` 管理——一组同形态的迷你图共享类型 / 样式 / 数据源映射。注意:不等同于已禁用的 `SPARKLINE()` 公式函数。
|
||||
|
||||
### `+sparkline-list`
|
||||
|
||||
@@ -30,8 +30,6 @@
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `get_workbook_structure` | `+workbook-info` | read | 工作簿 |
|
||||
@@ -48,8 +46,6 @@
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+workbook-info`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -154,8 +150,8 @@
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `--title` | 专有 | string | 是 | 新 spreadsheet 标题 |
|
||||
| `--folder-token` | 专有 | string | 否 | 目标文件夹 token;省略放根目录 |
|
||||
| `--headers` | 专有 | string + File + Stdin | 否 | 表头行 JSON 数组:`["列A","列B"]` |
|
||||
| `--data` | 专有 | string + File + Stdin | 否 | 初始数据 JSON 二维数组:`[["alice",95]]` |
|
||||
| `--headers` | 专有 | string + File + Stdin(简单 JSON) | 否 | 表头行 JSON 数组:`["列A","列B"]` |
|
||||
| `--values` | 专有 | string + File + Stdin(简单 JSON) | 否 | 初始数据 JSON 二维数组:`[["alice",95]]` |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+workbook-export`
|
||||
@@ -171,8 +167,6 @@
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。`+workbook-info` 只用前两者;`+sheet-*` 系列对单个工作表操作,需 `--sheet-id` 或 `--sheet-name`。
|
||||
|
||||
### `+workbook-info`
|
||||
|
||||
@@ -167,8 +167,6 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
|
||||
## Shortcuts
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成。CLI 的 shortcut 拆分、Risk 分级、分组、flag 表是事实源;本节不要手维护。
|
||||
|
||||
| MCP tool | CLI shortcut | Risk | 分组 |
|
||||
| --- | --- | --- | --- |
|
||||
| `set_cell_range` | `+cells-set` | write | 单元格 |
|
||||
@@ -180,8 +178,6 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
|
||||
## Flags
|
||||
|
||||
> 由 [`tool-shortcut-map.json`](../../../canonical-spec/tool-shortcut-map.json) 自动生成(包含从 base shortcut-flags 子表派生的 flag 信息)。本节不要手维护——改 base 表再 `npm run sync:tool-shortcut-map`。
|
||||
|
||||
### `+cells-set`
|
||||
|
||||
| Flag | 分类 | Type | 必填 | 说明 |
|
||||
@@ -191,7 +187,7 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--range` | 专有 | string | 是 | 写入区域 A1 格式 |
|
||||
| `--data` | 专有 | string + File + Stdin | 是 | JSON:`{"values": [[...], ...]}`;可含 `formula` / `cell_styles` / `comments` / `embed_image` 富信息 |
|
||||
| `--cells` | 专有 | string + File + Stdin(复合 JSON) | 是 | JSON:`{"values": [[...], ...]}`;可含 `formula` / `cell_styles` / `comments` / `embed_image` 富信息 |
|
||||
| `--allow-overwrite` | 专有 | bool | 否 | 允许覆盖非空 cell;默认 false 时遇非空 cell 报错 |
|
||||
| `--max-cells` | 专有 | int + Hidden | 否 | 防爆,默认 50000 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
@@ -205,7 +201,17 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--range` | 专有 | string | 是 | 目标范围 A1 格式(如 `A1:B2`) |
|
||||
| `--style` | 专有 | string + File + Stdin | 是 | 样式 JSON:`{"font":{"bold":true},"backColor":"#fff","border_styles":{...}}`;只改样式,不动 value/formula(底层走 set_cell_range 的 cell_styles + border_styles 字段) |
|
||||
| `--background-color` | 专有 | string | 否 | 背景颜色(十六进制,如 `#ffffff`) |
|
||||
| `--font-color` | 专有 | string | 否 | 字体颜色(十六进制,如 `#000000`) |
|
||||
| `--font-size` | 专有 | number | 否 | 字体大小(px,例:10、12、14) |
|
||||
| `--font-style` | 专有 | string + Enum | 否 | 字体样式 enum:`normal` / `italic` |
|
||||
| `--font-weight` | 专有 | string + Enum | 否 | 字重 enum:`normal` / `bold` |
|
||||
| `--font-line` | 专有 | string + Enum | 否 | 字体线条样式 enum:`none` / `underline` / `line-through` |
|
||||
| `--horizontal-alignment` | 专有 | string + Enum | 否 | 水平对齐 enum:`left` / `center` / `right` |
|
||||
| `--vertical-alignment` | 专有 | string + Enum | 否 | 垂直对齐 enum:`top` / `middle` / `bottom` |
|
||||
| `--word-wrap` | 专有 | string + Enum | 否 | 换行策略 enum:`overflow` / `auto-wrap` / `word-clip`(默认 `overflow`) |
|
||||
| `--number-format` | 专有 | string | 否 | 数字格式(例:文本 `@`、数字 `0.00`、货币 `$#,##0.00`、日期 `mm/dd/yyyy`) |
|
||||
| `--border-styles` | 专有 | string + File + Stdin(复合 JSON) | 否 | 边框配置 JSON:`{ top: {style,color,weight}, bottom: ..., left: ..., right: ... }`;4 方向结构相同 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
### `+cells-set-image`
|
||||
@@ -230,8 +236,8 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--range` | 专有 | string | 是 | 目标范围 A1 格式(如 `A2:A100`) |
|
||||
| `--options` | 专有 | string + File + Stdin | 是 | 选项 JSON 数组 `["opt1","opt2"]`;最多 500 项,每项 ≤100 字符,不含逗号 |
|
||||
| `--colors` | 专有 | string + File + Stdin | 否 | RGB hex 颜色数组(如 `["#1FB6C1","#F006C2"]`),长度必须与 `--options` 一致 |
|
||||
| `--options` | 专有 | string + File + Stdin(复合 JSON) | 是 | 选项 JSON 数组 `["opt1","opt2"]`;最多 500 项,每项 ≤100 字符,不含逗号 |
|
||||
| `--colors` | 专有 | string + File + Stdin(简单 JSON) | 否 | RGB hex 颜色数组(如 `["#1FB6C1","#F006C2"]`),长度必须与 `--options` 一致 |
|
||||
| `--multiple` | 专有 | bool | 否 | 启用多选;默认 `false` |
|
||||
| `--highlight` | 专有 | bool | 否 | 选项配色显示;默认 `false` |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
@@ -245,7 +251,7 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
| `--sheet-id` | 公共 | string | XOR | 工作表 reference_id(与 `--sheet-name` 二选一) |
|
||||
| `--sheet-name` | 公共 | string | XOR | 工作表名称(与 `--sheet-id` 二选一) |
|
||||
| `--range` | 专有 | string | 是 | 目标区域起点 A1(如 `Sheet1!A1`);自动按 CSV 行列数推断终点 |
|
||||
| `--csv` | 专有 | string + File + Stdin | 是 | RFC 4180 CSV 文本;只写纯值,不带公式/样式/批注 |
|
||||
| `--csv` | 专有 | string + File + Stdin(非 JSON 文本) | 是 | RFC 4180 CSV 文本;只写纯值,不带公式/样式/批注 |
|
||||
| `--allow-overwrite` | 专有 | bool | 否 | 允许覆盖;默认 false 时若目标非空报错 |
|
||||
| `--dry-run` | 系统 | bool | 否 | |
|
||||
|
||||
@@ -253,7 +259,7 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
|
||||
> 复合 JSON flag(`--data` / `--style` / `--options` / `--sort-keys`)的字段速查:只列顶层字段 + 一层嵌套结构。深层结构看 `## Examples` 段的真实示例;要拿完整 JSON Schema 跑 `lark-cli sheets <shortcut> --print-schema --flag <name>`(runtime introspection,待落地)。
|
||||
|
||||
### `+cells-set` `--data`
|
||||
### `+cells-set` `--cells`
|
||||
|
||||
|
||||
**顶层字段**:
|
||||
@@ -266,21 +272,15 @@ set_cell_range — range="A11:H11", cells=[[
|
||||
- `rich_text` (array<object>?) — 富文本内容 each: { attachment_name?: string, attachment_token?: string, attachment_uri?: string, file_size?: number, image_height?: number, …共 17 项 }
|
||||
- `value` (oneOf?) — 静态单元格值(文本、数字、布尔)
|
||||
|
||||
### `+cells-set-style` `--style`
|
||||
### `+cells-set-style` `--border-styles`
|
||||
|
||||
_单元格样式属性,包括字体、颜色、对齐方式和数字格式_
|
||||
_单元格边框配置,含 top/bottom/left/right 四个方向,每个方向的结构相同(见 top)_
|
||||
|
||||
**顶层字段**:
|
||||
- `background_color` (string?) — 背景颜色(十六进制,例如 "#ffffff")
|
||||
- `font_color` (string?) — 字体颜色(十六进制,例如 "#000000")
|
||||
- `font_line` (enum?) — 字体线条样式 [none / underline / line-through]
|
||||
- `font_size` (number?) — 字体大小(单位:px/像素,例如 10、12、14)
|
||||
- `font_style` (enum?) — 字体样式 [normal / italic]
|
||||
- `font_weight` (enum?) — 字重 [normal / bold]
|
||||
- `horizontal_alignment` (enum?) — 水平对齐方式 [left / center / right]
|
||||
- `number_format` (string?) — 数字格式(例如:文本用 "@"、数字用 "0.00"、货币用 "$#,##0.00"、日期用 "mm/dd/yyyy")
|
||||
- `vertical_alignment` (enum?) — 垂直对齐方式 [top / middle / bottom]
|
||||
- `word_wrap` (enum?) — 是否自动换行,默认溢出,可选自动换行或裁剪 [overflow / auto-wrap / word-clip]
|
||||
- `bottom` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
- `left` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
- `right` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
- `top` (object?) { color?: string, style?: enum, weight?: enum }
|
||||
|
||||
### `+dropdown-set` `--options`
|
||||
|
||||
@@ -297,8 +297,6 @@ _数据验证配置_
|
||||
|
||||
## Examples
|
||||
|
||||
> shortcut 拆分 / Risk / 分组 / flag 表都由 [`tool-shortcut-map.json`](../../tool-shortcut-map.json) 自动注入到上方 `## Shortcuts` / `## Flags` 段。本节只承载手维护补充:命令示例、Validate / DryRun / Execute 约束。
|
||||
|
||||
公共四件套:所有 shortcut 顶部排列 `--url` / `--spreadsheet-token` / `--sheet-id` / `--sheet-name`(XOR)。
|
||||
|
||||
### `+cells-set`
|
||||
|
||||
Reference in New Issue
Block a user