@tikomni/skills 1.0.5 → 1.0.7

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 (26) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +3 -3
  3. package/env.example +2 -2
  4. package/package.json +2 -2
  5. package/skills/social-media-crawl/SKILL.md +20 -16
  6. package/skills/social-media-crawl/agents/openai.yaml +2 -2
  7. package/skills/social-media-crawl/references/api-catalog/aliases.json +237 -0
  8. package/skills/social-media-crawl/references/api-catalog/capabilities.json +362 -0
  9. package/skills/social-media-crawl/references/api-catalog/metadata.json +11 -0
  10. package/skills/social-media-crawl/references/api-catalog/operations.json +100875 -0
  11. package/skills/social-media-crawl/references/api-catalog/overrides.json +10 -0
  12. package/skills/social-media-crawl/references/api-catalog/platforms.json +463 -0
  13. package/skills/social-media-crawl/references/api-routing-contract.md +73 -0
  14. package/skills/social-media-crawl/references/contracts/output-envelope.md +1 -1
  15. package/skills/social-media-crawl/scripts/core/api_catalog.py +104 -0
  16. package/skills/social-media-crawl/scripts/core/call_tikomni_api.py +269 -0
  17. package/skills/social-media-crawl/scripts/core/config_loader.py +0 -2
  18. package/skills/social-media-crawl/scripts/core/resolve_api_endpoint.py +176 -0
  19. package/skills/social-media-crawl/scripts/pipelines/run_douyin_single_work.py +16 -17
  20. package/skills/social-media-crawl/scripts/pipelines/run_xiaohongshu_single_work.py +16 -16
  21. package/skills/social-media-crawl/scripts/run_task.py +96 -0
  22. package/skills/social-media-crawl/tests/test_api_only_routing.py +130 -0
  23. package/skills/social-media-crawl/tests/test_fixed_pipeline_fallback.py +50 -0
  24. package/skills/social-media-crawl/references/guides/generic-mcp-objects.md +0 -40
  25. package/skills/social-media-crawl/references/mcp-usage-contract.md +0 -40
  26. package/skills/social-media-crawl/scripts/core/mcp_dispatch.py +0 -161
