@amaster.ai/pi-lark 0.1.2-beta.42 → 0.1.2-beta.44

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 (35) hide show
  1. package/package.json +3 -3
  2. package/skills/lark-approval/references/lark-approval-initiate.md +2 -5
  3. package/skills/lark-approval/references/lark-approval-instances-initiated.md +6 -0
  4. package/skills/lark-approval/references/lark-approval-tasks-query.md +9 -0
  5. package/skills/lark-approval/references/lark-approval-tasks-rollback.md +8 -2
  6. package/skills/lark-apps/SKILL.md +16 -10
  7. package/skills/lark-apps/references/lark-apps-access-scope-set.md +1 -1
  8. package/skills/lark-apps/references/lark-apps-db-execute.md +185 -1
  9. package/skills/lark-apps/references/lark-apps-db.md +1 -1
  10. package/skills/lark-apps/references/lark-apps-role.md +133 -0
  11. package/skills/lark-base/SKILL.md +6 -2
  12. package/skills/lark-base/references/dashboard-block-data-config.md +28 -2
  13. package/skills/lark-base/references/lark-base-cell-value.md +9 -4
  14. package/skills/lark-base/references/lark-base-dashboard.md +11 -2
  15. package/skills/lark-base/references/lark-base-data-query.md +9 -7
  16. package/skills/lark-base/references/lark-base-field-create.md +4 -2
  17. package/skills/lark-base/references/lark-base-field-json.md +52 -15
  18. package/skills/lark-base/references/lark-base-field-update.md +4 -2
  19. package/skills/lark-base/references/lark-base-view-set-filter.md +3 -1
  20. package/skills/lark-drive/SKILL.md +4 -2
  21. package/skills/lark-drive/references/lark-drive-delete.md +23 -11
  22. package/skills/lark-drive/references/lark-drive-move.md +5 -3
  23. package/skills/lark-drive/references/lark-drive-task-result.md +58 -5
  24. package/skills/lark-event/SKILL.md +2 -1
  25. package/skills/lark-event/references/lark-event-approval.md +170 -0
  26. package/skills/lark-slides/references/slides_xml_schema_definition.xml +7 -2
  27. package/skills/lark-slides/references/xml-format-guide.md +15 -0
  28. package/skills/lark-slides/scripts/xml_text_overlap_lint.py +264 -6
  29. package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +348 -6
  30. package/skills/lark-vc/SKILL.md +6 -3
  31. package/skills/lark-vc/references/vc-domain-boundaries.md +9 -1
  32. package/skills/lark-vc-agent/SKILL.md +1 -1
  33. package/skills/lark-wiki/SKILL.md +4 -2
  34. package/skills/lark-wiki/references/lark-wiki-move-to-drive.md +122 -0
  35. package/skills/lark-wiki/references/lark-wiki-move.md +5 -3
@@ -38,6 +38,8 @@ SXSD_ATTR_ALIASES = {
38
38
  "fontColor": "color",
39
39
  }
40
40
  SERVER_FILLED_SXSD_ATTRS = {"id"}
41
+ DEFAULT_TABLE_COLUMN_WIDTH = 110
42
+ DEFAULT_TABLE_ROW_HEIGHT = 37
41
43
  _SXSD_TAG_ATTRIBUTES_CACHE: dict[str, set[str]] | None = None
42
44
  _ICONPARK_ICON_TYPES_CACHE: set[str] | None = None
43
45
 
@@ -88,6 +90,84 @@ def extract_numeric_attribute(tag_source: str, name: str) -> int | float | None:
88
90
  return int(value) if value.is_integer() else value
89
91
 
90
92
 
