sophhub 0.2.4 → 0.3.0
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/notes-hub-assistant/skill.json +20 -0
- package/skills/notes-hub-assistant/src/SKILL.md +233 -0
- package/skills/notes-hub-assistant/src/scripts/_resolve_lark_cli.py +48 -0
- package/skills/notes-hub-assistant/src/scripts/openclaw_meeting_minutes.py +473 -0
- package/skills/notes-hub-assistant/src/scripts/openclaw_notes_crud.py +596 -0
- package/skills/notes-hub-assistant/src/scripts/openclaw_wolai_notes_crud.py +364 -0
- package/skills/notes-hub-assistant/src/scripts/run_meeting_minutes.py +79 -0
- package/skills/notes-hub-assistant/src/scripts/run_note_crud.py +37 -0
- package/skills/notes-hub-assistant/src/scripts/run_notionbot.py +36 -0
- package/skills/notes-hub-assistant/src/scripts/run_wolai_note_crud.py +27 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Wolai OpenAPI CRUD orchestrator."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import urllib.error
|
|
11
|
+
import urllib.parse
|
|
12
|
+
import urllib.request
|
|
13
|
+
from typing import Any, Dict, List, Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WolaiError(Exception):
|
|
17
|
+
def __init__(self, message: str, code: str = "wolai_error", detail: Any = None):
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.code = code
|
|
20
|
+
self.detail = detail
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def parse_args() -> argparse.Namespace:
|
|
24
|
+
parser = argparse.ArgumentParser(description="Wolai note CRUD via OpenAPI")
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--op",
|
|
27
|
+
required=True,
|
|
28
|
+
choices=["create", "read", "read-children", "update", "delete", "search"],
|
|
29
|
+
help="wolai operation",
|
|
30
|
+
)
|
|
31
|
+
parser.add_argument("--id", help="block/page id")
|
|
32
|
+
parser.add_argument("--parent-id", help="parent page or block id")
|
|
33
|
+
parser.add_argument("--content", help="plain text content for simple text blocks")
|
|
34
|
+
parser.add_argument(
|
|
35
|
+
"--block-json",
|
|
36
|
+
help='raw JSON for blocks payload, e.g. {"type":"text","content":"Hello"}',
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument("--query", help="keyword for lightweight search")
|
|
39
|
+
parser.add_argument("--cursor", help="pagination cursor")
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--limit",
|
|
42
|
+
type=int,
|
|
43
|
+
default=50,
|
|
44
|
+
help="page size for list endpoints (max 200 recommended)",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--app-token", help="Wolai app token; if missing, read WOLAI_APP_TOKEN env"
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument("--app-id", help="Wolai app id; fallback WOLAI_APP_ID env")
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"--app-secret", help="Wolai app secret; fallback WOLAI_APP_SECRET env"
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--base-url",
|
|
56
|
+
default=os.environ.get("WOLAI_OPENAPI_BASE_URL", "https://openapi.wolai.com/v1"),
|
|
57
|
+
help="Wolai OpenAPI base url",
|
|
58
|
+
)
|
|
59
|
+
return parser.parse_args()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def normalize_base_url(base_url: str) -> str:
|
|
63
|
+
return base_url.rstrip("/")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def parse_json_arg(raw: Optional[str], *, arg_name: str) -> Any:
|
|
67
|
+
if not raw:
|
|
68
|
+
return None
|
|
69
|
+
try:
|
|
70
|
+
return json.loads(raw)
|
|
71
|
+
except json.JSONDecodeError as exc:
|
|
72
|
+
raise WolaiError(
|
|
73
|
+
f"{arg_name} must be valid JSON",
|
|
74
|
+
code="validation",
|
|
75
|
+
detail={"arg": arg_name},
|
|
76
|
+
) from exc
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def build_text_block(content: str) -> Dict[str, Any]:
|
|
80
|
+
return {"type": "text", "content": content}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def request_json(
|
|
84
|
+
method: str,
|
|
85
|
+
url: str,
|
|
86
|
+
token: Optional[str] = None,
|
|
87
|
+
payload: Optional[Dict[str, Any]] = None,
|
|
88
|
+
) -> Any:
|
|
89
|
+
body = None
|
|
90
|
+
base_headers = {
|
|
91
|
+
"Accept": "application/json",
|
|
92
|
+
}
|
|
93
|
+
if payload is not None:
|
|
94
|
+
body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
|
|
95
|
+
base_headers["Content-Type"] = "application/json; charset=utf-8"
|
|
96
|
+
|
|
97
|
+
auth_variants: List[Optional[str]] = [None]
|
|
98
|
+
if token:
|
|
99
|
+
token_value = str(token).strip()
|
|
100
|
+
auth_variants = [token_value]
|
|
101
|
+
if token_value.lower().startswith("bearer "):
|
|
102
|
+
raw_token = token_value[7:].strip()
|
|
103
|
+
if raw_token:
|
|
104
|
+
auth_variants.append(raw_token)
|
|
105
|
+
else:
|
|
106
|
+
auth_variants.append(f"Bearer {token_value}")
|
|
107
|
+
|
|
108
|
+
last_http_error: Optional[urllib.error.HTTPError] = None
|
|
109
|
+
for idx, auth in enumerate(auth_variants):
|
|
110
|
+
headers = dict(base_headers)
|
|
111
|
+
if auth:
|
|
112
|
+
headers["Authorization"] = auth
|
|
113
|
+
req = urllib.request.Request(url=url, method=method, data=body, headers=headers)
|
|
114
|
+
try:
|
|
115
|
+
with urllib.request.urlopen(req) as resp:
|
|
116
|
+
raw = resp.read().decode("utf-8")
|
|
117
|
+
if not raw:
|
|
118
|
+
return {}
|
|
119
|
+
return json.loads(raw)
|
|
120
|
+
except urllib.error.HTTPError as exc:
|
|
121
|
+
last_http_error = exc
|
|
122
|
+
can_retry_auth = idx < len(auth_variants) - 1 and exc.code in (401, 403)
|
|
123
|
+
if can_retry_auth:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
raw = exc.read().decode("utf-8", errors="replace")
|
|
127
|
+
detail: Dict[str, Any] = {
|
|
128
|
+
"status": exc.code,
|
|
129
|
+
"body": raw,
|
|
130
|
+
"url": url,
|
|
131
|
+
"auth_variant": auth,
|
|
132
|
+
}
|
|
133
|
+
message = f"Wolai API request failed ({exc.code})"
|
|
134
|
+
try:
|
|
135
|
+
err_payload = json.loads(raw) if raw else {}
|
|
136
|
+
if isinstance(err_payload, dict):
|
|
137
|
+
if err_payload.get("message"):
|
|
138
|
+
message = str(err_payload["message"])
|
|
139
|
+
detail["response"] = err_payload
|
|
140
|
+
except json.JSONDecodeError:
|
|
141
|
+
pass
|
|
142
|
+
raise WolaiError(message, code="http_error", detail=detail) from exc
|
|
143
|
+
except urllib.error.URLError as exc:
|
|
144
|
+
raise WolaiError(
|
|
145
|
+
f"network error: {exc.reason}",
|
|
146
|
+
code="network_error",
|
|
147
|
+
detail={"url": url},
|
|
148
|
+
) from exc
|
|
149
|
+
|
|
150
|
+
if last_http_error:
|
|
151
|
+
raise WolaiError(
|
|
152
|
+
f"Wolai API request failed ({last_http_error.code})",
|
|
153
|
+
code="http_error",
|
|
154
|
+
detail={"url": url},
|
|
155
|
+
)
|
|
156
|
+
raise WolaiError("unexpected request flow", code="internal_error", detail={"url": url})
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def extract_app_token(payload: Dict[str, Any]) -> Optional[str]:
|
|
160
|
+
app_token = payload.get("app_token")
|
|
161
|
+
if isinstance(app_token, str) and app_token.strip():
|
|
162
|
+
return app_token.strip()
|
|
163
|
+
|
|
164
|
+
app_token_obj = payload.get("appToken")
|
|
165
|
+
if isinstance(app_token_obj, str) and app_token_obj.strip():
|
|
166
|
+
return app_token_obj.strip()
|
|
167
|
+
if isinstance(app_token_obj, dict):
|
|
168
|
+
nested = app_token_obj.get("app_token") or app_token_obj.get("appToken")
|
|
169
|
+
if isinstance(nested, str) and nested.strip():
|
|
170
|
+
return nested.strip()
|
|
171
|
+
|
|
172
|
+
data_obj = payload.get("data")
|
|
173
|
+
if isinstance(data_obj, dict):
|
|
174
|
+
nested = (
|
|
175
|
+
data_obj.get("app_token")
|
|
176
|
+
or data_obj.get("appToken")
|
|
177
|
+
or ((data_obj.get("appToken") or {}).get("app_token") if isinstance(data_obj.get("appToken"), dict) else None)
|
|
178
|
+
)
|
|
179
|
+
if isinstance(nested, str) and nested.strip():
|
|
180
|
+
return nested.strip()
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def resolve_token(args: argparse.Namespace, base_url: str) -> str:
|
|
185
|
+
explicit = args.app_token or os.environ.get("WOLAI_APP_TOKEN")
|
|
186
|
+
if explicit:
|
|
187
|
+
return explicit
|
|
188
|
+
|
|
189
|
+
app_id = args.app_id or os.environ.get("WOLAI_APP_ID")
|
|
190
|
+
app_secret = args.app_secret or os.environ.get("WOLAI_APP_SECRET")
|
|
191
|
+
if not app_id or not app_secret:
|
|
192
|
+
raise WolaiError(
|
|
193
|
+
"missing app token or app credentials (WOLAI_APP_TOKEN / WOLAI_APP_ID + WOLAI_APP_SECRET)",
|
|
194
|
+
code="missing_credentials",
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
payload = request_json(
|
|
198
|
+
"POST",
|
|
199
|
+
f"{base_url}/token",
|
|
200
|
+
payload={"appId": app_id, "appSecret": app_secret},
|
|
201
|
+
)
|
|
202
|
+
if not isinstance(payload, dict):
|
|
203
|
+
raise WolaiError("unexpected token response", code="invalid_token_response")
|
|
204
|
+
app_token = extract_app_token(payload)
|
|
205
|
+
if not app_token:
|
|
206
|
+
raise WolaiError(
|
|
207
|
+
"token response missing app token field",
|
|
208
|
+
code="invalid_token_response",
|
|
209
|
+
detail=payload,
|
|
210
|
+
)
|
|
211
|
+
return str(app_token)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def create_block(args: argparse.Namespace, base_url: str, token: str) -> Any:
|
|
215
|
+
if not args.parent_id:
|
|
216
|
+
raise WolaiError("missing --parent-id for create", code="validation")
|
|
217
|
+
blocks = parse_json_arg(args.block_json, arg_name="--block-json")
|
|
218
|
+
if blocks is None:
|
|
219
|
+
if not args.content:
|
|
220
|
+
raise WolaiError(
|
|
221
|
+
"missing --content or --block-json for create", code="validation"
|
|
222
|
+
)
|
|
223
|
+
blocks = build_text_block(args.content)
|
|
224
|
+
return request_json(
|
|
225
|
+
"POST",
|
|
226
|
+
f"{base_url}/blocks",
|
|
227
|
+
token=token,
|
|
228
|
+
payload={"parent_id": args.parent_id, "blocks": blocks},
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def read_block(args: argparse.Namespace, base_url: str, token: str) -> Any:
|
|
233
|
+
if not args.id:
|
|
234
|
+
raise WolaiError("missing --id for read", code="validation")
|
|
235
|
+
return request_json("GET", f"{base_url}/blocks/{args.id}", token=token)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def read_children(args: argparse.Namespace, base_url: str, token: str) -> Any:
|
|
239
|
+
target_id = args.id or args.parent_id
|
|
240
|
+
if not target_id:
|
|
241
|
+
raise WolaiError("missing --id or --parent-id for read-children", code="validation")
|
|
242
|
+
query: Dict[str, Any] = {}
|
|
243
|
+
if args.cursor:
|
|
244
|
+
query["cursor"] = args.cursor
|
|
245
|
+
if args.limit:
|
|
246
|
+
query["limit"] = args.limit
|
|
247
|
+
url = f"{base_url}/blocks/{target_id}/children"
|
|
248
|
+
if query:
|
|
249
|
+
url = f"{url}?{urllib.parse.urlencode(query)}"
|
|
250
|
+
return request_json("GET", url, token=token)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def update_block(args: argparse.Namespace, base_url: str, token: str) -> Any:
|
|
254
|
+
if not args.id:
|
|
255
|
+
raise WolaiError("missing --id for update", code="validation")
|
|
256
|
+
blocks = parse_json_arg(args.block_json, arg_name="--block-json")
|
|
257
|
+
if blocks is None:
|
|
258
|
+
if not args.content:
|
|
259
|
+
raise WolaiError(
|
|
260
|
+
"missing --content or --block-json for update", code="validation"
|
|
261
|
+
)
|
|
262
|
+
blocks = build_text_block(args.content)
|
|
263
|
+
payload = {"blocks": blocks}
|
|
264
|
+
# Wolai docs evolve; try PATCH first, then PUT.
|
|
265
|
+
try:
|
|
266
|
+
return request_json(
|
|
267
|
+
"PATCH",
|
|
268
|
+
f"{base_url}/blocks/{args.id}",
|
|
269
|
+
token=token,
|
|
270
|
+
payload=payload,
|
|
271
|
+
)
|
|
272
|
+
except WolaiError as first_exc:
|
|
273
|
+
try:
|
|
274
|
+
return request_json(
|
|
275
|
+
"PUT",
|
|
276
|
+
f"{base_url}/blocks/{args.id}",
|
|
277
|
+
token=token,
|
|
278
|
+
payload=payload,
|
|
279
|
+
)
|
|
280
|
+
except WolaiError as second_exc:
|
|
281
|
+
raise WolaiError(
|
|
282
|
+
"update failed with both PATCH and PUT",
|
|
283
|
+
code="update_failed",
|
|
284
|
+
detail={"patch_error": first_exc.detail, "put_error": second_exc.detail},
|
|
285
|
+
) from second_exc
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def delete_block(args: argparse.Namespace, base_url: str, token: str) -> Any:
|
|
289
|
+
if not args.id:
|
|
290
|
+
raise WolaiError("missing --id for delete", code="validation")
|
|
291
|
+
return request_json("DELETE", f"{base_url}/blocks/{args.id}", token=token)
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def search_blocks(args: argparse.Namespace, base_url: str, token: str) -> Any:
|
|
295
|
+
# OpenAPI currently has no dedicated full-text search endpoint in this skill.
|
|
296
|
+
# We do lightweight filtering from children list.
|
|
297
|
+
if not args.parent_id and not args.id:
|
|
298
|
+
raise WolaiError(
|
|
299
|
+
"search requires --parent-id (or --id) as search scope",
|
|
300
|
+
code="validation",
|
|
301
|
+
)
|
|
302
|
+
if not args.query:
|
|
303
|
+
raise WolaiError("missing --query for search", code="validation")
|
|
304
|
+
data = read_children(args, base_url, token)
|
|
305
|
+
if not isinstance(data, dict):
|
|
306
|
+
return {"matches": [], "raw": data}
|
|
307
|
+
|
|
308
|
+
records = data.get("data")
|
|
309
|
+
if not isinstance(records, list):
|
|
310
|
+
return {"matches": [], "raw": data}
|
|
311
|
+
|
|
312
|
+
needle = args.query.lower()
|
|
313
|
+
matches: List[Dict[str, Any]] = []
|
|
314
|
+
for item in records:
|
|
315
|
+
if not isinstance(item, dict):
|
|
316
|
+
continue
|
|
317
|
+
hay = json.dumps(item, ensure_ascii=False).lower()
|
|
318
|
+
if needle in hay:
|
|
319
|
+
matches.append(item)
|
|
320
|
+
return {
|
|
321
|
+
"matches": matches,
|
|
322
|
+
"total": len(matches),
|
|
323
|
+
"has_more": data.get("has_more"),
|
|
324
|
+
"next_cursor": data.get("next_cursor"),
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def run(args: argparse.Namespace) -> Any:
|
|
329
|
+
base_url = normalize_base_url(args.base_url)
|
|
330
|
+
token = resolve_token(args, base_url)
|
|
331
|
+
if args.op == "create":
|
|
332
|
+
return create_block(args, base_url, token)
|
|
333
|
+
if args.op == "read":
|
|
334
|
+
return read_block(args, base_url, token)
|
|
335
|
+
if args.op == "read-children":
|
|
336
|
+
return read_children(args, base_url, token)
|
|
337
|
+
if args.op == "update":
|
|
338
|
+
return update_block(args, base_url, token)
|
|
339
|
+
if args.op == "delete":
|
|
340
|
+
return delete_block(args, base_url, token)
|
|
341
|
+
if args.op == "search":
|
|
342
|
+
return search_blocks(args, base_url, token)
|
|
343
|
+
raise WolaiError(f"unsupported op: {args.op}", code="validation")
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def main() -> int:
|
|
347
|
+
args = parse_args()
|
|
348
|
+
try:
|
|
349
|
+
data = run(args)
|
|
350
|
+
out = {"ok": True, "op": args.op, "data": data, "error": None}
|
|
351
|
+
except WolaiError as exc:
|
|
352
|
+
out = {
|
|
353
|
+
"ok": False,
|
|
354
|
+
"op": args.op,
|
|
355
|
+
"data": None,
|
|
356
|
+
"error": {"code": exc.code, "message": str(exc), "detail": exc.detail},
|
|
357
|
+
}
|
|
358
|
+
json.dump(out, sys.stdout, ensure_ascii=False, indent=2)
|
|
359
|
+
sys.stdout.write("\n")
|
|
360
|
+
return 0
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
if __name__ == "__main__":
|
|
364
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cross-platform entrypoint for meeting minutes flow."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from urllib.parse import urlparse
|
|
12
|
+
|
|
13
|
+
from _resolve_lark_cli import resolve_lark_cli_prefix
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_args() -> argparse.Namespace:
|
|
17
|
+
parser = argparse.ArgumentParser(
|
|
18
|
+
description="Run meeting-minutes flow from Feishu minutes URL or token."
|
|
19
|
+
)
|
|
20
|
+
parser.add_argument("minutes_url_or_token", help="minutes URL or minute_token")
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"--output-dir",
|
|
23
|
+
default=str(Path.home() / ".openclaw" / "workspace" / ".meeting-minutes"),
|
|
24
|
+
help="artifact output dir",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument("--job-id", help="optional custom job id")
|
|
27
|
+
return parser.parse_args()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def extract_token(raw: str) -> str:
|
|
31
|
+
if raw.startswith("http://") or raw.startswith("https://"):
|
|
32
|
+
parsed = urlparse(raw)
|
|
33
|
+
token = parsed.path.rstrip("/").split("/")[-1]
|
|
34
|
+
return token
|
|
35
|
+
return raw.strip()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main() -> int:
|
|
39
|
+
args = parse_args()
|
|
40
|
+
token = extract_token(args.minutes_url_or_token)
|
|
41
|
+
if not token:
|
|
42
|
+
print(
|
|
43
|
+
json.dumps(
|
|
44
|
+
{"status": "failed", "error_message": "missing minute_token"},
|
|
45
|
+
ensure_ascii=False,
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
return 1
|
|
49
|
+
|
|
50
|
+
script_dir = Path(__file__).resolve().parent
|
|
51
|
+
base_dir = script_dir.parent
|
|
52
|
+
cli_prefix = resolve_lark_cli_prefix(base_dir)
|
|
53
|
+
runner = script_dir / "openclaw_meeting_minutes.py"
|
|
54
|
+
job_id = args.job_id or f"meeting-minutes-{token}"
|
|
55
|
+
|
|
56
|
+
proc = subprocess.run(
|
|
57
|
+
[
|
|
58
|
+
sys.executable,
|
|
59
|
+
str(runner),
|
|
60
|
+
*sum([["--lark-cli-prefix", p] for p in cli_prefix], []),
|
|
61
|
+
"--minute-token",
|
|
62
|
+
token,
|
|
63
|
+
"--job-id",
|
|
64
|
+
job_id,
|
|
65
|
+
"--output-dir",
|
|
66
|
+
args.output_dir,
|
|
67
|
+
],
|
|
68
|
+
capture_output=True,
|
|
69
|
+
text=True,
|
|
70
|
+
)
|
|
71
|
+
if proc.stdout:
|
|
72
|
+
sys.stdout.write(proc.stdout)
|
|
73
|
+
if proc.stderr:
|
|
74
|
+
sys.stderr.write(proc.stderr)
|
|
75
|
+
return proc.returncode
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
if __name__ == "__main__":
|
|
79
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cross-platform entrypoint for note CRUD flow."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from _resolve_lark_cli import resolve_lark_cli_prefix
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main() -> int:
|
|
14
|
+
script_dir = Path(__file__).resolve().parent
|
|
15
|
+
base_dir = script_dir.parent
|
|
16
|
+
cli_prefix = resolve_lark_cli_prefix(base_dir)
|
|
17
|
+
runner = script_dir / "openclaw_notes_crud.py"
|
|
18
|
+
|
|
19
|
+
proc = subprocess.run(
|
|
20
|
+
[
|
|
21
|
+
sys.executable,
|
|
22
|
+
str(runner),
|
|
23
|
+
*sum([["--lark-cli-prefix", p] for p in cli_prefix], []),
|
|
24
|
+
*sys.argv[1:],
|
|
25
|
+
],
|
|
26
|
+
capture_output=True,
|
|
27
|
+
text=True,
|
|
28
|
+
)
|
|
29
|
+
if proc.stdout:
|
|
30
|
+
sys.stdout.write(proc.stdout)
|
|
31
|
+
if proc.stderr:
|
|
32
|
+
sys.stderr.write(proc.stderr)
|
|
33
|
+
return proc.returncode
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cross-platform local wrapper for vibe-notionbot."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def resolve_prefix() -> list[str]:
|
|
12
|
+
npx = shutil.which("npx")
|
|
13
|
+
if npx:
|
|
14
|
+
return [npx, "-y", "-p", "vibe-notion", "vibe-notionbot"]
|
|
15
|
+
|
|
16
|
+
local_bin = shutil.which("vibe-notionbot")
|
|
17
|
+
if local_bin:
|
|
18
|
+
return [local_bin]
|
|
19
|
+
|
|
20
|
+
raise FileNotFoundError(
|
|
21
|
+
"missing notion runner: npx or vibe-notionbot not found on PATH."
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def main() -> int:
|
|
26
|
+
prefix = resolve_prefix()
|
|
27
|
+
proc = subprocess.run(prefix + sys.argv[1:], capture_output=True, text=True)
|
|
28
|
+
if proc.stdout:
|
|
29
|
+
sys.stdout.write(proc.stdout)
|
|
30
|
+
if proc.stderr:
|
|
31
|
+
sys.stderr.write(proc.stderr)
|
|
32
|
+
return proc.returncode
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cross-platform entrypoint for Wolai OpenAPI note CRUD flow."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def main() -> int:
|
|
12
|
+
script_dir = Path(__file__).resolve().parent
|
|
13
|
+
runner = script_dir / "openclaw_wolai_notes_crud.py"
|
|
14
|
+
proc = subprocess.run(
|
|
15
|
+
[sys.executable, str(runner), *sys.argv[1:]],
|
|
16
|
+
capture_output=True,
|
|
17
|
+
text=True,
|
|
18
|
+
)
|
|
19
|
+
if proc.stdout:
|
|
20
|
+
sys.stdout.write(proc.stdout)
|
|
21
|
+
if proc.stderr:
|
|
22
|
+
sys.stderr.write(proc.stderr)
|
|
23
|
+
return proc.returncode
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
if __name__ == "__main__":
|
|
27
|
+
raise SystemExit(main())
|