@@ -0,0 +1,10 @@
1
+ {
2
+ "capability_overrides": {},
3
+ "notes": [
4
+ "endpoint_id is generated automatically and must not be manually overridden.",
5
+ "Overrides may adjust platform, capability, recommended endpoint, alternatives, and aliases only."
6
+ ],
7
+ "platform_overrides": {},
8
+ "recommendation_overrides": {},
9
+ "schema_version": "v1"
10
+ }
@@ -0,0 +1,463 @@
1
+ [
2
+ {
3
+ "aliases": [
4
+ "b23.tv",
5
+ "bilibili",
6
+ "b站",
7
+ "哔哩哔哩"
8
+ ],
9
+ "capabilities": [
10
+ "comments",
11
+ "general",
12
+ "hot_search",
13
+ "livestream",
14
+ "media_upload",
15
+ "search",
16
+ "transcription",
17
+ "user_posts",
18
+ "user_profile",
19
+ "video_detail"
20
+ ],
21
+ "operation_count": 41,
22
+ "slug": "bilibili"
23
+ },
24
+ {
25
+ "aliases": [
26
+ "demo"
27
+ ],
28
+ "capabilities": [
29
+ "general"
30
+ ],
31
+ "operation_count": 1,
32
+ "slug": "demo"
33
+ },
34
+ {
35
+ "aliases": [
36
+ "douyin",
37
+ "dy",
38
+ "iesdouyin.com",
39
+ "v.douyin.com",
40
+ "抖音"
41
+ ],
42
+ "capabilities": [
43
+ "article_detail",
44
+ "comments",
45
+ "general",
46
+ "hot_search",
47
+ "livestream",
48
+ "media_upload",
49
+ "music",
50
+ "note_detail",
51
+ "product_detail",
52
+ "ranking",
53
+ "search",
54
+ "user_posts",
55
+ "user_profile",
56
+ "video_detail"
57
+ ],
58
+ "operation_count": 292,
59
+ "slug": "douyin"
60
+ },
61
+ {
62
+ "aliases": [
63
+ "douyin_search"
64
+ ],
65
+ "capabilities": [
66
+ "search"
67
+ ],
68
+ "operation_count": 1,
69
+ "slug": "douyin_search"
70
+ },
71
+ {
72
+ "aliases": [
73
+ "health"
74
+ ],
75
+ "capabilities": [
76
+ "general"
77
+ ],
78
+ "operation_count": 1,
79
+ "slug": "health"
80
+ },
81
+ {
82
+ "aliases": [
83
+ "hybrid"
84
+ ],
85
+ "capabilities": [
86
+ "video_detail"
87
+ ],
88
+ "operation_count": 1,
89
+ "slug": "hybrid"
90
+ },
91
+ {
92
+ "aliases": [
93
+ "ig",
94
+ "ins",
95
+ "instagram"
96
+ ],
97
+ "capabilities": [
98
+ "comments",
99
+ "general",
100
+ "music",
101
+ "ranking",
102
+ "search",
103
+ "user_posts",
104
+ "user_profile"
105
+ ],
106
+ "operation_count": 89,
107
+ "slug": "instagram"
108
+ },
109
+ {
110
+ "aliases": [
111
+ "ios_shortcut"
112
+ ],
113
+ "capabilities": [
114
+ "general"
115
+ ],
116
+ "operation_count": 1,
117
+ "slug": "ios_shortcut"
118
+ },
119
+ {
120
+ "aliases": [
121
+ "ks",
122
+ "kuaishou",
123
+ "快手"
124
+ ],
125
+ "capabilities": [
126
+ "comments",
127
+ "general",
128
+ "hot_search",
129
+ "livestream",
130
+ "note_detail",
131
+ "product_detail",
132
+ "search",
133
+ "transcription",
134
+ "user_posts",
135
+ "user_profile",
136
+ "video_detail"
137
+ ],
138
+ "operation_count": 34,
139
+ "slug": "kuaishou"
140
+ },
141
+ {
142
+ "aliases": [
143
+ "lemon8"
144
+ ],
145
+ "capabilities": [
146
+ "comments",
147
+ "general",
148
+ "hot_search",
149
+ "search",
150
+ "user_profile"
151
+ ],
152
+ "operation_count": 16,
153
+ "slug": "lemon8"
154
+ },
155
+ {
156
+ "aliases": [
157
+ "linkedin",
158
+ "领英"
159
+ ],
160
+ "capabilities": [
161
+ "comments",
162
+ "general",
163
+ "search",
164
+ "user_posts",
165
+ "user_profile"
166
+ ],
167
+ "operation_count": 25,
168
+ "slug": "linkedin"
169
+ },
170
+ {
171
+ "aliases": [
172
+ "pipixia",
173
+ "皮皮虾"
174
+ ],
175
+ "capabilities": [
176
+ "comments",
177
+ "general",
178
+ "hot_search",
179
+ "search",
180
+ "user_posts",
181
+ "user_profile",
182
+ "video_detail"
183
+ ],
184
+ "operation_count": 17,
185
+ "slug": "pipixia"
186
+ },
187
+ {
188
+ "aliases": [
189
+ "reddit"
190
+ ],
191
+ "capabilities": [
192
+ "comments",
193
+ "general",
194
+ "livestream",
195
+ "note_detail",
196
+ "ranking",
197
+ "search",
198
+ "user_profile"
199
+ ],
200
+ "operation_count": 24,
201
+ "slug": "reddit"
202
+ },
203
+ {
204
+ "aliases": [
205
+ "sora2"
206
+ ],
207
+ "capabilities": [
208
+ "comments",
209
+ "media_upload",
210
+ "note_detail",
211
+ "search",
212
+ "user_profile"
213
+ ],
214
+ "operation_count": 17,
215
+ "slug": "sora2"
216
+ },
217
+ {
218
+ "aliases": [
219
+ "temp_mail"
220
+ ],
221
+ "capabilities": [
222
+ "general",
223
+ "user_profile"
224
+ ],
225
+ "operation_count": 3,
226
+ "slug": "temp_mail"
227
+ },
228
+ {
229
+ "aliases": [
230
+ "threads"
231
+ ],
232
+ "capabilities": [
233
+ "comments",
234
+ "search",
235
+ "user_posts",
236
+ "user_profile"
237
+ ],
238
+ "operation_count": 11,
239
+ "slug": "threads"
240
+ },
241
+ {
242
+ "aliases": [
243
+ "tik tok",
244
+ "tiktok"
245
+ ],
246
+ "capabilities": [
247
+ "comments",
248
+ "general",
249
+ "livestream",
250
+ "media_upload",
251
+ "music",
252
+ "note_detail",
253
+ "product_detail",
254
+ "ranking",
255
+ "search",
256
+ "user_posts",
257
+ "user_profile",
258
+ "video_detail"
259
+ ],
260
+ "operation_count": 208,
261
+ "slug": "tiktok"
262
+ },
263
+ {
264
+ "aliases": [
265
+ "toutiao",
266
+ "今日头条",
267
+ "头条"
268
+ ],
269
+ "capabilities": [
270
+ "article_detail",
271
+ "comments",
272
+ "user_profile",
273
+ "video_detail"
274
+ ],
275
+ "operation_count": 7,
276
+ "slug": "toutiao"
277
+ },
278
+ {
279
+ "aliases": [
280
+ "twitter",
281
+ "x",
282
+ "x.com",
283
+ "推特"
284
+ ],
285
+ "capabilities": [
286
+ "comments",
287
+ "general",
288
+ "ranking",
289
+ "search",
290
+ "user_posts",
291
+ "user_profile"
292
+ ],
293
+ "operation_count": 13,
294
+ "slug": "twitter"
295
+ },
296
+ {
297
+ "aliases": [
298
+ "asr",
299
+ "transcription",
300
+ "u2",
301
+ "语音转写",
302
+ "转文案"
303
+ ],
304
+ "capabilities": [
305
+ "transcription"
306
+ ],
307
+ "operation_count": 2,
308
+ "slug": "u2"
309
+ },
310
+ {
311
+ "aliases": [
312
+ "media upload",
313
+ "u3",
314
+ "upload",
315
+ "中转",
316
+ "媒体上传"
317
+ ],
318
+ "capabilities": [
319
+ "media_upload"
320
+ ],
321
+ "operation_count": 2,
322
+ "slug": "u3"
323
+ },
324
+ {
325
+ "aliases": [
326
+ "wechat"
327
+ ],
328
+ "capabilities": [
329
+ "article_url"
330
+ ],
331
+ "operation_count": 1,
332
+ "slug": "wechat"
333
+ },
334
+ {
335
+ "aliases": [
336
+ "wechat channels",
337
+ "wechat_channels",
338
+ "微信视频号",
339
+ "视频号"
340
+ ],
341
+ "capabilities": [
342
+ "comments",
343
+ "hot_search",
344
+ "livestream",
345
+ "search",
346
+ "user_profile",
347
+ "video_detail"
348
+ ],
349
+ "operation_count": 10,
350
+ "slug": "wechat_channels"
351
+ },
352
+ {
353
+ "aliases": [
354
+ "mp.weixin.qq.com",
355
+ "wechat official accounts",
356
+ "wechat_mp",
357
+ "公众号",
358
+ "微信公众号"
359
+ ],
360
+ "capabilities": [
361
+ "article_detail",
362
+ "article_list",
363
+ "article_url",
364
+ "comments"
365
+ ],
366
+ "operation_count": 10,
367
+ "slug": "wechat_mp"
368
+ },
369
+ {
370
+ "aliases": [
371
+ "weibo",
372
+ "微博"
373
+ ],
374
+ "capabilities": [
375
+ "article_list",
376
+ "comments",
377
+ "general",
378
+ "hot_search",
379
+ "music",
380
+ "note_detail",
381
+ "ranking",
382
+ "search",
383
+ "transcription",
384
+ "user_posts",
385
+ "user_profile"
386
+ ],
387
+ "operation_count": 64,
388
+ "slug": "weibo"
389
+ },
390
+ {
391
+ "aliases": [
392
+ "xhs",
393
+ "xhslink.com",
394
+ "xiaohongshu",
395
+ "xiaohongshu.com",
396
+ "小红书"
397
+ ],
398
+ "capabilities": [
399
+ "comments",
400
+ "general",
401
+ "note_detail",
402
+ "product_detail",
403
+ "ranking",
404
+ "search",
405
+ "user_posts",
406
+ "user_profile"
407
+ ],
408
+ "operation_count": 78,
409
+ "slug": "xiaohongshu"
410
+ },
411
+ {
412
+ "aliases": [
413
+ "xigua",
414
+ "西瓜视频"
415
+ ],
416
+ "capabilities": [
417
+ "comments",
418
+ "search",
419
+ "user_posts",
420
+ "user_profile",
421
+ "video_detail"
422
+ ],
423
+ "operation_count": 7,
424
+ "slug": "xigua"
425
+ },
426
+ {
427
+ "aliases": [
428
+ "youtu.be",
429
+ "youtube",
430
+ "yt"
431
+ ],
432
+ "capabilities": [
433
+ "comments",
434
+ "general",
435
+ "livestream",
436
+ "media_upload",
437
+ "note_detail",
438
+ "ranking",
439
+ "search",
440
+ "transcription",
441
+ "user_profile",
442
+ "video_detail"
443
+ ],
444
+ "operation_count": 44,
445
+ "slug": "youtube"
446
+ },
447
+ {
448
+ "aliases": [
449
+ "zhihu",
450
+ "知乎"
451
+ ],
452
+ "capabilities": [
453
+ "article_detail",
454
+ "comments",
455
+ "general",
456
+ "search",
457
+ "user_profile",
458
+ "video_detail"
459
+ ],
460
+ "operation_count": 34,
461
+ "slug": "zhihu"
462
+ }
463
+ ]
@@ -0,0 +1,73 @@
1
+ # API Routing Contract
2
+
3
+ ## Scope
4
+
5
+ This skill calls TikOmni API only and does not require any external server configuration.
6
+
7
+ Supported social-media tasks include single works, posts, creator homepages, comments, search results, rankings, livestreams, products, subtitles, transcripts, and structured fields.
8
+
9
+ ## Required Inputs
10
+
11
+ - Auth: `TIKOMNI_API_KEY`.
12
+ - Default API base URL: `https://api.tikomni.com`.
13
+ - Optional override: `TIKOMNI_BASE_URL`.
14
+
15
+ Do not repeat the API key inside tool parameters.
16
+
17
+ ## Required Route Order
18
+
19
+ 1. Detect platform and capability.
20
+ 2. If a fixed pipeline matches, run that fixed pipeline.
21
+ 3. Otherwise resolve a TikOmni API endpoint:
22
+
23
+ ```text
24
+ python scripts/core/resolve_api_endpoint.py --platform <platform> --capability <capability> --json '<context>'
25
+ ```
26
+
27
+ 4. Call the API by `endpoint_id`:
28
+
29
+ ```text
30
+ python scripts/core/call_tikomni_api.py --endpoint-id <endpoint_id> --params '<json>'
31
+ ```
32
+
33
+ 5. For common tasks, the one-shot entry is allowed:
34
+
35
+ ```text
36
+ python scripts/run_task.py --platform <platform> --capability <capability> --params '<json>'
37
+ ```
38
+
39
+ ## Hard Rules
40
+
41
+ - Do not construct TikOmni API paths manually.
42
+ - Do not pass arbitrary `path` or `method` values to callers.
43
+ - Do not call endpoint paths that are absent from `references/api-catalog/operations.json`.
44
+ - Resolve `endpoint_id` first, then call the API.
45
+ - Return recommended endpoint and alternatives to the user or agent.
46
+ - Do not automatically call alternatives; alternatives are explicit next choices.
47
+ - If platform or capability is ambiguous, return a clarification request instead of guessing.
48
+
49
+ ## Variant Priority
50
+
51
+ For endpoints under the same platform and capability, default priority is:
52
+
53
+ ```text
54
+ app_v3 > app_v2 > app_v1/app > web_v3 > web_v2 > web_v1/web
55
+ ```
56
+
57
+ The catalog generator may apply deterministic specificity rules for common tasks such as hot search or article detail. All alternatives must remain visible.
58
+
59
+ ## U2/U3
60
+
61
+ U2 and U3 stay inside this skill because they support video-to-text workflows:
62
+
63
+ - U2 handles ASR / transcription.
64
+ - U3 handles media upload or relay when a source media URL is not directly usable.
65
+
66
+ Use them only as TikOmni API service capabilities.
67
+
68
+ ## Output Rules
69
+
70
+ - Keep factual fields separate from derived metadata.
71
+ - Include `request_id` when the API returns one.
72
+ - Include `endpoint_id`, `platform`, `capability`, `variant`, and `alternatives`.
73
+ - Include `completeness`, `missing_fields`, `error_reason`, and `extract_trace` in normalized task outputs.
@@ -18,4 +18,4 @@
18
18
  - `completeness` allows `complete`, `partial`, and `incomplete`.
19
19
  - `missing_fields` is the list of missing fields.
20
20
  - `error_reason` may be an empty string or `null` on success.
21
- - `extract_trace` records the fixed-pipeline or MCP dispatch steps. If the flow ends in browser/CDP fallback, it must also record the earlier MCP attempts and the fallback reason.
21
+ - `extract_trace` records fixed-pipeline steps or API-only resolver/caller steps, including `endpoint_id`, selected variant, alternatives, and request status when available.
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+ """Skill-local API catalog helpers."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import re
8
+ from pathlib import Path
9
+ from typing import Any, Dict, Iterable, List, Optional
10
+
11
+ SKILL_ROOT = Path(__file__).resolve().parents[2]
12
+ CATALOG_ROOT = SKILL_ROOT / "references" / "api-catalog"
13
+
14
+
15
+ def normalize_key(value: Any) -> str:
16
+ text = str(value or "").strip().lower()
17
+ text = re.sub(r"\s+", " ", text)
18
+ return text
19
+
20
+
21
+ def load_json(path: Path) -> Any:
22
+ with path.open("r", encoding="utf-8") as handle:
23
+ return json.load(handle)
24
+
25
+
26
+ def load_operations() -> List[Dict[str, Any]]:
27
+ payload = load_json(CATALOG_ROOT / "operations.json")
28
+ if not isinstance(payload, list):
29
+ raise ValueError("api_catalog_operations_must_be_list")
30
+ return [item for item in payload if isinstance(item, dict)]
31
+
32
+
33
+ def load_aliases() -> Dict[str, Any]:
34
+ payload = load_json(CATALOG_ROOT / "aliases.json")
35
+ if not isinstance(payload, dict):
36
+ raise ValueError("api_catalog_aliases_must_be_object")
37
+ return payload
38
+
39
+
40
+ def build_alias_index(alias_map: Dict[str, Iterable[Any]]) -> Dict[str, str]:
41
+ index: Dict[str, str] = {}
42
+ for slug, aliases in alias_map.items():
43
+ index[normalize_key(slug)] = str(slug)
44
+ for alias in aliases or []:
45
+ normalized = normalize_key(alias)
46
+ if normalized:
47
+ index[normalized] = str(slug)
48
+ return index
49
+
50
+
51
+ def resolve_alias(value: Optional[str], alias_map: Dict[str, Iterable[Any]]) -> str:
52
+ if not value:
53
+ return ""
54
+ normalized = normalize_key(value)
55
+ index = build_alias_index(alias_map)
56
+ return index.get(normalized, normalized.replace("-", "_").replace(" ", "_"))
57
+
58
+
59
+ def _has_non_ascii(value: str) -> bool:
60
+ return any(ord(char) > 127 for char in value)
61
+
62
+
63
+ def _alias_in_text(alias: str, normalized_text: str) -> bool:
64
+ if not alias:
65
+ return False
66
+ if _has_non_ascii(alias):
67
+ return alias in normalized_text
68
+ pattern = rf"(?<![a-z0-9_]){re.escape(alias)}(?![a-z0-9_])"
69
+ return re.search(pattern, normalized_text) is not None
70
+
71
+
72
+ def infer_alias_from_text(text: str, alias_map: Dict[str, Iterable[Any]]) -> str:
73
+ normalized_text = normalize_key(text)
74
+ if not normalized_text:
75
+ return ""
76
+ index = build_alias_index(alias_map)
77
+ hits: List[tuple[int, str, str]] = []
78
+ for alias, slug in index.items():
79
+ if not alias:
80
+ continue
81
+ if _alias_in_text(alias, normalized_text):
82
+ hits.append((len(alias), alias, slug))
83
+ if not hits:
84
+ return ""
85
+ hits.sort(reverse=True)
86
+ return hits[0][2]
87
+
88
+
89
+ def operation_by_endpoint_id(operations: Iterable[Dict[str, Any]], endpoint_id: str) -> Optional[Dict[str, Any]]:
90
+ for operation in operations:
91
+ if operation.get("endpoint_id") == endpoint_id:
92
+ return operation
93
+ return None
94
+
95
+
96
+ def sorted_operations(operations: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]:
97
+ return sorted(
98
+ operations,
99
+ key=lambda item: (
100
+ not bool(item.get("recommended")),
101
+ str(item.get("variant") or ""),
102
+ str(item.get("path") or ""),
103
+ ),
104
+ )