sophhub 0.2.1 → 0.2.3

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 (189) hide show
  1. package/package.json +1 -1
  2. package/skills/compact-context/skill.json +20 -0
  3. package/skills/compact-context/src/SKILL.md +133 -0
  4. package/skills/compact-context/src/scripts/check.sh +381 -0
  5. package/skills/compact-context/src/scripts/set-keep-recent.mjs +1337 -0
  6. package/skills/compact-context/src/scripts/setup.sh +96 -0
  7. package/skills/feishu-notes-assistant-universal/skill.json +20 -0
  8. package/skills/feishu-notes-assistant-universal/src/README.md +55 -0
  9. package/skills/feishu-notes-assistant-universal/src/SKILL.md +159 -0
  10. package/skills/feishu-notes-assistant-universal/src/bin/linux-amd64/lark-cli-openclaw +0 -0
  11. package/skills/feishu-notes-assistant-universal/src/bin/linux-arm64/lark-cli-openclaw +0 -0
  12. package/skills/feishu-notes-assistant-universal/src/scripts/_resolve_lark_cli.py +58 -0
  13. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_meeting_minutes.py +462 -0
  14. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud.py +547 -0
  15. package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud_test.py +181 -0
  16. package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.py +80 -0
  17. package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.sh +5 -0
  18. package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.py +32 -0
  19. package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.sh +5 -0
  20. package/skills/flight-booking/skill.json +9 -2
  21. package/skills/flight-booking/src/scripts/flight_booking.py +2 -1
  22. package/skills/image-classify/skill.json +5 -5
  23. package/skills/image-classify/src/SKILL.md +60 -67
  24. package/skills/image-classify/src/scripts/face_search.py +400 -15
  25. package/skills/image-classify/src/scripts/send_dm_message.py +332 -0
  26. package/skills/md2pdf-converter/skill.json +20 -0
  27. package/skills/md2pdf-converter/src/SKILL.md +244 -0
  28. package/skills/md2pdf-converter/src/_meta.json +6 -0
  29. package/skills/md2pdf-converter/src/scripts/generate_emoji_mapping.py +74 -0
  30. package/skills/md2pdf-converter/src/scripts/md2pdf-local.sh +291 -0
  31. package/skills/sophnet-bot-client/skill.json +20 -0
  32. package/skills/sophnet-bot-client/src/SKILL.md +255 -0
  33. package/skills/sophnet-bot-client/src/pyproject.toml +13 -0
  34. package/skills/sophnet-bot-client/src/scripts/__init__.py +0 -0
  35. package/skills/sophnet-bot-client/src/scripts/bot_client_proxy.py +165 -0
  36. package/skills/sophnet-bot-client/src/scripts/bot_client_safe.sh +29 -0
  37. package/skills/sophnet-bot-client/src/scripts/bot_client_setup.py +502 -0
  38. package/skills/sophnet-bot-client/src/tests/__init__.py +0 -0
  39. package/skills/sophnet-bot-client/src/tests/test_bot_client_proxy.py +255 -0
  40. package/skills/sophnet-bot-client/src/tests/test_bot_client_setup.py +679 -0
  41. package/skills/sophnet-bot-client/src/uv.lock +8 -0
  42. package/skills/sophnet-docx/skill.json +20 -0
  43. package/skills/sophnet-docx/src/SKILL.md +463 -0
  44. package/skills/sophnet-docx/src/package-lock.json +208 -0
  45. package/skills/sophnet-docx/src/package.json +16 -0
  46. package/skills/sophnet-docx/src/pyproject.toml +11 -0
  47. package/skills/sophnet-docx/src/scripts/__init__.py +1 -0
  48. package/skills/sophnet-docx/src/scripts/accept_changes.py +135 -0
  49. package/skills/sophnet-docx/src/scripts/comment.py +318 -0
  50. package/skills/sophnet-docx/src/scripts/ensure_uv_env.sh +68 -0
  51. package/skills/sophnet-docx/src/scripts/office/helpers/__init__.py +0 -0
  52. package/skills/sophnet-docx/src/scripts/office/helpers/merge_runs.py +199 -0
  53. package/skills/sophnet-docx/src/scripts/office/helpers/simplify_redlines.py +197 -0
  54. package/skills/sophnet-docx/src/scripts/office/pack.py +159 -0
  55. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  56. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  57. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  58. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  59. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  60. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  61. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  62. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  63. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  64. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  65. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  66. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  67. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  68. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  69. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  70. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  71. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  72. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  73. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  74. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  75. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  76. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  77. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  78. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  79. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  80. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  81. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  82. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  83. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  84. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  85. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  86. package/skills/sophnet-docx/src/scripts/office/schemas/mce/mc.xsd +75 -0
  87. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  88. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  89. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  90. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  91. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  92. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  93. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  94. package/skills/sophnet-docx/src/scripts/office/soffice.py +183 -0
  95. package/skills/sophnet-docx/src/scripts/office/unpack.py +132 -0
  96. package/skills/sophnet-docx/src/scripts/office/validate.py +111 -0
  97. package/skills/sophnet-docx/src/scripts/office/validators/__init__.py +15 -0
  98. package/skills/sophnet-docx/src/scripts/office/validators/base.py +847 -0
  99. package/skills/sophnet-docx/src/scripts/office/validators/docx.py +446 -0
  100. package/skills/sophnet-docx/src/scripts/office/validators/pptx.py +275 -0
  101. package/skills/sophnet-docx/src/scripts/office/validators/redlining.py +247 -0
  102. package/skills/sophnet-docx/src/scripts/templates/comments.xml +3 -0
  103. package/skills/sophnet-docx/src/scripts/templates/commentsExtended.xml +3 -0
  104. package/skills/sophnet-docx/src/scripts/templates/commentsExtensible.xml +3 -0
  105. package/skills/sophnet-docx/src/scripts/templates/commentsIds.xml +3 -0
  106. package/skills/sophnet-docx/src/scripts/templates/people.xml +3 -0
  107. package/skills/sophnet-docx/src/scripts/upload_file.sh +96 -0
  108. package/skills/sophnet-docx/src/uv.lock +320 -0
  109. package/skills/sophnet-pdf/skill.json +20 -0
  110. package/skills/sophnet-pdf/src/SKILL.md +413 -0
  111. package/skills/sophnet-pdf/src/forms.md +297 -0
  112. package/skills/sophnet-pdf/src/pyproject.toml +14 -0
  113. package/skills/sophnet-pdf/src/reference.md +612 -0
  114. package/skills/sophnet-pdf/src/scripts/check_bounding_boxes.py +65 -0
  115. package/skills/sophnet-pdf/src/scripts/check_fillable_fields.py +11 -0
  116. package/skills/sophnet-pdf/src/scripts/convert_pdf_to_images.py +33 -0
  117. package/skills/sophnet-pdf/src/scripts/create_validation_image.py +37 -0
  118. package/skills/sophnet-pdf/src/scripts/enhance_tutorial.py +558 -0
  119. package/skills/sophnet-pdf/src/scripts/ensure_uv_env.sh +68 -0
  120. package/skills/sophnet-pdf/src/scripts/extract_form_field_info.py +122 -0
  121. package/skills/sophnet-pdf/src/scripts/extract_form_structure.py +115 -0
  122. package/skills/sophnet-pdf/src/scripts/extract_pdf_content.py +35 -0
  123. package/skills/sophnet-pdf/src/scripts/fill_fillable_fields.py +98 -0
  124. package/skills/sophnet-pdf/src/scripts/fill_pdf_form_with_annotations.py +107 -0
  125. package/skills/sophnet-pdf/src/scripts/upload_file.sh +88 -0
  126. package/skills/sophnet-pdf/src/uv.lock +537 -0
  127. package/skills/sophnet-xlsx/skill.json +20 -0
  128. package/skills/sophnet-xlsx/src/SKILL.md +399 -0
  129. package/skills/sophnet-xlsx/src/pyproject.toml +11 -0
  130. package/skills/sophnet-xlsx/src/scripts/ensure_uv_env.sh +68 -0
  131. package/skills/sophnet-xlsx/src/scripts/office/helpers/__init__.py +0 -0
  132. package/skills/sophnet-xlsx/src/scripts/office/helpers/merge_runs.py +199 -0
  133. package/skills/sophnet-xlsx/src/scripts/office/helpers/simplify_redlines.py +197 -0
  134. package/skills/sophnet-xlsx/src/scripts/office/pack.py +159 -0
  135. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  136. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  137. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  138. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  139. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  140. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  141. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  142. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  143. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  144. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  145. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  146. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  147. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  148. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  149. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  150. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  151. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  152. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  153. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  154. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  155. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  156. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  157. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  158. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  159. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  160. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  161. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  162. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  163. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  164. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  165. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  166. package/skills/sophnet-xlsx/src/scripts/office/schemas/mce/mc.xsd +75 -0
  167. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  168. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  169. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  170. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  171. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  172. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  173. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  174. package/skills/sophnet-xlsx/src/scripts/office/soffice.py +183 -0
  175. package/skills/sophnet-xlsx/src/scripts/office/unpack.py +132 -0
  176. package/skills/sophnet-xlsx/src/scripts/office/validate.py +111 -0
  177. package/skills/sophnet-xlsx/src/scripts/office/validators/__init__.py +15 -0
  178. package/skills/sophnet-xlsx/src/scripts/office/validators/base.py +847 -0
  179. package/skills/sophnet-xlsx/src/scripts/office/validators/docx.py +446 -0
  180. package/skills/sophnet-xlsx/src/scripts/office/validators/pptx.py +275 -0
  181. package/skills/sophnet-xlsx/src/scripts/office/validators/redlining.py +247 -0
  182. package/skills/sophnet-xlsx/src/scripts/recalc.py +184 -0
  183. package/skills/sophnet-xlsx/src/scripts/upload_file.sh +96 -0
  184. package/skills/sophnet-xlsx/src/uv.lock +319 -0
  185. package/skills/wechat-article-publisher/skill.json +20 -0
  186. package/skills/wechat-article-publisher/src/SKILL.md +60 -0
  187. package/skills/wechat-article-publisher/src/config.json +7 -0
  188. package/skills/wechat-article-publisher/src/pyproject.toml +12 -0
  189. package/skills/wechat-article-publisher/src/scripts/publish_wechat.py +825 -0
