refactor(sheets): build shortcut flags generically from flag-defs.json

Replace flag-descriptions.en.json with the richer flag-defs.json (full
flag definitions: type / default / enum / input / hidden / required /
kind) synced from sheet-skill-spec. Add flagsFor(command) to materialize
each shortcut's []common.Flag straight from the JSON, skipping
system-kind flags the framework injects.

Migrate every sheets shortcut (including the CRUD/list/dim/merge/
visibility factories) to Flags: flagsFor("+command"), dropping all
hand-written flag literals plus the now-dead publicTokenFlags /
publicSheetFlags / styleFlatFlags helpers and enum vars. A coverage test
locks the Go-flags-match-JSON contract.

Align Go with the new spec where they diverged: +cells-get --ranges →
--range, font-size int → float64, +filter-view-create --range now
required, +sheet-create row/col-count defaults 200/20.
This commit is contained in:
xiongyuanwen-byted
2026-05-20 17:46:49 +08:00
parent ce852e26d8
commit 3d3e2c7f10
36 changed files with 5162 additions and 1491 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,634 +0,0 @@
{
"+workbook-info": {
"--url": "Spreadsheet locator",
"--spreadsheet-token": "Spreadsheet locator",
"--include-properties": "Whether to include each sheet's extended properties; default `true`"
},
"+sheet-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--title": "New sheet title",
"--index": "Insert position; appended to the end when omitted",
"--row-count": "Initial row count; default 100",
"--col-count": "Initial column count; default 26"
},
"+sheet-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--yes": "Confirm high-risk write (exit code 10 without this flag)"
},
"+sheet-rename": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--title": "New title"
},
"+sheet-move": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--index": "Target position (0-based)",
"--source-index": "Source position (0-based); optional. If omitted, the CLI runtime derives it from the current workbook index of `--sheet-id` / `--sheet-name`"
},
"+sheet-copy": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--title": "Copy title; auto-generated by the server when omitted",
"--index": "Insert position for the copy (0-based); appended to the end when omitted"
},
"+sheet-hide": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)"
},
"+sheet-unhide": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)"
},
"+sheet-set-tab-color": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--color": "Hex color like `#FF0000`; pass empty string `\"\"` to clear"
},
"+workbook-create": {
"--title": "Spreadsheet title",
"--folder-token": "Target folder token; placed at the drive root when omitted",
"--headers": "Header row as a JSON array: `[\"Col A\",\"Col B\"]`",
"--values": "Initial data as a 2D JSON array: `[[\"alice\",95]]`"
},
"+workbook-export": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--file-extension": "`xlsx` / `csv`; default `xlsx`. `csv` mode requires `--sheet-id`",
"--sheet-id": "Required only in csv mode: the sheet reference_id to export",
"--output-path": "Local save path; export is triggered but not downloaded when omitted"
},
"+sheet-info": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--include": "Comma-separated subset of `merges` / `row_heights` / `col_widths` / `hidden_rows` / `hidden_cols` / `groups` / `frozen`"
},
"+dim-insert": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--start": "Insert start position (0-based)",
"--end": "Insert end position (exclusive)",
"--inherit-style": "Style inheritance for the new row/column: `before` (from preceding) / `after` (from following) / `none` (default)"
},
"+dim-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--start": "Start position (0-based)",
"--end": "End position (exclusive)",
"--yes": "Confirm destructive write (exit code 10 without this flag); row/column deletion is irreversible"
},
"+dim-hide": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--start": "End position (0-based, inclusive)",
"--end": "End position (0-based, inclusive)"
},
"+dim-unhide": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--start": "End position (0-based, inclusive)",
"--end": "End position (0-based, inclusive)"
},
"+dim-freeze": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--count": "Freeze the first N rows/columns; pass 0 to unfreeze"
},
"+dim-group": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--start": "End position (0-based, inclusive)",
"--end": "End position (0-based, inclusive)",
"--depth": "Nesting level (used by `+dim-group`); default 1"
},
"+dim-ungroup": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--start": "End position (0-based, inclusive)",
"--end": "End position (0-based, inclusive)",
"--depth": "Nesting level (used by `+dim-group`); default 1"
},
"+dim-move": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--dimension": "`row` / `column`",
"--start": "Source range start position (0-based, inclusive)",
"--end": "Source range end position (0-based, inclusive)",
"--target": "Destination position (move target inserts before this index; 0-based)"
},
"+cells-get": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "A1 range, e.g. `Sheet1!A1:F10`",
"--include": "Comma-separated subset of `value` / `formula` / `style` / `comment` / `data_validation`",
"--cell-limit": "Safety cap; default 5000",
"--max-chars": "Safety cap; default 200000",
"--skip-hidden": "Skip hidden rows and columns; default `false`"
},
"+dropdown-get": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--range": "Target range (A1 notation; must include the sheet prefix, e.g. `sheet1!A2:A100`)"
},
"+csv-get": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "A1 range; reads the whole sheet's `current_region` when omitted",
"--value-render-option": "Cell value render mode: `ToString` / `FormattedValue` / `Formula` / `UnformattedValue`",
"--max-rows": "Safety cap; default 100000",
"--max-chars": "Safety cap; default 200000",
"--include-row-prefix": "Whether to prefix each row with `[row=N]`; default `true`",
"--skip-hidden": "Skip hidden rows and columns; default `false`",
"--dry-run": "Print the request path and parameters without executing"
},
"+cells-search": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--find": "Text to find (interpreted as regex when `--regex` is set)",
"--range": "Search range (A1 notation); whole sheet when omitted",
"--match-case": "Case-sensitive match",
"--match-entire-cell": "Match the entire cell content",
"--regex": "Interpret `--find` as a regex pattern",
"--include-formulas": "Also search within formula text",
"--max-matches": "Safety cap; default 5000"
},
"+cells-replace": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--find": "Text to find for replacement",
"--replacement": "Replacement text; pass empty string `\"\"` to delete matched content",
"--range": "Replace range (A1 notation); whole sheet when omitted",
"--match-case": "Also replace within formula text",
"--match-entire-cell": "Also replace within formula text",
"--regex": "Also replace within formula text",
"--include-formulas": "Also replace within formula text",
"--dry-run": "Required preflight: outputs `would_replace_count` for user confirmation before the actual replace"
},
"+cells-set": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Write range (A1 notation)",
"--cells": "JSON: `{\"values\": [[...], ...]}`; may include `formula` / `cell_styles` / `comments` / `embed_image` rich-cell fields",
"--allow-overwrite": "Allow overwriting non-empty cells; with default `false`, error if any target cell is non-empty",
"--max-cells": "Safety cap; default 50000"
},
"+cells-set-style": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Target range (A1 notation, e.g. `A1:B2`)",
"--background-color": "Background color (hex, e.g. `#ffffff`)",
"--font-color": "Font color (hex, e.g. `#000000`)",
"--font-size": "Font size in px (e.g. 10, 12, 14)",
"--font-style": "Font style enum: `normal` / `italic`",
"--font-weight": "Font weight enum: `normal` / `bold`",
"--font-line": "Font line style enum: `none` / `underline` / `line-through`",
"--horizontal-alignment": "Horizontal alignment enum: `left` / `center` / `right`",
"--vertical-alignment": "Vertical alignment enum: `top` / `middle` / `bottom`",
"--word-wrap": "Word-wrap strategy: `overflow` / `auto-wrap` / `word-clip`; default `overflow`",
"--number-format": "Number format pattern (e.g. text `@`, number `0.00`, currency `$#,##0.00`, date `mm/dd/yyyy`)",
"--border-styles": "Border config JSON: `{ top: {style,color,weight}, bottom: ..., left: ..., right: ... }`; same shape for all 4 sides"
},
"+cells-set-image": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Target cell (A1 notation; must be a single cell, e.g. `A1`; start and end must be identical)",
"--image": "Local image path (PNG / JPEG / JPG / GIF / BMP / JFIF / EXIF / TIFF / BPG / HEIC)",
"--name": "Image file name (with extension); defaults to the basename of `--image`"
},
"+dropdown-set": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Target range (A1 notation, e.g. `A2:A100`)",
"--options": "Options as a JSON array, e.g. `[\"opt1\",\"opt2\"]`; up to 500 items, each ≤100 chars, no commas",
"--colors": "RGB hex color array (e.g. `[\"#1FB6C1\",\"#F006C2\"]`); length must equal `--options`",
"--multiple": "Enable multi-select; default `false`",
"--highlight": "Color-highlight options; default `false`"
},
"+csv-put": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Top-left A1 anchor (e.g. `Sheet1!A1`); the bottom-right is inferred from CSV row/column counts",
"--csv": "RFC 4180 CSV text; plain values only (no formulas / styles / comments)",
"--allow-overwrite": "Allow overwriting; with default `false`, error if any target cell is non-empty"
},
"+cells-clear": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Range to clear (A1 notation)",
"--scope": "Clear scope: `content` (default, values only) / `formats` (formats only) / `all` (values and formats)",
"--yes": "Confirm destructive write (exit code 10 without this flag); clear is irreversible"
},
"+cells-merge": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Range to merge / unmerge (A1 notation)",
"--merge-type": "Merge direction (`+cells-merge` only): `all` / `rows` / `columns`; default `all`"
},
"+cells-unmerge": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Range to merge / unmerge (A1 notation)",
"--merge-type": "Merge direction (`+cells-merge` only): `all` / `rows` / `columns`; default `all`"
},
"+rows-resize": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--start": "Start row (0-based, inclusive)",
"--end": "End row (0-based, inclusive)",
"--type": "Sizing mode: `pixel` (explicit px value, requires `--size`) / `standard` (reset to default row height) / `auto` (fit content)",
"--size": "Row height in pixels (e.g. 30 / 40 / 60); required when `--type pixel`, ignored otherwise"
},
"+cols-resize": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--start": "Start column (0-based, inclusive)",
"--end": "End column (0-based, inclusive)",
"--type": "Sizing mode: `pixel` (explicit px value, requires `--size`) / `standard` (reset to default column width)",
"--size": "Column width in pixels (e.g. 80 / 120 / 200); required when `--type pixel`, ignored otherwise"
},
"+range-move": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--source-range": "Source A1 range",
"--target-sheet-id": "Destination sub-sheet id; defaults to the same sheet as the source",
"--target-range": "Destination A1 range (anchor cell is enough; size inferred from the source)",
"--paste-type": "Paste content type (`+range-copy` only): `values` / `formulas` / `formats` / `all`; default `all`"
},
"+range-copy": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--source-range": "Source A1 range",
"--target-sheet-id": "Destination sub-sheet id; defaults to the same sheet as the source",
"--target-range": "Destination A1 range (anchor cell is enough; size inferred from the source)",
"--paste-type": "Paste content type (`+range-copy` only): `values` / `formulas` / `formats` / `all`; default `all`"
},
"+range-fill": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--source-range": "Fill template range (seed cells for the series)",
"--target-range": "Destination fill range (A1 notation)",
"--series-type": "Fill series type: `auto` / `linear` / `growth` / `date` / `copy`; default `auto`"
},
"+range-sort": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Sort range (A1 notation; whether the header is included depends on `--has-header`)",
"--sort-keys": "Sort keys JSON: `[{\"col\":\"B\",\"order\":\"asc\"},{\"col\":\"D\",\"order\":\"desc\"}]`",
"--has-header": "Treat the first row as a header and exclude from sort; default `false`"
},
"+batch-update": {
"--url": "Spreadsheet locator (independent from per-operation sheet locator)",
"--spreadsheet-token": "Spreadsheet locator (independent from per-operation sheet locator)",
"--operations": "JSON: `{\"operations\":[{\"tool\":\"set_cell_range\",\"params\":{...}}, ...]}`; executed serially in array order",
"--yes": "Confirm high-risk write (exit code 10 without this flag)",
"--dry-run": "Print the request template for each sub-operation; no network side effects"
},
"+cells-batch-set-style": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--ranges": "Target ranges as a JSON array (e.g. `[\"sheet1!A1:B2\",\"sheet1!D1:D10\"]`); each item must include the sheet prefix; the same style is applied to all ranges",
"--background-color": "Background color (hex, e.g. `#ffffff`)",
"--font-color": "Font color (hex, e.g. `#000000`)",
"--font-size": "Font size in px (e.g. 10, 12, 14)",
"--font-style": "Font style enum: `normal` / `italic`",
"--font-weight": "Font weight enum: `normal` / `bold`",
"--font-line": "Font line style enum: `none` / `underline` / `line-through`",
"--horizontal-alignment": "Horizontal alignment enum: `left` / `center` / `right`",
"--vertical-alignment": "Vertical alignment enum: `top` / `middle` / `bottom`",
"--word-wrap": "Word-wrap strategy: `overflow` / `auto-wrap` / `word-clip`; default `overflow`",
"--number-format": "Number format pattern (e.g. text `@`, number `0.00`, currency `$#,##0.00`, date `mm/dd/yyyy`)",
"--border-styles": "Border config JSON (same shape as in +cells-set-style)"
},
"+dropdown-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--ranges": "Target ranges as a JSON array (e.g. `[\"sheet1!A2:A100\"]`); each item must include the sheet prefix",
"--options": "Options as a JSON array (e.g. `[\"opt1\",\"opt2\"]`)",
"--colors": "Color array (same length as `--options`)",
"--multiple": "Enable multi-select",
"--highlight": "Color-highlight options"
},
"+dropdown-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--ranges": "Target ranges as a JSON array (up to 100 items; each must include the sheet prefix)",
"--yes": "Confirm high-risk write (exit code 10 without this flag)"
},
"+chart-list": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--chart-id": "Filter to a single chart reference_id"
},
"+chart-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--properties": "Full chart config JSON (`position` / `data` / `properties` etc.); deeply nested, must be passed as JSON",
"--dry-run": "Print the request template; no side effects"
},
"+chart-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--chart-id": "Target chart reference_id",
"--properties": "Full or sufficiently complete chart config JSON (read back with `+chart-list` first, then patch)"
},
"+chart-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--chart-id": "Target chart reference_id",
"--yes": "Confirm destructive write (exit code 10 without this flag)"
},
"+pivot-list": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--pivot-table-id": "Filter by id"
},
"+pivot-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--properties": "JSON: `{\"data_range\":\"Sheet1!A1:F1000\",\"rows\":[...],\"columns\":[...],\"values\":[...],\"filters\":[...],\"show_row_grand_total\":true,\"show_col_grand_total\":true}`",
"--target-sheet-id": "Destination sub-sheet id for the pivot table; auto-creates a new sub-sheet when omitted (recommended)",
"--target-position": "Destination anchor cell (A1 notation, e.g. `A1`); default `A1`",
"--source": "Pivot table source range (A1 notation; format `SheetName!StartCell:EndCell`, e.g. `Sheet1!A1:D100`)",
"--range": "Pivot table placement (single A1 anchor for the top-left, e.g. `F1`); placed at the top-left of a newly created sub-sheet when omitted"
},
"+pivot-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--pivot-table-id": "Target pivot table id",
"--properties": "Full or sufficiently complete pivot config (read back with `+pivot-list --pivot-table-id <id>` first, then patch)"
},
"+pivot-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--pivot-table-id": "Target pivot table id",
"--yes": "Confirm destructive write (exit code 10 without this flag); delete is irreversible"
},
"+cond-format-list": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--rule-id": "Filter by rule id"
},
"+cond-format-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--properties": "Rule config JSON: `style` (required, applied on match), `attrs?` (rule-type-dependent params), `has_ref?`. `rule_type` and `ranges` are separate flags",
"--rule-type": "Conditional format rule type (13 values): `cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell`; takes precedence over the same-named field inside `--properties`",
"--ranges": "A1 ranges where the conditional format applies, as a JSON array (e.g. `[\"A1:A100\",\"C2:C50\"]`); takes precedence over the same-named field inside `--properties`"
},
"+cond-format-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--rule-id": "Target rule id",
"--properties": "Rule config JSON, same shape as `+cond-format-create --properties`; update overwrites the entire rule",
"--rule-type": "Conditional format rule type (13 values): `cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell`; takes precedence over the same-named field inside `--properties`",
"--ranges": "A1 ranges where the conditional format applies, as a JSON array (e.g. `[\"A1:A100\",\"C2:C50\"]`); takes precedence over the same-named field inside `--properties`"
},
"+cond-format-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--rule-id": "Target rule id",
"--yes": "Confirm destructive write (exit code 10 without this flag); delete is irreversible"
},
"+filter-list": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)"
},
"+filter-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--range": "Filter range (A1 notation, including header row, e.g. `A1:F1000`); do not duplicate the range field inside `--properties`",
"--properties": "Filter rule JSON: `rules` (required, per-column rule array), `filtered_columns?` (active column index hint). `range` is a separate flag (do not duplicate inside this JSON)"
},
"+filter-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--properties": "Filter rule JSON: `rules` and `filtered_columns?`; update overwrites the entire rule set (pass `rules: []` to clear). `range` is a separate flag",
"--range": "Range the filter applies to (A1 notation, e.g. `A1:F1000`); takes precedence over the same-named field inside `--properties`"
},
"+filter-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--yes": "Confirm destructive write (exit code 10 without this flag); delete is irreversible"
},
"+filter-view-list": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--view-id": "Filter by filter-view reference_id (returns the matching single view)"
},
"+filter-view-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--properties": "Filter-view rule JSON: `rules?` (per-column rule array), `filtered_columns?`. `range` and `view_name` are separate flags",
"--range": "Range the filter view applies to (A1 notation, e.g. `A1:F1000`); takes precedence over the same-named field inside `--properties`; required on create and must cover the header row",
"--view-name": "Filter-view name; auto-assigned by the server when omitted on create, kept unchanged when omitted on update; takes precedence over the same-named field inside `--properties`"
},
"+filter-view-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--view-id": "Target filter-view reference_id",
"--properties": "Filter-view rule JSON: `rules?`, `filtered_columns?`. `range` and `view_name` are separate flags; pass at least one of `--properties.rules` / `--range` / `--view-name`",
"--range": "Range the filter view applies to (A1 notation, e.g. `A1:F1000`); takes precedence over the same-named field inside `--properties`; omit to keep the current range on update",
"--view-name": "Filter-view name; auto-assigned by the server when omitted on create, kept unchanged when omitted on update; takes precedence over the same-named field inside `--properties`"
},
"+filter-view-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--view-id": "Target filter-view reference_id",
"--yes": "Confirm high-risk write (exit code 10 without this flag)"
},
"+sparkline-list": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--group-id": "Filter by group_id"
},
"+sparkline-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--properties": "JSON: `{\"type\":\"line|column|winLoss\",\"data_range\":\"A2:F10\",\"target_range\":\"G2:G10\",\"style\":{...},\"special_points\":{...}}`; `type` is a 3-value enum; row/column counts of `data_range` must match `target_range`"
},
"+sparkline-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--group-id": "Target group id",
"--properties": "Full or sufficiently complete sparkline config (read back with `+sparkline-list --group-id <id>` first, then patch); supports updating `type` / `data_range` / `target_range` / `style` / `special_points` etc."
},
"+sparkline-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--group-id": "Target group id",
"--yes": "Confirm destructive write (exit code 10 without this flag); delete is irreversible"
},
"+float-image-list": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--float-image-id": "Filter by id; lists all float images on the sheet when omitted"
},
"+float-image-create": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--image-name": "Image name, including extension (e.g. `logo.png`)",
"--image-token": "Image file_token (XOR with `--image-uri`). Common source: `image_token` returned by `+float-image-list`",
"--image-uri": "Image reference_id (XOR with `--image-token`); a prefixed string like `<|image|>:abcdef`",
"--position-row": "Row anchor of the image's top-left corner (0-based)",
"--position-col": "Column anchor of the image's top-left corner (column letter, e.g. `A` / `B`)",
"--size-width": "Image width in pixels",
"--size-height": "Image height in pixels",
"--offset-row": "Pixel offset within the anchor row, on top of `--position-row`",
"--offset-col": "Pixel offset within the anchor column, on top of `--position-col`",
"--z-index": "Image z-index controlling stacking order"
},
"+float-image-update": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--float-image-id": "Target float image id",
"--image-name": "Image name, including extension (e.g. `logo.png`)",
"--image-token": "Image file_token (XOR with `--image-uri`). Common source: `image_token` returned by `+float-image-list`",
"--image-uri": "Image reference_id (XOR with `--image-token`); a prefixed string like `<|image|>:abcdef`",
"--position-row": "Row anchor of the image's top-left corner (0-based)",
"--position-col": "Column anchor of the image's top-left corner (column letter, e.g. `A` / `B`)",
"--size-width": "Image width in pixels",
"--size-height": "Image height in pixels",
"--offset-row": "Pixel offset within the anchor row, on top of `--position-row`",
"--offset-col": "Pixel offset within the anchor column, on top of `--position-col`",
"--z-index": "Image z-index controlling stacking order"
},
"+float-image-delete": {
"--url": "Spreadsheet URL (XOR with `--spreadsheet-token`)",
"--spreadsheet-token": "Spreadsheet token (XOR with `--url`)",
"--sheet-id": "Sheet reference_id (XOR with `--sheet-name`)",
"--sheet-name": "Sheet name (XOR with `--sheet-id`)",
"--float-image-id": "Target float image id",
"--yes": "Confirm destructive write (exit code 10 without this flag); delete is irreversible"
}
}

View File

@@ -99,7 +99,7 @@ func TestExecute_CellsGet(t *testing.T) {
t.Parallel()
stub := toolOutputStub(testToken, "read", `{"ranges":[{"range":"A1:B2","cells":[[{"value":1}]]}]}`)
out, err := runShortcutWithStubs(t, CellsGet,
[]string{"--url", testURL, "--sheet-id", testSheetID, "--ranges", "A1:B2"}, stub)
[]string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:B2"}, stub)
if err != nil {
t.Fatalf("execute failed: %v\nout=%s", err, out)
}

View File

@@ -0,0 +1,94 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package sheets
import (
_ "embed"
"encoding/json"
"fmt"
"sync"
"github.com/larksuite/cli/shortcuts/common"
)
// ─── flag definitions, sourced from sheet-skill-spec ───────────────────
//
// data/flag-defs.json is the canonical, full definition of every CLI flag
// (name, type, default, desc, enum, input, hidden, required, kind),
// generated by sheet-skill-spec's sync script. We embed it and build each
// shortcut's []common.Flag from it at assembly time, so flag metadata never
// has to be hand-written in Go.
//
// Flags with kind == "system" (--dry-run, --yes, ...) are NOT materialized
// here: the framework auto-injects them based on Risk / DryRun / HasFormat.
// Do not hand-edit data/flag-defs.json; regenerate via the sync script.
//go:embed data/flag-defs.json
var flagDefsJSON []byte
type flagDef struct {
Name string `json:"name"`
Kind string `json:"kind"` // "public" | "own" | "system"
Type string `json:"type"` // string | bool | int | int64 | float64 | string_array | string_slice
Required string `json:"required"` // "required" | "optional" | "xor"
Desc string `json:"desc"`
Default string `json:"default"`
Hidden bool `json:"hidden"`
Enum []string `json:"enum"`
Input []string `json:"input"`
}
type commandDef struct {
Risk string `json:"risk"`
Flags []flagDef `json:"flags"`
}
var (
flagDefsOnce sync.Once
flagDefs map[string]commandDef
flagDefsErr error
)
func loadFlagDefs() (map[string]commandDef, error) {
flagDefsOnce.Do(func() {
flagDefs = make(map[string]commandDef)
if err := json.Unmarshal(flagDefsJSON, &flagDefs); err != nil {
flagDefsErr = fmt.Errorf("flag-defs.json: %w", err)
}
})
return flagDefs, flagDefsErr
}
// flagsFor builds the []common.Flag for a shortcut command directly from
// flag-defs.json. System-kind flags are skipped (the framework injects
// them). Panics if the command is absent or the JSON is malformed — this
// is a build-time data contract, so a missing entry is a programming error
// surfaced loudly at startup rather than a silent empty flag set.
func flagsFor(command string) []common.Flag {
defs, err := loadFlagDefs()
if err != nil {
panic(fmt.Sprintf("sheets: %v", err))
}
spec, ok := defs[command]
if !ok {
panic(fmt.Sprintf("sheets: no flag-defs.json entry for %q", command))
}
out := make([]common.Flag, 0, len(spec.Flags))
for _, df := range spec.Flags {
if df.Kind == "system" {
continue
}
out = append(out, common.Flag{
Name: df.Name,
Type: df.Type,
Default: df.Default,
Desc: df.Desc,
Hidden: df.Hidden,
Required: df.Required == "required",
Enum: df.Enum,
Input: df.Input,
})
}
return out
}

View File

@@ -0,0 +1,142 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package sheets
import (
"testing"
"github.com/larksuite/cli/shortcuts/common"
)
// TestFlagDefs_EmbedParses asserts the embedded flag-defs.json blob is valid
// JSON with at least one command entry.
func TestFlagDefs_EmbedParses(t *testing.T) {
t.Parallel()
defs, err := loadFlagDefs()
if err != nil {
t.Fatalf("loadFlagDefs error: %v", err)
}
if len(defs) == 0 {
t.Fatal("flag-defs.json has no command entries")
}
}
// TestFlagsFor_SkipsSystemFlags verifies system-kind flags (--dry-run, --yes)
// are never materialized into a shortcut's Flags slice — the framework injects
// those based on Risk / DryRun.
func TestFlagsFor_SkipsSystemFlags(t *testing.T) {
t.Parallel()
for _, cmd := range []string{"+sheet-delete", "+batch-update", "+csv-get"} {
for _, f := range flagsFor(cmd) {
if f.Name == "dry-run" || f.Name == "yes" {
t.Errorf("%s: system flag --%s leaked into Flags", cmd, f.Name)
}
}
}
}
// TestFlagsFor_MapsAllFields spot-checks that name/type/default/enum/input/
// required/hidden are carried over from the JSON correctly.
func TestFlagsFor_MapsAllFields(t *testing.T) {
t.Parallel()
byName := func(cmd, name string) *common.Flag {
flags := flagsFor(cmd)
for i := range flags {
if flags[i].Name == name {
return &flags[i]
}
}
return nil
}
// enum + default
rt := byName("+dim-insert", "inherit-style")
if rt == nil || len(rt.Enum) != 3 || rt.Default != "none" {
t.Errorf("+dim-insert --inherit-style not mapped: %+v", rt)
}
// required
title := byName("+sheet-create", "title")
if title == nil || !title.Required {
t.Errorf("+sheet-create --title should be required: %+v", title)
}
// xor is NOT cobra-required (enforced by Validate hooks)
url := byName("+sheet-create", "url")
if url == nil || url.Required {
t.Errorf("+sheet-create --url should not be cobra-required: %+v", url)
}
// hidden + int default
cap := byName("+cells-get", "cell-limit")
if cap == nil || !cap.Hidden || cap.Default != "5000" {
t.Errorf("+cells-get --cell-limit not mapped: %+v", cap)
}
// input sources
cells := byName("+cells-set", "cells")
if cells == nil || len(cells.Input) != 2 {
t.Errorf("+cells-set --cells should support file+stdin: %+v", cells)
}
// float64 type
fs := byName("+cells-set-style", "font-size")
if fs == nil || fs.Type != "float64" {
t.Errorf("+cells-set-style --font-size should be float64: %+v", fs)
}
}
// TestFlagsFor_EveryRegisteredCommandHasDefs ensures every shortcut returned by
// Shortcuts() has a flag-defs.json entry and that its flags match the JSON's
// non-system flags exactly (name + type + required + default + hidden). This is
// the contract that lets shortcuts drop hand-written flag literals.
func TestFlagsFor_EveryRegisteredCommandHasDefs(t *testing.T) {
t.Parallel()
defs, err := loadFlagDefs()
if err != nil {
t.Fatal(err)
}
for _, s := range Shortcuts() {
spec, ok := defs[s.Command]
if !ok {
t.Errorf("%s has no flag-defs.json entry", s.Command)
continue
}
want := map[string]flagDef{}
for _, df := range spec.Flags {
if df.Kind != "system" {
want[df.Name] = df
}
}
got := map[string]bool{}
for _, f := range s.Flags {
got[f.Name] = true
df, ok := want[f.Name]
if !ok {
t.Errorf("%s --%s present in Go but not in JSON (non-system)", s.Command, f.Name)
continue
}
ft := f.Type
if ft == "" {
ft = "string"
}
jt := df.Type
if jt == "" {
jt = "string"
}
if ft != jt {
t.Errorf("%s --%s type: go=%s json=%s", s.Command, f.Name, ft, jt)
}
if f.Required != (df.Required == "required") {
t.Errorf("%s --%s required: go=%v json=%s", s.Command, f.Name, f.Required, df.Required)
}
if f.Default != df.Default {
t.Errorf("%s --%s default: go=%q json=%q", s.Command, f.Name, f.Default, df.Default)
}
if f.Hidden != df.Hidden {
t.Errorf("%s --%s hidden: go=%v json=%v", s.Command, f.Name, f.Hidden, df.Hidden)
}
}
for name := range want {
if !got[name] {
t.Errorf("%s --%s in JSON but missing from Go Flags", s.Command, name)
}
}
}
}

View File

@@ -1,71 +0,0 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package sheets
import (
_ "embed"
"encoding/json"
"fmt"
"sync"
"github.com/larksuite/cli/shortcuts/common"
)
//go:embed data/flag-descriptions.en.json
var flagDescsJSON []byte
var (
flagDescsOnce sync.Once
flagDescs map[string]map[string]string
flagDescsErr error
)
func loadFlagDescs() (map[string]map[string]string, error) {
flagDescsOnce.Do(func() {
flagDescs = make(map[string]map[string]string)
flagDescsErr = json.Unmarshal(flagDescsJSON, &flagDescs)
if flagDescsErr != nil {
flagDescsErr = fmt.Errorf("flag-descriptions.en.json: %w", flagDescsErr)
}
})
return flagDescs, flagDescsErr
}
// flagDesc returns the description for a flag from the embedded
// flag-descriptions.en.json. command is e.g. "+workbook-info",
// flagName is e.g. "url" (without "--" prefix). Returns "" when
// no entry exists.
func flagDesc(command, flagName string) string {
descs, err := loadFlagDescs()
if err != nil || descs == nil {
return ""
}
cmd, ok := descs[command]
if !ok {
return ""
}
return cmd["--"+flagName]
}
// applyFlagDescs patches all Flag.Desc fields in the given shortcut
// slice with values from flag-descriptions.en.json. Flags without a
// JSON entry keep their existing Desc unchanged.
func applyFlagDescs(shortcuts []common.Shortcut) {
descs, err := loadFlagDescs()
if err != nil || descs == nil {
return
}
for i := range shortcuts {
cmd, ok := descs[shortcuts[i].Command]
if !ok {
continue
}
for j := range shortcuts[i].Flags {
key := "--" + shortcuts[i].Flags[j].Name
if desc, found := cmd[key]; found {
shortcuts[i].Flags[j].Desc = desc
}
}
}
}

View File

@@ -1,112 +0,0 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package sheets
import (
"testing"
)
func TestFlagDescs_EmbedParses(t *testing.T) {
t.Parallel()
descs, err := loadFlagDescs()
if err != nil {
t.Fatalf("loadFlagDescs error: %v", err)
}
if len(descs) == 0 {
t.Fatal("flag-descriptions.en.json has no entries")
}
}
func TestFlagDescs_SpotCheck(t *testing.T) {
t.Parallel()
cases := []struct {
command string
flagName string
}{
{"+workbook-info", "url"},
{"+cells-set", "range"},
{"+csv-get", "range"},
{"+batch-update", "operations"},
{"+chart-create", "properties"},
}
for _, tc := range cases {
desc := flagDesc(tc.command, tc.flagName)
if desc == "" {
t.Errorf("flagDesc(%q, %q) = empty; want a description", tc.command, tc.flagName)
}
}
}
func TestFlagDescs_UnknownReturnsEmpty(t *testing.T) {
t.Parallel()
if got := flagDesc("+no-such-cmd", "no-flag"); got != "" {
t.Errorf("expected empty for unknown command; got %q", got)
}
}
func TestApplyFlagDescs_OverridesHardcodedDesc(t *testing.T) {
t.Parallel()
all := Shortcuts()
descs, err := loadFlagDescs()
if err != nil {
t.Fatalf("loadFlagDescs: %v", err)
}
for _, s := range all {
cmd, ok := descs[s.Command]
if !ok {
continue
}
for _, f := range s.Flags {
key := "--" + f.Name
want, exists := cmd[key]
if !exists {
continue
}
if f.Desc != want {
t.Errorf("%s %s: Desc=%q, want=%q", s.Command, key, f.Desc, want)
}
}
}
}
func TestApplyFlagDescs_Coverage(t *testing.T) {
t.Parallel()
all := Shortcuts()
descs, err := loadFlagDescs()
if err != nil {
t.Fatalf("loadFlagDescs: %v", err)
}
// Framework-injected flags are not in the Flags slice but may
// appear in the JSON as documentation. Skip them.
frameworkFlags := map[string]bool{
"--yes": true,
"--dry-run": true,
}
// Every non-framework flag in the JSON should appear in the shortcut list.
for cmd, flags := range descs {
for flagKey := range flags {
if frameworkFlags[flagKey] {
continue
}
found := false
for _, s := range all {
if s.Command != cmd {
continue
}
for _, f := range s.Flags {
if "--"+f.Name == flagKey {
found = true
break
}
}
break
}
if !found {
t.Logf("JSON has %s %s but no matching flag in shortcut list (naming mismatch or not yet implemented)", cmd, flagKey)
}
}
}
}

View File

@@ -96,9 +96,9 @@ func printFlagSchemaFor(command string) func(flagName string) ([]byte, error) {
}
sort.Strings(flags)
return json.MarshalIndent(map[string]interface{}{
"shortcut": command,
"introspectable_flags": flags,
"hint": "run again with --flag-name <name> to dump the JSON Schema for that flag",
"shortcut": command,
"introspectable_flags": flags,
"hint": "run again with --flag-name <name> to dump the JSON Schema for that flag",
}, "", " ")
}
schema, ok := entry[flagName]