93
+ def sum_sizes(sizes: list[int | float]) -> int | float:
94
+ return sum(sizes)
95
+
96
+
97
+ def is_filled_size(size: int | float | None) -> bool:
98
+ return isinstance(size, (int, float)) and math.isfinite(size) and size > 0
99
+
100
+
101
+ def fill_last_size_gap(sizes: list[int | float], target_size: int | float) -> list[int | float]:
102
+ if not sizes:
103
+ return sizes
104
+ final_sizes = [
105
+ size if index == len(sizes) - 1 else max(1, math.floor(size + 0.5))
106
+ for index, size in enumerate(sizes)
107
+ ]
108
+ remaining_size = target_size - sum_sizes(final_sizes[:-1])
109
+ if remaining_size >= 1:
110
+ final_sizes[-1] = remaining_size
111
+ return final_sizes
112
+
113
+ size_to_redistribute = 1 - remaining_size
114
+ for index in range(len(final_sizes) - 2, -1, -1):
115
+ reduction = min(final_sizes[index] - 1, size_to_redistribute)
116
+ final_sizes[index] -= reduction
117
+ size_to_redistribute -= reduction
118
+ if size_to_redistribute == 0:
119
+ final_sizes[-1] = 1
120
+ return final_sizes
121
+
122
+ final_sizes[-1] = 1
123
+ return final_sizes
124
+
125
+
126
+ def solve_weighted_min_layout(
127
+ input_sizes: list[int | float | None], default_size: int | float, target_min_size: int | float | None
128
+ ) -> dict[str, Any]:
129
+ filled_indexes: list[int] = []
130
+ empty_indexes: list[int] = []
131
+ base_sizes: list[int | float] = []
132
+ for index, size in enumerate(input_sizes):
133
+ if is_filled_size(size):
134
+ filled_indexes.append(index)
135
+ base_sizes.append(size)
136
+ else:
137
+ empty_indexes.append(index)
138
+ base_sizes.append(0)
139
+ filled_sum = sum_sizes(base_sizes)
140
+
141
+ if target_min_size is None:
142
+ final_sizes = [default_size if index in empty_indexes else size for index, size in enumerate(base_sizes)]
143
+ return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": 1}
144
+
145
+ if not filled_indexes:
146
+ average_size = target_min_size / len(input_sizes)
147
+ final_sizes = fill_last_size_gap([average_size] * len(input_sizes), target_min_size)
148
+ return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": 1}
149
+
150
+ if empty_indexes:
151
+ remaining_size = target_min_size - filled_sum
152
+ final_sizes = [*base_sizes]
153
+ if remaining_size > 0:
154
+ average_size = remaining_size / len(empty_indexes)
155
+ empty_sizes = fill_last_size_gap([average_size] * len(empty_indexes), remaining_size)
156
+ for index, empty_size in zip(empty_indexes, empty_sizes):
157
+ final_sizes[index] = empty_size
158
+ else:
159
+ for index in empty_indexes:
160
+ final_sizes[index] = default_size
161
+ return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": 1}
162
+
163
+ ratio = max(1, target_min_size / filled_sum)
164
+ actual_size = max(target_min_size, filled_sum)
165
+ if ratio == 1:
166
+ return {"final_sizes": [*base_sizes], "actual_size": actual_size, "ratio": ratio}
167
+ final_sizes = fill_last_size_gap([size * ratio for size in base_sizes], actual_size)
168
+ return {"final_sizes": final_sizes, "actual_size": sum_sizes(final_sizes), "ratio": ratio}
169
+
170
+
91
171
  def strip_xml(value: str) -> str:
92
172
  stripped = re.sub(r"<!\[CDATA\[([\s\S]*?)\]\]>", r"\1", value)
93
173
  stripped = re.sub(r"<[^>]+>", " ", stripped)
@@ -515,8 +595,8 @@ def extract_elements(slide_xml: str) -> list[dict[str, Any]]:
515
595
  for match in re.finditer(r"<(shape|img|table|chart|whiteboard)\b([^>]*)>", slide_xml):
516
596
  kind, attrs = match.group(1), match.group(2)
517
597
  content = ""
518
- if kind == "shape":
519
- close_index = slide_xml.find("</shape>", match.end())
598
+ if kind in {"shape", "table"}:
599
+ close_index = slide_xml.find(f"</{kind}>", match.end())
520
600
  if close_index != -1:
