@amaster.ai/pi-lark 0.1.2-beta.52 → 0.1.2-beta.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/package.json +2 -2
  2. package/skills/lark-apps/SKILL.md +39 -6
  3. package/skills/lark-apps/references/lark-apps-cloud-dev.md +5 -4
  4. package/skills/lark-apps/references/lark-apps-create.md +6 -3
  5. package/skills/lark-apps/references/lark-apps-get.md +1 -1
  6. package/skills/lark-apps/references/lark-apps-list.md +1 -1
  7. package/skills/lark-apps/references/lark-apps-local-dev.md +27 -1
  8. package/skills/lark-apps/references/lark-apps-release-create.md +1 -1
  9. package/skills/lark-base/SKILL.md +4 -3
  10. package/skills/lark-base/references/lark-base-data-query-guide.md +8 -0
  11. package/skills/lark-base/references/lark-base-field-create.md +19 -8
  12. package/skills/lark-base/references/lark-base-field-json.md +3 -2
  13. package/skills/lark-doc/SKILL.md +25 -61
  14. package/skills/lark-doc/references/genres/business-analysis.md +30 -0
  15. package/skills/lark-doc/references/genres/data-report.md +32 -0
  16. package/skills/lark-doc/references/genres/email.md +38 -0
  17. package/skills/lark-doc/references/genres/execution-plan.md +27 -0
  18. package/skills/lark-doc/references/genres/formal-doc.md +37 -0
  19. package/skills/lark-doc/references/genres/meeting-minutes.md +24 -0
  20. package/skills/lark-doc/references/genres/memo-brief.md +25 -0
  21. package/skills/lark-doc/references/genres/official-redhead.md +73 -0
  22. package/skills/lark-doc/references/genres/prd.md +26 -0
  23. package/skills/lark-doc/references/genres/proposal.md +24 -0
  24. package/skills/lark-doc/references/genres/research-report.md +32 -0
  25. package/skills/lark-doc/references/genres/retrospective.md +25 -0
  26. package/skills/lark-doc/references/genres/route-consumer.md +37 -0
  27. package/skills/lark-doc/references/genres/route-creative.md +36 -0
  28. package/skills/lark-doc/references/genres/route-knowledge.md +39 -0
  29. package/skills/lark-doc/references/genres/route-marketing.md +40 -0
  30. package/skills/lark-doc/references/genres/route-media.md +36 -0
  31. package/skills/lark-doc/references/genres/route-opinion.md +38 -0
  32. package/skills/lark-doc/references/genres/route-personal-brand.md +36 -0
  33. package/skills/lark-doc/references/genres/route-platform.md +9 -0
  34. package/skills/lark-doc/references/genres/route-report.md +10 -0
  35. package/skills/lark-doc/references/genres/route-workplace.md +17 -0
  36. package/skills/lark-doc/references/genres/sop-tutorial.md +41 -0
  37. package/skills/lark-doc/references/genres/technical-doc.md +39 -0
  38. package/skills/lark-doc/references/genres/wechat.md +39 -0
  39. package/skills/lark-doc/references/genres/weekly-report.md +24 -0
  40. package/skills/lark-doc/references/genres/white-paper.md +32 -0
  41. package/skills/lark-doc/references/genres/xiaohongshu.md +38 -0
  42. package/skills/lark-doc/references/lark-doc-create-workflow.md +121 -0
  43. package/skills/lark-doc/references/lark-doc-create.md +22 -48
  44. package/skills/lark-doc/references/lark-doc-fetch.md +75 -92
  45. package/skills/lark-doc/references/lark-doc-history.md +3 -1
  46. package/skills/lark-doc/references/lark-doc-md.md +5 -1
  47. package/skills/lark-doc/references/lark-doc-script.md +76 -0
  48. package/skills/lark-doc/references/lark-doc-update.md +70 -222
  49. package/skills/lark-doc/references/lark-doc-whiteboard.md +5 -9
  50. package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +17 -12
  51. package/skills/lark-doc/references/lark-doc-xml.md +38 -167
  52. package/skills/lark-drive/SKILL.md +7 -5
  53. package/skills/lark-drive/references/lark-drive-copy.md +87 -0
  54. package/skills/lark-drive/references/lark-drive-update-title.md +78 -0
  55. package/skills/lark-im/SKILL.md +3 -3
  56. package/skills/lark-im/references/lark-im-message-enrichment.md +1 -1
  57. package/skills/lark-im/references/lark-im-messages-resources-download.md +19 -25
  58. package/skills/lark-im/references/lark-im-messages-search.md +1 -3
  59. package/skills/lark-sheets/SKILL.md +83 -82
  60. package/skills/lark-sheets/references/lark-sheets-batch-update.md +13 -58
  61. package/skills/lark-sheets/references/lark-sheets-chart.md +2 -1
  62. package/skills/lark-sheets/references/lark-sheets-conditional-format.md +1 -1
  63. package/skills/lark-sheets/references/lark-sheets-range-operations.md +5 -5
  64. package/skills/lark-sheets/references/lark-sheets-read-data.md +80 -6
  65. package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +21 -10
  66. package/skills/lark-sheets/references/lark-sheets-styles-put.md +93 -0
  67. package/skills/lark-sheets/references/lark-sheets-visual-standards.md +2 -2
  68. package/skills/lark-sheets/references/lark-sheets-workbook.md +4 -3
  69. package/skills/lark-sheets/references/lark-sheets-write-cells.md +40 -12
  70. package/skills/lark-sheets/scripts/lark_detect_subtables.py +593 -0
  71. package/skills/lark-sheets/scripts/lark_inspect_workbook.py +188 -0
  72. package/skills/lark-sheets/scripts/lark_profile_table.py +614 -0
  73. package/skills/lark-sheets/scripts/lark_sheet_range.py +176 -0
  74. package/skills/lark-sheets/scripts/lark_sheet_read_cli.py +184 -0
  75. package/skills/lark-sheets/scripts/sheets_df.py +21 -3
  76. package/skills/lark-slides/SKILL.md +11 -13
  77. package/skills/lark-slides/references/lark-slides-create.md +70 -39
  78. package/skills/lark-slides/references/lark-slides-edit-workflows.md +4 -7
  79. package/skills/lark-slides/references/lark-slides-update-slide.md +146 -0
  80. package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +26 -3
  81. package/skills/lark-slides/references/slides_chart_demo.xml +0 -1
  82. package/skills/lark-slides/references/troubleshooting.md +6 -6
  83. package/skills/lark-slides/references/validation-checklist.md +1 -1
  84. package/skills/lark-slides/references/xml-schema-quick-ref.md +0 -2
  85. package/skills/lark-whiteboard/SKILL.md +15 -8
  86. package/skills/lark-whiteboard/references/lark-whiteboard-export.md +4 -3
  87. package/skills/lark-whiteboard/references/lark-whiteboard-update.md +4 -4
  88. package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +19 -17
  89. package/skills/lark-whiteboard/routes/dsl.md +8 -2
  90. package/skills/lark-whiteboard/routes/mermaid.md +1 -1
  91. package/skills/lark-whiteboard/routes/svg-edit.md +5 -2
  92. package/skills/lark-whiteboard/routes/svg.md +3 -1
  93. package/skills/lark-whiteboard/scenes/mention.md +71 -0
  94. package/skills/lark-doc/references/lark-doc-word-stat.md +0 -93
  95. package/skills/lark-doc/references/style/lark-doc-create-workflow.md +0 -47
  96. package/skills/lark-doc/references/style/lark-doc-style.md +0 -68
  97. package/skills/lark-doc/references/style/lark-doc-update-workflow.md +0 -48
  98. package/skills/lark-doc/scripts/doc_word_stat.py +0 -1243
  99. package/skills/lark-slides/references/lark-slides-replace-pages.md +0 -97