@@ -0,0 +1,547 @@
1
+ #!/usr/bin/env python3
2
+ """OpenClaw note CRUD orchestrator for Feishu docs."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import re
10
+ import subprocess
11
+ import sys
12
+ from typing import Any
13
+
14
+
15
+ class CLIError(Exception):
16
+ def __init__(self, message: str, code: str = "cli_error", detail: Any = None):
17
+ super().__init__(message)
18
+ self.code = code
19
+ self.detail = detail
20
+
21
+
22
+ DOCX_URL_RE = re.compile(r"/docx/([A-Za-z0-9_-]+)")
23
+
24
+
25
+ def parse_args() -> argparse.Namespace:
26
+ parser = argparse.ArgumentParser(description="Feishu note CRUD via bundled lark-cli")
27
+ parser.add_argument(
28
+ "--op",
29
+ required=True,
30
+ choices=[
31
+ "create",
32
+ "read",
33
+ "update",
34
+ "delete",
35
+ "search",
36
+ "media-insert",
37
+ "media-download",
38
+ "comment",
39
+ "section-update",
40
+ ],
41
+ help="note operation",
42
+ )
43
+ parser.add_argument("--doc", help="doc token or doc/docx URL")
44
+ parser.add_argument("--title", help="note title")
45
+ parser.add_argument("--markdown", help="note markdown content")
46
+ parser.add_argument("--query", help="search keyword")
47
+ parser.add_argument("--mode", default="append", help="update mode")
48
+ parser.add_argument("--new-title", help="rename title while update")
49
+ parser.add_argument("--selection-by-title", help="title-based locator for update")
50
+ parser.add_argument(
51
+ "--selection-with-ellipsis", help="content locator for range update/delete"
52
+ )
53
+ parser.add_argument("--folder-token", help="target folder token when creating")
54
+ parser.add_argument("--wiki-node", help="target wiki node token when creating")
55
+ parser.add_argument("--wiki-space", help="target wiki space when creating")
56
+ parser.add_argument("--page-size", default="15", help="search page size")
57
+ parser.add_argument("--page-token", help="search page token")
58
+ parser.add_argument("--file", help="local file path for media insert")
59
+ parser.add_argument(
60
+ "--media-type",
61
+ default="image",
62
+ choices=["image", "file"],
63
+ help="media type for insert: image | file",
64
+ )
65
+ parser.add_argument(
66
+ "--token", help="media token or whiteboard token for media download"
67
+ )
68
+ parser.add_argument(
69
+ "--resource-type",
70
+ default="media",
71
+ choices=["media", "whiteboard"],
72
+ help="resource type for media download: media | whiteboard",
73
+ )
74
+ parser.add_argument("--output", help="local output path for media download")
75
+ parser.add_argument(
76
+ "--overwrite", action="store_true", help="overwrite existing download file"
77
+ )
78
+ parser.add_argument("--caption", help="image caption for media insert")
79
+ parser.add_argument(
80
+ "--align", choices=["left", "center", "right"], help="image alignment"
81
+ )
82
+ parser.add_argument("--comment", help="plain text comment body")
83
+ parser.add_argument(
84
+ "--comment-json",
85
+ help="raw reply_elements JSON array for advanced comment payloads",
86
+ )
87
+ parser.add_argument(
88
+ "--block-id", help="comment anchor block ID for local comment mode"
89
+ )
90
+ parser.add_argument(
91
+ "--full-comment",
92
+ action="store_true",
93
+ help="create full-document comment instead of local comment",
94
+ )
95
+ parser.add_argument("--section-title", help="title locator like '## Section'")
96
+ parser.add_argument(
97
+ "--section-mode",
98
+ choices=["replace", "delete", "insert_before", "insert_after"],
99
+ default="replace",
100
+ help="section update mode",
101
+ )
102
+ parser.add_argument("--as", dest="identity", default="user", help="user | bot")
103
+ parser.add_argument("--config-dir", help="optional LARKSUITE_CLI_CONFIG_DIR")
104
+ parser.add_argument("--lark-cli-bin", default="lark-cli", help="lark-cli binary")
105
+ return parser.parse_args()
106
+
107
+
108
+ def build_env(config_dir: str | None) -> dict[str, str]:
109
+ env = os.environ.copy()
110
+ if config_dir:
111
+ env["LARKSUITE_CLI_CONFIG_DIR"] = config_dir
112
+ return env
113
+
114
+
115
+ def run_cli(
116
+ cli_bin: str, env: dict[str, str], args: list[str], cwd: str | None = None
117
+ ) -> Any:
118
+ proc = subprocess.run(
119
+ [cli_bin] + args,
120
+ capture_output=True,
121
+ text=True,
122
+ env=env,
123
+ cwd=cwd,
124
+ )
125
+ stdout = proc.stdout.strip()
126
+ stderr = proc.stderr.strip()
127
+
128
+ parsed: Any = None
129
+ if stdout:
130
+ try:
131
+ parsed = json.loads(stdout)
132
+ except json.JSONDecodeError as exc:
133
+ raise CLIError(
134
+ f"lark-cli returned non-JSON stdout: {stdout[:400]}",
135
+ code="invalid_cli_output",
136
+ detail={"stderr": stderr, "args": args},
137
+ ) from exc
138
+
139
+ if proc.returncode != 0:
140
+ detail = parsed if isinstance(parsed, dict) else {"stderr": stderr}
141
+ raise CLIError(
142
+ f"lark-cli command failed: {' '.join(args)}",
143
+ code="lark_cli_failed",
144
+ detail=detail,
145
+ )
146
+ return parsed
147
+
148
+
149
+ def localize_cli_path(path: str) -> tuple[str, str]:
150
+ abs_path = os.path.abspath(path)
151
+ parent = os.path.dirname(abs_path) or os.getcwd()
152
+ return parent, f"./{os.path.basename(abs_path)}"
153
+
154
+
155
+ def unwrap_payload(payload: Any) -> Any:
156
+ if isinstance(payload, dict) and "ok" in payload:
157
+ if not payload.get("ok", False):
158
+ error = payload.get("error") or {}
159
+ raise CLIError(
160
+ error.get("message", "lark-cli returned an error envelope"),
161
+ code=error.get("type", "lark_error"),
162
+ detail=payload,
163
+ )
164
+ return payload.get("data")
165
+ return payload
166
+
167
+
168
+ def parse_doc_token(doc: str) -> str:
169
+ if not doc:
170
+ return ""
171
+ if doc.startswith("http://") or doc.startswith("https://"):
172
+ match = DOCX_URL_RE.search(doc)
173
+ if match:
174
+ return match.group(1)
175
+ return ""
176
+ return doc
177
+
178
+
179
+ def create_note(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
180
+ if not args.title:
181
+ raise CLIError("missing --title for create", code="validation")
182
+ if not args.markdown:
183
+ raise CLIError("missing --markdown for create", code="validation")
184
+ cmd = [
185
+ "docs",
186
+ "+create",
187
+ "--as",
188
+ args.identity,
189
+ "--title",
190
+ args.title,
191
+ "--markdown",
192
+ args.markdown,
193
+ ]
194
+ if args.folder_token:
195
+ cmd.extend(["--folder-token", args.folder_token])
196
+ if args.wiki_node:
197
+ cmd.extend(["--wiki-node", args.wiki_node])
198
+ if args.wiki_space:
199
+ cmd.extend(["--wiki-space", args.wiki_space])
200
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd))
201
+ if not isinstance(data, dict):
202
+ raise CLIError("unexpected docs +create payload", code="invalid_create_payload")
203
+ return data
204
+
205
+
206
+ def read_note(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
207
+ if not args.doc:
208
+ raise CLIError("missing --doc for read", code="validation")
209
+ cmd = [
210
+ "docs",
211
+ "+fetch",
212
+ "--as",
213
+ args.identity,
214
+ "--doc",
215
+ args.doc,
216
+ "--format",
217
+ "json",
218
+ ]
219
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd))
220
+ if not isinstance(data, dict):
221
+ raise CLIError("unexpected docs +fetch payload", code="invalid_read_payload")
222
+ return data
223
+
224
+
225
+ def update_note(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
226
+ if not args.doc:
227
+ raise CLIError("missing --doc for update", code="validation")
228
+ cmd = [
229
+ "docs",
230
+ "+update",
231
+ "--as",
232
+ args.identity,
233
+ "--doc",
234
+ args.doc,
235
+ "--mode",
236
+ args.mode,
237
+ ]
238
+ if args.markdown:
239
+ cmd.extend(["--markdown", args.markdown])
240
+ if args.new_title:
241
+ cmd.extend(["--new-title", args.new_title])
242
+ if args.selection_by_title:
243
+ cmd.extend(["--selection-by-title", args.selection_by_title])
244
+ if args.selection_with_ellipsis:
245
+ cmd.extend(["--selection-with-ellipsis", args.selection_with_ellipsis])
246
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd))
247
+ if not isinstance(data, dict):
248
+ raise CLIError("unexpected docs +update payload", code="invalid_update_payload")
249
+ return data
250
+
251
+
252
+ def search_note(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
253
+ cmd = [
254
+ "docs",
255
+ "+search",
256
+ "--as",
257
+ args.identity,
258
+ "--format",
259
+ "json",
260
+ "--page-size",
261
+ args.page_size,
262
+ ]
263
+ if args.query:
264
+ cmd.extend(["--query", args.query])
265
+ if args.page_token:
266
+ cmd.extend(["--page-token", args.page_token])
267
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd))
268
+ if not isinstance(data, dict):
269
+ raise CLIError("unexpected docs +search payload", code="invalid_search_payload")
270
+ return data
271
+
272
+
273
+ def delete_note(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
274
+ if not args.doc:
275
+ raise CLIError("missing --doc for delete", code="validation")
276
+ token = parse_doc_token(args.doc)
277
+ if not token:
278
+ raise CLIError(
279
+ "delete only supports doc token or /docx/<token> URL",
280
+ code="validation",
281
+ )
282
+
283
+ # Try direct delete first. Some tenants require explicit type param.
284
+ attempts = [
285
+ [
286
+ "api",
287
+ "DELETE",
288
+ f"/open-apis/drive/v1/files/{token}",
289
+ "--as",
290
+ args.identity,
291
+ "--format",
292
+ "json",
293
+ ],
294
+ [
295
+ "api",
296
+ "DELETE",
297
+ f"/open-apis/drive/v1/files/{token}",
298
+ "--as",
299
+ args.identity,
300
+ "--params",
301
+ '{"type":"docx"}',
302
+ "--format",
303
+ "json",
304
+ ],
305
+ ]
306
+ last_exc: CLIError | None = None
307
+ for cmd in attempts:
308
+ try:
309
+ payload = run_cli(args.lark_cli_bin, env, cmd)
310
+ return {
311
+ "deleted_doc_token": token,
312
+ "raw": unwrap_payload(payload),
313
+ }
314
+ except CLIError as exc:
315
+ last_exc = exc
316
+ assert last_exc is not None
317
+ raise last_exc
318
+
319
+
320
+ def media_insert(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
321
+ if not args.doc:
322
+ raise CLIError("missing --doc for media-insert", code="validation")
323
+ if not args.file:
324
+ raise CLIError("missing --file for media-insert", code="validation")
325
+
326
+ file_cwd, cli_file = localize_cli_path(args.file)
327
+ cmd = [
328
+ "docs",
329
+ "+media-insert",
330
+ "--as",
331
+ args.identity,
332
+ "--doc",
333
+ args.doc,
334
+ "--file",
335
+ cli_file,
336
+ "--type",
337
+ args.media_type,
338
+ ]
339
+ if args.align:
340
+ cmd.extend(["--align", args.align])
341
+ if args.caption:
342
+ cmd.extend(["--caption", args.caption])
343
+
344
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd, cwd=file_cwd))
345
+ if not isinstance(data, dict):
346
+ raise CLIError(
347
+ "unexpected docs +media-insert payload", code="invalid_media_insert_payload"
348
+ )
349
+ return data
350
+
351
+
352
+ def media_download(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
353
+ if not args.token:
354
+ raise CLIError("missing --token for media-download", code="validation")
355
+ if not args.output:
356
+ raise CLIError("missing --output for media-download", code="validation")
357
+
358
+ output_cwd, cli_output = localize_cli_path(args.output)
359
+ cmd = [
360
+ "docs",
361
+ "+media-download",
362
+ "--as",
363
+ args.identity,
364
+ "--token",
365
+ args.token,
366
+ "--type",
367
+ args.resource_type,
368
+ "--output",
369
+ cli_output,
370
+ ]
371
+ if args.overwrite:
372
+ cmd.append("--overwrite")
373
+
374
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd, cwd=output_cwd))
375
+ if not isinstance(data, dict):
376
+ raise CLIError(
377
+ "unexpected docs +media-download payload",
378
+ code="invalid_media_download_payload",
379
+ )
380
+ saved_path = data.get("saved_path")
381
+ if isinstance(saved_path, str) and not os.path.isabs(saved_path):
382
+ data["saved_path"] = os.path.abspath(os.path.join(output_cwd, saved_path))
383
+ return data
384
+
385
+
386
+ def build_comment_content(args: argparse.Namespace) -> str:
387
+ if args.comment_json:
388
+ try:
389
+ parsed = json.loads(args.comment_json)
390
+ except json.JSONDecodeError as exc:
391
+ raise CLIError(
392
+ "--comment-json must be valid JSON", code="validation"
393
+ ) from exc
394
+ if not isinstance(parsed, list):
395
+ raise CLIError(
396
+ "--comment-json must be a reply_elements JSON array",
397
+ code="validation",
398
+ )
399
+ return args.comment_json
400
+
401
+ if args.comment:
402
+ return json.dumps(
403
+ [{"type": "text", "text": args.comment}],
404
+ ensure_ascii=False,
405
+ )
406
+
407
+ raise CLIError(
408
+ "missing --comment or --comment-json for comment", code="validation"
409
+ )
410
+
411
+
412
+ def add_comment(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
413
+ if not args.doc:
414
+ raise CLIError("missing --doc for comment", code="validation")
415
+
416
+ cmd = [
417
+ "drive",
418
+ "+add-comment",
419
+ "--as",
420
+ args.identity,
421
+ "--doc",
422
+ args.doc,
423
+ "--content",
424
+ build_comment_content(args),
425
+ ]
426
+ if args.full_comment:
427
+ cmd.append("--full-comment")
428
+ if args.selection_with_ellipsis:
429
+ cmd.extend(["--selection-with-ellipsis", args.selection_with_ellipsis])
430
+ if args.block_id:
431
+ cmd.extend(["--block-id", args.block_id])
432
+
433
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd))
434
+ if not isinstance(data, dict):
435
+ raise CLIError(
436
+ "unexpected drive +add-comment payload", code="invalid_comment_payload"
437
+ )
438
+ return data
439
+
440
+
441
+ def normalize_section_markdown(section_title: str, markdown: str) -> str:
442
+ stripped = markdown.lstrip()
443
+ if stripped.startswith("#"):
444
+ return markdown
445
+ return f"{section_title}\n\n{markdown}"
446
+
447
+
448
+ def update_section(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
449
+ if not args.doc:
450
+ raise CLIError("missing --doc for section-update", code="validation")
451
+ if not args.section_title:
452
+ raise CLIError("missing --section-title for section-update", code="validation")
453
+
454
+ mode_map = {
455
+ "replace": "replace_range",
456
+ "delete": "delete_range",
457
+ "insert_before": "insert_before",
458
+ "insert_after": "insert_after",
459
+ }
460
+ cli_mode = mode_map[args.section_mode]
461
+ cmd = [
462
+ "docs",
463
+ "+update",
464
+ "--as",
465
+ args.identity,
466
+ "--doc",
467
+ args.doc,
468
+ "--mode",
469
+ cli_mode,
470
+ "--selection-by-title",
471
+ args.section_title,
472
+ ]
473
+ if args.new_title:
474
+ cmd.extend(["--new-title", args.new_title])
475
+ if args.section_mode != "delete":
476
+ if not args.markdown:
477
+ raise CLIError(
478
+ "missing --markdown for non-delete section-update",
479
+ code="validation",
480
+ )
481
+ markdown = args.markdown
482
+ if args.section_mode == "replace":
483
+ markdown = normalize_section_markdown(args.section_title, markdown)
484
+ cmd.extend(["--markdown", markdown])
485
+
486
+ data = unwrap_payload(run_cli(args.lark_cli_bin, env, cmd))
487
+ if not isinstance(data, dict):
488
+ raise CLIError(
489
+ "unexpected section update payload", code="invalid_section_update_payload"
490
+ )
491
+ enriched = dict(data)
492
+ enriched["section_title"] = args.section_title
493
+ enriched["section_mode"] = args.section_mode
494
+ return enriched
495
+
496
+
497
+ def run(args: argparse.Namespace, env: dict[str, str]) -> dict[str, Any]:
498
+ if args.op == "create":
499
+ return create_note(args, env)
500
+ if args.op == "read":
501
+ return read_note(args, env)
502
+ if args.op == "update":
503
+ return update_note(args, env)
504
+ if args.op == "search":
505
+ return search_note(args, env)
506
+ if args.op == "delete":
507
+ return delete_note(args, env)
508
+ if args.op == "media-insert":
509
+ return media_insert(args, env)
510
+ if args.op == "media-download":
511
+ return media_download(args, env)
512
+ if args.op == "comment":
513
+ return add_comment(args, env)
514
+ if args.op == "section-update":
515
+ return update_section(args, env)
516
+ raise CLIError(f"unsupported op: {args.op}", code="validation")
517
+
518
+
519
+ def main() -> int:
520
+ args = parse_args()
521
+ env = build_env(args.config_dir)
522
+ try:
523
+ data = run(args, env)
524
+ out = {
525
+ "ok": True,
526
+ "op": args.op,
527
+ "data": data,
528
+ "error": None,
529
+ }
530
+ except CLIError as exc:
531
+ out = {
532
+ "ok": False,
533
+ "op": args.op,
534
+ "data": None,
535
+ "error": {
536
+ "code": exc.code,
537
+ "message": str(exc),
538
+ "detail": exc.detail,
539
+ },
540
+ }
541
+ json.dump(out, sys.stdout, ensure_ascii=False, indent=2)
542
+ sys.stdout.write("\n")
543
+ return 0
544
+
545
+
546
+ if __name__ == "__main__":
547
+ raise SystemExit(main())