sophhub 0.2.1 → 0.2.2

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 (187) 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/image-classify/skill.json +5 -5
  21. package/skills/image-classify/src/SKILL.md +60 -67
  22. package/skills/image-classify/src/scripts/face_search.py +400 -15
  23. package/skills/image-classify/src/scripts/send_dm_message.py +332 -0
  24. package/skills/md2pdf-converter/skill.json +20 -0
  25. package/skills/md2pdf-converter/src/SKILL.md +244 -0
  26. package/skills/md2pdf-converter/src/_meta.json +6 -0
  27. package/skills/md2pdf-converter/src/scripts/generate_emoji_mapping.py +74 -0
  28. package/skills/md2pdf-converter/src/scripts/md2pdf-local.sh +291 -0
  29. package/skills/sophnet-bot-client/skill.json +20 -0
  30. package/skills/sophnet-bot-client/src/SKILL.md +255 -0
  31. package/skills/sophnet-bot-client/src/pyproject.toml +13 -0
  32. package/skills/sophnet-bot-client/src/scripts/__init__.py +0 -0
  33. package/skills/sophnet-bot-client/src/scripts/bot_client_proxy.py +165 -0
  34. package/skills/sophnet-bot-client/src/scripts/bot_client_safe.sh +29 -0
  35. package/skills/sophnet-bot-client/src/scripts/bot_client_setup.py +502 -0
  36. package/skills/sophnet-bot-client/src/tests/__init__.py +0 -0
  37. package/skills/sophnet-bot-client/src/tests/test_bot_client_proxy.py +255 -0
  38. package/skills/sophnet-bot-client/src/tests/test_bot_client_setup.py +679 -0
  39. package/skills/sophnet-bot-client/src/uv.lock +8 -0
  40. package/skills/sophnet-docx/skill.json +20 -0
  41. package/skills/sophnet-docx/src/SKILL.md +463 -0
  42. package/skills/sophnet-docx/src/package-lock.json +208 -0
  43. package/skills/sophnet-docx/src/package.json +16 -0
  44. package/skills/sophnet-docx/src/pyproject.toml +11 -0
  45. package/skills/sophnet-docx/src/scripts/__init__.py +1 -0
  46. package/skills/sophnet-docx/src/scripts/accept_changes.py +135 -0
  47. package/skills/sophnet-docx/src/scripts/comment.py +318 -0
  48. package/skills/sophnet-docx/src/scripts/ensure_uv_env.sh +68 -0
  49. package/skills/sophnet-docx/src/scripts/office/helpers/__init__.py +0 -0
  50. package/skills/sophnet-docx/src/scripts/office/helpers/merge_runs.py +199 -0
  51. package/skills/sophnet-docx/src/scripts/office/helpers/simplify_redlines.py +197 -0
  52. package/skills/sophnet-docx/src/scripts/office/pack.py +159 -0
  53. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  54. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  55. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  56. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  57. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  58. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  59. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  60. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  61. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  62. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  63. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  64. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  65. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  66. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  67. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  68. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  69. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  70. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  71. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  72. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  73. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  74. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  75. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  76. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  77. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  78. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  79. package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  80. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  81. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  82. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  83. package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  84. package/skills/sophnet-docx/src/scripts/office/schemas/mce/mc.xsd +75 -0
  85. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  86. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  87. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  88. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  89. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  90. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  91. package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  92. package/skills/sophnet-docx/src/scripts/office/soffice.py +183 -0
  93. package/skills/sophnet-docx/src/scripts/office/unpack.py +132 -0
  94. package/skills/sophnet-docx/src/scripts/office/validate.py +111 -0
  95. package/skills/sophnet-docx/src/scripts/office/validators/__init__.py +15 -0
  96. package/skills/sophnet-docx/src/scripts/office/validators/base.py +847 -0
  97. package/skills/sophnet-docx/src/scripts/office/validators/docx.py +446 -0
  98. package/skills/sophnet-docx/src/scripts/office/validators/pptx.py +275 -0
  99. package/skills/sophnet-docx/src/scripts/office/validators/redlining.py +247 -0
  100. package/skills/sophnet-docx/src/scripts/templates/comments.xml +3 -0
  101. package/skills/sophnet-docx/src/scripts/templates/commentsExtended.xml +3 -0
  102. package/skills/sophnet-docx/src/scripts/templates/commentsExtensible.xml +3 -0
  103. package/skills/sophnet-docx/src/scripts/templates/commentsIds.xml +3 -0
  104. package/skills/sophnet-docx/src/scripts/templates/people.xml +3 -0
  105. package/skills/sophnet-docx/src/scripts/upload_file.sh +96 -0
  106. package/skills/sophnet-docx/src/uv.lock +320 -0
  107. package/skills/sophnet-pdf/skill.json +20 -0
  108. package/skills/sophnet-pdf/src/SKILL.md +413 -0
  109. package/skills/sophnet-pdf/src/forms.md +297 -0
  110. package/skills/sophnet-pdf/src/pyproject.toml +14 -0
  111. package/skills/sophnet-pdf/src/reference.md +612 -0
  112. package/skills/sophnet-pdf/src/scripts/check_bounding_boxes.py +65 -0
  113. package/skills/sophnet-pdf/src/scripts/check_fillable_fields.py +11 -0
  114. package/skills/sophnet-pdf/src/scripts/convert_pdf_to_images.py +33 -0
  115. package/skills/sophnet-pdf/src/scripts/create_validation_image.py +37 -0
  116. package/skills/sophnet-pdf/src/scripts/enhance_tutorial.py +558 -0
  117. package/skills/sophnet-pdf/src/scripts/ensure_uv_env.sh +68 -0
  118. package/skills/sophnet-pdf/src/scripts/extract_form_field_info.py +122 -0
  119. package/skills/sophnet-pdf/src/scripts/extract_form_structure.py +115 -0
  120. package/skills/sophnet-pdf/src/scripts/extract_pdf_content.py +35 -0
  121. package/skills/sophnet-pdf/src/scripts/fill_fillable_fields.py +98 -0
  122. package/skills/sophnet-pdf/src/scripts/fill_pdf_form_with_annotations.py +107 -0
  123. package/skills/sophnet-pdf/src/scripts/upload_file.sh +88 -0
  124. package/skills/sophnet-pdf/src/uv.lock +537 -0
  125. package/skills/sophnet-xlsx/skill.json +20 -0
  126. package/skills/sophnet-xlsx/src/SKILL.md +399 -0
  127. package/skills/sophnet-xlsx/src/pyproject.toml +11 -0
  128. package/skills/sophnet-xlsx/src/scripts/ensure_uv_env.sh +68 -0
  129. package/skills/sophnet-xlsx/src/scripts/office/helpers/__init__.py +0 -0
  130. package/skills/sophnet-xlsx/src/scripts/office/helpers/merge_runs.py +199 -0
  131. package/skills/sophnet-xlsx/src/scripts/office/helpers/simplify_redlines.py +197 -0
  132. package/skills/sophnet-xlsx/src/scripts/office/pack.py +159 -0
  133. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  134. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  135. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  136. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  137. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  138. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  139. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  140. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  141. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  142. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  143. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  144. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  145. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  146. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  147. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  148. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  149. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  150. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  151. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  152. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  153. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  154. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  155. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  156. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  157. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  158. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  159. package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  160. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  161. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  162. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  163. package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  164. package/skills/sophnet-xlsx/src/scripts/office/schemas/mce/mc.xsd +75 -0
  165. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  166. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  167. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  168. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  169. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  170. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  171. package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  172. package/skills/sophnet-xlsx/src/scripts/office/soffice.py +183 -0
  173. package/skills/sophnet-xlsx/src/scripts/office/unpack.py +132 -0
  174. package/skills/sophnet-xlsx/src/scripts/office/validate.py +111 -0
  175. package/skills/sophnet-xlsx/src/scripts/office/validators/__init__.py +15 -0
  176. package/skills/sophnet-xlsx/src/scripts/office/validators/base.py +847 -0
  177. package/skills/sophnet-xlsx/src/scripts/office/validators/docx.py +446 -0
  178. package/skills/sophnet-xlsx/src/scripts/office/validators/pptx.py +275 -0
  179. package/skills/sophnet-xlsx/src/scripts/office/validators/redlining.py +247 -0
  180. package/skills/sophnet-xlsx/src/scripts/recalc.py +184 -0
  181. package/skills/sophnet-xlsx/src/scripts/upload_file.sh +96 -0
  182. package/skills/sophnet-xlsx/src/uv.lock +319 -0
  183. package/skills/wechat-article-publisher/skill.json +20 -0
  184. package/skills/wechat-article-publisher/src/SKILL.md +60 -0
  185. package/skills/wechat-article-publisher/src/config.json +7 -0
  186. package/skills/wechat-article-publisher/src/pyproject.toml +12 -0
  187. package/skills/wechat-article-publisher/src/scripts/publish_wechat.py +825 -0