@@ -0,0 +1,176 @@
1
+ # Copyright (c) 2026 Lark Technologies Pte. Ltd.
2
+ # SPDX-License-Identifier: MIT
3
+ """Range and coordinate helpers for Lark Sheet scripts."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import re
8
+ from dataclasses import dataclass
9
+
10
+ CELL_RE = re.compile(r"^\$?([A-Za-z]+)\$?([1-9][0-9]*)$")
11
+ ROW_RE = re.compile(r"^\$?([1-9][0-9]*)$")
12
+ COLUMN_RE = re.compile(r"^\$?([A-Za-z]+)$")
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class RangeBounds:
17
+ start_row: int
18
+ start_col: int
19
+ end_row: int
20
+ end_col: int
21
+
22
+ @property
23
+ def row_count(self) -> int:
24
+ return self.end_row - self.start_row + 1
25
+
26
+ @property
27
+ def col_count(self) -> int:
28
+ return self.end_col - self.start_col + 1
29
+
30
+
31
+ def col_to_index(col: str) -> int:
32
+ value = 0
33
+ for char in col.strip().upper():
34
+ if not ("A" <= char <= "Z"):
35
+ raise ValueError(f"Invalid column: {col}")
36
+ value = value * 26 + (ord(char) - ord("A") + 1)
37
+ if value <= 0:
38
+ raise ValueError(f"Invalid column: {col}")
39
+ return value
40
+
41
+
42
+ def index_to_col(index: int) -> str:
43
+ if index < 1:
44
+ raise ValueError(f"Column index must be >= 1: {index}")
45
+ chars = []
46
+ n = index
47
+ while n:
48
+ n, rem = divmod(n - 1, 26)
49
+ chars.append(chr(ord("A") + rem))
50
+ return "".join(reversed(chars))
51
+
52
+
53
+ def parse_cell(cell_ref: str) -> tuple[int, int]:
54
+ match = CELL_RE.match(cell_ref.strip())
55
+ if not match:
56
+ raise ValueError(f"Invalid cell reference: {cell_ref}")
57
+ col, row = match.groups()
58
+ return int(row), col_to_index(col)
59
+
60
+
61
+ def _parse_endpoint(endpoint: str) -> tuple[str, int, int] | tuple[str, int]:
62
+ cell = CELL_RE.match(endpoint)
63
+ if cell:
64
+ col, row = cell.groups()
65
+ return "cell", int(row), col_to_index(col)
66
+ row = ROW_RE.match(endpoint)
67
+ if row:
68
+ return "row", int(row.group(1))
69
+ column = COLUMN_RE.match(endpoint)
70
+ if column:
71
+ return "column", col_to_index(column.group(1))
72
+ raise ValueError(f"Invalid A1 range endpoint: {endpoint}")
73
+
74
+
75
+ def parse_range(
76
+ range_ref: str,
77
+ *,
78
+ max_row: int | None = None,
79
+ max_col: int | None = None,
80
+ ) -> RangeBounds:
81
+ """Parse the A1 range forms accepted by ``+csv-get``.
82
+
83
+ Open-ended forms need the caller's actual returned grid dimensions. This
84
+ keeps generated ranges finite without guessing a spreadsheet-wide limit.
85
+ """
86
+ ref = range_ref.strip()
87
+ if "!" in ref:
88
+ _, ref = ref.rsplit("!", 1)
89
+ if not ref:
90
+ raise ValueError(f"Invalid A1 range: {range_ref}")
91
+ parts = ref.split(":")
92
+ if len(parts) > 2:
93
+ raise ValueError(f"Invalid A1 range: {range_ref}")
94
+ start = _parse_endpoint(parts[0])
95
+ end = _parse_endpoint(parts[-1])
96
+
97
+ if len(parts) == 1:
98
+ if start[0] != "cell":
99
+ raise ValueError(f"A1 range must include a cell or ':' separator: {range_ref}")
100
+ _, row, col = start
101
+ return RangeBounds(row, col, row, col)
102
+
103
+ if start[0] == end[0] == "cell":
104
+ _, start_row, start_col = start
105
+ _, end_row, end_col = end
106
+ elif start[0] == end[0] == "row":
107
+ _, start_row = start
108
+ _, end_row = end
109
+ start_col = 1
110
+ if max_col is None:
111
+ raise ValueError(f"Range needs a maximum column: {range_ref}")
112
+ end_col = max_col
113
+ elif start[0] == end[0] == "column":
114
+ _, start_col = start
115
+ _, end_col = end
116
+ start_row = 1
117
+ if max_row is None:
118
+ raise ValueError(f"Range needs a maximum row: {range_ref}")
119
+ end_row = max_row
120
+ elif start[0] == "cell" and end[0] == "column":
121
+ _, start_row, start_col = start
122
+ _, end_col = end
123
+ if max_row is None:
124
+ raise ValueError(f"Range needs a maximum row: {range_ref}")
125
+ end_row = max(start_row, max_row)
126
+ elif start[0] == "cell" and end[0] == "row":
127
+ _, start_row, start_col = start
128
+ _, end_row = end
129
+ if max_col is None:
130
+ raise ValueError(f"Range needs a maximum column: {range_ref}")
131
+ end_col = max(start_col, max_col)
132
+ else:
133
+ raise ValueError(f"Invalid A1 range: {range_ref}")
134
+ return RangeBounds(min(start_row, end_row), min(start_col, end_col), max(start_row, end_row), max(start_col, end_col))
135
+
136
+
137
+ def format_cell(row: int, col: int) -> str:
138
+ if row < 1:
139
+ raise ValueError(f"Row must be >= 1: {row}")
140
+ return f"{index_to_col(col)}{row}"
141
+
142
+
143
+ def format_range(start_row: int, start_col: int, end_row: int, end_col: int) -> str:
144
+ bounds = RangeBounds(
145
+ min(start_row, end_row),
146
+ min(start_col, end_col),
147
+ max(start_row, end_row),
148
+ max(start_col, end_col),
149
+ )
150
+ start = format_cell(bounds.start_row, bounds.start_col)
151
+ end = format_cell(bounds.end_row, bounds.end_col)
152
+ return start if start == end else f"{start}:{end}"
153
+
154
+
155
+ def iter_cells(bounds: RangeBounds):
156
+ for row in range(bounds.start_row, bounds.end_row + 1):
157
+ for col in range(bounds.start_col, bounds.end_col + 1):
158
+ yield row, col
159
+
160
+
161
+ def ranges_intersect(a: RangeBounds, b: RangeBounds) -> bool:
162
+ return not (
163
+ a.end_row < b.start_row
164
+ or b.end_row < a.start_row
165
+ or a.end_col < b.start_col
166
+ or b.end_col < a.start_col
167
+ )
168
+
169
+
170
+ def range_union(a: RangeBounds, b: RangeBounds) -> RangeBounds:
171
+ return RangeBounds(
172
+ min(a.start_row, b.start_row),
173
+ min(a.start_col, b.start_col),
174
+ max(a.end_row, b.end_row),
175
+ max(a.end_col, b.end_col),
176
+ )
@@ -0,0 +1,184 @@
1
+ # Copyright (c) 2026 Lark Technologies Pte. Ltd.
2
+ # SPDX-License-Identifier: MIT
3
+ """Read-only Lark Sheet subset wrapper for the helper scripts."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ import subprocess
9
+ import sys
10
+ from typing import Any
11
+
12
+
13
+ class LarkCliError(RuntimeError):
14
+ def __init__(self, message: str, *, cmd: list[str] | None = None):
15
+ super().__init__(message)
16
+ self.cmd = cmd or []
17
+
18
+
19
+ def add_spreadsheet_args(
20
+ parser,
21
+ *,
22
+ require_sheet: bool = False,
23
+ allow_sheet: bool = True,
24
+ ) -> None:
25
+ spreadsheet = parser.add_mutually_exclusive_group(required=True)
26
+ spreadsheet.add_argument("--url")
27
+ spreadsheet.add_argument("--spreadsheet-token")
28
+ if allow_sheet:
29
+ sheet = parser.add_mutually_exclusive_group(required=require_sheet)
30
+ sheet.add_argument("--sheet-id")
31
+ sheet.add_argument("--sheet-name")
32
+
33
+
34
+ def _append_flag(cmd: list[str], name: str, value: Any) -> None:
35
+ flag = f"--{name.replace('_', '-')}"
36
+ if value is None:
37
+ return
38
+ if isinstance(value, bool):
39
+ cmd.append(flag if value else f"{flag}=false")
40
+ return
41
+ cmd.extend([flag, str(value)])
42
+
43
+
44
+ def run_sheets(
45
+ shortcut: str,
46
+ *,
47
+ url: str | None = None,
48
+ spreadsheet_token: str | None = None,
49
+ sheet_id: str | None = None,
50
+ sheet_name: str | None = None,
51
+ flags: dict[str, Any] | None = None,
52
+ timeout: int = 60,
53
+ ) -> dict[str, Any]:
54
+ if bool(url) == bool(spreadsheet_token):
55
+ raise LarkCliError("Pass exactly one of --url or --spreadsheet-token")
56
+ if sheet_id and sheet_name:
57
+ raise LarkCliError("Pass only one of --sheet-id or --sheet-name")
58
+
59
+ cmd = ["lark-cli", "sheets", shortcut]
60
+ _append_flag(cmd, "url", url)
61
+ _append_flag(cmd, "spreadsheet_token", spreadsheet_token)
62
+ _append_flag(cmd, "sheet_id", sheet_id)
63
+ _append_flag(cmd, "sheet_name", sheet_name)
64
+ for key, value in (flags or {}).items():
65
+ _append_flag(cmd, key, value)
66
+
67
+ try:
68
+ completed = subprocess.run(
69
+ cmd,
70
+ capture_output=True,
71
+ text=True,
72
+ timeout=timeout,
73
+ check=False,
74
+ )
75
+ except FileNotFoundError as exc:
76
+ raise LarkCliError("lark-cli not found", cmd=cmd) from exc
77
+ except subprocess.TimeoutExpired as exc:
78
+ raise LarkCliError(f"lark-cli timed out after {timeout}s", cmd=cmd) from exc
79
+
80
+ if completed.returncode != 0:
81
+ detail = (completed.stderr or completed.stdout or "").strip()
82
+ raise LarkCliError(detail or f"lark-cli exited with {completed.returncode}", cmd=cmd)
83
+
84
+ try:
85
+ envelope = json.loads(completed.stdout)
86
+ except json.JSONDecodeError as exc:
87
+ snippet = completed.stdout[:500].replace("\n", "\\n")
88
+ raise LarkCliError(f"lark-cli stdout was not JSON: {snippet}", cmd=cmd) from exc
89
+
90
+ if isinstance(envelope, dict) and envelope.get("ok") is False:
91
+ raise LarkCliError(json.dumps(envelope, ensure_ascii=False), cmd=cmd)
92
+ if not isinstance(envelope, dict):
93
+ raise LarkCliError("lark-cli returned a non-object JSON payload", cmd=cmd)
94
+ return envelope
95
+
96
+
97
+ def envelope_data(envelope: dict[str, Any]) -> dict[str, Any]:
98
+ data = envelope.get("data")
99
+ return data if isinstance(data, dict) else envelope
100
+
101
+
102
+ def emit_success(action: str, data: dict[str, Any], warnings: list[str] | None = None) -> None:
103
+ print(
104
+ json.dumps(
105
+ {
106
+ "ok": True,
107
+ "engine": "lark",
108
+ "action": action,
109
+ "data": data,
110
+ "warnings": warnings or [],
111
+ },
112
+ ensure_ascii=False,
113
+ indent=2,
114
+ )
115
+ )
116
+
117
+
118
+ def emit_error(action: str, message: str, warnings: list[str] | None = None) -> None:
119
+ print(
120
+ json.dumps(
121
+ {
122
+ "ok": False,
123
+ "engine": "lark",
124
+ "action": action,
125
+ "error": message,
126
+ "warnings": warnings or [],
127
+ },
128
+ ensure_ascii=False,
129
+ indent=2,
130
+ )
131
+ )
132
+ sys.exit(1)
133
+
134
+
135
+ def sheet_title(sheet: dict[str, Any]) -> str:
136
+ return str(sheet.get("title") or sheet.get("sheet_name") or sheet.get("name") or "")
137
+
138
+
139
+ def sheet_identifier(sheet: dict[str, Any]) -> str:
140
+ return str(sheet.get("sheet_id") or sheet.get("id") or "")
141
+
142
+
143
+ def sheet_locator(sheet: dict[str, Any]) -> dict[str, str]:
144
+ sid = sheet_identifier(sheet)
145
+ if sid:
146
+ return {"sheet_id": sid}
147
+ title = sheet_title(sheet)
148
+ if title:
149
+ return {"sheet_name": title}
150
+ return {}
151
+
152
+
153
+ def extract_sheets(workbook_data: dict[str, Any]) -> list[dict[str, Any]]:
154
+ sheets = workbook_data.get("sheets")
155
+ if isinstance(sheets, list):
156
+ return [sheet for sheet in sheets if isinstance(sheet, dict)]
157
+ workbook = workbook_data.get("workbook")
158
+ if isinstance(workbook, dict) and isinstance(workbook.get("sheets"), list):
159
+ return [sheet for sheet in workbook["sheets"] if isinstance(sheet, dict)]
160
+ return []
161
+
162
+
163
+ def resolve_target_sheets(
164
+ workbook_data: dict[str, Any],
165
+ *,
166
+ sheet_id: str | None = None,
167
+ sheet_name: str | None = None,
168
+ require_one: bool = False,
169
+ ) -> list[dict[str, Any]]:
170
+ sheets = extract_sheets(workbook_data)
171
+ if sheet_id:
172
+ matches = [sheet for sheet in sheets if sheet_identifier(sheet) == sheet_id]
173
+ elif sheet_name:
174
+ matches = [sheet for sheet in sheets if sheet_title(sheet) == sheet_name]
175
+ else:
176
+ matches = sheets
177
+
178
+ if require_one:
179
+ if len(matches) == 1:
180
+ return matches
181
+ if not matches:
182
+ raise LarkCliError("No matching sheet found")
183
+ raise LarkCliError("Multiple sheets matched; pass --sheet-id or --sheet-name")
184
+ return matches
@@ -19,11 +19,29 @@ import pandas as pd
19
19
 
