sophhub 0.2.3 → 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/consensus/skill.json +20 -0
- package/skills/consensus/src/SKILL.md +93 -0
- package/skills/deepwiki/skill.json +20 -0
- package/skills/deepwiki/src/SKILL.md +45 -0
- package/skills/deepwiki/src/_meta.json +6 -0
- package/skills/deepwiki/src/scripts/deepwiki.js +135 -0
- package/skills/feishu-bitable/skill.json +20 -0
- package/skills/feishu-bitable/src/CHECKLIST.md +150 -0
- package/skills/feishu-bitable/src/README.md +178 -0
- package/skills/feishu-bitable/src/SKILL.md +113 -0
- package/skills/feishu-bitable/src/_meta.json +6 -0
- package/skills/feishu-bitable/src/api.js +381 -0
- package/skills/feishu-bitable/src/bin/cli.js +284 -0
- package/skills/feishu-bitable/src/description.md +143 -0
- package/skills/feishu-bitable/src/examples/create-records.json +52 -0
- package/skills/feishu-bitable/src/examples/create-table.json +64 -0
- package/skills/feishu-bitable/src/package-lock.json +324 -0
- package/skills/feishu-bitable/src/package.json +33 -0
- package/skills/feishu-bitable/src/publish-config.json +14 -0
- package/skills/feishu-bitable/src/test-simple.js +61 -0
- package/skills/feishu-bitable/src/utils.js +261 -0
- package/skills/google-maps/skill.json +20 -0
- package/skills/google-maps/src/SKILL.md +237 -0
- package/skills/google-maps/src/_meta.json +6 -0
- package/skills/google-maps/src/lib/map_helper.py +912 -0
- package/skills/large-task-router/skill.json +20 -0
- package/skills/large-task-router/src/SKILL.md +79 -0
- package/skills/large-task-router/src/templates/plan.md +74 -0
- 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
- package/skills/skillhub/skill.json +11 -4
- package/skills/skillhub/src/SKILL.md +11 -1
- package/skills/sophnet-dailynews/skill.json +20 -0
- package/skills/sophnet-dailynews/src/SKILL.md +179 -0
- package/skills/sophnet-dailynews/src/cache.json +151 -0
- package/skills/sophnet-dailynews/src/sources.json +230 -0
- package/skills/sophnet-schedule/skill.json +20 -0
- package/skills/sophnet-schedule/src/ARCHITECTURE.md +321 -0
- package/skills/sophnet-schedule/src/IMPROVEMENTS.md +145 -0
- package/skills/sophnet-schedule/src/SKILL.md +1050 -0
- package/skills/sophnet-schedule/src/_meta.json +6 -0
- package/skills/sophnet-schedule/src/api/__init__.py +0 -0
- package/skills/sophnet-schedule/src/api/models.py +245 -0
- package/skills/sophnet-schedule/src/apps/add_event.py +237 -0
- package/skills/sophnet-schedule/src/apps/check_reminders.py +112 -0
- package/skills/sophnet-schedule/src/apps/check_roc.py +246 -0
- package/skills/sophnet-schedule/src/apps/generate_daily_plan.py +342 -0
- package/skills/sophnet-schedule/src/apps/import_events.py +216 -0
- package/skills/sophnet-schedule/src/apps/monitor_calendar_changes.py +140 -0
- package/skills/sophnet-schedule/src/apps/register_tasks.py +169 -0
- package/skills/sophnet-schedule/src/apps/sync_roc_to_gcal.py +174 -0
- package/skills/sophnet-schedule/src/compat.py +66 -0
- package/skills/sophnet-schedule/src/config/__init__.py +0 -0
- package/skills/sophnet-schedule/src/config/reminder_rules.yaml +96 -0
- package/skills/sophnet-schedule/src/config/roc_events.yaml +44 -0
- package/skills/sophnet-schedule/src/config/settings.py +133 -0
- package/skills/sophnet-schedule/src/config/task_registry.yaml +92 -0
- package/skills/sophnet-schedule/src/docs/FRONTEND_INTEGRATION_GUIDE.md +437 -0
- package/skills/sophnet-schedule/src/gcal/__init__.py +0 -0
- package/skills/sophnet-schedule/src/gcal/client.py +374 -0
- package/skills/sophnet-schedule/src/gcal/models.py +91 -0
- package/skills/sophnet-schedule/src/requirements.txt +6 -0
- package/skills/sophnet-schedule/src/scripts/setup_gcal_token.py +85 -0
- package/skills/sophnet-schedule/src/server.py +669 -0
- package/skills/sophnet-schedule/src/services/__init__.py +0 -0
- package/skills/sophnet-schedule/src/services/calendar_backend.py +139 -0
- package/skills/sophnet-schedule/src/services/conflict_detector.py +96 -0
- package/skills/sophnet-schedule/src/services/datetime_utils.py +117 -0
- package/skills/sophnet-schedule/src/services/event_classifier.py +100 -0
- package/skills/sophnet-schedule/src/services/event_diff.py +160 -0
- package/skills/sophnet-schedule/src/services/google_integration.py +500 -0
- package/skills/sophnet-schedule/src/services/job_store.py +100 -0
- package/skills/sophnet-schedule/src/services/local_event_store.py +266 -0
- package/skills/sophnet-schedule/src/services/reminder_planner.py +116 -0
- package/skills/sophnet-schedule/src/services/runtime_utils.py +31 -0
- package/skills/sophnet-schedule/src/services/table_parser.py +286 -0
- package/skills/sophnet-schedule/src/services/task_builder.py +167 -0
- package/skills/sophnet-schedule/src/services/time_window.py +72 -0
- package/skills/sophnet-stock/skill.json +20 -0
- package/skills/sophnet-stock/src/App-Plan.md +442 -0
- package/skills/sophnet-stock/src/README.md +214 -0
- package/skills/sophnet-stock/src/SKILL.md +236 -0
- package/skills/sophnet-stock/src/TODO.md +394 -0
- package/skills/sophnet-stock/src/_meta.json +6 -0
- package/skills/sophnet-stock/src/docs/ARCHITECTURE.md +408 -0
- package/skills/sophnet-stock/src/docs/CONCEPT.md +233 -0
- package/skills/sophnet-stock/src/docs/HOT_SCANNER.md +288 -0
- package/skills/sophnet-stock/src/docs/README.md +95 -0
- package/skills/sophnet-stock/src/docs/USAGE.md +465 -0
- package/skills/sophnet-stock/src/scripts/analyze_stock.py +2565 -0
- package/skills/sophnet-stock/src/scripts/dividends.py +365 -0
- package/skills/sophnet-stock/src/scripts/hot_scanner.py +582 -0
- package/skills/sophnet-stock/src/scripts/portfolio.py +548 -0
- package/skills/sophnet-stock/src/scripts/rumor_scanner.py +342 -0
- package/skills/sophnet-stock/src/scripts/test_stock_analysis.py +409 -0
- package/skills/sophnet-stock/src/scripts/watchlist.py +336 -0
- package/skills/xiaohongshu/skill.json +20 -0
- package/skills/xiaohongshu/src/SKILL.md +91 -0
- package/skills/xiaohongshu/src/_meta.json +6 -0
- package/skills/xiaohongshu/src/assets/card.html +216 -0
- package/skills/xiaohongshu/src/assets/cover.html +82 -0
- package/skills/xiaohongshu/src/assets/example.md +84 -0
- package/skills/xiaohongshu/src/assets/styles.css +318 -0
- package/skills/xiaohongshu/src/scripts/render_xhs_v2.py +737 -0
- package/skills/xiaohongshu/src/scripts/sign_server.py +158 -0
- package/skills/xiaohongshu/src/scripts/stealth.min.js +7 -0
- package/skills/xiaohongshu/src/scripts/xhs_tool.py +186 -0
- package/skills/xiaohongshu/src/workflow.py +185 -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())
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skillhub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"types": [
|
|
5
5
|
"builtin"
|
|
6
6
|
],
|
|
7
|
-
"displayName": "",
|
|
8
|
-
"description": "",
|
|
7
|
+
"displayName": "技能商店与安装",
|
|
8
|
+
"description": "在 Sophnet/Sophclaw 上搜索、列出、安装 skill(含 UI 设计等关键词);SophHub 优先,ClawHub 兜底。",
|
|
9
9
|
"changelog": [
|
|
10
|
+
{
|
|
11
|
+
"version": "2.0.1",
|
|
12
|
+
"date": "2026-04-16",
|
|
13
|
+
"changes": [
|
|
14
|
+
"扩充 SKILL.md 触发描述:覆盖「Sophnet UI 设计 skill」「再找一下」等口语检索场景"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
10
17
|
{
|
|
11
18
|
"version": "2.0.0",
|
|
12
19
|
"date": "2026-04-09",
|
|
@@ -16,5 +23,5 @@
|
|
|
16
23
|
}
|
|
17
24
|
],
|
|
18
25
|
"createdAt": "2026-04-09",
|
|
19
|
-
"updatedAt": "2026-04-
|
|
26
|
+
"updatedAt": "2026-04-16"
|
|
20
27
|
}
|
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: skillhub
|
|
3
|
-
description:
|
|
3
|
+
description: 当用户要在 Sophnet / Sophclaw / OpenClaw 上查找、搜索、列出或安装 skill(技能、插件)时使用;包括「商店里有没有某某 skill」「Sophnet 有没有 UI 设计 / 前端 / 图表 / 自动化 相关 skill」「再去 SophHub 找一下」「用 npx sophhub 搜 skill」等说法。也适用于 clawhub、社区技能商店、下载 skill 到本地。与具体业务实现无关、仅涉及「发现与安装技能」时选本 skill。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SkillHub
|
|
7
7
|
|
|
8
8
|
统一的技能安装入口,整合 Sophnet 平台 Sophclaw 的技能商店(SophHub,优先)和 **ClawHub**(社区商店,兜底)。
|
|
9
9
|
|
|
10
|
+
## 何时应使用本 Skill(触发提示)
|
|
11
|
+
|
|
12
|
+
用户可能用口语或中英文混说,只要意图是 **在商店里找 skill / 安装 skill**,即应走本流程,例如:
|
|
13
|
+
|
|
14
|
+
- 「Sophnet / sophnet 有没有 **UI 设计** / UX / 界面 / 前端 相关的 skill?」「商店里有没有做海报 / 图表 / 视频的 skill?」
|
|
15
|
+
- 「你再去 **找一下** skill」「在 SophHub / 技能商店 **搜一下**」「**列出** 商店里能装的技能」
|
|
16
|
+
- 「**安装** 某个 skill」「从 Sophnet 平台 **下载** skill 到本地」「npx sophhub 怎么用」
|
|
17
|
+
|
|
18
|
+
**不要**把「已经知道 skill 名称、只想写业务代码」误判为本 skill;但若用户明确要先在商店里 **检索、确认、安装** 再使用,仍用本 skill。
|
|
19
|
+
|
|
10
20
|
## 安装流程
|
|
11
21
|
|
|
12
22
|
当用户需要安装一个技能时,按以下顺序操作:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sophnet-dailynews",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"types": [
|
|
5
|
+
"store"
|
|
6
|
+
],
|
|
7
|
+
"displayName": "每日新闻助手",
|
|
8
|
+
"description": "支持实时新闻检索、分类、摘要与分析,以及基于 AI 的个性化新闻推荐和深度报道。",
|
|
9
|
+
"changelog": [
|
|
10
|
+
{
|
|
11
|
+
"version": "1.0.0",
|
|
12
|
+
"date": "2026-04-14",
|
|
13
|
+
"changes": [
|
|
14
|
+
"初次提交"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"createdAt": "2026-04-14",
|
|
19
|
+
"updatedAt": "2026-04-14"
|
|
20
|
+
}
|