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.
- package/package.json +1 -1
- package/skills/compact-context/skill.json +20 -0
- package/skills/compact-context/src/SKILL.md +133 -0
- package/skills/compact-context/src/scripts/check.sh +381 -0
- package/skills/compact-context/src/scripts/set-keep-recent.mjs +1337 -0
- package/skills/compact-context/src/scripts/setup.sh +96 -0
- package/skills/feishu-notes-assistant-universal/skill.json +20 -0
- package/skills/feishu-notes-assistant-universal/src/README.md +55 -0
- package/skills/feishu-notes-assistant-universal/src/SKILL.md +159 -0
- package/skills/feishu-notes-assistant-universal/src/bin/linux-amd64/lark-cli-openclaw +0 -0
- package/skills/feishu-notes-assistant-universal/src/bin/linux-arm64/lark-cli-openclaw +0 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/_resolve_lark_cli.py +58 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_meeting_minutes.py +462 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud.py +547 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/openclaw_notes_crud_test.py +181 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.py +80 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_meeting_minutes.sh +5 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.py +32 -0
- package/skills/feishu-notes-assistant-universal/src/scripts/run_note_crud.sh +5 -0
- package/skills/flight-booking/skill.json +9 -2
- package/skills/flight-booking/src/scripts/flight_booking.py +2 -1
- package/skills/image-classify/skill.json +5 -5
- package/skills/image-classify/src/SKILL.md +60 -67
- package/skills/image-classify/src/scripts/face_search.py +400 -15
- package/skills/image-classify/src/scripts/send_dm_message.py +332 -0
- package/skills/md2pdf-converter/skill.json +20 -0
- package/skills/md2pdf-converter/src/SKILL.md +244 -0
- package/skills/md2pdf-converter/src/_meta.json +6 -0
- package/skills/md2pdf-converter/src/scripts/generate_emoji_mapping.py +74 -0
- package/skills/md2pdf-converter/src/scripts/md2pdf-local.sh +291 -0
- package/skills/sophnet-bot-client/skill.json +20 -0
- package/skills/sophnet-bot-client/src/SKILL.md +255 -0
- package/skills/sophnet-bot-client/src/pyproject.toml +13 -0
- package/skills/sophnet-bot-client/src/scripts/__init__.py +0 -0
- package/skills/sophnet-bot-client/src/scripts/bot_client_proxy.py +165 -0
- package/skills/sophnet-bot-client/src/scripts/bot_client_safe.sh +29 -0
- package/skills/sophnet-bot-client/src/scripts/bot_client_setup.py +502 -0
- package/skills/sophnet-bot-client/src/tests/__init__.py +0 -0
- package/skills/sophnet-bot-client/src/tests/test_bot_client_proxy.py +255 -0
- package/skills/sophnet-bot-client/src/tests/test_bot_client_setup.py +679 -0
- package/skills/sophnet-bot-client/src/uv.lock +8 -0
- package/skills/sophnet-docx/skill.json +20 -0
- package/skills/sophnet-docx/src/SKILL.md +463 -0
- package/skills/sophnet-docx/src/package-lock.json +208 -0
- package/skills/sophnet-docx/src/package.json +16 -0
- package/skills/sophnet-docx/src/pyproject.toml +11 -0
- package/skills/sophnet-docx/src/scripts/__init__.py +1 -0
- package/skills/sophnet-docx/src/scripts/accept_changes.py +135 -0
- package/skills/sophnet-docx/src/scripts/comment.py +318 -0
- package/skills/sophnet-docx/src/scripts/ensure_uv_env.sh +68 -0
- package/skills/sophnet-docx/src/scripts/office/helpers/__init__.py +0 -0
- package/skills/sophnet-docx/src/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/sophnet-docx/src/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/sophnet-docx/src/scripts/office/pack.py +159 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/sophnet-docx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/sophnet-docx/src/scripts/office/soffice.py +183 -0
- package/skills/sophnet-docx/src/scripts/office/unpack.py +132 -0
- package/skills/sophnet-docx/src/scripts/office/validate.py +111 -0
- package/skills/sophnet-docx/src/scripts/office/validators/__init__.py +15 -0
- package/skills/sophnet-docx/src/scripts/office/validators/base.py +847 -0
- package/skills/sophnet-docx/src/scripts/office/validators/docx.py +446 -0
- package/skills/sophnet-docx/src/scripts/office/validators/pptx.py +275 -0
- package/skills/sophnet-docx/src/scripts/office/validators/redlining.py +247 -0
- package/skills/sophnet-docx/src/scripts/templates/comments.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/commentsExtended.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/commentsIds.xml +3 -0
- package/skills/sophnet-docx/src/scripts/templates/people.xml +3 -0
- package/skills/sophnet-docx/src/scripts/upload_file.sh +96 -0
- package/skills/sophnet-docx/src/uv.lock +320 -0
- package/skills/sophnet-pdf/skill.json +20 -0
- package/skills/sophnet-pdf/src/SKILL.md +413 -0
- package/skills/sophnet-pdf/src/forms.md +297 -0
- package/skills/sophnet-pdf/src/pyproject.toml +14 -0
- package/skills/sophnet-pdf/src/reference.md +612 -0
- package/skills/sophnet-pdf/src/scripts/check_bounding_boxes.py +65 -0
- package/skills/sophnet-pdf/src/scripts/check_fillable_fields.py +11 -0
- package/skills/sophnet-pdf/src/scripts/convert_pdf_to_images.py +33 -0
- package/skills/sophnet-pdf/src/scripts/create_validation_image.py +37 -0
- package/skills/sophnet-pdf/src/scripts/enhance_tutorial.py +558 -0
- package/skills/sophnet-pdf/src/scripts/ensure_uv_env.sh +68 -0
- package/skills/sophnet-pdf/src/scripts/extract_form_field_info.py +122 -0
- package/skills/sophnet-pdf/src/scripts/extract_form_structure.py +115 -0
- package/skills/sophnet-pdf/src/scripts/extract_pdf_content.py +35 -0
- package/skills/sophnet-pdf/src/scripts/fill_fillable_fields.py +98 -0
- package/skills/sophnet-pdf/src/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/sophnet-pdf/src/scripts/upload_file.sh +88 -0
- package/skills/sophnet-pdf/src/uv.lock +537 -0
- package/skills/sophnet-xlsx/skill.json +20 -0
- package/skills/sophnet-xlsx/src/SKILL.md +399 -0
- package/skills/sophnet-xlsx/src/pyproject.toml +11 -0
- package/skills/sophnet-xlsx/src/scripts/ensure_uv_env.sh +68 -0
- package/skills/sophnet-xlsx/src/scripts/office/helpers/__init__.py +0 -0
- package/skills/sophnet-xlsx/src/scripts/office/helpers/merge_runs.py +199 -0
- package/skills/sophnet-xlsx/src/scripts/office/helpers/simplify_redlines.py +197 -0
- package/skills/sophnet-xlsx/src/scripts/office/pack.py +159 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/sophnet-xlsx/src/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/sophnet-xlsx/src/scripts/office/soffice.py +183 -0
- package/skills/sophnet-xlsx/src/scripts/office/unpack.py +132 -0
- package/skills/sophnet-xlsx/src/scripts/office/validate.py +111 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/__init__.py +15 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/base.py +847 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/docx.py +446 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/pptx.py +275 -0
- package/skills/sophnet-xlsx/src/scripts/office/validators/redlining.py +247 -0
- package/skills/sophnet-xlsx/src/scripts/recalc.py +184 -0
- package/skills/sophnet-xlsx/src/scripts/upload_file.sh +96 -0
- package/skills/sophnet-xlsx/src/uv.lock +319 -0
- package/skills/wechat-article-publisher/skill.json +20 -0
- package/skills/wechat-article-publisher/src/SKILL.md +60 -0
- package/skills/wechat-article-publisher/src/config.json +7 -0
- package/skills/wechat-article-publisher/src/pyproject.toml +12 -0
- 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())
|