20
20
  def df_to_sheet(df, name, formats=None):
21
21
  """Pack one DataFrame into one entry of a `+table-put --sheets` payload."""
22
+ packed = json.loads(df.to_json(orient="split", date_format="iso"))
23
+ # The protocol requires string column names. pandas keeps integer labels
24
+ # (e.g. the default RangeIndex columns 0/1/2) as JSON numbers, while the
25
+ # dtypes dict keys get stringified during JSON serialization — the CLI
26
+ # then rejects `columns` ("cannot unmarshal number into … type string")
27
+ # and the dtype lookup would miss anyway. Stringify every key once, and
28
+ # refuse to continue when that conversion silently merges two columns.
29
+ normalized_labels = [str(c) for c in df.columns]
30
+ columns = [str(c) for c in packed["columns"]]
31
+ if normalized_labels != columns:
32
+ columns = normalized_labels
33
+ if len(set(columns)) != len(columns):
34
+ raise ValueError(
35
+ "column labels collide after str() conversion; "
36
+ "rename the DataFrame columns before packing"
37
+ )
38
+ packed["columns"] = columns
39
+ dtype_values = list(df.dtypes)
22
40
  return {
23
41
  "name": name,
24
- **json.loads(df.to_json(orient="split", date_format="iso")),
25
- "dtypes": df.dtypes.astype(str).to_dict(),
26
- **({"formats": formats} if formats else {}),
42
+ **packed,
43
+ "dtypes": {key: str(dtype) for key, dtype in zip(columns, dtype_values)},
44
+ **({"formats": {str(k): v for k, v in formats.items()}} if formats else {}),
27
45
  }
28
46
 
29
47
 
@@ -79,10 +79,10 @@ metadata:
79
79
 
80
80
  | 用户需求 | 优先动作 | 关键文档 / 命令 |
81
81
  |----------|----------|-----------------|
82
- | 新建 PPT | 先规划 `slide_plan.json`,再按复杂度选择一步或两步创建 | `planning-layer.md`、`visual-planning.md`、`asset-planning.md`、`lark-slides-create.md`、`slides +create`、`slides +add-slide`、`lark-slides-add-slide.md`(两步创建逐页添加) |
82
+ | 新建 PPT | 先规划 `slide_plan.json`,再按页数选择一步或两步创建 | `planning-layer.md`、`visual-planning.md`、`asset-planning.md`、`lark-slides-create.md`、`slides +create`、`slides +add-slide`、`lark-slides-add-slide.md`(两步创建逐页添加) |
83
83
  | 用户要求使用模板,或提供 PPTX 文件要求修改、美化 | 将模板导入为 Slides 再编辑 | `lark-slides-pptx-template-workflows.md` |
84
- | 编辑单个标题、文本块、图片或局部元素 | 优先块级替换/插入,不改页序 | `slides +replace-slide`、`lark-slides-replace-slide.md` |
85
- | 一页改动很多、要改背景或删除若干元素,或要整页重建一页/多页 | 在原 presentation 内按页重建,不创建新 Slides 链接 | `slides +replace-pages`、`lark-slides-replace-pages.md` |
84
+ | 编辑单个标题、文本块、图片或局部元素 | 块级替换/插入,**只动点名的 block,同页其他元素不受影响**;不改页序 | `slides +replace-slide`、`lark-slides-replace-slide.md` |
85
+ | 一页改动很多(批量字体/配色)、要改页面背景、要删掉若干元素 | 整页覆盖,`slide_id` 和页序不变;带原 `id` 写回的元素保留 id,不带 `id` 的会作为新元素插入并拿到新 id;**代价是没写进 `--content` 的元素会被删除,所以改个别元素不要用它** | `slides +update-slide`、`lark-slides-update-slide.md` |
86
86
  | 给已有 PPT 追加或插入页面 | 一次一页,`--slide` 支持 `@file` 绕开 shell 转义 | `slides +add-slide`、`lark-slides-add-slide.md` |
87
87
  | 删除页面 | 按 `slide_id` 单页删除,删前先回读确认 | `slides +delete-slide`、`lark-slides-delete-slide.md` |
88
88
  | 读取或分析已有 PPT | 解析 slides/wiki token,用 shortcut 回读全文 XML 或读取单页 XML,保存 `xml_presentation_id`、`slide_id`、`revision_id` | `slides +xml-get`、`xml_presentation.slide.get`、`lark-slides-xml-presentations-get.md` |
@@ -106,15 +106,13 @@ metadata:
106
106
 
107
107
  **CRITICAL — 新建演示文稿或大幅改写页面时,规划 `asset_need` MUST 遵循 [asset-planning.md](references/asset-planning.md):只做元数据规划,必须有 `fallback_if_missing`,不得要求真实搜索、下载或上传素材。**
108
108
 
109
- **CRITICAL — 将完整 `<slide>` XML 提交给 `slides +create --slides`、`slides +add-slide`、`xml_presentation.slide create` 或 `slides +replace-pages` 之前,MUST 先把待提交 XML 保存到本地文件并运行唯一版式准出入口 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py);`summary.error_count` 必须为 0 才能调用接口。**
109
+ **CRITICAL — 将完整 `<slide>` XML 提交给 `slides +create`、`slides +add-slide` 或 `slides +update-slide` 之前,MUST 先把待提交 XML 保存到本地文件并运行唯一版式准出入口 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py);`summary.error_count` 必须为 0 才能调用接口。**
110
110
 
111
- **CRITICAL — 创建或大幅改写后,MUST 按 [validation-checklist.md](references/validation-checklist.md) 做显式验证:回读全文 XML、核对页数和关键元素,并使用 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py) 统一检查 XML、越界、重叠、空白页和内容稀疏风险。**
111
+ **CRITICAL — 创建、大幅改写或每次通过 `slides +update-slide` 整页写回后,MUST 按 [validation-checklist.md](references/validation-checklist.md) 做显式验证:回读全文 XML、核对页数和关键元素,并使用 [`scripts/xml_text_overlap_lint.py`](scripts/xml_text_overlap_lint.py) 统一检查 XML、越界、重叠、空白页和内容稀疏风险。**
112
112
 
113
113
  **CRITICAL — 创建前自检或失败排障时,MUST 按 [troubleshooting.md](references/troubleshooting.md) 检查 XML 转义、结构、shell 截断、图片 token、3350001 和布局风险。**
114
114
 
115
- **CRITICAL — 当前 Slide Engine 后端仍在灰度期间,`slides +replace-pages --help` 及其输出中的 `deprecated` 字段会提示改用 `+update-slide`,该提示在本 skill 生效期间暂不适用;MUST 忽略该提示并继续使用 `+replace-pages`,不得切换到 `+update-slide`。后端全量并由新版 skill 恢复路由后,再使用 `+update-slide`。**
116
-
117
- **编辑已有幻灯片页面**:单个标题、文本块、图片或局部元素优先用 [`+replace-slide`](references/lark-slides-replace-slide.md)(块级替换/插入,不动页序);一页改动很多、要改背景或删除若干元素,以及一页或多页的整页重建,优先用 [`+replace-pages`](references/lark-slides-replace-pages.md) 在原 presentation 内按页重建,避免 `slides +create` 生成新链接。选择 action 和完整读-改-写流程见 [`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)。
115
+ **编辑已有幻灯片页面**:单个标题、文本块、图片或局部元素优先用 [`+replace-slide`](references/lark-slides-replace-slide.md)(块级替换/插入,不动页序);一页里改动很多(例如批量换字体)、要改背景、或要删掉若干元素时用 [`+update-slide`](references/lark-slides-update-slide.md) 整页覆盖(`slide_id` 和页序不变,但没写进 `--content` 的元素会被删除);**多页大改就对每一页各跑一次 `+update-slide`**。选择 action 和完整读-改-写流程见 [`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)。
118
116
 
119
117
  **用户要求使用模板**:按 [lark-slides-pptx-template-workflows.md](references/lark-slides-pptx-template-workflows.md) 处理。
120
118
 
@@ -153,7 +151,7 @@ lark-cli auth login --domain slides
153
151
  - 创建:[`lark-slides-create.md`](references/lark-slides-create.md)、[`lark-slides-add-slide.md`](references/lark-slides-add-slide.md)(逐页添加 / 给已有 PPT 追加页面)
154
152
  - 删除页面:[`lark-slides-delete-slide.md`](references/lark-slides-delete-slide.md)
155
153
  - 阅读:[`lark-slides-xml-presentations-get.md`](references/lark-slides-xml-presentations-get.md)
156
- - 编辑:[`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)、[`lark-slides-replace-slide.md`](references/lark-slides-replace-slide.md)、[`lark-slides-replace-pages.md`](references/lark-slides-replace-pages.md)
154
+ - 编辑:[`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)、[`lark-slides-replace-slide.md`](references/lark-slides-replace-slide.md)、[`lark-slides-update-slide.md`](references/lark-slides-update-slide.md)
157
155
  - 历史版本:[`lark-slides-history.md`](references/lark-slides-history.md)
158
156
  - 截图:[`lark-slides-screenshot.md`](references/lark-slides-screenshot.md)
159
157
  - 图片:[`lark-slides-media-upload.md`](references/lark-slides-media-upload.md)
@@ -249,7 +247,7 @@ N. 结尾页:[结尾文案]
249
247
  | URL 格式 | 示例 | Token 类型 | 处理方式 |
250
248
  |----------|------|-----------|----------|
251
249
  | `/slides/` | `https://example.larkoffice.com/slides/xxxxxxxxxxxxx` | `xml_presentation_id` | URL 路径中的 token 直接作为 `xml_presentation_id` 使用 |
252
- | `/wiki/` | `https://example.larkoffice.com/wiki/wikcnxxxxxxxxx` | `wiki_token` | ⚠️ **不能直接使用**,需要先查询获取真实的 `obj_token` |
250
+ | `/wiki/` | `https://xxx.feishu.cn/wiki/wikcn_EXAMPLE_NODE_TOKEN_123456` | `wiki_token` | ⚠️ **不能直接使用**,需要先查询获取真实的 `obj_token` |
253
251
 
254
252
  > 带 `--presentation` 的 slides shortcut 都会自动解析以上两种 URL;直接调用原生 API 时仍需手动解析 wiki 链接。
255
253
 
@@ -258,7 +256,7 @@ N. 结尾页:[结尾文案]
258
256
  知识库链接(`/wiki/TOKEN`)不能直接当 `xml_presentation_id`。直接调用原生 API 前,先用 Wiki shortcut 查询节点,确认 `data.obj_type == "slides"`,再用 `data.obj_token` 作为真实 presentation ID。
259
257
 
260
258
  ```bash
261
- lark-cli wiki +node-get --node-token '<wiki_url>' --as user --format json
259
+ lark-cli wiki +node-get --node-token 'https://xxx.feishu.cn/wiki/wikcn_EXAMPLE_NODE_TOKEN_123456' --as user --format json
262
260
  ```
263
261
 
264
262
  节点解析必须与后续 Slides 操作使用相同身份;下游明确使用 `--as bot` 时,这里也改为 `--as bot`。
@@ -292,7 +290,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli slides +<verb> [flags]`
292
290
  | [`+screenshot`](references/lark-slides-screenshot.md) | 把幻灯片页面截图保存为本地图片;用 `--slide-number` 指定页码(从 1 开始,多页重复传入)或用 `--slide-id` 指定页面;单张用 `--output .lark-slides/screenshots/<deck-or-task-id>/page-01`,批量用 `--output-dir .lark-slides/screenshots/<deck-or-task-id>`(一次最多 10 页);后续必须读取返回的 `output` / `screenshots[].path` |
293
291
  | [`+media-upload`](references/lark-slides-media-upload.md) | 上传本地图片到指定演示文稿,返回 `file_token`(用作 `<img src="...">`),最大 20 MB |
294
292
  | [`+replace-slide`](references/lark-slides-replace-slide.md) | 对已有幻灯片页面进行块级替换/插入(`block_replace` / `block_insert`),自动注入 id 和 `<content/>`,不改变页序 |
295
- | [`+replace-pages`](references/lark-slides-replace-pages.md) | 在原演示文稿内重建一页或多页:先创建新页到旧页前,再删除旧页;适合已有 Slides 的整页大改,不新建链接 |
293
+ | [`+update-slide`](references/lark-slides-update-slide.md) | 把一整页 XML 交给已有页面,页面变成 `--content` 描述的样子;能一次改样式/插入/删除/备注/背景,`slide_id` 和页序不变。**没写进 `--content` 的元素会被删除** |
296
294
 
297
295
  没有 Shortcut 覆盖时使用原生 API。高频资源:`slides +xml-get` 读取全文;`xml_presentation.slide.create/delete/get/replace` 管理单页。
298
296
 
@@ -311,7 +309,7 @@ lark-cli slides <resource> <method> [flags] # 调用 API
311
309
  4. **文本通过 `<content>` 表达**:必须用 `<content><p>...</p></content>`,不能把文字直接写在 shape 内;注意 `<content>` 只是 XML 元素,不是 `--parts` 的字段名——part 里装 XML 的字段,`block_replace` 是 `replacement`,`block_insert` 是 `insertion`
312
310
  5. **保存关键 ID**:后续操作需要 `xml_presentation_id`、`slide_id`、`revision_id`
313
311
  6. **删除谨慎**:删除不可逆,删前先回读确认 `slide_id`
314
- 7. **编辑已有页面优先原链接更新**:修改单个 shape/img 用 `+replace-slide`(`block_replace` / `block_insert`),不要整页重建;一页改动很多、要改背景或删除若干元素,以及一页或多页的整页重建,用 `+replace-pages`,不要用 `slides +create` 新建整份 PPT;追加/插入单页用 `+add-slide`、删除单页用 `+delete-slide`,只有这些 shortcut 未覆盖的参数才手动调 `slide.create` / `slide.delete`
312
+ 7. **编辑已有页面优先原链接更新**:修改单个 shape/img 用 `+replace-slide`(`block_replace` / `block_insert`),不要整页重建;一页改动很多或要改背景用 `+update-slide` 整页覆盖(保 `slide_id` 和页序),多页整页重建就对每页各跑一次 `+update-slide`,不要用 `slides +create` 新建整份 PPT;追加/插入单页用 `+add-slide`、删除单页用 `+delete-slide`,只有这些 shortcut 未覆盖的参数才手动调 `slide.create` / `slide.delete`
315
313
  8. **`<img src>` 只能用上传到飞书 drive 的 `file_token`,禁止使用 http(s) 外链 URL**:飞书 slides 渲染端不会代理外链图片,外链 src 在 PPT 里通常不显示或显示破图。流程必须是「先把图存到本地 → 用 `slides +media-upload` 上传,或在 `+create --slides` 的 XML 里写 `<img src="@./path">` 占位符自动上传 → 拿 `file_token` 写进 `<img src>`」。如果用户给了网图链接,先 `curl`/下载到 CWD 内再走上传流程,不要直接把外链 URL 塞进 `src`。**图片最大 20 MB**(slides upload API 不支持分片上传)。
316
314
 
317
315
  > **注意**:如果 md 内容与 `slides_xml_schema_definition.xml` 或 `lark-cli schema slides.<resource>.<method>` 输出不一致,以后两者为准。
@@ -11,14 +11,14 @@
11
11
 
12
12
  | 场景 | 推荐方式 |
13
13
  |------|----------|
14
- | 简单 XML(1-3 页、结构简单、几乎无复杂中文和特殊字符) | `slides +create --slides '[...]'` 一步创建 |
15
- | 复杂 XML(多页、含中文、大段文本、复杂布局、嵌套引号、特殊字符较多) | **两步创建**:先 `slides +create` 创建空白 PPT,再用 [`+add-slide`](lark-slides-add-slide.md) 逐页添加(`--slide @file` 可绕开 shell 转义) |
14
+ | 不超过 10 页 | 每页存一个 XML 文件,`slides +create --slide @page-01.xml --slide @page-02.xml ...` 一步创建 |
15
+ | 超过 10 页 | **两步创建**:先 `slides +create` 创建空白 PPT,再用 [`+add-slide`](lark-slides-add-slide.md) 逐页添加 |
16
16
  | 已有 PPT 继续追加或插入页面 | 使用 [`+add-slide`](lark-slides-add-slide.md),必要时配合 `--before-slide-id` |
17
17
 
18
- > [!WARNING]
19
- > `--slides '[...]'` 的风险点主要在 shell 参数传递,而不是单纯页数。即使只有 1 页,只要 XML 足够复杂,也建议使用两步创建法。
20
18
  > [!IMPORTANT]
21
- > `slides +create --slides` 底层会逐页创建,不是原子操作。中途失败时先记录 `xml_presentation_id`,回读确认当前状态,再继续修复或追加。
19
+ > `slides +create` 带页面时底层会逐页创建,不是原子操作。中途失败时先记录 `xml_presentation_id`,回读确认当前状态,再继续修复或追加。
20
+
21
+ **CRITICAL — 提交前必须先跑版式 lint**:把待提交的 `<slide>` XML 存成本地文件,运行 [`scripts/xml_text_overlap_lint.py`](../scripts/xml_text_overlap_lint.py),`summary.error_count` 必须为 0。
22
22
 
23
23
  ## 命令
24
24
 
@@ -26,31 +26,22 @@
26
26
  # 创建空白 PPT
27
27
  lark-cli slides +create --title "项目汇报"
28
28
 
29
- # 创建 PPT + 添加 slide 页面
30
- lark-cli slides +create --title "项目汇报" --slides '[
31
- "<slide xmlns=\"https://www.larkoffice.com/sml/2.0\"><data><shape type=\"text\" topLeftX=\"80\" topLeftY=\"80\" width=\"800\" height=\"120\"><content textType=\"title\"><p>封面</p></content></shape></data></slide>",
32
- "<slide xmlns=\"https://www.larkoffice.com/sml/2.0\"><data><shape type=\"text\" topLeftX=\"80\" topLeftY=\"80\" width=\"800\" height=\"120\"><content textType=\"title\"><p>第二页</p></content></shape></data></slide>"
33
- ]'
29
+ # 创建 PPT + 添加页面:每页一个 XML 文件,重复 --slide,顺序即页序
30
+ lark-cli slides +create --as user --title "项目汇报" \
31
+ --slide @.lark-slides/plan/project/slide-01.xml \
32
+ --slide @.lark-slides/plan/project/slide-02.xml
33
+
34
+ # 已有组装好的 JSON 数组:从文件或 stdin 读
35
+ lark-cli slides +create --as user --title "项目汇报" --slides @./deck.json
36
+ cat deck.json | lark-cli slides +create --as user --title "项目汇报" --slides -
34
37
 
35
38
  # 以应用身份创建(自动授权当前用户)
36
39
  lark-cli slides +create --title "项目汇报" --as bot
37
40
 
38
41
  # 预览(不执行)
39
- lark-cli slides +create --title "项目汇报" --slides '[...]' --dry-run
40
- ```
41
-
42
- 用 `--slides` 一步创建时,按页保存 XML,再用 `jq --rawfile` 组装参数,不要手写转义:
43
-
44
- ```bash
45
- lark-cli slides +create --as user --title "项目汇报" \
46
- --slides "$(jq -n \
47
- --rawfile s1 .lark-slides/plan/project/slide-01.xml \
48
- --rawfile s2 .lark-slides/plan/project/slide-02.xml \
49
- '[$s1, $s2]')"
42
+ lark-cli slides +create --title "项目汇报" --slide @./slide-01.xml --dry-run
50
43
  ```
