@amaster.ai/pi-lark 0.1.7 → 0.1.9

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 (215) hide show
  1. package/package.json +2 -2
  2. package/skills/lark-apps/SKILL.md +41 -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-db.md +130 -2
  6. package/skills/lark-apps/references/lark-apps-get.md +1 -1
  7. package/skills/lark-apps/references/lark-apps-list.md +1 -1
  8. package/skills/lark-apps/references/lark-apps-local-dev.md +27 -1
  9. package/skills/lark-apps/references/lark-apps-release-create.md +1 -1
  10. package/skills/lark-apps/references/lark-apps-user-id-convert.md +63 -0
  11. package/skills/lark-base/SKILL.md +155 -159
  12. package/skills/lark-base/references/{lark-base-role-guide.md → lark-base-advanced-permission-and-role.md} +5 -5
  13. package/skills/lark-base/references/lark-base-app-block-data-config.md +122 -0
  14. package/skills/lark-base/references/lark-base-app.md +225 -0
  15. package/skills/lark-base/references/lark-base-cell-value.md +26 -19
  16. package/skills/lark-base/references/{dashboard-block-data-config.md → lark-base-dashboard-block-config.md} +37 -5
  17. package/skills/lark-base/references/lark-base-dashboard-block-get-data.md +18 -2
  18. package/skills/lark-base/references/lark-base-dashboard.md +25 -12
  19. package/skills/lark-base/references/lark-base-data-analysis-pandas.md +93 -0
  20. package/skills/lark-base/references/lark-base-data-analysis-python-stdlib.md +120 -0
  21. package/skills/lark-base/references/lark-base-data-query.md +8 -11
  22. package/skills/lark-base/references/lark-base-field-create.md +13 -45
  23. package/skills/lark-base/references/{formula-field-guide.md → lark-base-field-formula.md} +1 -1
  24. package/skills/lark-base/references/{lookup-field-guide.md → lark-base-field-lookup.md} +1 -1
  25. package/skills/lark-base/references/{lark-base-field-json.md → lark-base-field-schema.md} +18 -100
  26. package/skills/lark-base/references/lark-base-field-update.md +13 -51
  27. package/skills/lark-base/references/lark-base-filter-condition.md +19 -31
  28. package/skills/lark-base/references/lark-base-record-batch-create.md +5 -1
  29. package/skills/lark-base/references/lark-base-record-batch-update.md +5 -2
  30. package/skills/lark-base/references/lark-base-record-query-and-analysis-cloud-sop.md +145 -0
  31. package/skills/lark-base/references/lark-base-record-query-and-analysis-sop.md +233 -0
  32. package/skills/lark-base/references/{role-config.md → lark-base-role-config.md} +2 -2
  33. package/skills/lark-base/references/lark-base-view-set-filter.md +1 -1
  34. package/skills/lark-base/references/lark-base-workflow-schema.md +2 -2
  35. package/skills/lark-base/references/{lark-base-workflow-guide.md → lark-base-workflow.md} +1 -1
  36. package/skills/lark-calendar/SKILL.md +3 -1
  37. package/skills/lark-calendar/references/lark-calendar-create.md +4 -3
  38. package/skills/lark-doc/SKILL.md +26 -61
  39. package/skills/lark-doc/references/genres/business-analysis.md +30 -0
  40. package/skills/lark-doc/references/genres/data-report.md +32 -0
  41. package/skills/lark-doc/references/genres/email.md +38 -0
  42. package/skills/lark-doc/references/genres/execution-plan.md +27 -0
  43. package/skills/lark-doc/references/genres/formal-doc.md +37 -0
  44. package/skills/lark-doc/references/genres/meeting-minutes.md +24 -0
  45. package/skills/lark-doc/references/genres/memo-brief.md +25 -0
  46. package/skills/lark-doc/references/genres/official-redhead.md +73 -0
  47. package/skills/lark-doc/references/genres/prd.md +26 -0
  48. package/skills/lark-doc/references/genres/proposal.md +24 -0
  49. package/skills/lark-doc/references/genres/research-report.md +32 -0
  50. package/skills/lark-doc/references/genres/retrospective.md +25 -0
  51. package/skills/lark-doc/references/genres/route-consumer.md +37 -0
  52. package/skills/lark-doc/references/genres/route-creative.md +36 -0
  53. package/skills/lark-doc/references/genres/route-knowledge.md +39 -0
  54. package/skills/lark-doc/references/genres/route-marketing.md +40 -0
  55. package/skills/lark-doc/references/genres/route-media.md +36 -0
  56. package/skills/lark-doc/references/genres/route-opinion.md +38 -0
  57. package/skills/lark-doc/references/genres/route-personal-brand.md +36 -0
  58. package/skills/lark-doc/references/genres/route-platform.md +9 -0
  59. package/skills/lark-doc/references/genres/route-report.md +10 -0
  60. package/skills/lark-doc/references/genres/route-workplace.md +17 -0
  61. package/skills/lark-doc/references/genres/sop-tutorial.md +41 -0
  62. package/skills/lark-doc/references/genres/technical-doc.md +39 -0
  63. package/skills/lark-doc/references/genres/wechat.md +39 -0
  64. package/skills/lark-doc/references/genres/weekly-report.md +24 -0
  65. package/skills/lark-doc/references/genres/white-paper.md +32 -0
  66. package/skills/lark-doc/references/genres/xiaohongshu.md +38 -0
  67. package/skills/lark-doc/references/lark-doc-create-workflow.md +121 -0
  68. package/skills/lark-doc/references/lark-doc-create.md +22 -48
  69. package/skills/lark-doc/references/lark-doc-fetch.md +80 -92
  70. package/skills/lark-doc/references/lark-doc-history.md +16 -15
  71. package/skills/lark-doc/references/lark-doc-md.md +5 -1
  72. package/skills/lark-doc/references/lark-doc-media-download.md +2 -1
  73. package/skills/lark-doc/references/lark-doc-script.md +76 -0
  74. package/skills/lark-doc/references/lark-doc-update.md +73 -221
  75. package/skills/lark-doc/references/lark-doc-whiteboard.md +5 -9
  76. package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +17 -12
  77. package/skills/lark-doc/references/lark-doc-xml.md +38 -167
  78. package/skills/lark-drive/SKILL.md +11 -7
  79. package/skills/lark-drive/references/lark-drive-apply-permission.md +1 -1
  80. package/skills/lark-drive/references/lark-drive-copy.md +87 -0
  81. package/skills/lark-drive/references/lark-drive-download.md +29 -2
  82. package/skills/lark-drive/references/lark-drive-export.md +4 -0
  83. package/skills/lark-drive/references/lark-drive-member-remove.md +59 -0
  84. package/skills/lark-drive/references/lark-drive-preview.md +21 -2
  85. package/skills/lark-drive/references/lark-drive-push.md +5 -1
  86. package/skills/lark-drive/references/lark-drive-search.md +2 -0
  87. package/skills/lark-drive/references/lark-drive-task-result.md +3 -0
  88. package/skills/lark-drive/references/lark-drive-update-title.md +78 -0
  89. package/skills/lark-event/SKILL.md +7 -4
  90. package/skills/lark-event/references/lark-event-vc.md +8 -2
  91. package/skills/lark-im/SKILL.md +14 -9
  92. package/skills/lark-im/references/lark-im-chat-list.md +9 -2
  93. package/skills/lark-im/references/lark-im-chat-members-list.md +7 -4
  94. package/skills/lark-im/references/lark-im-chat-messages-list.md +10 -3
  95. package/skills/lark-im/references/lark-im-chat-search.md +9 -2
  96. package/skills/lark-im/references/lark-im-feed-group-list-item.md +2 -2
  97. package/skills/lark-im/references/lark-im-feed-group-list.md +2 -2
  98. package/skills/lark-im/references/lark-im-feed-shortcut-list.md +1 -1
  99. package/skills/lark-im/references/lark-im-flag-list.md +2 -2
  100. package/skills/lark-im/references/lark-im-message-enrichment.md +1 -1
  101. package/skills/lark-im/references/lark-im-messages-resources-download.md +19 -25
  102. package/skills/lark-im/references/lark-im-messages-search.md +4 -5
  103. package/skills/lark-im/references/lark-im-threads-messages-list.md +8 -4
  104. package/skills/lark-mail/references/lark-mail-triage.md +19 -4
  105. package/skills/lark-minutes/SKILL.md +12 -6
  106. package/skills/lark-minutes/references/lark-minutes-apply-permission.md +95 -0
  107. package/skills/lark-minutes/references/lark-minutes-detail.md +7 -6
  108. package/skills/lark-minutes/references/lark-minutes-download.md +4 -2
  109. package/skills/lark-minutes/references/lark-minutes-search.md +6 -7
  110. package/skills/lark-note/SKILL.md +13 -9
  111. package/skills/lark-note/references/lark-note-detail.md +5 -2
  112. package/skills/lark-note/references/lark-note-transcript.md +2 -0
  113. package/skills/lark-shared/SKILL.md +39 -3
  114. package/skills/lark-sheets/SKILL.md +83 -82
  115. package/skills/lark-sheets/references/lark-sheets-batch-update.md +13 -58
  116. package/skills/lark-sheets/references/lark-sheets-chart.md +2 -1
  117. package/skills/lark-sheets/references/lark-sheets-conditional-format.md +1 -1
  118. package/skills/lark-sheets/references/lark-sheets-range-operations.md +5 -5
  119. package/skills/lark-sheets/references/lark-sheets-read-data.md +80 -6
  120. package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +21 -10
  121. package/skills/lark-sheets/references/lark-sheets-styles-put.md +93 -0
  122. package/skills/lark-sheets/references/lark-sheets-visual-standards.md +2 -2
  123. package/skills/lark-sheets/references/lark-sheets-workbook.md +4 -3
  124. package/skills/lark-sheets/references/lark-sheets-write-cells.md +40 -12
  125. package/skills/lark-sheets/scripts/lark_detect_subtables.py +593 -0
  126. package/skills/lark-sheets/scripts/lark_inspect_workbook.py +188 -0
  127. package/skills/lark-sheets/scripts/lark_profile_table.py +614 -0
  128. package/skills/lark-sheets/scripts/lark_sheet_range.py +176 -0
  129. package/skills/lark-sheets/scripts/lark_sheet_read_cli.py +184 -0
  130. package/skills/lark-sheets/scripts/sheets_df.py +21 -3
  131. package/skills/lark-slides/SKILL.md +64 -81
  132. package/skills/lark-slides/references/cli/lark-slides-add-slide.md +92 -0
  133. package/skills/lark-slides/references/cli/lark-slides-create.md +176 -0
  134. package/skills/lark-slides/references/cli/lark-slides-delete-slide.md +65 -0
  135. package/skills/lark-slides/references/cli/lark-slides-history.md +132 -0
  136. package/skills/lark-slides/references/cli/lark-slides-media-upload.md +103 -0
  137. package/skills/lark-slides/references/cli/lark-slides-replace-slide.md +259 -0
  138. package/skills/lark-slides/references/cli/lark-slides-screenshot.md +115 -0
  139. package/skills/lark-slides/references/cli/lark-slides-update-slide.md +163 -0
  140. package/skills/lark-slides/references/cli/lark-slides-xml-presentation-slide-get.md +110 -0
  141. package/skills/lark-slides/references/cli/lark-slides-xml-presentation-slide-replace.md +188 -0
  142. package/skills/lark-slides/references/cli/lark-slides-xml-presentations-get.md +157 -0
  143. package/skills/lark-slides/references/iconpark-index.json +5 -41901
  144. package/skills/lark-slides/references/iconpark.md +3 -44
  145. package/skills/lark-slides/references/lark-slides-add-slide.md +5 -0
  146. package/skills/lark-slides/references/lark-slides-create.md +3 -162
  147. package/skills/lark-slides/references/lark-slides-delete-slide.md +5 -0
  148. package/skills/lark-slides/references/lark-slides-edit-workflows.md +3 -142
  149. package/skills/lark-slides/references/lark-slides-history.md +3 -130
  150. package/skills/lark-slides/references/lark-slides-media-upload.md +3 -124
  151. package/skills/lark-slides/references/lark-slides-pptx-template-workflows.md +3 -83
  152. package/skills/lark-slides/references/lark-slides-replace-slide.md +3 -235
  153. package/skills/lark-slides/references/lark-slides-screenshot.md +3 -95
  154. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md +3 -108
  155. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-replace.md +3 -186
  156. package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +3 -132
  157. package/skills/lark-slides/references/planning-layer.md +1 -1
  158. package/skills/lark-slides/references/slides_chart_demo.xml +5 -1416
  159. package/skills/lark-slides/references/slides_xml_schema_definition.xml +3 -3468
  160. package/skills/lark-slides/references/troubleshooting.md +3 -61
  161. package/skills/lark-slides/references/validation-checklist.md +3 -154
  162. package/skills/lark-slides/references/workflow/error-handling.md +62 -0
  163. package/skills/lark-slides/references/workflow/slides-editing.md +143 -0
  164. package/skills/lark-slides/references/workflow/template-editing.md +85 -0
  165. package/skills/lark-slides/references/workflow/validation-xml.md +156 -0
  166. package/skills/lark-slides/references/xml/iconpark-index.json +37458 -0
  167. package/skills/lark-slides/references/xml/iconpark.md +46 -0
  168. package/skills/lark-slides/references/xml/slides_chart_demo.xml +1415 -0
  169. package/skills/lark-slides/references/xml/slides_xml_schema_definition.xml +3514 -0
  170. package/skills/lark-slides/references/xml/xml-schema-quick-ref.md +497 -0
  171. package/skills/lark-slides/references/xml-schema-quick-ref.md +3 -483
  172. package/skills/lark-slides/scripts/iconpark_tool.py +1 -1
  173. package/skills/lark-slides/scripts/sxsd_validator.py +154 -10
  174. package/skills/lark-slides/scripts/xml_lint.py +2989 -0
  175. package/skills/lark-slides/scripts/xml_lint_test.py +4720 -0
  176. package/skills/lark-slides/scripts/xml_text_overlap_lint.py +3 -2691
  177. package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +5 -3788
  178. package/skills/lark-task/SKILL.md +12 -0
  179. package/skills/lark-task/references/lark-task-create.md +3 -1
  180. package/skills/lark-vc/SKILL.md +15 -5
  181. package/skills/lark-vc/references/lark-vc-detail.md +11 -6
  182. package/skills/{lark-vc-agent/references/lark-vc-agent-meeting-events.md → lark-vc/references/lark-vc-meeting-events.md} +121 -20
  183. package/skills/{lark-vc-agent/references/lark-vc-agent-meeting-list-active.md → lark-vc/references/lark-vc-meeting-list-active.md} +2 -2
  184. package/skills/{lark-vc-agent/references/lark-vc-agent-meeting-message-send.md → lark-vc/references/lark-vc-meeting-message-send.md} +3 -3
  185. package/skills/lark-vc/references/lark-vc-recording.md +8 -6
  186. package/skills/lark-vc/references/vc-domain-boundaries.md +8 -1
  187. package/skills/lark-vc-agent/SKILL.md +24 -9
  188. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-join.md +2 -2
  189. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-leave.md +2 -2
  190. package/skills/lark-whiteboard/SKILL.md +15 -8
  191. package/skills/lark-whiteboard/references/lark-whiteboard-export.md +4 -3
  192. package/skills/lark-whiteboard/references/lark-whiteboard-update.md +4 -4
  193. package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +19 -17
  194. package/skills/lark-whiteboard/routes/dsl.md +8 -2
  195. package/skills/lark-whiteboard/routes/mermaid.md +1 -1
  196. package/skills/lark-whiteboard/routes/svg-edit.md +5 -2
  197. package/skills/lark-whiteboard/routes/svg.md +3 -1
  198. package/skills/lark-whiteboard/scenes/mention.md +71 -0
  199. package/skills/lark-wiki/SKILL.md +8 -4
  200. package/skills/lark-wiki/references/lark-wiki-delete-space.md +6 -3
  201. package/skills/lark-wiki/references/lark-wiki-node-copy.md +5 -19
  202. package/skills/lark-wiki/references/lark-wiki-node-create.md +19 -2
  203. package/skills/lark-wiki/references/lark-wiki-node-get.md +15 -0
  204. package/skills/lark-wiki/references/lark-wiki-node-list.md +1 -1
  205. package/skills/lark-base/references/lark-base-data-analysis-sop.md +0 -210
  206. package/skills/lark-base/references/lark-base-data-query-guide.md +0 -61
  207. package/skills/lark-base/references/lark-base-record-upsert.md +0 -63
  208. package/skills/lark-doc/references/lark-doc-word-stat.md +0 -93
  209. package/skills/lark-doc/references/style/lark-doc-create-workflow.md +0 -47
  210. package/skills/lark-doc/references/style/lark-doc-style.md +0 -68
  211. package/skills/lark-doc/references/style/lark-doc-update-workflow.md +0 -48
  212. package/skills/lark-doc/scripts/doc_word_stat.py +0 -1243
  213. package/skills/lark-slides/references/lark-slides-replace-pages.md +0 -95
  214. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +0 -219
  215. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +0 -126
@@ -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