@@ -0,0 +1,462 @@
1
+ #!/usr/bin/env python3
2
+ """OpenClaw downstream meeting-minutes orchestrator.
3
+
4
+ This script implements the reusable half of the OpenClaw flow:
5
+
6
+ minute_token -> lark-cli minutes/vc/docs -> Markdown -> JSON result
7
+
8
+ It intentionally does not upload audio into Feishu Minutes. That step must be
9
+ handled by an upstream ingest adapter which returns a minute_token.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import os
17
+ import subprocess
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+
23
+ REQUIRED_SCOPES = (
24
+ "vc:note:read "
25
+ "minutes:minutes:readonly "
26
+ "minutes:minutes.artifacts:read "
27
+ "minutes:minutes.transcript:export "
28
+ "docx:document:create"
29
+ )
30
+
31
+
32
+ class CLIError(Exception):
33
+ def __init__(self, message: str, code: str = "cli_error", detail: Any = None):
34
+ super().__init__(message)
35
+ self.code = code
36
+ self.detail = detail
37
+
38
+
39
+ def parse_args() -> argparse.Namespace:
40
+ parser = argparse.ArgumentParser(
41
+ description="Build meeting minutes Markdown from an existing minute_token."
42
+ )
43
+ parser.add_argument("--minute-token", required=True, help="Feishu minute_token")
44
+ parser.add_argument("--job-id", required=True, help="OpenClaw job ID")
45
+ parser.add_argument("--title", help="Override meeting title")
46
+ parser.add_argument("--folder-token", help="Target Feishu folder token")
47
+ parser.add_argument(
48
+ "--output-dir",
49
+ default=".openclaw-meeting-minutes",
50
+ help="Directory for transcript/artifact files",
51
+ )
52
+ parser.add_argument(
53
+ "--config-dir",
54
+ help="Override LARKSUITE_CLI_CONFIG_DIR for per-user isolation",
55
+ )
56
+ parser.add_argument(
57
+ "--lark-cli-bin",
58
+ default="lark-cli",
59
+ help="Path to the lark-cli executable",
60
+ )
61
+ return parser.parse_args()
62
+
63
+
64
+ def build_env(config_dir: str | None) -> dict[str, str]:
65
+ env = os.environ.copy()
66
+ if config_dir:
67
+ env["LARKSUITE_CLI_CONFIG_DIR"] = config_dir
68
+ return env
69
+
70
+
71
+ def run_cli(
72
+ cli_bin: str,
73
+ env: dict[str, str],
74
+ args: list[str],
75
+ check: bool = True,
76
+ cwd: Path | None = None,
77
+ ) -> Any:
78
+ proc = subprocess.run(
79
+ [cli_bin] + args,
80
+ capture_output=True,
81
+ text=True,
82
+ env=env,
83
+ cwd=str(cwd) if cwd else None,
84
+ )
85
+ stdout = proc.stdout.strip()
86
+ stderr = proc.stderr.strip()
87
+
88
+ parsed: Any = None
89
+ if stdout:
90
+ try:
91
+ parsed = json.loads(stdout)
92
+ except json.JSONDecodeError as exc:
93
+ raise CLIError(
94
+ f"lark-cli returned non-JSON stdout: {stdout[:400]}",
95
+ code="invalid_cli_output",
96
+ detail={"stderr": stderr, "args": args},
97
+ ) from exc
98
+
99
+ if check and proc.returncode != 0:
100
+ error_detail = parsed if isinstance(parsed, dict) else {"stderr": stderr}
101
+ raise CLIError(
102
+ f"lark-cli command failed: {' '.join(args)}",
103
+ code="lark_cli_failed",
104
+ detail=error_detail,
105
+ )
106
+
107
+ return parsed
108
+
109
+
110
+ def unwrap_payload(payload: Any) -> Any:
111
+ if isinstance(payload, dict) and "ok" in payload:
112
+ if not payload.get("ok", False):
113
+ error = payload.get("error") or {}
114
+ raise CLIError(
115
+ error.get("message", "lark-cli returned an error envelope"),
116
+ code=error.get("type", "lark_error"),
117
+ detail=payload,
118
+ )
119
+ return payload.get("data")
120
+ return payload
121
+
122
+
123
+ def ensure_auth(cli_bin: str, env: dict[str, str]) -> None:
124
+ run_cli(cli_bin, env, ["auth", "check", "--scope", REQUIRED_SCOPES], check=True)
125
+
126
+
127
+ def minute_info(cli_bin: str, env: dict[str, str], minute_token: str) -> dict[str, Any]:
128
+ payload = run_cli(
129
+ cli_bin,
130
+ env,
131
+ [
132
+ "api",
133
+ "GET",
134
+ f"/open-apis/minutes/v1/minutes/{minute_token}",
135
+ "--as",
136
+ "user",
137
+ "--format",
138
+ "json",
139
+ ],
140
+ )
141
+ data = unwrap_payload(payload)
142
+ if isinstance(data, dict) and "data" in data and isinstance(data["data"], dict):
143
+ data = data["data"]
144
+ if not isinstance(data, dict):
145
+ raise CLIError("unexpected minutes.get payload", code="invalid_minutes_payload")
146
+ minute = data.get("minute")
147
+ if not isinstance(minute, dict):
148
+ raise CLIError("minutes.get missing minute object", code="missing_minute")
149
+ return minute
150
+
151
+
152
+ def notes_info(
153
+ cli_bin: str, env: dict[str, str], minute_token: str, output_dir: Path
154
+ ) -> dict[str, Any]:
155
+ output_dir = output_dir.resolve()
156
+ payload = run_cli(
157
+ cli_bin,
158
+ env,
159
+ [
160
+ "vc",
161
+ "+notes",
162
+ "--minute-tokens",
163
+ minute_token,
164
+ "--output-dir",
165
+ "vc-notes-artifacts",
166
+ "--format",
167
+ "json",
168
+ ],
169
+ cwd=output_dir,
170
+ )
171
+ data = unwrap_payload(payload)
172
+ if not isinstance(data, dict):
173
+ raise CLIError("unexpected vc +notes payload", code="invalid_notes_payload")
174
+ notes = data.get("notes")
175
+ if not isinstance(notes, list) or not notes:
176
+ raise CLIError("vc +notes returned no notes", code="missing_notes")
177
+ first = notes[0]
178
+ if not isinstance(first, dict):
179
+ raise CLIError("invalid note item shape", code="invalid_note_item")
180
+ if first.get("error"):
181
+ raise CLIError(str(first["error"]), code="note_query_failed", detail=first)
182
+ artifacts = first.get("artifacts")
183
+ if isinstance(artifacts, dict):
184
+ transcript_file = artifacts.get("transcript_file")
185
+ if isinstance(transcript_file, str) and transcript_file:
186
+ transcript_path = Path(transcript_file)
187
+ if not transcript_path.is_absolute():
188
+ artifacts["transcript_file"] = str(
189
+ (output_dir / transcript_path).resolve()
190
+ )
191
+ return first
192
+
193
+
194
+ def read_text(path: str | None) -> str:
195
+ if not path:
196
+ return ""
197
+ p = Path(path)
198
+ if not p.exists():
199
+ return ""
200
+ return p.read_text(encoding="utf-8").strip()
201
+
202
+
203
+ def transcript_excerpt(text: str, limit: int = 2000) -> str:
204
+ if not text:
205
+ return ""
206
+ if len(text) <= limit:
207
+ return text
208
+ return text[:limit].rstrip() + "\n..."
209
+
210
+
211
+ def format_inline(value: Any) -> str:
212
+ if value in (None, "", [], {}):
213
+ return "暂无 AI 产物"
214
+ if isinstance(value, str):
215
+ return value.strip() or "暂无 AI 产物"
216
+ if isinstance(value, list):
217
+ items = [format_list_item(item) for item in value]
218
+ items = [item for item in items if item]
219
+ return "\n".join(items) if items else "暂无 AI 产物"
220
+ if isinstance(value, dict):
221
+ return "```json\n" + json.dumps(value, ensure_ascii=False, indent=2) + "\n```"
222
+ return str(value)
223
+
224
+
225
+ def format_list_item(item: Any) -> str:
226
+ if isinstance(item, str):
227
+ return f"- {item}"
228
+ if isinstance(item, dict):
229
+ preferred = [
230
+ item.get("title"),
231
+ item.get("summary"),
232
+ item.get("content"),
233
+ item.get("text"),
234
+ item.get("todo"),
235
+ item.get("speaker"),
236
+ ]
237
+ text = next((str(v).strip() for v in preferred if v), "")
238
+ if text:
239
+ extras: list[str] = []
240
+ for key in ("assignee", "owner", "deadline", "timestamp", "time"):
241
+ if item.get(key):
242
+ extras.append(f"{key}: {item[key]}")
243
+ if extras:
244
+ return f"- {text} ({', '.join(extras)})"
245
+ return f"- {text}"
246
+ return "- " + json.dumps(item, ensure_ascii=False, sort_keys=True)
247
+ return f"- {item}"
248
+
249
+
250
+ def build_markdown(
251
+ title: str,
252
+ minute_token: str,
253
+ minutes_url: str,
254
+ note: dict[str, Any],
255
+ transcript_text: str,
256
+ doc_url: str = "",
257
+ ) -> str:
258
+ artifacts = note.get("artifacts") or {}
259
+ if not isinstance(artifacts, dict):
260
+ artifacts = {}
261
+ lines = [
262
+ f"# {title}",
263
+ "",
264
+ "## 会议信息",
265
+ "",
266
+ f"- 标题:{title}",
267
+ f"- minute_token:`{minute_token}`",
268
+ f"- 妙记链接:{minutes_url or '暂无'}",
269
+ f"- 纪要文档 Token:`{note.get('note_doc_token') or '暂无'}`",
270
+ f"- 逐字稿文档 Token:`{note.get('verbatim_doc_token') or '暂无'}`",
271
+ f"- 逐字稿文件:{artifacts.get('transcript_file') or '暂无'}",
272
+ "",
273
+ "## 摘要",
274
+ "",
275
+ format_inline(artifacts.get("summary")),
276
+ "",
277
+ "## 待办",
278
+ "",
279
+ format_inline(artifacts.get("todos")),
280
+ "",
281
+ "## 章节",
282
+ "",
283
+ format_inline(artifacts.get("chapters")),
284
+ "",
285
+ "## 逐字稿",
286
+ "",
287
+ transcript_text or "暂无 AI 产物",
288
+ "",
289
+ "## 原始飞书产物",
290
+ "",
291
+ f"- 妙记链接:{minutes_url or '暂无'}",
292
+ f"- 飞书文档链接:{doc_url or '待创建'}",
293
+ f"- 纪要文档 Token:`{note.get('note_doc_token') or '暂无'}`",
294
+ f"- 逐字稿文档 Token:`{note.get('verbatim_doc_token') or '暂无'}`",
295
+ ]
296
+ return "\n".join(lines).strip() + "\n"
297
+
298
+
299
+ def create_doc(
300
+ cli_bin: str,
301
+ env: dict[str, str],
302
+ title: str,
303
+ markdown: str,
304
+ folder_token: str | None,
305
+ ) -> dict[str, Any]:
306
+ args = [
307
+ "docs",
308
+ "+create",
309
+ "--as",
310
+ "user",
311
+ "--title",
312
+ title,
313
+ "--markdown",
314
+ markdown,
315
+ ]
316
+ if folder_token:
317
+ args.extend(["--folder-token", folder_token])
318
+ payload = run_cli(cli_bin, env, args)
319
+ data = unwrap_payload(payload)
320
+ if not isinstance(data, dict):
321
+ raise CLIError("unexpected docs +create payload", code="invalid_doc_payload")
322
+ return data
323
+
324
+
325
+ def partial_result(
326
+ job_id: str,
327
+ title: str,
328
+ minute_token: str,
329
+ minutes_url: str,
330
+ markdown: str,
331
+ transcript_file: str,
332
+ transcript_text: str,
333
+ error: CLIError,
334
+ ) -> dict[str, Any]:
335
+ return {
336
+ "job_id": job_id,
337
+ "status": "partial",
338
+ "title": title,
339
+ "minute_token": minute_token,
340
+ "minutes_url": minutes_url,
341
+ "markdown": markdown,
342
+ "feishu_doc_url": "",
343
+ "feishu_doc_token": "",
344
+ "transcript_file": transcript_file,
345
+ "transcript_excerpt": transcript_excerpt(transcript_text),
346
+ "error_code": error.code,
347
+ "error_message": str(error),
348
+ }
349
+
350
+
351
+ def failed_result(job_id: str, minute_token: str, error: CLIError) -> dict[str, Any]:
352
+ return {
353
+ "job_id": job_id,
354
+ "status": "failed",
355
+ "title": "",
356
+ "minute_token": minute_token,
357
+ "minutes_url": "",
358
+ "markdown": "",
359
+ "feishu_doc_url": "",
360
+ "feishu_doc_token": "",
361
+ "transcript_file": "",
362
+ "transcript_excerpt": "",
363
+ "error_code": error.code,
364
+ "error_message": str(error),
365
+ }
366
+
367
+
368
+ def main() -> int:
369
+ args = parse_args()
370
+ env = build_env(args.config_dir)
371
+ base_output_dir = Path(args.output_dir).resolve()
372
+ job_dir = base_output_dir / args.job_id
373
+ job_dir.mkdir(parents=True, exist_ok=True)
374
+
375
+ try:
376
+ ensure_auth(args.lark_cli_bin, env)
377
+ minute = minute_info(args.lark_cli_bin, env, args.minute_token)
378
+ note = notes_info(args.lark_cli_bin, env, args.minute_token, job_dir)
379
+ except CLIError as exc:
380
+ json.dump(
381
+ failed_result(args.job_id, args.minute_token, exc),
382
+ sys.stdout,
383
+ ensure_ascii=False,
384
+ indent=2,
385
+ )
386
+ sys.stdout.write("\n")
387
+ return 0
388
+
389
+ title = args.title or str(minute.get("title") or "会议纪要")
390
+ minutes_url = str(minute.get("url") or "")
391
+ artifacts = note.get("artifacts") or {}
392
+ transcript_file = ""
393
+ if isinstance(artifacts, dict):
394
+ transcript_file = str(artifacts.get("transcript_file") or "")
395
+ transcript_text = read_text(transcript_file)
396
+
397
+ markdown = build_markdown(
398
+ title=title,
399
+ minute_token=args.minute_token,
400
+ minutes_url=minutes_url,
401
+ note=note,
402
+ transcript_text=transcript_text,
403
+ )
404
+
405
+ try:
406
+ doc = create_doc(
407
+ args.lark_cli_bin,
408
+ env,
409
+ title,
410
+ markdown,
411
+ args.folder_token,
412
+ )
413
+ except CLIError as exc:
414
+ json.dump(
415
+ partial_result(
416
+ job_id=args.job_id,
417
+ title=title,
418
+ minute_token=args.minute_token,
419
+ minutes_url=minutes_url,
420
+ markdown=markdown,
421
+ transcript_file=transcript_file,
422
+ transcript_text=transcript_text,
423
+ error=exc,
424
+ ),
425
+ sys.stdout,
426
+ ensure_ascii=False,
427
+ indent=2,
428
+ )
429
+ sys.stdout.write("\n")
430
+ return 0
431
+
432
+ doc_url = str(doc.get("doc_url") or "")
433
+ doc_token = str(doc.get("doc_id") or "")
434
+ final_markdown = build_markdown(
435
+ title=title,
436
+ minute_token=args.minute_token,
437
+ minutes_url=minutes_url,
438
+ note=note,
439
+ transcript_text=transcript_text,
440
+ doc_url=doc_url,
441
+ )
442
+ result = {
443
+ "job_id": args.job_id,
444
+ "status": "succeeded",
445
+ "title": title,
446
+ "minute_token": args.minute_token,
447
+ "minutes_url": minutes_url,
448
+ "markdown": final_markdown,
449
+ "feishu_doc_url": doc_url,
450
+ "feishu_doc_token": doc_token,
451
+ "transcript_file": transcript_file,
452
+ "transcript_excerpt": transcript_excerpt(transcript_text),
453
+ "error_code": "",
454
+ "error_message": "",
455
+ }
456
+ json.dump(result, sys.stdout, ensure_ascii=False, indent=2)
457
+ sys.stdout.write("\n")
458
+ return 0
459
+
460
+
461
+ if __name__ == "__main__":
462
+ raise SystemExit(main())