51
44
 
52
- `--rawfile` 会把文件内容作为字符串读入 JSON,自动处理 XML 中的引号和换行;不要手动拼接带大量转义符的 JSON 字符串。
53
-
54
45
  ## 返回值
55
46
 
56
47
  工具成功执行后,返回一个 JSON 对象,包含以下字段:
@@ -59,15 +50,15 @@ lark-cli slides +create --as user --title "项目汇报" \
59
50
  - **`title`**(string):演示文稿标题
60
51
  - **`url`**(string,可选):演示文稿的在线链接,如有返回则务必展示给用户(需要 drive 相关权限;若获取失败则不返回此字段)
61
52
  - **`revision_id`**(integer):演示文稿版本号
62
- - **`slide_ids`**(string[],可选):仅传 `--slides` 时返回,成功添加的页面 ID 列表
63
- - **`slides_added`**(integer,可选):仅传 `--slides` 时返回,成功添加的页面数量
64
- - **`images_uploaded`**(integer,可选):仅 `--slides` 中含 `@<本地路径>` 占位符时返回,已上传的去重后图片数量
53
+ - **`slide_ids`**(string[],可选):带页面创建时返回,成功添加的页面 ID 列表
54
+ - **`slides_added`**(integer,可选):带页面创建时返回,成功添加的页面数量
55
+ - **`images_uploaded`**(integer,可选):页面 XML 中含 `@<本地路径>` 占位符时返回,已上传的去重后图片数量
65
56
  - **`permission_grant`**(object,可选):仅 `--as bot` 时返回,说明是否已自动为当前 CLI 用户授予可管理权限
66
57
 
67
58
  > [!IMPORTANT]
68
- > 不传 `--slides` 时,`slides +create` 只创建空白演示文稿。创建后用 [`+add-slide`](lark-slides-add-slide.md) 逐页添加 slide 内容。
59
+ > 不带页面参数时,`slides +create` 只创建空白演示文稿。创建后用 [`+add-slide`](lark-slides-add-slide.md) 逐页添加 slide 内容。
69
60
  >
70
- > 传了 `--slides` 时,CLI 先创建空白演示文稿,再逐页调用 slide 创建接口添加页面。如果某一页添加失败,CLI 会停止并报错,已创建的演示文稿和已添加的页面会保留。
61
+ > 带了页面时,CLI 先创建空白演示文稿,再逐页调用 slide 创建接口添加页面。如果某一页添加失败,CLI 会停止并报错,已创建的演示文稿和已添加的页面会保留。
71
62
  >