521
601
  content = slide_xml[match.end() : close_index]
522
602
 
@@ -525,6 +605,15 @@ def extract_elements(slide_xml: str) -> list[dict[str, Any]]:
525
605
  y = extract_numeric_attribute(attrs, "topLeftY")
526
606
  width = extract_numeric_attribute(attrs, "width")
527
607
  height = extract_numeric_attribute(attrs, "height")
608
+ rotation = extract_numeric_attribute(attrs, "rotation") or 0
609
+ table_layouts: dict[str, dict[str, Any] | None] = {}
610
+ if kind == "table":
611
+ width, table_layouts["width"] = resolve_table_dimension(
612
+ content, width, extract_table_column_sizes, DEFAULT_TABLE_COLUMN_WIDTH
613
+ )
614
+ height, table_layouts["height"] = resolve_table_dimension(
615
+ content, height, extract_table_row_sizes, DEFAULT_TABLE_ROW_HEIGHT
616
+ )
528
617
  if all(value is not None for value in [x, y, width, height]):
529
618
  element = {
530
619
  "id": element_id,
@@ -534,8 +623,17 @@ def extract_elements(slide_xml: str) -> list[dict[str, Any]]:
534
623
  "y": y,
535
624
  "width": width,
536
625
  "height": height,
626
+ "rotation": rotation,
537
627
  "order": len(elements),
538
628
  }
629
+ if kind == "table":
630
+ element.update(
631
+ {
632
+ "declared_width": extract_numeric_attribute(attrs, "width"),
633
+ "declared_height": extract_numeric_attribute(attrs, "height"),
634
+ "table_layouts": table_layouts,
635
+ }
636
+ )
539
637
  if kind == "shape":