View File

@@ -57,26 +57,6 @@ func extractSpreadsheetToken(input string) string {
return input
}
// publicTokenFlags is the leading pair of every canonical sheets shortcut.
// Shortcuts targeting a single sheet append the public sheet-id / sheet-name
// XOR pair on top of this; workbook-level shortcuts use this pair only.
func publicTokenFlags() []common.Flag {
return []common.Flag{
{Name: "url", Desc: "spreadsheet URL (XOR --spreadsheet-token)"},
{Name: "spreadsheet-token", Desc: "spreadsheet token (XOR --url)"},
}
}
// publicSheetFlags extends publicTokenFlags with the sheet selector pair.
// Use for any +sheet-* / +cells-* / +dim-* / object shortcut that operates
// on an existing single sub-sheet.
func publicSheetFlags() []common.Flag {
return append(publicTokenFlags(),
common.Flag{Name: "sheet-id", Desc: "sheet reference_id (XOR --sheet-name)"},
common.Flag{Name: "sheet-name", Desc: "sheet title (XOR --sheet-id)"},
)
}
// resolveSheetSelector validates the --sheet-id / --sheet-name XOR and
// returns whichever was supplied. Network-free.
//
@@ -169,35 +149,6 @@ func requireJSONArray(runtime *common.RuntimeContext, name string) ([]interface{
// ─── 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.
@@ -209,8 +160,8 @@ func buildCellStyleFromFlags(runtime *common.RuntimeContext) map[string]interfac
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 runtime.Changed("font-size") && runtime.Float64("font-size") > 0 {
style["font_size"] = runtime.Float64("font-size")
}
if v := runtime.Str("font-style"); v != "" {
style["font_style"] = v

View File

@@ -40,11 +40,7 @@ var BatchUpdate = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicTokenFlags(),
common.Flag{Name: "operations", Input: []string{common.File, common.Stdin}, Required: true,
Desc: "operations JSON array: [{tool_name, input}, ...] (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"},
),
Flags: flagsFor("+batch-update"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -142,12 +138,7 @@ var CellsBatchSetStyle = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
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()...,
),
Flags: flagsFor("+cells-batch-set-style"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -240,16 +231,7 @@ var DropdownUpdate = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: 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!A2:A100\"])"},
common.Flag{Name: "options", Input: []string{common.File, common.Stdin}, Required: true,
Desc: "options JSON array (e.g. [\"alpha\",\"beta\"])"},
common.Flag{Name: "colors", Input: []string{common.File, common.Stdin},
Desc: "optional RGB hex color array (must equal --options length)"},
common.Flag{Name: "multiple", Type: "bool", Desc: "enable multi-select"},
common.Flag{Name: "highlight", Type: "bool", Desc: "color-highlight options"},
),
Flags: flagsFor("+dropdown-update"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -294,10 +276,7 @@ var DropdownDelete = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicTokenFlags(),
common.Flag{Name: "ranges", Input: []string{common.File, common.Stdin}, Required: true,
Desc: "JSON array of sheet-prefixed A1 ranges (max 100)"},
),
Flags: flagsFor("+dropdown-delete"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err

View File

@@ -33,14 +33,10 @@ import (
// idFlag / idField empty → no per-object id flag (only filter uses that
// today, and it has its own bespoke shortcuts further down).
type objectCRUDSpec struct {
commandPrefix string // e.g. "+chart" → +chart-create / -update / -delete
toolName string // e.g. "manage_chart_object"
idFlag string // e.g. "chart-id"
idField string // e.g. "chart_id"
createDataDesc string // help text for --properties on create
updateDataDesc string // help text for --properties on update
createExtraFlags []common.Flag
updateExtraFlags []common.Flag
commandPrefix string // e.g. "+chart" → +chart-create / -update / -delete
toolName string // e.g. "manage_chart_object"
idFlag string // e.g. "chart-id"
idField string // e.g. "chart_id"
// 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
@@ -51,10 +47,7 @@ type objectCRUDSpec struct {
}
func newObjectCreateShortcut(spec objectCRUDSpec) common.Shortcut {
flags := append(publicSheetFlags(),
common.Flag{Name: "properties", Input: []string{common.File, common.Stdin}, Required: true, Desc: spec.createDataDesc},
)
flags = append(flags, spec.createExtraFlags...)
flags := flagsFor(spec.commandPrefix + "-create")
return common.Shortcut{
Service: "sheets",
Command: spec.commandPrefix + "-create",
@@ -121,18 +114,7 @@ func objectCreateInput(runtime *common.RuntimeContext, token, sheetID, sheetName
}
func newObjectUpdateShortcut(spec objectCRUDSpec) common.Shortcut {
flags := publicSheetFlags()
if spec.idFlag != "" {
flags = append(flags, common.Flag{
Name: spec.idFlag, Required: true,
Desc: "target object reference_id (maps to " + spec.idField + " on the wire)",
})
}
flags = append(flags, common.Flag{
Name: "properties", Input: []string{common.File, common.Stdin}, Required: true,
Desc: spec.updateDataDesc,
})
flags = append(flags, spec.updateExtraFlags...)
flags := flagsFor(spec.commandPrefix + "-update")
return common.Shortcut{
Service: "sheets",
Command: spec.commandPrefix + "-update",
@@ -205,13 +187,7 @@ func objectUpdateInput(runtime *common.RuntimeContext, token, sheetID, sheetName
}
func newObjectDeleteShortcut(spec objectCRUDSpec) common.Shortcut {
flags := publicSheetFlags()
if spec.idFlag != "" {
flags = append(flags, common.Flag{
Name: spec.idFlag, Required: true,
Desc: "target object reference_id (maps to " + spec.idField + " on the wire)",
})
}
flags := flagsFor(spec.commandPrefix + "-delete")
return common.Shortcut{
Service: "sheets",
Command: spec.commandPrefix + "-delete",
@@ -273,12 +249,10 @@ func objectDeleteInput(runtime *common.RuntimeContext, token, sheetID, sheetName
// chart
var chartSpec = objectCRUDSpec{
commandPrefix: "+chart",
toolName: "manage_chart_object",
idFlag: "chart-id",
idField: "chart_id",
createDataDesc: "chart properties JSON (position / data / properties etc.); see lark-sheets-chart.md for the shape",
updateDataDesc: "full or partial chart properties JSON (`+chart-list --chart-id <id>` first, then patch)",
commandPrefix: "+chart",
toolName: "manage_chart_object",
idFlag: "chart-id",
idField: "chart_id",
}
var ChartCreate = newObjectCreateShortcut(chartSpec)
var ChartUpdate = newObjectUpdateShortcut(chartSpec)
@@ -287,18 +261,10 @@ var ChartDelete = newObjectDeleteShortcut(chartSpec)
// 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: { 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)"},
},
commandPrefix: "+pivot",
toolName: "manage_pivot_table_object",
idFlag: "pivot-table-id",
idField: "pivot_table_id",
enhanceCreateInput: func(rt *common.RuntimeContext, input map[string]interface{}) {
if v := strings.TrimSpace(rt.Str("target-sheet-id")); v != "" {
input["target_sheet_id"] = v
@@ -325,17 +291,6 @@ var PivotDelete = newObjectDeleteShortcut(pivotSpec)
// conditional format — CLI surface uses --rule-id (short), wired to the
// 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 {
@@ -361,10 +316,6 @@ var condFormatSpec = objectCRUDSpec{
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,
}
@@ -374,12 +325,10 @@ var CondFormatDelete = newObjectDeleteShortcut(condFormatSpec)
// sparkline — CLI uses --group-id (higher level) as the object selector.
var sparklineSpec = objectCRUDSpec{
commandPrefix: "+sparkline",
toolName: "manage_sparkline_object",
idFlag: "group-id",
idField: "group_id",
createDataDesc: "sparkline group JSON: { type: line|column|winLoss, source_range, target_range, ... }",
updateDataDesc: "full or partial sparkline group JSON (`+sparkline-list --group-id <id>` first, then patch)",
commandPrefix: "+sparkline",
toolName: "manage_sparkline_object",
idFlag: "group-id",
idField: "group_id",
}
var SparklineCreate = newObjectCreateShortcut(sparklineSpec)
var SparklineUpdate = newObjectUpdateShortcut(sparklineSpec)
@@ -389,19 +338,6 @@ var SparklineDelete = newObjectDeleteShortcut(sparklineSpec)
// 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)"},
}
// 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
@@ -452,11 +388,7 @@ func newFloatImageWriteShortcut(command, description, op string, withIDFlag, isH
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...)
flags := flagsFor(command)
return common.Shortcut{
Service: "sheets",
Command: command,
@@ -550,10 +482,6 @@ var FloatImageDelete = newObjectDeleteShortcut(floatImageDeleteSpec)
// 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 {
@@ -572,10 +500,6 @@ var filterViewSpec = objectCRUDSpec{
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,
}
@@ -600,11 +524,7 @@ var FilterCreate = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
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: "properties", Input: []string{common.File, common.Stdin},
Desc: "optional rules JSON: { rules: [...], filtered_columns?: [...] }; empty filter when omitted"},
),
Flags: flagsFor("+filter-create"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -686,11 +606,7 @@ var FilterUpdate = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
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"},
),
Flags: flagsFor("+filter-update"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -757,7 +673,7 @@ var FilterDelete = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: publicSheetFlags(),
Flags: flagsFor("+filter-delete"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err

View File

@@ -152,7 +152,7 @@ func TestObjectCRUDShortcuts_DryRun(t *testing.T) {
{
name: "+filter-view-create",
sc: FilterViewCreate,
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--properties", `{"view_name":"v1","range":"A1:Z100"}`},
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:Z100", "--properties", `{"view_name":"v1"}`},
toolName: "manage_filter_view_object",
wantInput: map[string]interface{}{
"excel_id": testToken,

View File

@@ -31,17 +31,10 @@ type objectListSpec struct {
// Optional id filter. Empty filterFlag → no filter flag exposed.
filterFlag string // CLI flag name (without leading --), e.g. "chart-id"
filterField string // tool input key, e.g. "chart_id"
filterDesc string // flag help text
}
func newObjectListShortcut(spec objectListSpec) common.Shortcut {
flags := publicSheetFlags()
if spec.filterFlag != "" {
flags = append(flags, common.Flag{
Name: spec.filterFlag,
Desc: spec.filterDesc,
})
}
flags := flagsFor(spec.command)
return common.Shortcut{
Service: "sheets",
Command: spec.command,
@@ -102,7 +95,6 @@ var ChartList = newObjectListShortcut(objectListSpec{
toolName: "get_chart_objects",
filterFlag: "chart-id",
filterField: "chart_id",
filterDesc: "optional chart reference_id; returns just that chart when set",
})
// PivotList — list pivot tables on a sheet.
@@ -112,7 +104,6 @@ var PivotList = newObjectListShortcut(objectListSpec{
toolName: "get_pivot_table_objects",
filterFlag: "pivot-table-id",
filterField: "pivot_table_id",
filterDesc: "optional pivot table reference_id; returns just that pivot when set",
})
// CondFormatList — list conditional format rules. CLI's --rule-id maps to
@@ -123,7 +114,6 @@ var CondFormatList = newObjectListShortcut(objectListSpec{
toolName: "get_conditional_format_objects",
filterFlag: "rule-id",
filterField: "conditional_format_id",
filterDesc: "optional rule reference_id (maps to conditional_format_id server-side)",
})
// FilterList — list active sheet-level filters. No id filter because each
@@ -143,7 +133,6 @@ var FilterViewList = newObjectListShortcut(objectListSpec{
toolName: "get_filter_view_objects",
filterFlag: "view-id",
filterField: "view_id",
filterDesc: "optional filter-view reference_id; returns just that view when set",
})
// SparklineList — list sparkline groups on a sheet. The tool also accepts
@@ -155,7 +144,6 @@ var SparklineList = newObjectListShortcut(objectListSpec{
toolName: "get_sparkline_objects",
filterFlag: "group-id",
filterField: "group_id",
filterDesc: "optional sparkline group reference_id; returns all sparklines in that group",
})
// FloatImageList — list floating images on a sheet (vs. embedded
@@ -166,5 +154,4 @@ var FloatImageList = newObjectListShortcut(objectListSpec{
toolName: "get_float_image_objects",
filterFlag: "float-image-id",
filterField: "float_image_id",
filterDesc: "optional floating-image reference_id; returns just that image when set",
})

View File

@@ -35,11 +35,7 @@ var CellsClear = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "range", Required: true, Desc: "A1 range to clear (e.g. A1:C10 / D3:D / 3:3)"},
common.Flag{Name: "scope", Enum: []string{"content", "formats", "all"}, Default: "content",
Desc: "what to clear: content (values+formulas only, default) / formats / all"},
),
Flags: flagsFor("+cells-clear"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -107,15 +103,7 @@ var CellsUnmerge = newMergeShortcut(
)
func newMergeShortcut(command, desc, op string, withMergeType bool) common.Shortcut {
flags := append(publicSheetFlags(),
common.Flag{Name: "range", Required: true, Desc: "A1 range to merge / unmerge (e.g. A1:C3)"},
)
if withMergeType {
flags = append(flags, common.Flag{
Name: "merge-type", Enum: []string{"all", "rows", "columns"}, Default: "all",
Desc: "merge strategy: all (one cell) / rows / columns",
})
}
flags := flagsFor(command)
return common.Shortcut{
Service: "sheets",
Command: command,
@@ -191,9 +179,6 @@ func mergeInput(runtime *common.RuntimeContext, token, sheetID, sheetName, op st
// Both shortcuts share the underlying resize_range tool; --end is inclusive
// in the new CLI surface (was exclusive in the legacy +dim-resize).
var rowsResizeTypeEnum = []string{"pixel", "standard", "auto"}
var colsResizeTypeEnum = []string{"pixel", "standard"}
// RowsResize wraps resize_range for row heights. --type auto enables
// auto-fit (rows only); --type pixel requires --size.
var RowsResize = common.Shortcut{
@@ -204,14 +189,8 @@ var RowsResize = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "start", Type: "int", Required: true, Desc: "0-based start row (inclusive)"},
common.Flag{Name: "end", Type: "int", Required: true, Desc: "0-based end row (inclusive)"},
common.Flag{Name: "type", Required: true, Enum: rowsResizeTypeEnum,
Desc: "sizing mode: `pixel` (needs --size) / `standard` (reset to default) / `auto` (auto-fit row height)"},
common.Flag{Name: "size", Type: "int", Default: "0", Desc: "row height in pixels (e.g. 30); required with --type pixel, ignored otherwise"},
),
Validate: validateResize("row"),
Flags: flagsFor("+rows-resize"),
Validate: validateResize("row"),
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, _ := resolveSpreadsheetToken(runtime)
sheetID, sheetName, _ := resolveSheetSelector(runtime)
@@ -245,14 +224,8 @@ var ColsResize = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "start", Type: "int", Required: true, Desc: "0-based start column (inclusive)"},
common.Flag{Name: "end", Type: "int", Required: true, Desc: "0-based end column (inclusive)"},
common.Flag{Name: "type", Required: true, Enum: colsResizeTypeEnum,
Desc: "sizing mode: `pixel` (needs --size) / `standard` (reset to default); `auto` is rows-only"},
common.Flag{Name: "size", Type: "int", Default: "0", Desc: "column width in pixels (e.g. 120); required with --type pixel, ignored otherwise"},
),
Validate: validateResize("column"),
Flags: flagsFor("+cols-resize"),
Validate: validateResize("column"),
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, _ := resolveSpreadsheetToken(runtime)
sheetID, sheetName, _ := resolveSheetSelector(runtime)
@@ -358,11 +331,7 @@ var RangeMove = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "source-range", Required: true, Desc: "source A1 range (e.g. A1:C5)"},
common.Flag{Name: "target-range", Required: true, Desc: "target A1 starting cell (size derived from source)"},
common.Flag{Name: "target-sheet-id", Desc: "destination sheet id (cross-sheet); omit for same sheet"},
),
Flags: flagsFor("+range-move"),
Validate: validateRangeMoveOrCopy,
DryRun: transformDryRunFn("move", false, false),
Execute: transformExecuteFn("move", false, false),
@@ -378,13 +347,7 @@ var RangeCopy = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "source-range", Required: true, Desc: "source A1 range"},
common.Flag{Name: "target-range", Required: true, Desc: "target A1 starting cell"},
common.Flag{Name: "target-sheet-id", Desc: "destination sheet id (cross-sheet); omit for same sheet"},
common.Flag{Name: "paste-type", Enum: []string{"values", "formulas", "formats", "all"}, Default: "all",
Desc: "what to copy: values / formulas / formats / all (default)"},
),
Flags: flagsFor("+range-copy"),
Validate: validateRangeMoveOrCopy,
DryRun: transformDryRunFn("copy", true, false),
Execute: transformExecuteFn("copy", true, false),
@@ -402,12 +365,7 @@ var RangeFill = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "source-range", Required: true, Desc: "template A1 range with seed cells"},
common.Flag{Name: "target-range", Required: true, Desc: "target fill range (must be disjoint from source)"},
common.Flag{Name: "series-type", Enum: []string{"auto", "linear", "growth", "date", "copy"}, Default: "auto",
Desc: "auto / linear / growth / date → tool fillSeries; copy → tool copyCells"},
),
Flags: flagsFor("+range-fill"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -455,12 +413,7 @@ var RangeSort = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "range", Required: true, Desc: "A1 range to sort"},
common.Flag{Name: "sort-keys", Input: []string{common.File, common.Stdin}, Required: true,
Desc: "sort keys JSON, e.g. [{\"col\":\"B\",\"order\":\"asc\"},{\"col\":\"D\",\"order\":\"desc\"}]"},
common.Flag{Name: "has-header", Type: "bool", Desc: "treat first row as header (excluded from sort); default false"},
),
Flags: flagsFor("+range-sort"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -613,10 +566,10 @@ func rangeSortInput(runtime *common.RuntimeContext, token, sheetID, sheetName st
return nil, err
}
input := map[string]interface{}{
"excel_id": token,
"operation": "sort",
"range": strings.TrimSpace(runtime.Str("range")),
"sort_conditions": keys,
"excel_id": token,
"operation": "sort",
"range": strings.TrimSpace(runtime.Str("range")),
"sort_conditions": keys,
}
sheetSelectorForToolInput(input, sheetID, sheetName)
if runtime.Bool("has-header") {

View File

@@ -19,8 +19,6 @@ import (
// The sandbox tool (export_sheet_to_sandbox) is Sheet-Tool-only and has no
// CLI surface here.
var cellsGetIncludeEnum = []string{"value", "formula", "style", "comment", "data_validation"}
// CellsGet wraps get_cell_ranges: read multiple A1 ranges and return per-cell
// values, formulas, styles, and other metadata as requested via --include.
var CellsGet = common.Shortcut{
@@ -31,13 +29,7 @@ var CellsGet = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "ranges", Type: "string_array", Required: true, Desc: "A1 ranges (repeat: --ranges A1:B2 --ranges D1:E5)"},
common.Flag{Name: "include", Type: "string_slice", Enum: cellsGetIncludeEnum, Desc: "categories to include (default: value+style). value|formula|style|comment|data_validation"},
common.Flag{Name: "skip-hidden", Type: "bool", Desc: "skip hidden rows/cols"},
common.Flag{Name: "cell-limit", Type: "int", Default: "5000", Hidden: true, Desc: "anti-burst cell scan cap"},
common.Flag{Name: "max-chars", Type: "int", Default: "200000", Hidden: true, Desc: "anti-burst response char cap"},
),
Flags: flagsFor("+cells-get"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -45,8 +37,8 @@ var CellsGet = common.Shortcut{
if _, _, err := resolveSheetSelector(runtime); err != nil {
return err
}
if len(runtime.StrArray("ranges")) == 0 {
return common.FlagErrorf("--ranges is required")
if len(runtime.StrArray("range")) == 0 {
return common.FlagErrorf("--range is required")
}
return nil
},
@@ -76,7 +68,7 @@ var CellsGet = common.Shortcut{
func cellsGetInput(runtime *common.RuntimeContext, token, sheetID, sheetName string) map[string]interface{} {
input := map[string]interface{}{
"excel_id": token,
"ranges": runtime.StrArray("ranges"),
"ranges": runtime.StrArray("range"),
}
sheetSelectorForToolInput(input, sheetID, sheetName)
applyIncludeToCellsGet(input, runtime.StrSlice("include"))
@@ -129,17 +121,7 @@ var CsvGet = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "range", Desc: "A1 range; omit for the sheet's current_region"},
common.Flag{
Name: "value-render-option", Enum: []string{"formatted_value", "raw_value", "formula"},
Desc: "value rendering: formatted_value (default) / raw_value / formula",
},
common.Flag{Name: "include-row-prefix", Type: "bool", Default: "true", Desc: "keep [row=N] line prefix; pass --include-row-prefix=false to strip"},
common.Flag{Name: "skip-hidden", Type: "bool", Desc: "skip hidden rows/cols"},
common.Flag{Name: "max-rows", Type: "int", Default: "100000", Hidden: true, Desc: "anti-burst row cap"},
common.Flag{Name: "max-chars", Type: "int", Default: "200000", Hidden: true, Desc: "anti-burst char cap"},
),
Flags: flagsFor("+csv-get"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -228,9 +210,7 @@ var DropdownGet = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicTokenFlags(),
common.Flag{Name: "range", Required: true, Desc: "A1 range with sheet prefix (e.g. sheet1!A2:A100)"},
),
Flags: flagsFor("+dropdown-get"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err

View File

@@ -23,7 +23,7 @@ func TestReadDataShortcuts_DryRun(t *testing.T) {
{
name: "+cells-get multi-range + include=style,formula",
sc: CellsGet,
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--ranges", "A1:B2", "--ranges", "D1:E5", "--include", "style,formula"},
args: []string{"--url", testURL, "--sheet-id", testSheetID, "--range", "A1:B2", "--range", "D1:E5", "--include", "style,formula"},
toolName: "get_cell_ranges",
wantInput: map[string]interface{}{
"excel_id": testToken,

View File

@@ -27,16 +27,7 @@ var CellsSearch = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "find", Required: true, Desc: "text to search for (regex when --regex is set)"},
common.Flag{Name: "range", Desc: "optional A1 range to scope the search"},
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"},
common.Flag{Name: "regex", Type: "bool", Desc: "treat --find as a regular expression"},
common.Flag{Name: "include-formulas", Type: "bool", Desc: "also search inside formula text"},
common.Flag{Name: "offset", Type: "int", Default: "0", Desc: "pagination offset (use next_offset from previous page)"},
common.Flag{Name: "max-matches", Type: "int", Default: "5000", Hidden: true, Desc: "anti-burst match cap"},
),
Flags: flagsFor("+cells-search"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -122,15 +113,7 @@ var CellsReplace = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "find", Required: true, Desc: "text to find (regex when --regex is set)"},
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"},
common.Flag{Name: "regex", Type: "bool", Desc: "treat --find as a regular expression"},
common.Flag{Name: "include-formulas", Type: "bool", Desc: "also replace inside formula text"},
),
Flags: flagsFor("+cells-replace"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err

View File

@@ -33,15 +33,7 @@ var SheetInfo = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "range", Desc: "optional A1-style range to scope the query (e.g. A1:C20 / 3:6 / C:E); omit for whole sheet"},
common.Flag{
Name: "include",
Type: "string_slice",
Enum: []string{"merges", "row_heights", "col_widths", "hidden_rows", "hidden_cols", "groups", "frozen"},
Desc: "filter returned categories (comma-separated). Omit for all.",
},
),
Flags: flagsFor("+sheet-info"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -121,9 +113,6 @@ func infoTypeFromInclude(include []string) string {
// ─── +dim-* (modify_sheet_structure) ──────────────────────────────────
// dimEnum bounds the allowed values for --dimension across every +dim-* shortcut.
var dimEnum = []string{"row", "column"}
// DimInsert inserts blank rows / columns and optionally inherits style from
// the adjacent dimension.
var DimInsert = common.Shortcut{
@@ -134,13 +123,8 @@ var DimInsert = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "dimension", Required: true, Enum: dimEnum, Desc: "`row` or `column`"},
common.Flag{Name: "start", Type: "int", Required: true, Desc: "0-based start position (inclusive)"},
common.Flag{Name: "end", Type: "int", Required: true, Desc: "0-based end position (exclusive)"},
common.Flag{Name: "inherit-style", Enum: []string{"before", "after", "none"}, Default: "none", Desc: "inherit cell style from the row/column before, after, or neither"},
),
Validate: validateDimRange,
Flags: flagsFor("+dim-insert"),
Validate: validateDimRange,
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, _ := resolveSpreadsheetToken(runtime)
sheetID, sheetName, _ := resolveSheetSelector(runtime)
@@ -193,12 +177,8 @@ var DimDelete = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "dimension", Required: true, Enum: dimEnum, Desc: "`row` or `column`"},
common.Flag{Name: "start", Type: "int", Required: true, Desc: "0-based start position (inclusive)"},
common.Flag{Name: "end", Type: "int", Required: true, Desc: "0-based end position (exclusive)"},
),
Validate: validateDimRange,
Flags: flagsFor("+dim-delete"),
Validate: validateDimRange,
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, _ := resolveSpreadsheetToken(runtime)
sheetID, sheetName, _ := resolveSheetSelector(runtime)
@@ -251,10 +231,7 @@ var DimFreeze = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "dimension", Required: true, Enum: dimEnum, Desc: "`row` or `column`"},
common.Flag{Name: "count", Type: "int", Required: true, Desc: "number of leading rows/columns to freeze; 0 unfreezes the chosen dimension"},
),
Flags: flagsFor("+dim-freeze"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -363,12 +340,8 @@ func newDimRangeOpShortcut(command, desc, op, risk string) common.Shortcut {
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "dimension", Required: true, Enum: dimEnum, Desc: "`row` or `column`"},
common.Flag{Name: "start", Type: "int", Required: true, Desc: "0-based start position (inclusive)"},
common.Flag{Name: "end", Type: "int", Required: true, Desc: "0-based end position (exclusive)"},
),
Validate: validateDimRange,
Flags: flagsFor(command),
Validate: validateDimRange,
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
token, _ := resolveSpreadsheetToken(runtime)
sheetID, sheetName, _ := resolveSheetSelector(runtime)
@@ -397,17 +370,7 @@ func newDimRangeOpShortcut(command, desc, op, risk string) common.Shortcut {
// --depth (currently unused server-side — accepted for forward-compat per
// the canonical spec) and --group-state (group only, defaults to expand).
func newDimGroupShortcut(command, desc, op string) common.Shortcut {
flags := append(publicSheetFlags(),
common.Flag{Name: "dimension", Required: true, Enum: dimEnum, Desc: "`row` or `column`"},
common.Flag{Name: "start", Type: "int", Required: true, Desc: "0-based start position (inclusive)"},
common.Flag{Name: "end", Type: "int", Required: true, Desc: "0-based end position (exclusive)"},
common.Flag{Name: "depth", Type: "int", Default: "1", Desc: "nesting level (currently honored only when the server-side endpoint supports it)"},
)
if op == "group" {
flags = append(flags,
common.Flag{Name: "group-state", Enum: []string{"expand", "fold"}, Default: "expand", Desc: "initial state of the new group"},
)
}
flags := flagsFor(command)
return common.Shortcut{
Service: "sheets",
Command: command,
@@ -512,12 +475,7 @@ var DimMove = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only", "sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "dimension", Required: true, Enum: dimEnum, Desc: "`row` or `column`"},
common.Flag{Name: "start", Type: "int", Required: true, Desc: "source start (0-indexed, inclusive)"},
common.Flag{Name: "end", Type: "int", Required: true, Desc: "source end (0-indexed, inclusive)"},
common.Flag{Name: "target", Type: "int", Required: true, Desc: "destination index (0-indexed); rows/cols move to land BEFORE this index"},
),
Flags: flagsFor("+dim-move"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err

View File

@@ -46,7 +46,7 @@ var WorkbookInfo = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: publicTokenFlags(),
Flags: flagsFor("+workbook-info"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
_, err := resolveSpreadsheetToken(runtime)
return err
@@ -88,12 +88,7 @@ var SheetCreate = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicTokenFlags(),
common.Flag{Name: "title", Desc: "new sheet title", Required: true},
common.Flag{Name: "index", Type: "int", Default: "-1", Desc: "insertion position (0-based); omit to append"},
common.Flag{Name: "row-count", Type: "int", Default: "0", Desc: "initial row count; omit for tool default (200)"},
common.Flag{Name: "col-count", Type: "int", Default: "0", Desc: "initial column count; omit for tool default (20)"},
),
Flags: flagsFor("+sheet-create"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -155,7 +150,7 @@ var SheetDelete = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: publicSheetFlags(),
Flags: flagsFor("+sheet-delete"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -202,9 +197,7 @@ var SheetRename = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "title", Desc: "new sheet title", Required: true},
),
Flags: flagsFor("+sheet-rename"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -269,10 +262,7 @@ var SheetMove = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read", "sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "index", Type: "int", Required: true, Desc: "target position (0-based)"},
common.Flag{Name: "source-index", Type: "int", Default: "-1", Desc: "source position (0-based); omitted → auto-derived from --sheet-id/--sheet-name's current workbook position"},
),
Flags: flagsFor("+sheet-move"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -367,10 +357,7 @@ var SheetCopy = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "title", Desc: "title for the duplicated sheet (server-generated when omitted)"},
common.Flag{Name: "index", Type: "int", Default: "-1", Desc: "insertion position for the copy (0-based); omit to append"},
),
Flags: flagsFor("+sheet-copy"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -432,7 +419,7 @@ func newSheetVisibilityShortcut(command, desc, op string) common.Shortcut {
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: publicSheetFlags(),
Flags: flagsFor(command),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -477,9 +464,7 @@ var SheetSetTabColor = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "color", Desc: "hex color like #FF0000; pass empty string to clear"},
),
Flags: flagsFor("+sheet-set-tab-color"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -545,12 +530,7 @@ var WorkbookCreate = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:create", "sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: []common.Flag{
{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: "values", Input: []string{common.File, common.Stdin}, Desc: "initial data JSON 2D array, e.g. [[\"alice\",95]]"},
},
Flags: flagsFor("+workbook-create"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if strings.TrimSpace(runtime.Str("title")) == "" {
return common.FlagErrorf("--title is required")
@@ -707,11 +687,7 @@ var WorkbookExport = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:read", "docs:document:export", "drive:drive.metadata:readonly"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicTokenFlags(),
common.Flag{Name: "file-extension", Enum: []string{"xlsx", "csv"}, Default: "xlsx", Desc: "xlsx (whole workbook) or csv (one sheet via --sheet-id)"},
common.Flag{Name: "sheet-id", Desc: "csv mode only: target sheet reference_id to export"},
common.Flag{Name: "output-path", Desc: "local file path to save into; omit to just trigger and report the file_token"},
),
Flags: flagsFor("+workbook-export"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err

View File

@@ -159,10 +159,10 @@ func TestSheetMove_DryRunResolvePlaceholders(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
wantSheetID string
wantSourceIdx interface{}
name string
args []string
wantSheetID string
wantSourceIdx interface{}
}{
{
name: "id only, no source-index → both literal + placeholder",

View File

@@ -42,13 +42,7 @@ var CellsSet = common.Shortcut{
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:C10); cells dimensions must match"},
common.Flag{Name: "cells", Input: []string{common.File, common.Stdin}, Required: true,
Desc: "JSON 2D matrix of cell objects, e.g. [[{\"value\":\"a\"},{\"value\":\"b\"}],[{\"value\":1},{\"value\":2}]]; dimensions must match --range"},
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"},
),
Flags: flagsFor("+cells-set"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -123,11 +117,7 @@ var CellsSetStyle = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(
append(publicSheetFlags(),
common.Flag{Name: "range", Required: true, Desc: "target A1 range (e.g. A1:B2)"}),
styleFlatFlags()...,
),
Flags: flagsFor("+cells-set-style"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -223,13 +213,7 @@ var CsvPut = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "csv", Input: []string{common.File, common.Stdin}, Required: true,
Desc: "CSV text (RFC 4180); supports @file or stdin via -"},
common.Flag{Name: "start-cell", Default: "A1", Required: true, Desc: "single A1 anchor cell, e.g. A1 / B5"},
common.Flag{Name: "allow-overwrite", Type: "bool", Default: "true",
Desc: "allow overwriting non-empty cells (default true); false errors if any target cell is non-empty"},
),
Flags: flagsFor("+csv-put"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -300,15 +284,7 @@ var DropdownSet = common.Shortcut{
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. A2:A100)"},
common.Flag{Name: "options", Input: []string{common.File, common.Stdin}, Required: true,
Desc: "options JSON array (e.g. [\"opt1\",\"opt2\"]); ≤500 items, ≤100 chars each, no commas"},
common.Flag{Name: "colors", Input: []string{common.File, common.Stdin},
Desc: "optional RGB hex array (e.g. [\"#1FB6C1\",\"#F006C2\"]); length must equal --options"},
common.Flag{Name: "multiple", Type: "bool", Desc: "enable multi-select; default false"},
common.Flag{Name: "highlight", Type: "bool", Desc: "color-highlight options; default false"},
),
Flags: flagsFor("+dropdown-set"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err
@@ -551,11 +527,7 @@ var CellsSetImage = common.Shortcut{
Scopes: []string{"sheets:spreadsheet:write_only", "drive:file:upload"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: append(publicSheetFlags(),
common.Flag{Name: "range", Required: true, Desc: "single target cell (e.g. A1; start/end must equal)"},
common.Flag{Name: "image", Required: true, Desc: "local image path (PNG/JPEG/JPG/GIF/BMP/JFIF/EXIF/TIFF/BPG/HEIC)"},
common.Flag{Name: "name", Desc: "uploaded file name (with extension); defaults to basename(--image)"},
),
Flags: flagsFor("+cells-set-image"),
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if _, err := resolveSpreadsheetToken(runtime); err != nil {
return err

View File

@@ -14,7 +14,6 @@ import "github.com/larksuite/cli/shortcuts/common"
// `--print-schema --flag-name <name>` locally.
func Shortcuts() []common.Shortcut {
all := shortcutList()
applyFlagDescs(all)
withSchema := commandsWithFlagSchema()
for i := range all {
if _, ok := withSchema[all[i].Command]; ok {

View File

@@ -37,7 +37,8 @@ _公共URL/token无 sheet 定位) · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--operations` | string + File + Stdin复合 JSON | 是 | JSON`{"operations":[{"tool":"`+cells-set`","params":{...}}, ...]}`;按数组顺序串行执行 |
| `--operations` | string + File + Stdin复合 JSON | required | JSON 数组:`[{"tool_name":"`+cells-set`","input":{...}}, ...]`,按声明顺序串行执行`tool_name` 为底层工具名(如 `+cells-set` / `+cells-clear` / `+dim-{insert|delete|hide|unhide|freeze|group|ungroup}``input` 结构与单独调用该工具一致 |
| `--continue-on-error` | bool | optional | 遇子操作失败时继续执行剩余操作;默认 false首个失败即整批中断 |
### `+cells-batch-set-style`
@@ -45,18 +46,18 @@ _公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--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 | 否 | 字体大小px10、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 |
| `--ranges` | string + File + Stdin简单 JSON | required | 目标范围 JSON 数组,每项必须带 sheet 前缀(如 `["sheet1!A1:B2","sheet1!D1:D10"]`);所有 range 应用同一组 style |
| `--background-color` | string | optional | 背景颜色(十六进制,如 `#ffffff` |
| `--font-color` | string | optional | 字体颜色(十六进制,如 `#000000` |
| `--font-size` | float64 | optional | 字体大小px10、12、14 |
| `--font-style` | string | optional | 字体样式(可选值`normal` / `italic` |
| `--font-weight` | string | optional | 字重(可选值`normal` / `bold` |
| `--font-line` | string | optional | 字体线条样式(可选值`none` / `underline` / `line-through` |
| `--horizontal-alignment` | string | optional | 水平对齐(可选值`left` / `center` / `right` |
| `--vertical-alignment` | string | optional | 垂直对齐(可选值`top` / `middle` / `bottom` |
| `--word-wrap` | string | optional | 换行策略(可选值`overflow` / `auto-wrap` / `word-clip` |
| `--number-format` | string | optional | 数字格式(例:文本 `@`、数字 `0.00`、货币 `$#,##0.00`、日期 `mm/dd/yyyy` |
| `--border-styles` | string + File + Stdin复合 JSON | optional | 边框配置 JSON结构同 +cells-set-style |
### `+dropdown-update`
@@ -64,11 +65,11 @@ _公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--ranges` | string + File + Stdin简单 JSON | | 目标范围 JSON 数组(如 `["sheet1!A2:A100"]`),每项必须带 sheet 前缀 |
| `--options` | string + File + Stdin复合 JSON | | 选项 JSON 数组(如 `["opt1","opt2"]` |
| `--colors` | string + File + Stdin简单 JSON | | 颜色数组(与 `--options` 等长) |
| `--multiple` | bool | | 启用多选 |
| `--highlight` | bool | | 选项配色 |
| `--ranges` | string + File + Stdin简单 JSON | required | 目标范围 JSON 数组(如 `["sheet1!A2:A100"]`),每项必须带 sheet 前缀 |
| `--options` | string + File + Stdin复合 JSON | required | 选项 JSON 数组(如 `["opt1","opt2"]` |
| `--colors` | string + File + Stdin简单 JSON | optional | 颜色数组(与 `--options` 等长) |
| `--multiple` | bool | optional | 启用多选 |
| `--highlight` | bool | optional | 选项配色 |
### `+dropdown-delete`
@@ -76,7 +77,7 @@ _公共URL/token无 sheet 定位) · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--ranges` | string + File + Stdin简单 JSON | | 目标范围 JSON 数组(最多 100 个,每项必须带 sheet 前缀) |
| `--ranges` | string + File + Stdin简单 JSON | required | 目标范围 JSON 数组(最多 100 个,每项必须带 sheet 前缀) |
## Schemas
@@ -102,16 +103,10 @@ _单元格边框配置含 top/bottom/left/right 四个方向,每个方向
### `+dropdown-update` `--options`
_数据验证配置_
_列表选项type='list' 时必填)_
**顶层字段**
- `help_text` (string?) — 验证失败时显示的提示文本
- `items` (array<string>?) — 列表选项type='list' 时必填)
- `operator` (enum?) — 比较运算符type='number'/'date'/'textLength' 时必填) [equal / notEqual / greaterThan / greaterThanOrEqual / lessThan / lessThanOrEqual / between / notBetween]
- `range` (string?) — 源数据区域type='listFromRange' 时必填,格式:'SheetName!A1:A10'
- `support_multiple_values` (boolean?) — 列表验证是否支持多选type='list'/'listFromRange' 时可选,默认 false
- `type` (enum) — 数据验证类型list下拉列表、listFromRange引用范围下拉列表、number数字、date日期、textLength文本长度、… [list / listFromRange / number / date / textLength / checkbox]
- `values` (array<oneOf>?) — 比较值operator 为 'between'/'notBetween' 时需要两个值,其它运算符需要一个值)
**数组项**(类型 string
- 标量string
## Examples
@@ -125,10 +120,10 @@ _数据验证配置_
lark-cli sheets +batch-update --url "https://example.feishu.cn/sheets/shtXXX" --yes \
--operations @ops.json
# ops.json array<{tool_name, input}>:
# ops.json array<{tool_name, input}>tool_name 是 CLI shortcut 名:
# [
# {"tool_name": "modify_sheet_structure", "input": {"sheet_id":"...","operation":"insert","dimension":"row","start":10,"end":12}},
# {"tool_name": "set_cell_range", "input": {"sheet_id":"...","range":"A11:B12","cells":[[{"value":"a"},{"value":"b"}],[{"value":"c"},{"value":"d"}]]}}
# {"tool_name": "+dim-insert", "input": {"sheet_id":"...","dimension":"row","start":10,"end":12}},
# {"tool_name": "+cells-set", "input": {"sheet_id":"...","range":"A11:B12","cells":[[{"value":"a"},{"value":"b"}],[{"value":"c"},{"value":"d"}]]}}
# ]
```
@@ -137,9 +132,9 @@ lark-cli sheets +batch-update --url "https://example.feishu.cn/sheets/shtXXX" --
> ```jsonc
> // 在 C 列前插入新列 → 写表头 C1 → 回填 C2:C100 共 99 行
> [
> {"tool_name": "`+dim-{insert|delete|hide|unhide|freeze|group|ungroup}`",
> "input": {"sheet_id": "...", "operation": "insert", "dimension": "column", "start": 3, "end": 4}},
> {"tool_name": "`+cells-set`",
> {"tool_name": "+dim-insert",
> "input": {"sheet_id": "...", "dimension": "column", "start": 3, "end": 4}},
> {"tool_name": "+cells-set",
> "input": {"sheet_id": "...", "range": "C1:C100",
> "cells": [[{"value":"score"}], [{"value":95}], [{"value":87}], /* ... 97 more rows ... */ ]}}
> ]

View File

@@ -111,7 +111,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--chart-id` | string | | 指定单个图表 reference_id 过滤 |
| `--chart-id` | string | optional | 指定单个图表 reference_id 过滤 |
### `+chart-create`
@@ -119,7 +119,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | | 图表完整配置 JSON`position` / `data` / `properties` 等);结构嵌套深,统一走 JSON 注入 |
| `--properties` | string + File + Stdin复合 JSON | required | 图表完整配置 JSON`position` / `data` / `properties` 等);结构嵌套深,统一走 JSON 注入 |
### `+chart-update`
@@ -127,8 +127,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--chart-id` | string | | 目标图表 reference_id |
| `--properties` | string + File + Stdin复合 JSON | | 完整或足够完整的图表配置 JSON`+chart-list` 回读再 patch |
| `--chart-id` | string | required | 目标图表 reference_id |
| `--properties` | string + File + Stdin复合 JSON | required | 完整或足够完整的图表配置 JSON`+chart-list` 回读再 patch |
### `+chart-delete`
@@ -136,7 +136,7 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--chart-id` | string | | 目标图表 reference_id |
| `--chart-id` | string | required | 目标图表 reference_id |
## Schemas

View File

@@ -87,7 +87,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--rule-id` | string | | 按规则 id 过滤 |
| `--rule-id` | string | optional | 按规则 id 过滤 |
### `+cond-format-create`
@@ -95,9 +95,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | | 规则配置 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 项);优先级高于 `--properties` 中同名字段 |
| `--ranges` | string + File + Stdin简单 JSON | | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--properties` 中同名字段 |
| `--properties` | string + File + Stdin复合 JSON | required | 规则配置 JSON`style`(命中样式,必填)和 `attrs?`(规则参数列表,因 `rule_type` 不同结构而异)/ `has_ref?``rule_type``ranges` 已拎为独立 flag |
| `--rule-type` | string | required | 条件格式规则类型;优先级高于 `--properties` 中同名字段(可选值`cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell` |
| `--ranges` | string + File + Stdin简单 JSON | required | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--properties` 中同名字段 |
### `+cond-format-update`
@@ -105,10 +105,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--rule-id` | string | | 目标规则 id |
| `--properties` | string + File + Stdin复合 JSON | | 规则配置 JSON结构同 `+cond-format-create``--properties`update 是整组覆盖式 |
| `--rule-type` | string + Enum | 是 | 条件格式规则类型 enum`cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell`(共 13 项);优先级高于 `--properties` 中同名字段 |
| `--ranges` | string + File + Stdin简单 JSON | | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--properties` 中同名字段 |
| `--rule-id` | string | required | 目标规则 id |
| `--properties` | string + File + Stdin复合 JSON | required | 规则配置 JSON结构同 `+cond-format-create``--properties`update 是整组覆盖式 |
| `--rule-type` | string | required | 条件格式规则类型;优先级高于 `--properties` 中同名字段(可选值`cellValue` / `formula` / `duplicate` / `unique` / `topBottom` / `aboveBelowAverage` / `dataBar` / `colorScale` / `iconSet` / `textContains` / `dateOccurring` / `blankCell` / `errorCell` |
| `--ranges` | string + File + Stdin简单 JSON | required | 应用条件格式的 A1 范围 JSON 数组(如 `["A1:A100","C2:C50"]`);优先级高于 `--properties` 中同名字段 |
### `+cond-format-delete`
@@ -116,7 +116,7 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--rule-id` | string | | 目标规则 id |
| `--rule-id` | string | required | 目标规则 id |
## Schemas
@@ -147,7 +147,7 @@ _创建/更新的条件格式属性_
# 重复值高亮
lark-cli sheets +cond-format-create --url "..." --sheet-id "$SID" \
--rule-type duplicate --ranges '["A1:A100"]' \
--properties '{"style":{"background_color":"#FFD7D7"}}'
--properties '{"style":{"back_color":"#FFD7D7"}}'
# 数据条
lark-cli sheets +cond-format-create --url "..." --sheet-id "$SID" \

View File

@@ -40,7 +40,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--view-id` | string | | 按筛选视图 reference_id 过滤(命中即只返回单个视图) |
| `--view-id` | string | optional | 按筛选视图 reference_id 过滤(命中即只返回单个视图) |
### `+filter-view-create`
@@ -48,9 +48,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | | 筛选视图规则 JSON`rules?`(列级筛选规则数组)和 `filtered_columns?``range``view_name` 是独立 flag |
| `--range` | string | | 筛选视图作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段create 必填,必须覆盖表头行 |
| `--view-name` | string | | 筛选视图名称create 不传时系统自动分配update 不传时保留原名;优先级高于 `--properties` 中同名字段 |
| `--properties` | string + File + Stdin复合 JSON | required | 筛选视图规则 JSON`rules?`(列级筛选规则数组)和 `filtered_columns?``range``view_name` 是独立 flag |
| `--range` | string | required | 筛选视图作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段create 必填,必须覆盖表头行 |
| `--view-name` | string | optional | 筛选视图名称create 不传时系统自动分配update 不传时保留原名;优先级高于 `--properties` 中同名字段 |
### `+filter-view-update`
@@ -58,10 +58,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--view-id` | string | | 目标筛选视图 reference_id |
| `--properties` | string + File + Stdin复合 JSON | | 筛选视图规则 JSON`rules?``filtered_columns?``range``view_name` 是独立 flag至少传 `--properties.rules` / `--range` / `--view-name` 之一 |
| `--range` | string | | 筛选视图作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段update 时省略表示保留当前 range |
| `--view-name` | string | | 筛选视图名称create 不传时系统自动分配update 不传时保留原名;优先级高于 `--properties` 中同名字段 |
| `--view-id` | string | required | 目标筛选视图 reference_id |
| `--properties` | string + File + Stdin复合 JSON | required | 筛选视图规则 JSON`rules?``filtered_columns?``range``view_name` 是独立 flag至少传 `--properties.rules` / `--range` / `--view-name` 之一 |
| `--range` | string | optional | 筛选视图作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段update 时省略表示保留当前 range |
| `--view-name` | string | optional | 筛选视图名称create 不传时系统自动分配update 不传时保留原名;优先级高于 `--properties` 中同名字段 |
### `+filter-view-delete`
@@ -69,7 +69,7 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--view-id` | string | | 目标筛选视图 reference_id |
| `--view-id` | string | required | 目标筛选视图 reference_id |
## Schemas
@@ -108,7 +108,7 @@ lark-cli sheets +filter-view-list --url "..." --sheet-id "$SID" --view-id vAbcde
```bash
lark-cli sheets +filter-view-create --url "..." --sheet-id "$SID" \
--view-name "活跃用户" --range "A1:F1000" \
--properties '{"rules":[{"col":"C","filter_type":"number","compare":"greater","expected":[100]}]}'
--properties '{"rules":[{"column_index":"C","conditions":[{"type":"number","compare_type":"greaterThan","values":[100]}]}]}'
```
> `--range` **必须覆盖表头行**(如 `A1:F1000`),不能只包含数据行;`--view-name` 重名时服务端自动改名。

View File

@@ -50,8 +50,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 筛选范围A1 表示法,含表头行,如 `A1:F1000`);不要重复写入 `--properties` 中的 range 字段 |
| `--properties` | string + File + Stdin复合 JSON | | 筛选规则 JSON`rules`(列级筛选规则数组,必填)和 `filtered_columns?`(激活列索引提示)。`range` 是独立 flag不要再放此 JSON 里) |
| `--range` | string | required | 筛选范围A1 表示法,含表头行,如 `A1:F1000`);不要重复写入 `--properties` 中的 range 字段 |
| `--properties` | string + File + Stdin复合 JSON | optional | 筛选规则 JSON`rules`(列级筛选规则数组,必填)和 `filtered_columns?`(激活列索引提示)。`range` 是独立 flag不要再放此 JSON 里) |
### `+filter-update`
@@ -59,8 +59,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | | 筛选规则 JSON`rules``filtered_columns?`update 是整组覆盖式(传空 `rules: []` 清空)。`range` 已拎为独立 flag |
| `--range` | string | | 筛选作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段 |
| `--properties` | string + File + Stdin复合 JSON | required | 筛选规则 JSON`rules``filtered_columns?`update 是整组覆盖式(传空 `rules: []` 清空)。`range` 已拎为独立 flag |
| `--range` | string | required | 筛选作用的单元格范围A1 表示法,如 `A1:F1000`);优先级高于 `--properties` 中同名字段 |
### `+filter-delete`
@@ -94,7 +94,7 @@ _创建/更新的筛选器属性_
```bash
lark-cli sheets +filter-create --url "..." --sheet-id "$SID" \
--range "A1:F1000" \
--properties '{"rules":[{"col":"B","filter_type":"multiValue","expected":["北京","上海"]}]}'
--properties '{"rules":[{"column_index":"B","conditions":[{"type":"multiValue","compare_type":"equal","values":["北京","上海"]}]}]}'
```
### `+filter-update`

View File

@@ -51,7 +51,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--float-image-id` | string | | 按 id 过滤;省略时列工作表全部 |
| `--float-image-id` | string | optional | 按 id 过滤;省略时列工作表全部 |
### `+float-image-create`
@@ -59,16 +59,16 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--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` 这种带前缀的字符串 |
| `--position-row` | int | | 图片左上角所在行0-based |
| `--position-col` | string | | 图片左上角所在列(列字母,如 `A` / `B` |
| `--size-width` | int | | 图片宽度(像素) |
| `--size-height` | int | | 图片高度(像素) |
| `--offset-row` | int | | 在 `--position-row` 基础上的行内偏移(像素) |
| `--offset-col` | int | | 在 `--position-col` 基础上的列内偏移(像素) |
| `--z-index` | int | | 图片 Z 轴层级,控制重叠顺序 |
| `--image-name` | string | required | 图片名称,含扩展名(如 `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` 这种带前缀的字符串 |
| `--position-row` | int | required | 图片左上角所在行0-based |
| `--position-col` | string | required | 图片左上角所在列(列字母,如 `A` / `B` |
| `--size-width` | int | required | 图片宽度(像素) |
| `--size-height` | int | required | 图片高度(像素) |
| `--offset-row` | int | optional | 在 `--position-row` 基础上的行内偏移(像素) |
| `--offset-col` | int | optional | 在 `--position-col` 基础上的列内偏移(像素) |
| `--z-index` | int | optional | 图片 Z 轴层级,控制重叠顺序 |
### `+float-image-update`
@@ -76,17 +76,17 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--float-image-id` | string | | 目标图片 id |
| `--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` 这种带前缀的字符串 |
| `--position-row` | int | | 图片左上角所在行0-based |
| `--position-col` | string | | 图片左上角所在列(列字母,如 `A` / `B` |
| `--size-width` | int | | 图片宽度(像素) |
| `--size-height` | int | | 图片高度(像素) |
| `--offset-row` | int | | 在 `--position-row` 基础上的行内偏移(像素) |
| `--offset-col` | int | | 在 `--position-col` 基础上的列内偏移(像素) |
| `--z-index` | int | | 图片 Z 轴层级,控制重叠顺序 |
| `--float-image-id` | string | required | 目标图片 id |
| `--image-name` | string | required | 图片名称,含扩展名(如 `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` 这种带前缀的字符串 |
| `--position-row` | int | required | 图片左上角所在行0-based |
| `--position-col` | string | required | 图片左上角所在列(列字母,如 `A` / `B` |
| `--size-width` | int | required | 图片宽度(像素) |
| `--size-height` | int | required | 图片高度(像素) |
| `--offset-row` | int | optional | 在 `--position-row` 基础上的行内偏移(像素) |
| `--offset-col` | int | optional | 在 `--position-col` 基础上的列内偏移(像素) |
| `--z-index` | int | optional | 图片 Z 轴层级,控制重叠顺序 |
### `+float-image-delete`
@@ -94,7 +94,7 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--float-image-id` | string | | 目标图片 id |
| `--float-image-id` | string | required | 目标图片 id |
## Examples
@@ -120,12 +120,24 @@ lark-cli sheets +float-image-create --url "..." --sheet-id "$SID" \
### `+float-image-update`
> 必须先 `+float-image-list --float-image-id <id>` 回读当前完整属性,再通过 `--image-name` / `--position-*` / `--size-*` 等独立 flag 改对应字段。
> **patch 模式**:除了 `--float-image-id`(必填,定位目标图片)外,其它字段都可选——只传你需要改的那几个,未传的字段保持原值不变。至少传一个改动字段。
>
> 推荐流程:先 `+float-image-list --float-image-id <id>` 回读当前完整属性,再针对要改的字段调一次 `+float-image-update`。
```bash
# 只改位置,保留其它属性
lark-cli sheets +float-image-update --url "..." --sheet-id "$SID" \
--float-image-id "$IMG_ID" --position-row 5 --position-col C
# 只换图,位置/尺寸不变
lark-cli sheets +float-image-update --url "..." --sheet-id "$SID" \
--float-image-id "$IMG_ID" --image-name "new-logo.png" --image-token "$NEW_TOKEN"
```
### `+float-image-delete`
### Validate / DryRun / Execute 约束
- `Validate`XOR 公共四件套;`+float-image-create` 校验 `--image-name` 非空,`--image-token``--image-uri` 互斥且至少一个非空,`--position-row/col``--size-width/height` 必填且为合法整数;`+float-image-update` 必须 `--float-image-id``+float-image-delete` 强制 `--yes``--dry-run`
- `Validate`XOR 公共四件套;`+float-image-create` 校验 `--image-name` 非空,`--image-token``--image-uri` 互斥且至少一个非空,`--position-row/col``--size-width/height` 必填且为合法整数;`+float-image-update` 必须 `--float-image-id`,其余 `--image-name` / `--image-token` / `--image-uri` / `--position-*` / `--size-*` / `--offset-*` / `--z-index` 至少传 1 个patch 模式:未传字段保持原值)`+float-image-delete` 强制 `--yes``--dry-run`
- `DryRun`:写操作输出"将要 POST/PATCH/DELETE 的 float_image 请求模板"。
- `Execute`:写后调用 `+float-image-list --float-image-id <id>` 回读envelope.meta.verification 给出新位置 / 尺寸对比。

View File

@@ -53,7 +53,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--pivot-table-id` | string | | 按 id 过滤 |
| `--pivot-table-id` | string | optional | 按 id 过滤 |
### `+pivot-create`
@@ -61,11 +61,11 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--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 | | 落点起始 cellA1 格式,如 `A1`),默认 `A1` |
| `--source` | string | | 透视表源数据区域A1 表示法,格式 `SheetName!StartCell:EndCell`,如 `Sheet1!A1:D100` |
| `--range` | string | | 透视表放置位置(左上角 A1 单值,如 `F1`);省略时放在新建子表的左上角 |
| `--properties` | string + File + Stdin复合 JSON | required | JSON`{"data_range":"Sheet1!A1:F1000","rows":[...],"columns":[...],"values":[...],"filters":[...],"show_row_grand_total":true,"show_col_grand_total":true}` |
| `--target-sheet-id` | string | optional | 透视表落点子表 id省略时自动新建子表推荐 |
| `--target-position` | string | optional | 落点起始 cellA1 格式,如 `A1`),默认 `A1` |
| `--source` | string | required | 透视表源数据区域A1 表示法,格式 `SheetName!StartCell:EndCell`,如 `Sheet1!A1:D100` |
| `--range` | string | optional | 透视表放置位置(左上角 A1 单值,如 `F1`);省略时放在新建子表的左上角 |
### `+pivot-update`
@@ -73,8 +73,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--pivot-table-id` | string | | 目标透视表 id |
| `--properties` | string + File + Stdin复合 JSON | | 完整或足够完整的配置(先 `+pivot-list --pivot-table-id <id>` 回读再 patch |
| `--pivot-table-id` | string | required | 目标透视表 id |
| `--properties` | string + File + Stdin复合 JSON | required | 完整或足够完整的配置(先 `+pivot-list --pivot-table-id <id>` 回读再 patch |
### `+pivot-delete`
@@ -82,7 +82,7 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--pivot-table-id` | string | | 目标透视表 id |
| `--pivot-table-id` | string | required | 目标透视表 id |
## Schemas

View File

@@ -101,8 +101,8 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 清除范围A1 格式) |
| `--scope` | string + Enum | 否 | 清除范围 enum`content`(默认,仅清内容)/ `formats`(仅清格式)/ `all`(清内容 + 格式) |
| `--range` | string | required | 清除范围A1 格式) |
| `--scope` | string | optional | 清除范围 enum`content`(默认,仅清内容)/ `formats`(仅清格式)/ `all`(清内容 + 格式)(可选值:`content` / `formats` / `all` |
### `+cells-merge`
@@ -110,8 +110,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 待合并 / 取消合并的范围A1 格式) |
| `--merge-type` | string + Enum | 否 | 合并方向 enum(仅 `+cells-merge``all` / `rows` / `columns`,默认 `all` |
| `--range` | string | required | 待合并 / 取消合并的范围A1 格式) |
| `--merge-type` | string | optional | 合并方向(仅 `+cells-merge`(可选值`all` / `rows` / `columns` |
### `+cells-unmerge`
@@ -119,8 +119,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 待合并 / 取消合并的范围A1 格式) |
| `--merge-type` | string + Enum | 否 | 合并方向 enum`+cells-merge``all` / `rows` / `columns`,默认 `all` |
| `--range` | string | required | 待合并 / 取消合并的范围A1 格式) |
### `+rows-resize`
@@ -128,10 +127,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--start` | int | | 起始行0-based, inclusive |
| `--end` | int | | 结束行0-based, inclusive |
| `--type` | string + Enum | 是 | 尺寸方式 enum`pixel`(指定 px 像素值,需配 `--size`/ `standard`(重置为默认标准行高)/ `auto`(自动适应内容) |
| `--size` | int | | 行高像素30 / 40 / 60`--type pixel` 时必填,其它 type 忽略 |
| `--start` | int | required | 起始行0-based, inclusive |
| `--end` | int | required | 结束行0-based, inclusive |
| `--type` | string | required | 尺寸方式 enum`pixel`(指定 px 像素值,需配 `--size`/ `standard`(重置为默认标准行高)/ `auto`(自动适应内容)(可选值:`pixel` / `standard` / `auto` |
| `--size` | int | optional | 行高像素30 / 40 / 60`--type pixel` 时必填,其它 type 忽略 |
### `+cols-resize`
@@ -139,10 +138,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--start` | int | | 起始列0-based, inclusive |
| `--end` | int | | 结束列0-based, inclusive |
| `--type` | string + Enum | 是 | 尺寸方式 enum`pixel`(指定 px 像素值,需配 `--size`/ `standard`(重置为默认标准列宽) |
| `--size` | int | | 列宽像素80 / 120 / 200`--type pixel` 时必填,其它 type 忽略 |
| `--start` | int | required | 起始列0-based, inclusive |
| `--end` | int | required | 结束列0-based, inclusive |
| `--type` | string | required | 尺寸方式 enum`pixel`(指定 px 像素值,需配 `--size`/ `standard`(重置为默认标准列宽)(可选值:`pixel` / `standard` |
| `--size` | int | optional | 列宽像素80 / 120 / 200`--type pixel` 时必填,其它 type 忽略 |
### `+range-move`
@@ -150,10 +149,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--source-range` | string | | 源 A1 范围 |
| `--target-sheet-id` | string | | 目标子表 id省略时同源 sheet |
| `--target-range` | string | | 目标 A1 范围(传起点 cell 即可,按源尺寸自动推断) |
| `--paste-type` | string + Enum | 否 | 粘贴内容 enum`+range-copy``values` / `formulas` / `formats` / `all`,默认 `all` |
| `--source-range` | string | required | 源 A1 范围 |
| `--target-sheet-id` | string | optional | 目标子表 id省略时同源 sheet |
| `--target-range` | string | required | 目标 A1 范围(传起点 cell 即可,按源尺寸自动推断) |
### `+range-copy`
@@ -161,10 +159,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--source-range` | string | | 源 A1 范围 |
| `--target-sheet-id` | string | | 目标子表 id省略时同源 sheet |
| `--target-range` | string | | 目标 A1 范围(传起点 cell 即可,按源尺寸自动推断) |
| `--paste-type` | string + Enum | 否 | 粘贴内容 enum(仅 `+range-copy``values` / `formulas` / `formats` / `all`,默认 `all` |
| `--source-range` | string | required | 源 A1 范围 |
| `--target-sheet-id` | string | optional | 目标子表 id省略时同源 sheet |
| `--target-range` | string | required | 目标 A1 范围(传起点 cell 即可,按源尺寸自动推断) |
| `--paste-type` | string | optional | 粘贴内容(仅 `+range-copy`(可选值`values` / `formulas` / `formats` / `all` |
### `+range-fill`
@@ -172,9 +170,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--source-range` | string | | 填充模板范围(系列起始 cells |
| `--target-range` | string | | 目标填充范围A1 格式) |
| `--series-type` | string + Enum | 否 | 填充序列类型 enum`auto` / `linear` / `growth` / `date` / `copy`,默认 `auto` |
| `--source-range` | string | required | 填充模板范围(系列起始 cells |
| `--target-range` | string | required | 目标填充范围A1 格式) |
| `--series-type` | string | optional | 填充序列类型(可选值`auto` / `linear` / `growth` / `date` / `copy` |
### `+range-sort`
@@ -182,9 +180,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 排序范围A1 格式;含或不含表头由 `--has-header` 决定) |
| `--sort-keys` | string + File + Stdin复合 JSON | | JSON`[{"col":"B","order":"asc"},{"col":"D","order":"desc"}]` |
| `--has-header` | bool | | 第一行是表头不参与排序,默认 false |
| `--range` | string | required | 排序范围A1 格式;含或不含表头由 `--has-header` 决定) |
| `--sort-keys` | string + File + Stdin复合 JSON | required | JSON 数组`[{"column":"<列字母>","ascending":<bool>}, ...]` |
| `--has-header` | bool | optional | 第一行是表头不参与排序,默认 false |
## Schemas

View File

@@ -88,11 +88,11 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | 是 | A1 范围,如 `Sheet1!A1:F10` |
| `--include` | string_slice + Enum | 否 | `value` / `formula` / `style` / `comment` / `data_validation`,逗号分隔 |
| `--cell-limit` | int + Hidden | 否 | 防爆,默认 5000 |
| `--max-chars` | int + Hidden | 否 | 防爆,默认 200000 |
| `--skip-hidden` | bool | | 跳过隐藏行列,默认 `false` |
| `--range` | string_array | required | A1 范围,如 `Sheet1!A1:F10` |
| `--include` | string_slice | optional | 要返回的信息类别,逗号分隔多个(可选值:`value` / `formula` / `style` / `comment` / `data_validation` |
| `--cell-limit` | int | optional | 防爆,默认 5000 |
| `--max-chars` | int | optional | 防爆,默认 200000 |
| `--skip-hidden` | bool | optional | 跳过隐藏行列,默认 `false` |
### `+dropdown-get`
@@ -100,7 +100,7 @@ _公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 目标范围A1 格式,必须带 sheet 前缀,如 `sheet1!A2:A100` |
| `--range` | string | required | 目标范围A1 格式,必须带 sheet 前缀,如 `sheet1!A2:A100` |
### `+csv-get`
@@ -108,12 +108,12 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | A1 范围;省略时读整表的 `current_region` |
| `--value-render-option` | string + Enum | 否 | 单元格取值模式 enum`ToString` / `FormattedValue` / `Formula` / `UnformattedValue` |
| `--max-rows` | int + Hidden | 否 | 防爆,默认 100000 |
| `--max-chars` | int + Hidden | 否 | 防爆,默认 200000 |
| `--include-row-prefix` | bool | | 是否在每行前加 `[row=N]` 前缀,默认 `true` |
| `--skip-hidden` | bool | | 跳过隐藏行列,默认 `false` |
| `--range` | string | optional | A1 范围;省略时读整表的 `current_region` |
| `--value-render-option` | string | optional | 单元格取值模式(可选值:`formatted_value` / `raw_value` / `formula` |
| `--max-rows` | int | optional | 防爆,默认 100000 |
| `--max-chars` | int | optional | 防爆,默认 200000 |
| `--include-row-prefix` | bool | optional | 是否在每行前加 `[row=N]` 前缀,默认 `true` |
| `--skip-hidden` | bool | optional | 跳过隐藏行列,默认 `false` |
## Examples

View File

@@ -37,13 +37,14 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--find` | string | | 待查找文本(与 `--regex` 配合时按正则解释) |
| `--range` | string | | 查找范围A1 格式);省略时整表 |
| `--match-case` | bool | | 大小写敏感 |
| `--match-entire-cell` | bool | | 完全匹配整个单元格 |
| `--regex` | bool | | 把 `--find` 按正则解释 |
| `--include-formulas` | bool | | 也在公式文本中搜索 |
| `--max-matches` | int + Hidden | 否 | 防爆,默认 5000 |
| `--find` | string | required | 待查找文本(与 `--regex` 配合时按正则解释) |
| `--range` | string | optional | 查找范围A1 格式);省略时整表 |
| `--match-case` | bool | optional | 大小写敏感 |
| `--match-entire-cell` | bool | optional | 完全匹配整个单元格 |
| `--regex` | bool | optional | 把 `--find` 按正则解释 |
| `--include-formulas` | bool | optional | 也在公式文本中搜索 |
| `--max-matches` | int | optional | 防爆,默认 5000 |
| `--offset` | int | optional | 跳过前 N 个匹配(分页用),默认 0 |
### `+cells-replace`
@@ -51,13 +52,13 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--find` | string | | 待替换文本 |
| `--replacement` | string | | 替换为;传空字符串 `""` 等价于「删除内容」 |
| `--range` | string | | 替换范围A1 格式);省略时整表 |
| `--match-case` | bool | 否 | 也在公式文本中替换 |
| `--match-entire-cell` | bool | 否 | 也在公式文本中替换 |
| `--regex` | bool | 否 | 也在公式文本中替换 |
| `--include-formulas` | bool | | 也在公式文本中替换 |
| `--find` | string | required | 待替换文本 |
| `--replacement` | string | required | 替换为;传空字符串 `""` 等价于「删除内容」 |
| `--range` | string | optional | 替换范围A1 格式);省略时整表 |
| `--match-case` | bool | optional | 大小写敏感 |
| `--match-entire-cell` | bool | optional | 完全匹配整个单元格 |
| `--regex` | bool | optional | 把 `--find` 按正则解释 |
| `--include-formulas` | bool | optional | 也在公式文本中替换 |
## Examples

View File

@@ -57,7 +57,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--include` | string_slice + Enum | 否 | `merges` / `row_heights` / `col_widths` / `hidden_rows` / `hidden_cols` / `groups` / `frozen`,逗号分隔 |
| `--include` | string_slice | optional | 要返回的结构信息类别,逗号分隔多个(可选值:`merges` / `row_heights` / `col_widths` / `hidden_rows` / `hidden_cols` / `groups` / `frozen` |
| `--range` | string | optional | 限定只返回该 A1 范围的结构信息;省略时返回整表 |
### `+dim-insert`
@@ -65,10 +66,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--start` | int | | 插入起始位置0-based |
| `--end` | int | | 插入结束位置exclusive |
| `--inherit-style` | string + Enum | 否 | 新行/列样式继承策略 enum`before`(继承前一行/列)/ `after`(继承后一行/列)/ `none`(默认) |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--start` | int | required | 插入起始位置0-based |
| `--end` | int | required | 插入结束位置exclusive |
| `--inherit-style` | string | optional | 新行/列样式继承策略 enum`before`(继承前一行/列)/ `after`(继承后一行/列)/ `none`(默认)(可选值:`before` / `after` / `none` |
### `+dim-delete`
@@ -76,9 +77,9 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--start` | int | | 起始位置0-based |
| `--end` | int | | 结束位置exclusive |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--start` | int | required | 起始位置0-based |
| `--end` | int | required | 结束位置exclusive |
### `+dim-hide`
@@ -86,9 +87,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--start` | int | | 结束位置0-based, inclusive |
| `--end` | int | | 结束位置0-based, inclusive |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--start` | int | required | 起始位置0-based, inclusive |
| `--end` | int | required | 结束位置0-based, inclusive |
### `+dim-unhide`
@@ -96,9 +97,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--start` | int | | 结束位置0-based, inclusive |
| `--end` | int | | 结束位置0-based, inclusive |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--start` | int | required | 起始位置0-based, inclusive |
| `--end` | int | required | 结束位置0-based, inclusive |
### `+dim-freeze`
@@ -106,8 +107,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--count` | int | | 冻结前 N 行/列;传 0 解除冻结 |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--count` | int | required | 冻结前 N 行/列;传 0 解除冻结 |
### `+dim-group`
@@ -115,10 +116,11 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--start` | int | | 结束位置0-based, inclusive |
| `--end` | int | | 结束位置0-based, inclusive |
| `--depth` | int | | 嵌套层级(`+dim-group` 用),默认 1 |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--start` | int | required | 起始位置0-based, inclusive |
| `--end` | int | required | 结束位置0-based, inclusive |
| `--depth` | int | optional | 嵌套层级(`+dim-group` 用),默认 1 |
| `--group-state` | string | optional | 分组初始展开状态(可选值:`expand` / `fold` |
### `+dim-ungroup`
@@ -126,10 +128,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--start` | int | | 结束位置0-based, inclusive |
| `--end` | int | | 结束位置0-based, inclusive |
| `--depth` | int | | 嵌套层级(`+dim-group` 用),默认 1 |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--start` | int | required | 起始位置0-based, inclusive |
| `--end` | int | required | 结束位置0-based, inclusive |
| `--depth` | int | optional | 嵌套层级(`+dim-group` 用),默认 1 |
### `+dim-move`
@@ -137,10 +139,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--dimension` | string + Enum | 是 | `row` / `column` |
| `--start` | int | | 源起止区间的起始位置0-based, inclusive |
| `--end` | int | | 源起止区间的结束位置0-based, inclusive |
| `--target` | int | | 目标位置move 到该 index 之前0-based |
| `--dimension` | string | required | 维度方向(行或列)(可选值:`row` / `column` |
| `--start` | int | required | 源起止区间的起始位置0-based, inclusive |
| `--end` | int | required | 源起止区间的结束位置0-based, inclusive |
| `--target` | int | required | 目标位置move 到该 index 之前0-based |
## Examples

View File

@@ -37,7 +37,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--group-id` | string | | 按 group_id 过滤 |
| `--group-id` | string | optional | 按 group_id 过滤 |
### `+sparkline-create`
@@ -45,7 +45,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--properties` | string + File + Stdin复合 JSON | | JSON`{"type":"line\|column\|winLoss","data_range":"A2:F10","target_range":"G2:G10","style":{...},"special_points":{...}}`type 三种 enumdata_range 与 target_range 行/列数需对齐 |
| `--properties` | string + File + Stdin复合 JSON | required | JSON`{config共享样式配置, sparklines迷你图数组}`;完整字段结构跑 `--print-schema` |
### `+sparkline-update`
@@ -53,8 +53,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--group-id` | string | | 目标组 id |
| `--properties` | string + File + Stdin复合 JSON | 是 | 完整或足够完整的配置(`+sparkline-list --group-id <id>` 回读再 patch);可改 `type` / `data_range` / `target_range` / `style` / `special_points` 等字段 |
| `--group-id` | string | required | 目标组 id |
| `--properties` | string + File + Stdin复合 JSON | required | JSON`{config, sparklines}``+sparkline-list --group-id <id>` 回读再 patch;完整字段结构跑 `--print-schema` |
### `+sparkline-delete`
@@ -62,7 +62,7 @@ _公共四件套 · 系统:`--yes`、`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--group-id` | string | | 目标组 id |
| `--group-id` | string | required | 目标组 id |
## Schemas

View File

@@ -50,9 +50,7 @@
_公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--include-properties` | bool | 否 | 是否返回每个 sheet 的扩展属性(默认 true |
_仅含公共 / 系统 flag。_
### `+sheet-create`
@@ -60,10 +58,10 @@ _公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | | 新工作表名称 |
| `--index` | int | | 插入位置;省略时附加到末尾 |
| `--row-count` | int | | 初始行数默认 100 |
| `--col-count` | int | | 初始列数默认 26 |
| `--title` | string | required | 新工作表名称 |
| `--index` | int | optional | 插入位置;省略时附加到末尾 |
| `--row-count` | int | optional | 初始行数默认 200上限 50000 |
| `--col-count` | int | optional | 初始列数默认 20上限 200 |
### `+sheet-delete`
@@ -77,7 +75,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | | 新名称 |
| `--title` | string | required | 新名称 |
### `+sheet-move`
@@ -85,8 +83,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--index` | int | | 目标位置0-based |
| `--source-index` | int | | 源位置0-based可选未传时由 CLI runtime 根据 `--sheet-id` / `--sheet-name` 当前在工作簿中的 index 自动派生 |
| `--index` | int | required | 目标位置0-based |
| `--source-index` | int | optional | 源位置0-based可选未传时由 CLI runtime 根据 `--sheet-id` / `--sheet-name` 当前在工作簿中的 index 自动派生 |
### `+sheet-copy`
@@ -94,8 +92,8 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | | 副本名称;省略时由服务端生成 |
| `--index` | int | | 副本插入位置0-based省略时附加到末尾 |
| `--title` | string | optional | 副本名称;省略时由服务端生成 |
| `--index` | int | optional | 副本插入位置0-based省略时附加到末尾 |
### `+sheet-hide`
@@ -115,7 +113,7 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--color` | string | | Hex 色值如 `#FF0000`,传空 `""` 清除 |
| `--color` | string | required | Hex 色值如 `#FF0000`,传空 `""` 清除 |
### `+workbook-create`
@@ -123,10 +121,10 @@ _系统`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--title` | string | | 新 spreadsheet 标题 |
| `--folder-token` | string | | 目标文件夹 token省略时放在云空间根目录 |
| `--headers` | string + File + Stdin简单 JSON | | 表头行 JSON 数组:`["列A","列B"]` |
| `--values` | string + File + Stdin简单 JSON | | 初始数据 JSON 二维数组:`[["alice",95]]` |
| `--title` | string | required | 新 spreadsheet 标题 |
| `--folder-token` | string | optional | 目标文件夹 token省略时放在云空间根目录 |
| `--headers` | string + File + Stdin简单 JSON | optional | 表头行 JSON 数组:`["列A","列B"]` |
| `--values` | string + File + Stdin简单 JSON | optional | 初始数据 JSON 二维数组:`[["alice",95]]` |
### `+workbook-export`
@@ -134,9 +132,9 @@ _公共URL/token无 sheet 定位) · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--file-extension` | string + Enum | 否 | `xlsx` / `csv`,默认 `xlsx`csv 模式必须配 `--sheet-id` |
| `--sheet-id` | string | | 仅 csv 模式必填:指定要导出的 sheet reference_id |
| `--output-path` | string | | 本地保存路径;省略时只触发导出不下载 |
| `--file-extension` | string | optional | 导出文件格式;`csv` 模式必须配 `--sheet-id`(可选值:`xlsx` / `csv` |
| `--sheet-id` | string | optional | 仅 csv 模式必填:指定要导出的 sheet reference_id |
| `--output-path` | string | optional | 本地保存路径;省略时只触发导出不下载 |
## Examples

View File

@@ -184,10 +184,10 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 写入区域A1 格式) |
| `--cells` | string + File + Stdin复合 JSON | 是 | JSON`{"values": [[...], ...]}`;可含 `formula` / `cell_styles` / `comments` / `embed_image` 富信息 |
| `--allow-overwrite` | bool | | 允许覆盖非空 cell默认 false 时遇非空 cell 报错 |
| `--max-cells` | int + Hidden | 否 | 防爆,默认 50000 |
| `--range` | string | required | 写入区域A1 格式) |
| `--cells` | string + File + Stdin复合 JSON | required | JSON2D 数组 `[[{cell},...],...]`,维度与 `--range` 完全一致;每个 cell 可含 `value` / `formula` / `cell_styles` / `note` / `rich_text`(含 `type="embed-image"` 单元格嵌图)等,完整字段跑 `--print-schema` |
| `--allow-overwrite` | bool | optional | 允许覆盖非空 cell默认 true设为 false 时遇非空 cell 报错 |
| `--max-cells` | int | optional | 防爆,默认 50000 |
### `+cells-set-style`
@@ -195,18 +195,18 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 目标范围A1 格式,如 `A1:B2` |
| `--background-color` | string | | 背景颜色(十六进制,如 `#ffffff` |
| `--font-color` | string | | 字体颜色(十六进制,如 `#000000` |
| `--font-size` | number | 否 | 字体大小px10、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 方向结构相同 |
| `--range` | string | required | 目标范围A1 格式,如 `A1:B2` |
| `--background-color` | string | optional | 背景颜色(十六进制,如 `#ffffff` |
| `--font-color` | string | optional | 字体颜色(十六进制,如 `#000000` |
| `--font-size` | float64 | optional | 字体大小px10、12、14 |
| `--font-style` | string | optional | 字体样式(可选值`normal` / `italic` |
| `--font-weight` | string | optional | 字重(可选值`normal` / `bold` |
| `--font-line` | string | optional | 字体线条样式(可选值`none` / `underline` / `line-through` |
| `--horizontal-alignment` | string | optional | 水平对齐(可选值`left` / `center` / `right` |
| `--vertical-alignment` | string | optional | 垂直对齐(可选值`top` / `middle` / `bottom` |
| `--word-wrap` | string | optional | 换行策略(可选值`overflow` / `auto-wrap` / `word-clip` |
| `--number-format` | string | optional | 数字格式(例:文本 `@`、数字 `0.00`、货币 `$#,##0.00`、日期 `mm/dd/yyyy` |
| `--border-styles` | string + File + Stdin复合 JSON | optional | 边框配置 JSON`{ top: {style,color,weight}, bottom: ..., left: ..., right: ... }`4 方向结构相同 |
### `+cells-set-image`
@@ -214,9 +214,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 目标单元格A1 格式,必须单 cell`A1`;起止 cell 须相同) |
| `--image` | string | | 本地图片路径(支持 PNG / JPEG / JPG / GIF / BMP / JFIF / EXIF / TIFF / BPG / HEIC |
| `--name` | string | | 图片文件名(含扩展名);省略时取 `--image` 的 basename |
| `--range` | string | required | 目标单元格A1 格式,必须单 cell`A1`;起止 cell 须相同) |
| `--image` | string | required | 本地图片路径(支持 PNG / JPEG / JPG / GIF / BMP / JFIF / EXIF / TIFF / BPG / HEIC |
| `--name` | string | optional | 图片文件名(含扩展名);省略时取 `--image` 的 basename |
### `+dropdown-set`
@@ -224,11 +224,11 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 目标范围A1 格式,如 `A2:A100` |
| `--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` |
| `--range` | string | required | 目标范围A1 格式,如 `A2:A100` |
| `--options` | string + File + Stdin复合 JSON | required | 选项 JSON 数组 `["opt1","opt2"]`;最多 500 项,每项 ≤100 字符,不含逗号 |
| `--colors` | string + File + Stdin简单 JSON | optional | RGB hex 颜色数组(如 `["#1FB6C1","#F006C2"]`),长度必须与 `--options` 一致 |
| `--multiple` | bool | optional | 启用多选;默认 `false` |
| `--highlight` | bool | optional | 选项配色显示;默认 `false` |
### `+csv-put`
@@ -236,9 +236,9 @@ _公共四件套 · 系统:`--dry-run`_
| Flag | Type | 必填 | 说明 |
| --- | --- | --- | --- |
| `--range` | string | | 目标区域起点 A1`Sheet1!A1`);终点按 CSV 实际行列数自动推断 |
| `--csv` | string + File + Stdin非 JSON 文本) | | RFC 4180 CSV 文本;只写纯值,不带公式/样式/批注 |
| `--allow-overwrite` | bool | | 允许覆盖默认 false 时若目标非空报错 |
| `--start-cell` | string | required | 目标区域起点 A1`Sheet1!A1`);终点按 CSV 实际行列数自动推断 |
| `--csv` | string + File + Stdin非 JSON 文本) | required | RFC 4180 CSV 文本;只写纯值,不带公式/样式/批注 |
| `--allow-overwrite` | bool | optional | 允许覆盖默认 true设为 false 时若目标非空报错 |
## Schemas
@@ -269,16 +269,10 @@ _单元格边框配置含 top/bottom/left/right 四个方向,每个方向
### `+dropdown-set` `--options`
_数据验证配置_
_列表选项type='list' 时必填)_
**顶层字段**
- `help_text` (string?) — 验证失败时显示的提示文本
- `items` (array<string>?) — 列表选项type='list' 时必填)
- `operator` (enum?) — 比较运算符type='number'/'date'/'textLength' 时必填) [equal / notEqual / greaterThan / greaterThanOrEqual / lessThan / lessThanOrEqual / between / notBetween]
- `range` (string?) — 源数据区域type='listFromRange' 时必填,格式:'SheetName!A1:A10'
- `support_multiple_values` (boolean?) — 列表验证是否支持多选type='list'/'listFromRange' 时可选,默认 false
- `type` (enum) — 数据验证类型list下拉列表、listFromRange引用范围下拉列表、number数字、date日期、textLength文本长度、… [list / listFromRange / number / date / textLength / checkbox]
- `values` (array<oneOf>?) — 比较值operator 为 'between'/'notBetween' 时需要两个值,其它运算符需要一个值)
**数组项**(类型 string
- 标量string
## Examples