72
63
  > 如果演示文稿是**以应用身份(bot)创建**的,如 `lark-cli slides +create --as bot`,CLI 会**尝试为当前 CLI 用户自动授予该演示文稿的 `full_access`(可管理权限)**。
73
64
  >
@@ -83,27 +74,67 @@ lark-cli slides +create --as user --title "项目汇报" \
83
74
  | 参数 | 必填 | 说明 |
84
75
  |------|------|------|
85
76
  | `--title` | 否 | 演示文稿标题(不传则默认 "Untitled") |
86
- | `--slides` | 否 | slide 内容 JSON 数组,每个元素是一个 `<slide>` XML 字符串(最多 10 个;超过 10 页请先用 `+create` 创建空白 PPT,再用 [`+add-slide`](lark-slides-add-slide.md) 逐页添加) |
77
+ | `--slide` | 否 | 一页 `<slide>` XML,或 `@路径`;可重复,最多 10 次。格式见[页面输入形式](#页面输入形式) |
78
+ | `--slides` | 否 | 页面 XML 的 JSON 字符串数组,最多 10 个;支持 `@文件` 和 `-`(stdin)。格式见[页面输入形式](#页面输入形式) |
79
+
80
+ 10 页是 CLI 的上限,服务端每次只接收一页。超过 10 页时先用 `+create` 创建空白 PPT,再用 [`+add-slide`](lark-slides-add-slide.md) 逐页添加。
81
+
82
+ 两种形式的每一页都会在发请求前校验成「单个完整的 `<slide>` 文档」。不合格的页在创建演示文稿之前报错并指出页序号,不会留下空壳演示文稿。
83
+
84
+ ## 页面输入形式
85
+
86
+ 页面内容有 `--slide` 和 `--slides` 两种传法,二选一,同时传会报错。
87
87
 
88
- ## `--slides` 参数格式
88
+ 两种形式的 `@路径` 都必须是 CWD 内的相对路径(如 `./slide-01.xml`);绝对路径和 `../` 会被拒(报 `invalid file path`)。XML 写在别的目录时,先 `cd` 过去或把文件拷进 CWD 再执行。
89
+
90
+ ### `--slide`:一页一个文件
91
+
92
+ 可重复,重复次数即页数,出现顺序即页序。值是一页完整的 `<slide>` XML,或读取该 XML 的 `@路径`。
93
+
94
+ 文件内容就是这一页 XML 本身,外面没有引号或方括号:
95
+
96
+ ```xml
97
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
98
+ <data>…第1页…</data>
99
+ </slide>
100
+ ```
101
+
102
+ 文件内容不需要转义:引号、换行、中文原样写。
103
+
104
+ ### `--slides`:一个 JSON 数组
105
+
106
+ 值是 JSON 字符串数组,每个元素是一整页 XML,支持 `@文件` 和 `-`(stdin)。
107
+
108
+ 文件内容是一个 JSON 文档,XML 以 JSON 字符串出现,其中的 `"` 写作 `\"`,换行写作 `\n`:
89
109
 
90
110
  ```json
91
111
  [
92
- "<slide xmlns=\"https://www.larkoffice.com/sml/2.0\">...第1页XML...</slide>",
93
- "<slide xmlns=\"https://www.larkoffice.com/sml/2.0\">...第2页XML...</slide>"
112
+ "<slide xmlns=\"https://www.larkoffice.com/sml/2.0\"><data>…第1页…</data></slide>",
113
+ "<slide xmlns=\"https://www.larkoffice.com/sml/2.0\"><data>…第2页…</data></slide>"
94
114
  ]
95
115
  ```
96
116
 
97
- JSON string 数组,每个元素是一页 slide 的完整 XML。CLI 内部负责包装成 API 所需的 `{"slide": {"content": "..."}}` 格式并逐页调用。
117
+ 数组元素是页面 XML 原文。包装成 API 所需的 `{"slide": {"content": …}}` 并逐页调用由 CLI 完成。
118
+
119
+ > [!WARNING]
120
+ > `--slides '[...]'` 的风险点主要在 shell 参数传递,而不是单纯页数。即使只有 1 页,只要 XML 足够复杂,也建议改用 `--slide @page-01.xml` 逐页传文件。
98
121
 
99
- ### 本地图片:`@<path>` 占位符
122
+ ## 本地图片:`@<path>` 占位符
100
123
 
101
124
  `<img>` 元素的 `src` 属性如果以 `@` 开头,CLI 会把它当作本地文件路径,自动上传到当前演示文稿,并把占位符替换为返回的 `file_token`。
102
125
 
126
+ `slide-01.xml`:
127
+
128
+ ```xml
129
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
130
+ <data>
131
+ <img src="@./assets/chart.png" topLeftX="100" topLeftY="100" width="320" height="180"/>
132
+ </data>
133
+ </slide>
134
+ ```
135
+
103
136
  ```bash
104
- lark-cli slides +create --as user --title "图测试" --slides '[
105
- "<slide xmlns=\"https://www.larkoffice.com/sml/2.0\"><data><img src=\"@./assets/chart.png\" topLeftX=\"100\" topLeftY=\"100\" width=\"320\" height=\"180\"/></data></slide>"
106
- ]'
137
+ lark-cli slides +create --as user --title "图测试" --slide @./slide-01.xml
107
138
  ```
108
139
 
109
140
  行为:
@@ -120,11 +151,11 @@ lark-cli slides +create --as user --title "图测试" --slides '[
120
151
 
121
152
  ## 创建后续步骤
122
153
 
123
- 如果没有使用 `--slides`,`slides +create` 返回的 `xml_presentation_id` 用于后续操作:
154
+ 创建空白 PPT 时,`slides +create` 返回的 `xml_presentation_id` 用于后续操作:
124
155
 
125
156
  ```bash
126
157
  # 第 1 步:创建空白 PPT
127
- PRES_ID=$(lark-cli slides +create --title "项目汇报" | jq -r '.data.xml_presentation_id')
158
+ PRES_ID=$(lark-cli slides +create --title "项目汇报" --jq '.data.xml_presentation_id')
128
159
 
129
160
  # 第 2 步:逐页添加(--slide 支持 @file,复杂 XML 优先走文件)
130
161
  lark-cli slides +add-slide --as user \