540
638
  element.update(
541
639
  {
@@ -867,11 +965,158 @@ def detect_whiteboard_external_overlaps(
867
965
  return issues
868
966
 
869
967
 
968
+ def element_canvas_bbox(element: dict[str, Any]) -> dict[str, int | float]:
969
+ bbox = {key: element[key] for key in ("x", "y", "width", "height")}
970
+ if element["kind"] != "chart" and not (element["kind"] == "shape" and element["type"] == "text"):
971
+ return bbox
972
+
973
+ rotation = element["rotation"]
974
+ if not isinstance(rotation, (int, float)) or not math.isfinite(rotation):
975
+ rotation = 0
976
+ rotation %= 360
977
+ if math.isclose(rotation, 0, abs_tol=1e-9):
978
+ return bbox
979
+ radians = math.radians(rotation)
980
+ sine = abs(math.sin(radians))
981
+ cosine = abs(math.cos(radians))
982
+ sine = 0 if math.isclose(sine, 0, abs_tol=1e-12) else sine
983
+ cosine = 0 if math.isclose(cosine, 0, abs_tol=1e-12) else cosine
984
+ rotated_width = element["width"] * cosine + element["height"] * sine
985
+ rotated_height = element["width"] * sine + element["height"] * cosine
986
+ return {
987
+ "x": element["x"] - (rotated_width - element["width"]) / 2,
988
+ "y": element["y"] - (rotated_height - element["height"]) / 2,
989
+ "width": rotated_width,
990
+ "height": rotated_height,
991
+ }
992
+
993
+
994
+ def detect_elements_out_of_canvas(
995
+ elements: list[dict[str, Any]], slide_width: int | float, slide_height: int | float
996
+ ) -> list[dict[str, Any]]:
997
+ issues: list[dict[str, Any]] = []
998
+ for element in (
999
+ element
1000
+ for element in elements
1001
+ if element["kind"] in {"table", "chart"}
1002
+ or (element["kind"] == "shape" and element["type"] == "text")
1003
+ ):
1004
+ bbox = element_canvas_bbox(element)
1005
+ overflow = {
1006
+ "left": max(-bbox["x"], 0),
1007
+ "top": max(-bbox["y"], 0),
1008
+ "right": max(bbox["x"] + bbox["width"] - slide_width, 0),
1009
+ "bottom": max(bbox["y"] + bbox["height"] - slide_height, 0),
1010
+ }
1011
+ overflow_details = [
1012
+ f"{side} by {amount:g}px" for side, amount in overflow.items() if amount > 0
1013
+ ]
1014
+ if not overflow_details:
1015
+ continue
1016
+ issues.append(
1017
+ {
1018
+ "level": "error",
1019
+ "code": f'{element["kind"]}_out_of_canvas',
1020
+ "elements": [element["id"]],
1021
+ "canvas": {"width": slide_width, "height": slide_height},
1022
+ "bbox": bbox,
1023
+ "overflow": overflow,
1024
+ "message": (
1025
+ f'{element["kind"]} {element["id"]} exceeds the {slide_width:g}x{slide_height:g} canvas '
1026
+ f'({", ".join(overflow_details)})'
1027
+ ),
1028
+ "hint": (
1029
+ "Move the table inside the canvas, reduce table.width/table.height, or split the table across "
1030
+ "slides."
1031
+ if element["kind"] == "table"
1032
+ else f'Move the {element["kind"]} inside the canvas or reduce its width/height.'
1033
+ ),
1034
+ }
1035
+ )
1036
+ return issues
1037
+
1038
+
1039
+ def extract_table_column_sizes(table_xml: str) -> list[int | float | None]:
1040
+ sizes: list[int | float | None] = []
1041
+ for match in re.finditer(r"<col\b([^>]*)/?>", table_xml):
1042
+ attrs = match.group(1)
1043
+ span = extract_numeric_attribute(attrs, "span") or 1
1044
+ span_count = int(span) if math.isfinite(span) and span > 0 and float(span).is_integer() else 1
1045
+ sizes.extend([extract_numeric_attribute(attrs, "width")] * span_count)
1046
+ return sizes
1047
+
1048
+
1049
+ def extract_table_row_sizes(table_xml: str) -> list[int | float | None]:
1050
+ return [extract_numeric_attribute(match.group(1), "height") for match in re.finditer(r"<tr\b([^>]*)>", table_xml)]
1051
+
1052
+
1053
+ def resolve_table_dimension(
1054
+ table_xml: str,
1055
+ declared_size: int | float | None,
1056
+ extract_sizes: Any,
1057
+ default_size: int | float,
1058
+ ) -> tuple[int | float | None, dict[str, Any] | None]:
1059
+ input_sizes = extract_sizes(table_xml)
1060
+ if not input_sizes:
1061
+ return declared_size, None
1062
+ layout = solve_weighted_min_layout(
1063
+ input_sizes, default_size, declared_size if is_filled_size(declared_size) else None
1064
+ )
1065
+ return layout["actual_size"], layout
1066
+
1067
+
1068
+ def format_size(size: int | float) -> str:
1069
+ return f"{size:g}"
1070
+
1071
+
1072
+ def detect_table_layout_size_mismatches(elements: list[dict[str, Any]]) -> list[dict[str, Any]]:
1073
+ issues: list[dict[str, Any]] = []
1074
+ dimensions = {
1075
+ "width": ("col", "column widths"),
1076
+ "height": ("tr", "row heights"),
1077
+ }
1078
+ for table in (element for element in elements if element["kind"] == "table"):
1079
+ for dimension, (child_tag, child_description) in dimensions.items():
1080
+ target_size = table[f"declared_{dimension}"]
1081
+ if not is_filled_size(target_size):
1082
+ continue
1083
+ layout = table["table_layouts"][dimension]
1084
+ if layout is None:
1085
+ continue
1086
+ actual_size = layout["actual_size"]
1087
+ if math.isclose(actual_size, target_size, rel_tol=1e-9, abs_tol=1e-9):
1088
+ continue
1089
+ issues.append(
1090
+ {
1091
+ "level": "info",
1092
+ "code": "table_resolved_size_mismatch",
1093
+ "elements": [table["id"]],
1094
+ "dimension": dimension,
1095
+ "declared_size": target_size,
1096
+ "resolved_size": actual_size,
1097
+ "resolved_sizes": layout["final_sizes"],
1098
+ "message": (
1099
+ f'table {table["id"]} declares {dimension}={format_size(target_size)}px, but its '
1100
+ f"{child_description} resolve to {format_size(actual_size)}px"
1101
+ ),
1102
+ "hint": (
1103
+ f"Set table.{dimension} to {format_size(actual_size)}px, or adjust <{child_tag}> sizes "
1104
+ f"so their resolved total matches {format_size(target_size)}px."
1105
+ ),
1106
+ }
1107
+ )
1108
+ return issues
1109
+
1110
+
870
1111
  def lint_slide(
871
1112
  slide_xml: str, slide_number: int, slide_width: int | float = 960, slide_height: int | float = 540
872
1113
  ) -> dict[str, Any]:
873
1114
  elements = extract_elements(slide_xml)
874
- issues: list[dict[str, Any]] = detect_whiteboard_external_overlaps(elements, slide_width, slide_height)
1115
+ issues: list[dict[str, Any]] = [
1116
+ *detect_whiteboard_external_overlaps(elements, slide_width, slide_height),
1117
+ *detect_elements_out_of_canvas(elements, slide_width, slide_height),
1118
+ *detect_table_layout_size_mismatches(elements),
1119
+ ]
875
1120
 
876
1121
  for index, left in enumerate(elements):
877
1122
  for right in elements[index + 1 :]:
@@ -896,7 +1141,7 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
896
1141
  return {
897
1142
  "file": source_path,
898
1143
  "slide_size": {"width": 960, "height": 540},
899
- "summary": {"slide_count": 0, "error_count": 1, "warning_count": 0},
1144
+ "summary": {"slide_count": 0, "error_count": 1, "warning_count": 0, "info_count": 0},
900
1145
  "issues": [xml_error],
901
1146
  "slides": [],
902
1147
  }
@@ -908,10 +1153,16 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
908
1153
  if namespace_issues:
909
1154
  error_count = sum(1 for issue in top_level_issues if issue["level"] == "error")
910
1155
  warning_count = sum(1 for issue in top_level_issues if issue["level"] == "warning")
1156
+ info_count = sum(1 for issue in top_level_issues if issue["level"] == "info")
911
1157
  return {
912
1158
  "file": source_path,
913
1159
  "slide_size": {"width": 960, "height": 540},
914
- "summary": {"slide_count": 0, "error_count": error_count, "warning_count": warning_count},
1160
+ "summary": {
1161
+ "slide_count": 0,
1162
+ "error_count": error_count,
1163
+ "warning_count": warning_count,
1164
+ "info_count": info_count,
1165
+ },
915
1166
  "issues": top_level_issues,
916
1167
  "slides": [],
917
1168
  }
@@ -924,10 +1175,17 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
924
1175
  error_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "error")
925
1176
  warning_count = sum(1 for issue in top_level_issues if issue["level"] == "warning")
926
1177
  warning_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "warning")
1178
+ info_count = sum(1 for issue in top_level_issues if issue["level"] == "info")
1179
+ info_count += sum(1 for slide in slides for issue in slide["issues"] if issue["level"] == "info")
927
1180
  result = {
928
1181
  "file": source_path,
929
1182
  "slide_size": {"width": presentation["width"], "height": presentation["height"]},
930
- "summary": {"slide_count": len(slides), "error_count": error_count, "warning_count": warning_count},
1183
+ "summary": {
1184
+ "slide_count": len(slides),
1185
+ "error_count": error_count,
1186
+ "warning_count": warning_count,
1187
+ "info_count": info_count,
1188
+ },
931
1189
  "slides": slides,
932
1190
  }
933
1191
  if top_level_issues: