socialdatax-skills 0.2.5 → 0.2.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.
package/README.md CHANGED
@@ -48,7 +48,7 @@ Common search phrases for this skill package:
48
48
  - Kuaishou current platform MCP registry name: `com.52choujiang/kuaishou-insights`
49
49
  - Kuaishou future platform MCP registry name: `com.socialdatax/kuaishou-insights`
50
50
  - Unified MCP registry name: none; this package installs skills and calls explicit platform services.
51
- - Current public capability version: `0.2.5`
51
+ - Current public capability version: `0.2.7`
52
52
 
53
53
  ## Direct CLI
54
54
 
@@ -61,6 +61,7 @@ npx -y socialdatax-skills@latest xhs search --keyword "露营桌" --pretty
61
61
  npx -y socialdatax-skills@latest xhs hot-search --pretty
62
62
  npx -y socialdatax-skills@latest xhs detail --note-id "<note_id>" --pretty
63
63
  npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --pretty
64
+ npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --all --include-replies --pretty
64
65
  npx -y socialdatax-skills@latest xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty
65
66
  npx -y socialdatax-skills@latest xhs user-info --user-id "<user_id>" --pretty
66
67
  npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --pretty
@@ -68,6 +69,7 @@ npx -y socialdatax-skills@latest douyin hot-search --pretty
68
69
  npx -y socialdatax-skills@latest douyin search --keyword "露营桌" --pretty
69
70
  npx -y socialdatax-skills@latest douyin detail --aweme-id "<aweme_id>" --pretty
70
71
  npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --pretty
72
+ npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty
71
73
  npx -y socialdatax-skills@latest douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty
72
74
  npx -y socialdatax-skills@latest douyin user-info --sec-user-id "<sec_user_id>" --pretty
73
75
  npx -y socialdatax-skills@latest douyin user-info --profile-url "<profile_url_or_share_text>" --pretty
@@ -80,6 +82,7 @@ npx -y socialdatax-skills@latest kuaishou search --keyword "露营桌" --pretty
80
82
  npx -y socialdatax-skills@latest kuaishou detail --photo-id "<photo_id>" --pretty
81
83
  npx -y socialdatax-skills@latest kuaishou detail --url "<kuaishou_content_url_or_share_text>" --pretty
82
84
  npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --pretty
85
+ npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty
83
86
  npx -y socialdatax-skills@latest kuaishou comments --url "<kuaishou_content_url_or_share_text>" --pretty
84
87
  npx -y socialdatax-skills@latest kuaishou replies --photo-id "<photo_id>" --comment-id "<comment_id>" --pretty
85
88
  npx -y socialdatax-skills@latest kuaishou user-info --user-id "<user_id>" --pretty
@@ -90,6 +93,16 @@ npx -y socialdatax-skills@latest kuaishou user-posts --profile-url "<profile_url
90
93
 
91
94
  The direct CLI prints a JSON envelope with `platform`, `tool`, `arguments`, and `data`.
92
95
 
96
+ For list-style commands, use `--pages <n>` to fetch N pages from the current starting point and `--max-items <n>` to cap the merged `data.items` output. Search commands support `--pages` but not `--all`, because search has no stable complete-result boundary.
97
+
98
+ For comments, replies, creator content lists, and Douyin creator series, use `--all` to continue until the returned `next_page_token` is empty. `--all` has no default item or page cap; add `--max-items <n>` or use `--pages <n>` when you want a bounded run.
99
+
100
+ For first-level comments, add `--include-replies` to fetch the second-level replies under each returned first-level comment. Multi-page output keeps `data.items` as the merged first-level list; each item gets `replies`, `replies_page_count`, and `replies_next_page_token`.
101
+
102
+ For commands that accept `--page-token`, continue only with the complete returned `next_page_token` from the same pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
103
+
104
+ XHS search uses numeric `--page`; Douyin and Kuaishou search do not accept `--page`. For Douyin and Kuaishou search, omit `--page-token` on the first request and pass the complete returned `next_page_token` only when continuing the same search chain.
105
+
93
106
  Kuaishou search uses `--keyword` and optional `--page-token`; it does not accept Douyin semantic filters.
94
107
 
95
108
  Douyin search filters use semantic values: `--sort-type` supports `general`,
@@ -234,7 +247,7 @@ Current Douyin workflows include:
234
247
  - Search related Douyin works by keyword.
235
248
  - Resolve a Douyin content page link, short link, share text, or aweme_id into structured work details.
236
249
  - Fetch paginated first-level comments for comment analysis.
237
- - Fetch paginated replies under a first-level comment.
250
+ - Fetch paginated replies under a first-level comment; pass both `aweme_id` and `comment_id`, and use `page_token` to continue pagination.
238
251
  - Continue Douyin comment and reply pagination only when `next_page_token` is non-empty; an empty string means there are no more comments or replies to request.
239
252
  - Read creator profile data from a profile link, short link, share text, or sec_user_id.
240
253
  - Fetch paginated creator work lists from a sec_user_id, profile link, short link, or share text.
@@ -261,12 +274,12 @@ Current Douyin workflows include:
261
274
  | Tool | Public purpose |
262
275
  | --- | --- |
263
276
  | `douyin_get_hot_search_list` | Fetch the current Douyin main hot search list. |
264
- | `douyin_search_videos` | Search Douyin works by keyword with optional paging and filters. |
277
+ | `douyin_search_videos` | Search Douyin works by keyword with optional `page_token` continuation and filters; do not pass `page`. |
265
278
  | `douyin_get_video_detail_by_aweme_id` | Fetch structured Douyin work details when the caller already has an aweme_id. |
266
279
  | `douyin_get_video_detail_by_url` | Resolve a Douyin content page link, short link, or share text into structured Douyin work details. |
267
280
  | `douyin_get_video_comments_by_aweme_id` | Fetch paginated first-level comments when the caller already has an aweme_id. |
268
281
  | `douyin_get_video_comments_by_url` | Fetch paginated first-level comments directly from a Douyin content page URL, short link, or share text. |
269
- | `douyin_get_video_comment_replies_by_comment_id` | Fetch paginated replies under a first-level Douyin comment by aweme_id and comment_id. |
282
+ | `douyin_get_video_comment_replies_by_comment_id` | Fetch paginated replies under a first-level Douyin comment; pass both aweme_id and comment_id, and use page_token to continue pagination. |
270
283
  | `douyin_get_user_info_by_sec_user_id` | Fetch creator profile data when the caller already has a sec_user_id. |
271
284
  | `douyin_get_user_info_by_profile_url` | Resolve a profile link, short link, or share text into creator profile data. |
272
285
  | `douyin_get_user_posted_videos_by_sec_user_id` | Fetch a paginated list of works published by a creator when the caller already has a sec_user_id. |
@@ -279,7 +292,7 @@ Current Douyin workflows include:
279
292
  | Tool | Public purpose |
280
293
  | --- | --- |
281
294
  | `kuaishou_get_hot_search_list` | Get the current Kuaishou / 快手 short-video hot list. |
282
- | `kuaishou_search_videos` | Search Kuaishou works by natural-language keyword with optional paging. |
295
+ | `kuaishou_search_videos` | Search Kuaishou works by natural-language keyword with optional `page_token` continuation; do not pass `page`. |
283
296
  | `kuaishou_get_video_detail_by_photo_id` | Fetch structured work details when the caller already has a photo_id. |
284
297
  | `kuaishou_get_video_detail_by_url` | Resolve a Kuaishou work page link, short link, or share text into structured work details. |
285
298
  | `kuaishou_get_video_comments_by_photo_id` | Fetch paginated first-level comments when the caller already has a photo_id. |
package/cli.mjs CHANGED
@@ -7,7 +7,7 @@ import { dirname, join, resolve } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
 
9
9
  const PACKAGE_NAME = "socialdatax-skills";
10
- const PACKAGE_VERSION = "0.2.5";
10
+ const PACKAGE_VERSION = "0.2.7";
11
11
  const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
12
12
  const LOG_PREFIX = `[${PACKAGE_NAME}]`;
13
13
  const MIN_NODE_VERSION = "20.18.1";
@@ -50,17 +50,53 @@ const AVAILABLE_SKILLS = [
50
50
  },
51
51
  ];
52
52
  const AVAILABLE_SKILL_NAMES = AVAILABLE_SKILLS.map((skill) => skill.name);
53
- const BOOLEAN_OPTIONS = new Set(["dryRun", "force", "json", "pretty"]);
53
+ const BOOLEAN_OPTIONS = new Set([
54
+ "all",
55
+ "dryRun",
56
+ "force",
57
+ "includeReplies",
58
+ "json",
59
+ "pretty",
60
+ ]);
61
+ const DIRECT_BOOLEAN_OPTIONS = new Set(["all", "includeReplies", "pretty"]);
54
62
  const INSTALL_TARGETS = ["openclaw", "hermes", "agents", "codex", "claude-code", "claude"];
55
63
  const VALID_SCOPES = ["user", "workspace", "shared"];
56
64
  const XHS_DIRECT_ACTION_OPTIONS = {
57
65
  "hot-search": ["pretty"],
58
- search: ["keyword", "page", "sortType", "noteType", "publishTimeRange", "pretty"],
66
+ search: [
67
+ "keyword",
68
+ "page",
69
+ "pages",
70
+ "all",
71
+ "maxItems",
72
+ "sortType",
73
+ "noteType",
74
+ "publishTimeRange",
75
+ "pretty",
76
+ ],
59
77
  detail: ["noteId", "url", "pretty"],
60
- comments: ["noteId", "url", "pageToken", "pretty"],
61
- "sub-comments": ["noteId", "commentId", "pageToken", "pretty"],
78
+ comments: [
79
+ "noteId",
80
+ "url",
81
+ "pageToken",
82
+ "pages",
83
+ "all",
84
+ "maxItems",
85
+ "includeReplies",
86
+ "pretty",
87
+ ],
88
+ "sub-comments": [
89
+ "noteId",
90
+ "commentId",
91
+ "pageToken",
92
+ "pages",
93
+ "all",
94
+ "maxItems",
95
+ "includeReplies",
96
+ "pretty",
97
+ ],
62
98
  "user-info": ["userId", "profileUrl", "pretty"],
63
- "user-posts": ["userId", "profileUrl", "pageToken", "pretty"],
99
+ "user-posts": ["userId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
64
100
  };
65
101
  const XHS_DIRECT_ACTION_NAMES = Object.keys(XHS_DIRECT_ACTION_OPTIONS).join(", ");
66
102
  const XHS_OPTION_DISPLAY_NAMES = {
@@ -73,6 +109,10 @@ const XHS_OPTION_DISPLAY_NAMES = {
73
109
  noteId: "--note-id",
74
110
  commentId: "--comment-id",
75
111
  pageToken: "--page-token",
112
+ pages: "--pages",
113
+ maxItems: "--max-items",
114
+ all: "--all",
115
+ includeReplies: "--include-replies",
76
116
  profileUrl: "--profile-url",
77
117
  userId: "--user-id",
78
118
  };
@@ -93,6 +133,9 @@ const DOUYIN_DIRECT_ACTION_OPTIONS = {
93
133
  search: [
94
134
  "keyword",
95
135
  "pageToken",
136
+ "pages",
137
+ "all",
138
+ "maxItems",
96
139
  "sortType",
97
140
  "publishTimeRange",
98
141
  "durationRange",
@@ -100,11 +143,29 @@ const DOUYIN_DIRECT_ACTION_OPTIONS = {
100
143
  "pretty",
101
144
  ],
102
145
  detail: ["awemeId", "url", "pretty"],
103
- comments: ["awemeId", "url", "pageToken", "pretty"],
104
- replies: ["awemeId", "commentId", "pageToken", "pretty"],
146
+ comments: [
147
+ "awemeId",
148
+ "url",
149
+ "pageToken",
150
+ "pages",
151
+ "all",
152
+ "maxItems",
153
+ "includeReplies",
154
+ "pretty",
155
+ ],
156
+ replies: [
157
+ "awemeId",
158
+ "commentId",
159
+ "pageToken",
160
+ "pages",
161
+ "all",
162
+ "maxItems",
163
+ "includeReplies",
164
+ "pretty",
165
+ ],
105
166
  "user-info": ["secUserId", "profileUrl", "pretty"],
106
- "user-posts": ["secUserId", "profileUrl", "pageToken", "pretty"],
107
- "user-series": ["secUserId", "profileUrl", "pageToken", "pretty"],
167
+ "user-posts": ["secUserId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
168
+ "user-series": ["secUserId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
108
169
  };
109
170
  const DOUYIN_DIRECT_ACTION_NAMES = Object.keys(DOUYIN_DIRECT_ACTION_OPTIONS).join(", ");
110
171
  const DOUYIN_SEARCH_SORT_TYPES = ["general", "time_descending", "like_count_descending"];
@@ -119,6 +180,10 @@ const DOUYIN_SEARCH_CONTENT_TYPES = ["all", "video", "image"];
119
180
  const DOUYIN_OPTION_DISPLAY_NAMES = {
120
181
  keyword: "--keyword",
121
182
  pageToken: "--page-token",
183
+ pages: "--pages",
184
+ maxItems: "--max-items",
185
+ all: "--all",
186
+ includeReplies: "--include-replies",
122
187
  sortType: "--sort-type",
123
188
  publishTimeRange: "--publish-time-range",
124
189
  durationRange: "--duration-range",
@@ -131,17 +196,39 @@ const DOUYIN_OPTION_DISPLAY_NAMES = {
131
196
  };
132
197
  const KUAISHOU_DIRECT_ACTION_OPTIONS = {
133
198
  "hot-search": ["pretty"],
134
- search: ["keyword", "pageToken", "pretty"],
199
+ search: ["keyword", "pageToken", "pages", "all", "maxItems", "pretty"],
135
200
  detail: ["photoId", "url", "pretty"],
136
- comments: ["photoId", "url", "pageToken", "pretty"],
137
- replies: ["photoId", "commentId", "pageToken", "pretty"],
201
+ comments: [
202
+ "photoId",
203
+ "url",
204
+ "pageToken",
205
+ "pages",
206
+ "all",
207
+ "maxItems",
208
+ "includeReplies",
209
+ "pretty",
210
+ ],
211
+ replies: [
212
+ "photoId",
213
+ "commentId",
214
+ "pageToken",
215
+ "pages",
216
+ "all",
217
+ "maxItems",
218
+ "includeReplies",
219
+ "pretty",
220
+ ],
138
221
  "user-info": ["userId", "profileUrl", "pretty"],
139
- "user-posts": ["userId", "profileUrl", "pageToken", "pretty"],
222
+ "user-posts": ["userId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
140
223
  };
141
224
  const KUAISHOU_DIRECT_ACTION_NAMES = Object.keys(KUAISHOU_DIRECT_ACTION_OPTIONS).join(", ");
142
225
  const KUAISHOU_OPTION_DISPLAY_NAMES = {
143
226
  keyword: "--keyword",
144
227
  pageToken: "--page-token",
228
+ pages: "--pages",
229
+ maxItems: "--max-items",
230
+ all: "--all",
231
+ includeReplies: "--include-replies",
145
232
  url: "--url",
146
233
  profileUrl: "--profile-url",
147
234
  photoId: "--photo-id",
@@ -252,7 +339,7 @@ const PLATFORMS = {
252
339
  },
253
340
  {
254
341
  name: "douyin_get_video_comment_replies_by_comment_id",
255
- description: "Fetch paginated replies under a first-level Douyin comment by aweme_id and comment_id.",
342
+ description: "Fetch paginated replies under a first-level Douyin comment; pass both aweme_id and comment_id, and use page_token to continue pagination.",
256
343
  },
257
344
  {
258
345
  name: "douyin_get_user_info_by_sec_user_id",
@@ -280,7 +367,8 @@ const PLATFORMS = {
280
367
  },
281
368
  {
282
369
  name: "douyin_search_videos",
283
- description: "Search Douyin works by keyword with optional paging and filters.",
370
+ description:
371
+ "Search Douyin works by keyword with optional page_token continuation and filters; do not pass page.",
284
372
  },
285
373
  ],
286
374
  },
@@ -304,7 +392,8 @@ const PLATFORMS = {
304
392
  },
305
393
  {
306
394
  name: "kuaishou_search_videos",
307
- description: "Search Kuaishou works by natural-language keyword with optional paging.",
395
+ description:
396
+ "Search Kuaishou works by natural-language keyword with optional page_token continuation; do not pass page.",
308
397
  },
309
398
  {
310
399
  name: "kuaishou_get_video_detail_by_photo_id",
@@ -471,9 +560,9 @@ function validateXhsDirectActionOptions(action, options) {
471
560
  }
472
561
 
473
562
  validateKnownOptions(options, allowedOptions);
474
- validateFlagOption(options, "pretty", "--pretty");
563
+ validateDirectPaginationOptions("xhs", action, options);
475
564
  for (const key of allowedOptions) {
476
- if (key !== "pretty") {
565
+ if (!DIRECT_BOOLEAN_OPTIONS.has(key)) {
477
566
  requireOptionValue(options, key, XHS_OPTION_DISPLAY_NAMES[key]);
478
567
  }
479
568
  }
@@ -486,9 +575,9 @@ function validateDouyinDirectActionOptions(action, options) {
486
575
  }
487
576
 
488
577
  validateKnownOptions(options, allowedOptions);
489
- validateFlagOption(options, "pretty", "--pretty");
578
+ validateDirectPaginationOptions("douyin", action, options);
490
579
  for (const key of allowedOptions) {
491
- if (key !== "pretty") {
580
+ if (!DIRECT_BOOLEAN_OPTIONS.has(key)) {
492
581
  requireOptionValue(options, key, DOUYIN_OPTION_DISPLAY_NAMES[key]);
493
582
  }
494
583
  }
@@ -501,14 +590,35 @@ function validateKuaishouDirectActionOptions(action, options) {
501
590
  }
502
591
 
503
592
  validateKnownOptions(options, allowedOptions);
504
- validateFlagOption(options, "pretty", "--pretty");
593
+ validateDirectPaginationOptions("kuaishou", action, options);
505
594
  for (const key of allowedOptions) {
506
- if (key !== "pretty") {
595
+ if (!DIRECT_BOOLEAN_OPTIONS.has(key)) {
507
596
  requireOptionValue(options, key, KUAISHOU_OPTION_DISPLAY_NAMES[key]);
508
597
  }
509
598
  }
510
599
  }
511
600
 
601
+ function validateDirectPaginationOptions(platformId, action, options) {
602
+ validateFlagOption(options, "pretty", "--pretty");
603
+ validateFlagOption(options, "all", "--all");
604
+ validateFlagOption(options, "includeReplies", "--include-replies");
605
+ if (options.all && options.pages !== undefined) {
606
+ throw new Error("Use only one of --all or --pages.");
607
+ }
608
+ if (options.pages !== undefined) {
609
+ parsePositiveIntegerOption(options.pages, "--pages");
610
+ }
611
+ if (options.maxItems !== undefined) {
612
+ parsePositiveIntegerOption(options.maxItems, "--max-items");
613
+ }
614
+ if (options.all && action === "search") {
615
+ throw new Error(`--all is not supported for ${platformId} search. Use --pages instead.`);
616
+ }
617
+ if (options.includeReplies && action !== "comments") {
618
+ throw new Error(`--include-replies is only supported for ${platformId} comments.`);
619
+ }
620
+ }
621
+
512
622
  function parsePositiveIntegerOption(value, displayName) {
513
623
  const text = String(value);
514
624
  if (!/^\d+$/.test(text)) {
@@ -992,6 +1102,9 @@ function printHelp() {
992
1102
  console.log(` npx -y ${PACKAGE_SPEC} xhs comments --note-id "<note_id>" --pretty`);
993
1103
  console.log(" Call the XHS comments tool directly and print JSON.");
994
1104
  console.log("");
1105
+ console.log(` npx -y ${PACKAGE_SPEC} xhs comments --note-id "<note_id>" --all --include-replies --pretty`);
1106
+ console.log(" Fetch all XHS first-level comments and nested replies.");
1107
+ console.log("");
995
1108
  console.log(
996
1109
  ` npx -y ${PACKAGE_SPEC} xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty`
997
1110
  );
@@ -1015,10 +1128,13 @@ function printHelp() {
1015
1128
  console.log(` npx -y ${PACKAGE_SPEC} douyin comments --aweme-id "<aweme_id>" --pretty`);
1016
1129
  console.log(" Call the Douyin work comments tool directly and print JSON.");
1017
1130
  console.log("");
1131
+ console.log(` npx -y ${PACKAGE_SPEC} douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty`);
1132
+ console.log(" Fetch all Douyin first-level comments and nested replies.");
1133
+ console.log("");
1018
1134
  console.log(
1019
1135
  ` npx -y ${PACKAGE_SPEC} douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty`
1020
1136
  );
1021
- console.log(" Call the Douyin comment replies tool directly and print JSON.");
1137
+ console.log(" Call the Douyin comment replies tool with aweme_id and comment_id; use page_token for pagination.");
1022
1138
  console.log("");
1023
1139
  console.log(` npx -y ${PACKAGE_SPEC} douyin user-info --sec-user-id "<sec_user_id>" --pretty`);
1024
1140
  console.log(" Call the Douyin creator profile tool directly and print JSON.");
@@ -1053,6 +1169,9 @@ function printHelp() {
1053
1169
  console.log(` npx -y ${PACKAGE_SPEC} kuaishou comments --photo-id "<photo_id>" --pretty`);
1054
1170
  console.log(" Call the Kuaishou work comments tool directly and print JSON.");
1055
1171
  console.log("");
1172
+ console.log(` npx -y ${PACKAGE_SPEC} kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty`);
1173
+ console.log(" Fetch all Kuaishou first-level comments and nested replies.");
1174
+ console.log("");
1056
1175
  console.log(` npx -y ${PACKAGE_SPEC} kuaishou comments --url "<kuaishou_content_url_or_share_text>" --pretty`);
1057
1176
  console.log(" Call the Kuaishou work comments tool from a work link or share text.");
1058
1177
  console.log("");
@@ -1148,6 +1267,15 @@ function printHelp() {
1148
1267
  console.log(" --user-id <user_id>");
1149
1268
  console.log(" --sec-user-id <sec_user_id>");
1150
1269
  console.log(" --page <number>");
1270
+ console.log(" XHS search only. Douyin and Kuaishou search do not accept --page.");
1271
+ console.log(" --pages <number>");
1272
+ console.log(" Fetch and merge N pages from the current starting point for search, comments, replies, creator content lists, and creator series.");
1273
+ console.log(" --all");
1274
+ console.log(" Continue comments, replies, creator content lists, and creator series until next_page_token is empty; not supported for search.");
1275
+ console.log(" --max-items <number>");
1276
+ console.log(" Stop after collecting this many primary items.");
1277
+ console.log(" --include-replies");
1278
+ console.log(" For comments commands, also fetch nested second-level replies for each returned first-level comment.");
1151
1279
  console.log(" --sort-type <general|time_descending|like_count_descending|comment_count_descending|collect_count_descending>");
1152
1280
  console.log(" XHS sort meanings: general=default, time_descending=newest, like_count_descending=most liked, comment_count_descending=most commented, collect_count_descending=most collected.");
1153
1281
  console.log(" --note-type <all|image|video> XHS search note type filter; default is all.");
@@ -1162,6 +1290,7 @@ function printHelp() {
1162
1290
  console.log(" --content-type <all|video|image>");
1163
1291
  console.log(" Douyin content type filter; omit for all content types.");
1164
1292
  console.log(" --page-token <token>");
1293
+ console.log(" Continue token-paginated commands with the complete returned next_page_token. For Douyin and Kuaishou search, omit it on the first request.");
1165
1294
  console.log(" --pretty Pretty-print direct CLI JSON output.");
1166
1295
  console.log(" --json Print doctor output as JSON.");
1167
1296
  console.log(" --target <openclaw|hermes|agents|codex|claude-code|claude>");
@@ -1210,7 +1339,9 @@ async function runXhsDirectCommand(args) {
1210
1339
  validateXhsDirectActionOptions(action, options);
1211
1340
 
1212
1341
  const operation = buildXhsOperation(action, options);
1213
- const data = await callDirectOperation(operation);
1342
+ const data = shouldUsePaginatedDirectOutput(options)
1343
+ ? await callPaginatedDirectOperation(operation, options)
1344
+ : await callDirectOperation(operation);
1214
1345
  const envelope = {
1215
1346
  platform: operation.platform.id,
1216
1347
  tool: operation.tool,
@@ -1235,7 +1366,9 @@ async function runDouyinDirectCommand(args) {
1235
1366
  validateDouyinDirectActionOptions(action, options);
1236
1367
 
1237
1368
  const operation = buildDouyinOperation(action, options);
1238
- const data = await callDirectOperation(operation);
1369
+ const data = shouldUsePaginatedDirectOutput(options)
1370
+ ? await callPaginatedDirectOperation(operation, options)
1371
+ : await callDirectOperation(operation);
1239
1372
  const envelope = {
1240
1373
  platform: operation.platform.id,
1241
1374
  tool: operation.tool,
@@ -1260,7 +1393,9 @@ async function runKuaishouDirectCommand(args) {
1260
1393
  validateKuaishouDirectActionOptions(action, options);
1261
1394
 
1262
1395
  const operation = buildKuaishouOperation(action, options);
1263
- const data = await callDirectOperation(operation);
1396
+ const data = shouldUsePaginatedDirectOutput(options)
1397
+ ? await callPaginatedDirectOperation(operation, options)
1398
+ : await callDirectOperation(operation);
1264
1399
  const envelope = {
1265
1400
  platform: operation.platform.id,
1266
1401
  tool: operation.tool,
@@ -1773,6 +1908,469 @@ async function callDirectOperation(operation) {
1773
1908
  }
1774
1909
  }
1775
1910
 
1911
+ function shouldUsePaginatedDirectOutput(options) {
1912
+ return Boolean(
1913
+ options.all ||
1914
+ options.pages !== undefined ||
1915
+ options.maxItems !== undefined ||
1916
+ options.includeReplies
1917
+ );
1918
+ }
1919
+
1920
+ async function callPaginatedDirectOperation(operation, options) {
1921
+ const pagination = parseDirectPaginationOptions(options);
1922
+ return collectPaginatedDirectData(operation, pagination, {
1923
+ includeReplies: Boolean(options.includeReplies),
1924
+ });
1925
+ }
1926
+
1927
+ function parseDirectPaginationOptions(options) {
1928
+ return {
1929
+ all: Boolean(options.all),
1930
+ pages:
1931
+ options.pages === undefined
1932
+ ? undefined
1933
+ : parsePositiveIntegerOption(options.pages, "--pages"),
1934
+ maxItems:
1935
+ options.maxItems === undefined
1936
+ ? undefined
1937
+ : parsePositiveIntegerOption(options.maxItems, "--max-items"),
1938
+ };
1939
+ }
1940
+
1941
+ async function collectPaginatedDirectData(
1942
+ operation,
1943
+ pagination,
1944
+ { includeReplies = false } = {}
1945
+ ) {
1946
+ const pageLimit = pagination.all ? Number.POSITIVE_INFINITY : pagination.pages || 1;
1947
+ const collectedItems = [];
1948
+ const itemDedupeState = createPaginatedItemDedupeState();
1949
+ const seenNextMarkers = initialPaginationMarkers(operation);
1950
+ let pageCount = 0;
1951
+ let lastPageData;
1952
+ let nextMarker;
1953
+ let parentContextData = {};
1954
+ let currentOperation = cloneDirectOperation(operation);
1955
+
1956
+ while (pageCount < pageLimit) {
1957
+ const pageData = await callDirectOperation(currentOperation);
1958
+ pageCount += 1;
1959
+ lastPageData = pageData;
1960
+ parentContextData = mergeParentContextData(parentContextData, pageData);
1961
+
1962
+ const pageItems = uniquePaginatedPageItems(
1963
+ operation,
1964
+ directPageItems(pageData),
1965
+ itemDedupeState
1966
+ );
1967
+ const candidateItems = itemsForRemainingLimit(
1968
+ pageItems,
1969
+ collectedItems.length,
1970
+ pagination.maxItems
1971
+ );
1972
+ const decoratedItems =
1973
+ includeReplies && operation.operation === "comments"
1974
+ ? await attachRepliesToCommentItems(
1975
+ currentOperation,
1976
+ candidateItems,
1977
+ parentContextData
1978
+ )
1979
+ : candidateItems;
1980
+ appendItemsWithLimit(collectedItems, decoratedItems, pagination.maxItems);
1981
+
1982
+ nextMarker = readNextPageMarker(currentOperation, pageData);
1983
+ const markerKey = nextMarker === undefined ? undefined : String(nextMarker);
1984
+ const markerRepeated =
1985
+ markerKey !== undefined && seenNextMarkers.has(markerKey);
1986
+ if (
1987
+ !nextMarker ||
1988
+ reachedMaxItems(collectedItems, pagination.maxItems) ||
1989
+ pageCount >= pageLimit
1990
+ ) {
1991
+ if (markerRepeated) {
1992
+ nextMarker = undefined;
1993
+ }
1994
+ break;
1995
+ }
1996
+ if (markerRepeated) {
1997
+ throw new Error(
1998
+ `Pagination stopped because ${nextMarkerName(currentOperation)} repeated.`
1999
+ );
2000
+ }
2001
+ seenNextMarkers.add(markerKey);
2002
+ currentOperation = operationWithNextPageMarker(currentOperation, nextMarker);
2003
+ }
2004
+
2005
+ return buildPaginatedData({
2006
+ operation,
2007
+ lastPageData,
2008
+ parentContextData,
2009
+ items: collectedItems,
2010
+ pageCount,
2011
+ nextMarker,
2012
+ });
2013
+ }
2014
+
2015
+ function cloneDirectOperation(operation) {
2016
+ return {
2017
+ ...operation,
2018
+ arguments: { ...operation.arguments },
2019
+ };
2020
+ }
2021
+
2022
+ function directPageItems(pageData) {
2023
+ return Array.isArray(pageData?.items) ? pageData.items : [];
2024
+ }
2025
+
2026
+ function createPaginatedItemDedupeState() {
2027
+ return { seenKeys: new Set() };
2028
+ }
2029
+
2030
+ function uniquePaginatedPageItems(operation, items, itemDedupeState) {
2031
+ const uniqueItems = [];
2032
+ for (const item of items) {
2033
+ const dedupeKeys = paginatedItemDedupeKeys(operation, item);
2034
+ if (dedupeKeys.length === 0) {
2035
+ uniqueItems.push(item);
2036
+ continue;
2037
+ }
2038
+ if (dedupeKeys.some((key) => itemDedupeState.seenKeys.has(key))) {
2039
+ recordPaginatedItemDedupeKeys(itemDedupeState, dedupeKeys);
2040
+ continue;
2041
+ }
2042
+ recordPaginatedItemDedupeKeys(itemDedupeState, dedupeKeys);
2043
+ uniqueItems.push(item);
2044
+ }
2045
+ return uniqueItems;
2046
+ }
2047
+
2048
+ function recordPaginatedItemDedupeKeys(itemDedupeState, dedupeKeys) {
2049
+ for (const key of dedupeKeys) {
2050
+ itemDedupeState.seenKeys.add(key);
2051
+ }
2052
+ }
2053
+
2054
+ function paginatedItemDedupeKeys(operation, item) {
2055
+ if (shouldDeduplicateCommentItems(operation)) {
2056
+ const commentId = itemStringField(item, "comment_id");
2057
+ return commentId ? [`comment:${commentId}`] : [];
2058
+ }
2059
+ if (!shouldDeduplicateContentItems(operation)) {
2060
+ return [];
2061
+ }
2062
+ const keys = [];
2063
+ const itemId = contentItemId(operation, item);
2064
+ if (itemId) {
2065
+ keys.push(`${operation.operation}:${operation.platform.id}:id:${itemId}`);
2066
+ }
2067
+ const xhsFingerprint = xhsContentItemFingerprint(operation, item);
2068
+ if (xhsFingerprint) {
2069
+ keys.push(`${operation.operation}:xhs:fingerprint:${xhsFingerprint}`);
2070
+ }
2071
+ return keys;
2072
+ }
2073
+
2074
+ function shouldDeduplicateCommentItems(operation) {
2075
+ return ["comments", "replies", "sub-comments"].includes(operation.operation);
2076
+ }
2077
+
2078
+ function shouldDeduplicateContentItems(operation) {
2079
+ return ["search", "user-posts", "user-series"].includes(operation.operation);
2080
+ }
2081
+
2082
+ function contentItemId(operation, item) {
2083
+ switch (operation.platform.id) {
2084
+ case "xhs":
2085
+ return itemStringField(item, "note_id");
2086
+ case "douyin":
2087
+ if (operation.operation === "user-series") {
2088
+ return itemStringField(item, "series_id");
2089
+ }
2090
+ return itemStringField(item, "aweme_id");
2091
+ case "kuaishou":
2092
+ return itemStringField(item, "photo_id");
2093
+ default:
2094
+ return undefined;
2095
+ }
2096
+ }
2097
+
2098
+ function xhsContentItemFingerprint(operation, item) {
2099
+ if (
2100
+ operation.platform.id !== "xhs" ||
2101
+ !shouldDeduplicateContentItems(operation)
2102
+ ) {
2103
+ return undefined;
2104
+ }
2105
+ const authorId = itemStringField(item?.author, "user_id");
2106
+ const publishTime = itemStringField(item, "publish_time");
2107
+ const publishTimeNumber = Number(publishTime);
2108
+ const coverToken = xhsImageUrlToken(item?.cover_image_url);
2109
+ if (
2110
+ !authorId ||
2111
+ !publishTime ||
2112
+ !Number.isFinite(publishTimeNumber) ||
2113
+ publishTimeNumber <= 0 ||
2114
+ !coverToken
2115
+ ) {
2116
+ return undefined;
2117
+ }
2118
+ return `${authorId}\t${publishTime}\t${coverToken}`;
2119
+ }
2120
+
2121
+ function xhsImageUrlToken(imageUrl) {
2122
+ const urlValue = stringValue(imageUrl);
2123
+ if (!urlValue) {
2124
+ return undefined;
2125
+ }
2126
+ let pathname;
2127
+ try {
2128
+ pathname = new URL(urlValue).pathname;
2129
+ } catch {
2130
+ return undefined;
2131
+ }
2132
+ let token = pathname.replace(/^\/+/, "");
2133
+ const webpicContentIndex = token.indexOf("/c/");
2134
+ if (webpicContentIndex !== -1) {
2135
+ token = token.slice(webpicContentIndex + 3);
2136
+ }
2137
+ token = token.split("!", 1)[0];
2138
+ return token || undefined;
2139
+ }
2140
+
2141
+ function itemStringField(item, field) {
2142
+ if (!item || typeof item !== "object") {
2143
+ return undefined;
2144
+ }
2145
+ return stringValue(item[field]);
2146
+ }
2147
+
2148
+ function stringValue(value) {
2149
+ if (value === undefined || value === null || value === "") {
2150
+ return undefined;
2151
+ }
2152
+ return String(value);
2153
+ }
2154
+
2155
+ function mergeParentContextData(current, pageData) {
2156
+ return {
2157
+ ...current,
2158
+ ...pickDefinedFields(pageData, ["note_id", "aweme_id", "photo_id"]),
2159
+ };
2160
+ }
2161
+
2162
+ function pickDefinedFields(source, fields) {
2163
+ if (!source || typeof source !== "object") {
2164
+ return {};
2165
+ }
2166
+ const picked = {};
2167
+ for (const field of fields) {
2168
+ if (source[field] !== undefined && source[field] !== null && source[field] !== "") {
2169
+ picked[field] = source[field];
2170
+ }
2171
+ }
2172
+ return picked;
2173
+ }
2174
+
2175
+ function itemsForRemainingLimit(items, collectedItemCount, maxItems) {
2176
+ if (maxItems === undefined) {
2177
+ return items;
2178
+ }
2179
+ const remaining = maxItems - collectedItemCount;
2180
+ return remaining > 0 ? items.slice(0, remaining) : [];
2181
+ }
2182
+
2183
+ function appendItemsWithLimit(target, source, maxItems) {
2184
+ if (maxItems === undefined) {
2185
+ target.push(...source);
2186
+ return;
2187
+ }
2188
+ const remaining = maxItems - target.length;
2189
+ if (remaining <= 0) {
2190
+ return;
2191
+ }
2192
+ target.push(...source.slice(0, remaining));
2193
+ }
2194
+
2195
+ function reachedMaxItems(items, maxItems) {
2196
+ return maxItems !== undefined && items.length >= maxItems;
2197
+ }
2198
+
2199
+ function readNextPageMarker(operation, pageData) {
2200
+ if (operation.platform.id === "xhs" && operation.operation === "search") {
2201
+ const page = pageData?.next_page;
2202
+ return page === undefined || page === null || page === "" ? undefined : page;
2203
+ }
2204
+ const token = pageData?.next_page_token;
2205
+ return typeof token === "string" && token ? token : undefined;
2206
+ }
2207
+
2208
+ function nextMarkerName(operation) {
2209
+ return operation.platform.id === "xhs" && operation.operation === "search"
2210
+ ? "next_page"
2211
+ : "next_page_token";
2212
+ }
2213
+
2214
+ function initialPaginationMarkers(operation) {
2215
+ const marker = currentPageMarker(operation);
2216
+ return marker === undefined ? new Set() : new Set([String(marker)]);
2217
+ }
2218
+
2219
+ function currentPageMarker(operation) {
2220
+ if (operation.platform.id === "xhs" && operation.operation === "search") {
2221
+ return operation.arguments.page;
2222
+ }
2223
+ return operation.arguments.page_token;
2224
+ }
2225
+
2226
+ function operationWithNextPageMarker(operation, nextMarker) {
2227
+ const nextOperation = cloneDirectOperation(operation);
2228
+ if (operation.platform.id === "xhs" && operation.operation === "search") {
2229
+ nextOperation.arguments.page = nextMarker;
2230
+ } else {
2231
+ nextOperation.arguments.page_token = nextMarker;
2232
+ }
2233
+ return nextOperation;
2234
+ }
2235
+
2236
+ function buildPaginatedData({
2237
+ operation,
2238
+ lastPageData,
2239
+ parentContextData,
2240
+ items,
2241
+ pageCount,
2242
+ nextMarker,
2243
+ }) {
2244
+ const data = {
2245
+ ...(lastPageData && typeof lastPageData === "object" ? lastPageData : {}),
2246
+ ...parentContextData,
2247
+ items,
2248
+ page_count: pageCount,
2249
+ item_count: items.length,
2250
+ };
2251
+ if (operation.platform.id === "xhs" && operation.operation === "search") {
2252
+ data.next_page = nextMarker ?? null;
2253
+ } else {
2254
+ data.next_page_token = nextMarker || "";
2255
+ }
2256
+ return data;
2257
+ }
2258
+
2259
+ async function attachRepliesToCommentItems(operation, items, pageData) {
2260
+ const decorated = [];
2261
+ for (const item of items) {
2262
+ const comment = { ...item };
2263
+ if (commentMayHaveReplies(comment)) {
2264
+ const repliesOperation = buildRepliesOperationForComment(
2265
+ operation.platform,
2266
+ comment,
2267
+ operation.arguments,
2268
+ pageData
2269
+ );
2270
+ const repliesData = await collectPaginatedDirectData(
2271
+ repliesOperation,
2272
+ { all: true, pages: undefined, maxItems: undefined },
2273
+ { includeReplies: false }
2274
+ );
2275
+ comment.replies = repliesData.items;
2276
+ comment.replies_page_count = repliesData.page_count;
2277
+ comment.replies_next_page_token = repliesData.next_page_token || "";
2278
+ } else {
2279
+ comment.replies = [];
2280
+ comment.replies_page_count = 0;
2281
+ comment.replies_next_page_token = "";
2282
+ }
2283
+ decorated.push(comment);
2284
+ }
2285
+ return decorated;
2286
+ }
2287
+
2288
+ function commentMayHaveReplies(comment) {
2289
+ if (typeof comment.reply_count === "number") {
2290
+ return comment.reply_count > 0;
2291
+ }
2292
+ const replyCount = parseNonNegativeIntegerString(comment.reply_count);
2293
+ if (replyCount !== undefined) {
2294
+ return replyCount > 0;
2295
+ }
2296
+ if (typeof comment.has_replies === "boolean") {
2297
+ return comment.has_replies;
2298
+ }
2299
+ return true;
2300
+ }
2301
+
2302
+ function parseNonNegativeIntegerString(value) {
2303
+ if (typeof value !== "string" || !/^\d+$/.test(value)) {
2304
+ return undefined;
2305
+ }
2306
+ const parsed = Number(value);
2307
+ return Number.isSafeInteger(parsed) ? parsed : undefined;
2308
+ }
2309
+
2310
+ function buildRepliesOperationForComment(platform, comment, parentArguments, parentData) {
2311
+ if (!comment.comment_id) {
2312
+ throw new Error("Cannot fetch comment replies because comment_id is missing.");
2313
+ }
2314
+ switch (platform.id) {
2315
+ case "xhs":
2316
+ {
2317
+ const noteId = comment.note_id || parentArguments.note_id || parentData?.note_id;
2318
+ if (!noteId) {
2319
+ throw new Error("Cannot fetch XHS comment replies because note_id is missing.");
2320
+ }
2321
+ return buildDirectOperation(
2322
+ "sub-comments",
2323
+ {
2324
+ tool: "xhs_get_note_sub_comments_by_comment_id",
2325
+ toolArguments: {
2326
+ note_id: noteId,
2327
+ comment_id: comment.comment_id,
2328
+ },
2329
+ },
2330
+ platform
2331
+ );
2332
+ }
2333
+ case "douyin":
2334
+ {
2335
+ const awemeId = comment.aweme_id || parentArguments.aweme_id || parentData?.aweme_id;
2336
+ if (!awemeId) {
2337
+ throw new Error("Cannot fetch Douyin comment replies because aweme_id is missing.");
2338
+ }
2339
+ return buildDirectOperation(
2340
+ "replies",
2341
+ {
2342
+ tool: "douyin_get_video_comment_replies_by_comment_id",
2343
+ toolArguments: {
2344
+ aweme_id: awemeId,
2345
+ comment_id: comment.comment_id,
2346
+ },
2347
+ },
2348
+ platform
2349
+ );
2350
+ }
2351
+ case "kuaishou":
2352
+ {
2353
+ const photoId = comment.photo_id || parentArguments.photo_id || parentData?.photo_id;
2354
+ if (!photoId) {
2355
+ throw new Error("Cannot fetch Kuaishou comment replies because photo_id is missing.");
2356
+ }
2357
+ return buildDirectOperation(
2358
+ "replies",
2359
+ {
2360
+ tool: "kuaishou_get_video_comment_replies_by_comment_id",
2361
+ toolArguments: {
2362
+ photo_id: photoId,
2363
+ comment_id: comment.comment_id,
2364
+ },
2365
+ },
2366
+ platform
2367
+ );
2368
+ }
2369
+ default:
2370
+ throw new Error(`Unsupported comment reply platform: ${platform.id}.`);
2371
+ }
2372
+ }
2373
+
1776
2374
  async function callMcpBackend(operation) {
1777
2375
  ensureSupportedNodeVersion();
1778
2376
  const { platform, tool } = operation;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socialdatax-skills",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "SocialDataX / 社媒数据助手 skill installer and direct CLI helper for hosted platform social media MCP services.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -40,30 +40,37 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
40
40
 
41
41
  ```bash
42
42
  npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --pretty
43
+ npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --all --include-replies --pretty
43
44
  npx -y socialdatax-skills@latest xhs comments --url "<note_url_or_share_text>" --pretty
44
45
  npx -y socialdatax-skills@latest xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty
45
46
  npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --pretty
47
+ npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty
46
48
  npx -y socialdatax-skills@latest douyin comments --url "<douyin_content_url_or_share_text>" --pretty
47
49
  npx -y socialdatax-skills@latest douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty
48
50
  npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --pretty
51
+ npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty
49
52
  npx -y socialdatax-skills@latest kuaishou comments --url "<kuaishou_content_url_or_share_text>" --pretty
50
53
  npx -y socialdatax-skills@latest kuaishou replies --photo-id "<photo_id>" --comment-id "<comment_id>" --pretty
51
54
  ```
52
55
 
53
56
  Optional arguments:
54
57
 
55
- - XHS `--note-id <note_id>`: preferred when the note ID is already known and should anchor the comment thread.
58
+ - XHS `--note-id <note_id>`: use the complete 24-character lowercase hexadecimal `note_id` returned from search, detail, comments, or creator note lists; do not pass only a prefix.
56
59
  - Douyin `--aweme-id <aweme_id>`: preferred when the video ID is already known and should anchor the comment thread.
57
60
  - `--url <url_or_share_text>`: use for a content page URL, short link, or share text for first-level comments.
58
61
  - Douyin URL safety: do not pass `video.play_url`; use a Douyin content page URL, short link, or share text instead.
59
62
  - `--comment-id <comment_id>`: required for reply commands; use the first-level comment ID under the same content item.
60
- - `--page-token <next_page_token>`: opaque pagination token; pass back exactly the token returned by the previous page and do not reuse it across content items or comments.
63
+ - `--page-token <next_page_token>`: opaque pagination token; pass the complete returned `next_page_token` back unchanged for the same content item or comment chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
64
+ - `--pages <n>`: fetch and merge N pages of first-level comments or replies.
65
+ - `--all`: continue first-level comments or replies until `next_page_token` is empty; there is no default item or page cap.
66
+ - `--max-items <n>`: stop after collecting N primary comments or replies.
67
+ - `--include-replies`: for first-level `comments` commands only, also fetch all second-level replies under each returned first-level comment.
61
68
  - `--pretty`: output formatting only.
62
69
  - Kuaishou `--photo-id <photo_id>`: preferred when the Kuaishou work photo_id is already known and should anchor the comment thread.
63
70
 
64
71
  Use either the content ID option or the URL option for first-level comments, not both. For reply commands, use the content ID together with `--comment-id`.
65
72
 
66
- The command prints JSON with `platform`, `tool`, `arguments`, and `data`.
73
+ The command prints JSON with `platform`, `tool`, `arguments`, and `data`. Multi-page output keeps merged primary comments in `data.items` and adds `page_count`, `item_count`, and the next-page marker. With `--include-replies`, each first-level comment includes `replies`, `replies_page_count`, and `replies_next_page_token`.
67
74
 
68
75
  ## Safety Boundary
69
76
 
@@ -84,14 +91,14 @@ MCP tools matching the direct CLI commands above:
84
91
  - `kuaishou_get_video_comment_replies_by_comment_id`
85
92
 
86
93
  If MCP tools are already available in the current agent, use one of these tools:
87
- - `xhs_get_note_comments_by_note_id`: use when the note ID is known.
94
+ - `xhs_get_note_comments_by_note_id`: use when the complete 24-character lowercase hexadecimal `note_id` is known; do not pass only a prefix.
88
95
  - `xhs_get_note_comments_by_note_url`: use for note URLs, short links, or share text.
89
- - `xhs_get_note_sub_comments_by_comment_id`: use when the note ID and first-level comment ID are known.
96
+ - `xhs_get_note_sub_comments_by_comment_id`: use when the complete 24-character lowercase hexadecimal `note_id` and first-level comment ID are known; do not pass only a note ID prefix.
90
97
  - `douyin_get_video_comments_by_aweme_id`: use when the aweme_id is known.
91
98
  - `douyin_get_video_comments_by_url`: use for Douyin content page URLs, short links, or share text; do not pass playback URLs such as `video.play_url`.
92
- - `douyin_get_video_comment_replies_by_comment_id`: use when the aweme_id and first-level comment ID are known.
99
+ - `douyin_get_video_comment_replies_by_comment_id`: use when both aweme_id and first-level comment ID are known; use page_token to continue pagination.
93
100
 
94
- Comment pagination uses `page_token`. Pass back exactly the returned `next_page_token`; tokens are bound to the current content item and should not be reused across objects.
101
+ Comment pagination uses opaque `page_token` values. Pass the complete returned `next_page_token` back unchanged for the same content item or comment chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses. Prefer CLI `--pages`, `--all`, and `--include-replies` when the user asks for multiple pages or a full first-level plus second-level comment tree.
95
102
  For Douyin comments and replies, continue only when `next_page_token` is non-empty; an empty string means there are no more comments or replies to request.
96
103
  XHS reply pagination also uses `page_token` and is bound to the current comment.
97
104
  - `kuaishou_get_video_comments_by_photo_id`: use when the photo_id is known.
@@ -49,7 +49,7 @@ npx -y socialdatax-skills@latest kuaishou detail --url "<kuaishou_content_url_or
49
49
 
50
50
  Optional arguments:
51
51
 
52
- - XHS `--note-id <note_id>`: preferred when the note ID is already known from search, comments, or a previous detail result.
52
+ - XHS `--note-id <note_id>`: use the complete 24-character lowercase hexadecimal `note_id` returned from search, comments, creator note lists, or a previous detail result; do not pass only a prefix.
53
53
  - XHS `--url <note_url_or_share_text>`: use for a note link, short link, or share text.
54
54
  - Douyin `--aweme-id <aweme_id>`: preferred when the Douyin work ID is already known.
55
55
  - Douyin `--url <douyin_content_url_or_share_text>`: use for a Douyin content page URL, short link, or share text; do not pass `video.play_url`.
@@ -77,7 +77,7 @@ MCP tools matching the direct CLI commands above:
77
77
  - `kuaishou_get_video_detail_by_url`
78
78
 
79
79
  If MCP tools are already available in the current agent, use one of these tools:
80
- - `xhs_get_note_detail_by_note_id`: use when a note ID is already known.
80
+ - `xhs_get_note_detail_by_note_id`: use when the complete 24-character lowercase hexadecimal `note_id` is already known; do not pass only a prefix.
81
81
  - `xhs_get_note_detail_by_note_url`: use for note URLs, short links, or share text.
82
82
  - `douyin_get_video_detail_by_aweme_id`: use when an aweme_id is already known.
83
83
  - `douyin_get_video_detail_by_url`: use for Douyin content page URLs, short links, or share text; do not pass playback URLs such as `video.play_url`.
@@ -87,6 +87,8 @@ If MCP tools are already available in the current agent, use one of these tools:
87
87
  ## Output Guidance
88
88
 
89
89
  Return factual fields such as title or description, content, author, publish time, interaction counts, images, and media summary when available.
90
+ For XHS detail results, preserve any returned `note_url` exactly, including `xsec_token` query parameters. Do not modify, truncate, redact, mask, normalize, rebuild, or synthesize the URL from `note_id`; if `note_url` is null, show the `note_id` or say that no directly openable full link is available.
91
+ For XHS `note_id`, copy the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
90
92
  For Douyin detail, include `content_type` when available.
91
93
  For Douyin detail, use `images` for image/text posts; `video` is the platform player resource and may be audio for image/text posts; `music` is the bound music or original-sound asset.
92
94
  Detail access is read-only and does not provide account actions.
@@ -40,8 +40,11 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
40
40
 
41
41
  ```bash
42
42
  npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pretty
43
+ npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pages 3 --pretty
43
44
  npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pretty
45
+ npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pages 3 --pretty
44
46
  npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pretty
47
+ npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pages 3 --pretty
45
48
  ```
46
49
 
47
50
  Required arguments:
@@ -56,13 +59,17 @@ Optional arguments:
56
59
  - XHS `--sort-type <general|time_descending|like_count_descending|comment_count_descending|collect_count_descending>`: optional sort value; omit it for default sorting.
57
60
  - XHS `--note-type <all|image|video>`: optional note type filter; default is `all`.
58
61
  - XHS `--publish-time-range <all|day|week|half_year>`: optional publish-time filter; default is `all`.
59
- - Douyin `--page-token <next_page_token>`: opaque pagination token returned by the previous page.
62
+ - Douyin `--page-token <next_page_token>`: opaque pagination token; omit it on the first search request. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
60
63
  - Douyin `--sort-type <general|time_descending|like_count_descending>`: optional sort value; omit it for the default sort.
61
64
  - Douyin `--publish-time-range <all|day|week|half_year>`: optional publish-time filter; omit it for no publish-time filter.
62
65
  - Douyin `--duration-range <all|under_1_minute|one_to_five_minutes|over_5_minutes>`: optional duration filter; omit it for no duration filter.
63
66
  - Douyin `--content-type <all|video|image>`: optional content type filter; omit it for all content types.
67
+ - XHS `--pages <n>`: fetch and merge N search pages from the current starting point; continue with returned `next_page`.
68
+ - Douyin `--pages <n>`: fetch and merge N search pages from the current starting point; continue with returned `next_page_token`.
69
+ - Kuaishou `--pages <n>`: fetch and merge N search pages from the current starting point; continue with returned `next_page_token`.
70
+ - `--max-items <n>`: stop after collecting N search results.
64
71
  - `--pretty`: output formatting only.
65
- - Kuaishou `--page-token <next_page_token>`: opaque pagination token returned by the previous page.
72
+ - Kuaishou `--page-token <next_page_token>`: opaque pagination token; omit it on the first search request. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
66
73
 
67
74
  XHS sort values:
68
75
  - `general`: default sorting.
@@ -104,9 +111,10 @@ Douyin content type filter values:
104
111
  - `video`: video works.
105
112
  - `image`: image/text posts.
106
113
 
107
- The command prints JSON with `platform`, `tool`, `arguments`, and `data`.
114
+ The command prints JSON with `platform`, `tool`, `arguments`, and `data`. Search supports `--pages` and `--max-items`, but not `--all`, because search has no stable complete-result boundary. Multi-page output keeps merged results in `data.items` and adds `page_count`, `item_count`, and the next-page marker.
108
115
  Kuaishou search pagination:
109
116
  - Continue only when `next_page_token` is not empty.
117
+ - Pass the complete returned `next_page_token` back unchanged as `page_token` for the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
110
118
 
111
119
  ## Safety Boundary
112
120
 
@@ -131,21 +139,27 @@ Use the same meanings as the CLI XHS sort and filter values above.
131
139
 
132
140
  For Douyin, call `douyin_search_videos` with:
133
141
  - `keyword`: required search phrase or topic.
134
- - `page_token`: optional opaque pagination token from the previous page.
142
+ - `page_token`: optional opaque pagination token. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
143
+ Do not pass `page` to `douyin_search_videos`; omit `page_token` on the first request.
135
144
  - `sort_type`: optional, one of `general`, `time_descending`, `like_count_descending`; omit it for the default sort.
136
145
  - `publish_time_range`: optional publish-time filter, one of `all`, `day`, `week`, `half_year`; omit it for no publish-time filter.
137
146
  - `duration_range`: optional duration filter, one of `all`, `under_1_minute`, `one_to_five_minutes`, `over_5_minutes`; omit it for no duration filter.
138
147
  - `content_type`: optional content type filter, one of `all`, `video`, `image`; omit it for all content types.
139
148
 
140
149
  Continue XHS pagination only when `next_page` is not `null`, and keep the same `keyword`, `sort_type`, `note_type`, and `publish_time_range` while changing only `page`.
141
- Continue Douyin pagination only when `next_page_token` is not empty.
150
+ Continue Douyin pagination only when `next_page_token` is not empty. Pass the complete returned `next_page_token` back unchanged as `page_token` for the same keyword and filter chain.
142
151
  Do not stop only because one page has empty `items`.
143
152
  For Kuaishou, call `kuaishou_search_videos` with:
144
- Continue Kuaishou pagination only when `next_page_token` is not empty.
153
+ - `keyword`: required search phrase or topic.
154
+ - `page_token`: optional opaque pagination token. Continue only with the complete returned `next_page_token` from the same search pagination chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
155
+ Do not pass `page` to `kuaishou_search_videos`; omit `page_token` on the first request.
156
+ Continue Kuaishou pagination only when `next_page_token` is not empty. Pass the complete returned `next_page_token` back unchanged as `page_token` for the same keyword chain.
145
157
 
146
158
  ## Output Guidance
147
159
 
148
160
  Summarize visible evidence separately from interpretation. Include useful content IDs, URLs, titles or descriptions, authors, counts, and publish time when the user needs traceability.
161
+ For XHS search results, preserve any returned `note_url` exactly, including `xsec_token` query parameters. Do not modify, truncate, redact, mask, normalize, rebuild, or synthesize the URL from `note_id`.
162
+ For XHS `note_id`, copy the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
149
163
  For Douyin search results, include `content_type` when the user needs traceability.
150
164
  For Douyin image/text search results, use returned `images` and treat `video.media_type="audio"` as an audio player resource rather than a video post.
151
165
  For Kuaishou search results, include `photo_id`, `share_url`, author facts, and visible interaction counts when the user needs traceability.
@@ -41,12 +41,16 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
41
41
 
42
42
  ```bash
43
43
  npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --pretty
44
+ npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --all --pretty
44
45
  npx -y socialdatax-skills@latest xhs user-posts --profile-url "<profile_url_or_share_text>" --pretty
45
46
  npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --pretty
47
+ npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --all --pretty
46
48
  npx -y socialdatax-skills@latest douyin user-posts --profile-url "<profile_url_or_share_text>" --pretty
47
49
  npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --pretty
50
+ npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --all --pretty
48
51
  npx -y socialdatax-skills@latest douyin user-series --profile-url "<profile_url_or_share_text>" --pretty
49
52
  npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --pretty
53
+ npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --all --pretty
50
54
  npx -y socialdatax-skills@latest kuaishou user-posts --profile-url "<profile_url_or_share_text>" --pretty
51
55
  ```
52
56
 
@@ -57,14 +61,17 @@ Optional arguments:
57
61
  - Douyin `--sec-user-id <sec_user_id>`: preferred when the creator sec_user_id is already known.
58
62
  - Douyin `--profile-url <profile_url_or_share_text>`: use for a profile URL, short link, or profile share text.
59
63
  - Douyin `user-series`: use for a creator's short-drama series list instead of regular published works.
60
- - `--page-token <next_page_token>`: opaque pagination token; pass back exactly the token returned by the previous page and do not reuse it across users.
64
+ - `--page-token <next_page_token>`: opaque pagination token; pass the complete returned `next_page_token` back unchanged for the same creator content-list or series chain. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
65
+ - `--pages <n>`: fetch and merge N pages of creator content or creator series.
66
+ - `--all`: continue until `next_page_token` is empty; there is no default item or page cap.
67
+ - `--max-items <n>`: stop after collecting N creator content or series items.
61
68
  - `--pretty`: output formatting only.
62
69
  - Kuaishou `--user-id <user_id>`: preferred when the creator user_id is already known.
63
70
  - Kuaishou `--profile-url <profile_url_or_share_text>`: use for a profile URL, short link, or profile share text.
64
71
 
65
72
  Use either the ID option or the profile URL option for a single command, not both.
66
73
 
67
- The command prints JSON with `platform`, `tool`, `arguments`, and `data`.
74
+ The command prints JSON with `platform`, `tool`, `arguments`, and `data`. Multi-page output keeps merged creator content or series items in `data.items` and adds `page_count`, `item_count`, and `next_page_token`.
68
75
 
69
76
  ## Safety Boundary
70
77
 
@@ -91,14 +98,15 @@ If MCP tools are already available in the current agent, use one of these tools:
91
98
  - `douyin_get_user_series_by_sec_user_id`: preferred for creator short-drama series when `sec_user_id` is already known.
92
99
  - `douyin_get_user_series_by_profile_url`: use for creator short-drama series from profile URLs, short links, or profile share text.
93
100
 
94
- Creator content-list and series pagination use `page_token`. Pass back exactly the returned `next_page_token`; tokens are bound to the current user and command family and should not be reused across users.
101
+ Creator content-list and series pagination use opaque `page_token` values. Pass the complete returned `next_page_token` back unchanged for the same user and command family. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses. Prefer CLI `--pages`, `--all`, and `--max-items` when the user asks for multiple pages or all available creator content.
95
102
  - `kuaishou_get_user_posted_videos_by_user_id`: preferred when `user_id` is already known.
96
103
  - `kuaishou_get_user_posted_videos_by_profile_url`: use for profile URLs, short links, or profile share text.
97
- Kuaishou creator work pagination uses `page_token`; pass back exactly the returned `next_page_token` and do not reuse it across users.
104
+ Kuaishou creator work pagination uses opaque `page_token` values; pass the complete returned `next_page_token` back unchanged for the same user. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
98
105
 
99
106
  ## Output Guidance
100
107
 
101
108
  Summarize content-list evidence by title or description, summary, publish time, interaction counts, media links, and content type when present.
109
+ For XHS creator note-list results, copy each returned `note_id` as the complete 24-character lowercase hexadecimal ID exactly; do not pass or display only a prefix.
102
110
  For Douyin image/text posts, use `image_urls` rather than assuming a video playback URL exists.
103
111
  For Douyin short-drama series, report series IDs, titles, descriptions, covers, prices, and author facts when present.
104
112
  Use returned content IDs to chain into detail or comment analysis when needed.
@@ -57,22 +57,31 @@ Prefer the direct CLI when the agent can run shell commands. It does not require
57
57
  ```bash
58
58
  npx -y socialdatax-skills@latest xhs hot-search --pretty
59
59
  npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pretty
60
+ npx -y socialdatax-skills@latest xhs search --keyword "<keyword>" --pages 3 --pretty
60
61
  npx -y socialdatax-skills@latest xhs detail --note-id "<note_id>" --pretty
61
62
  npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --pretty
63
+ npx -y socialdatax-skills@latest xhs comments --note-id "<note_id>" --all --include-replies --pretty
62
64
  npx -y socialdatax-skills@latest xhs user-info --user-id "<user_id>" --pretty
63
65
  npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --pretty
66
+ npx -y socialdatax-skills@latest xhs user-posts --user-id "<user_id>" --all --pretty
64
67
  npx -y socialdatax-skills@latest douyin hot-search --pretty
65
68
  npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pretty
69
+ npx -y socialdatax-skills@latest douyin search --keyword "<keyword>" --pages 3 --pretty
66
70
  npx -y socialdatax-skills@latest douyin detail --aweme-id "<aweme_id>" --pretty
67
71
  npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --pretty
72
+ npx -y socialdatax-skills@latest douyin comments --aweme-id "<aweme_id>" --all --include-replies --pretty
68
73
  npx -y socialdatax-skills@latest douyin user-info --sec-user-id "<sec_user_id>" --pretty
69
74
  npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --pretty
75
+ npx -y socialdatax-skills@latest douyin user-posts --sec-user-id "<sec_user_id>" --all --pretty
70
76
  npx -y socialdatax-skills@latest kuaishou hot-search --pretty
71
77
  npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pretty
78
+ npx -y socialdatax-skills@latest kuaishou search --keyword "<keyword>" --pages 3 --pretty
72
79
  npx -y socialdatax-skills@latest kuaishou detail --photo-id "<photo_id>" --pretty
73
80
  npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --pretty
81
+ npx -y socialdatax-skills@latest kuaishou comments --photo-id "<photo_id>" --all --include-replies --pretty
74
82
  npx -y socialdatax-skills@latest kuaishou user-info --user-id "<user_id>" --pretty
75
83
  npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --pretty
84
+ npx -y socialdatax-skills@latest kuaishou user-posts --user-id "<user_id>" --all --pretty
76
85
  ```
77
86
 
78
87
  Additional direct CLI entrypoints:
@@ -87,6 +96,7 @@ npx -y socialdatax-skills@latest douyin comments --url "<douyin_content_url_or_s
87
96
  npx -y socialdatax-skills@latest douyin user-info --profile-url "<profile_url_or_share_text>" --pretty
88
97
  npx -y socialdatax-skills@latest douyin user-posts --profile-url "<profile_url_or_share_text>" --pretty
89
98
  npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --pretty
99
+ npx -y socialdatax-skills@latest douyin user-series --sec-user-id "<sec_user_id>" --all --pretty
90
100
  npx -y socialdatax-skills@latest douyin user-series --profile-url "<profile_url_or_share_text>" --pretty
91
101
  npx -y socialdatax-skills@latest xhs sub-comments --note-id "<note_id>" --comment-id "<comment_id>" --pretty
92
102
  npx -y socialdatax-skills@latest douyin replies --aweme-id "<aweme_id>" --comment-id "<comment_id>" --pretty
@@ -110,7 +120,12 @@ Required arguments:
110
120
 
111
121
  Optional arguments:
112
122
 
113
- - `--page` and `--page-token`: continue only with returned next-page values and keep the original topic, content item, or creator target stable.
123
+ - XHS numeric `page` is only for XHS search; Douyin and Kuaishou search use `page_token` only and do not accept `page`. Omit `page_token` on the first Douyin/Kuaishou search request, and continue only with the complete returned `next_page_token` from the same chain.
124
+ - `--page-token <next_page_token>`: use for Douyin/Kuaishou search continuation and for token-paginated comments, replies, creator posts, creator notes, and creator series. Keep the original topic, content item, or creator target stable. Do not modify, truncate, redact, mask, omit, normalize, rebuild, generate, or replace the middle with ellipses.
125
+ - `--pages <n>`: fetch and merge N pages for search, comments, replies, creator posts, or creator series.
126
+ - `--all`: fetch comments, replies, creator posts, or creator series until `next_page_token` is empty; do not use it for search.
127
+ - `--max-items <n>`: stop after collecting N primary results.
128
+ - `--include-replies`: for first-level comments, also fetch nested second-level replies under each returned comment.
114
129
  - `--sort-type`, `--note-type`, `--publish-time-range`, `--duration-range`, and `--content-type`: apply only to XHS or Douyin keyword research commands when the user asks for filtering; Kuaishou search currently uses `--keyword` and optional `--page-token`.
115
130
  - `--comment-id`: required for reply/sub-comment commands together with the content ID.
116
131
  - `--pretty`: output formatting only.
@@ -171,7 +186,7 @@ MCP tools matching the direct CLI commands above:
171
186
  - `kuaishou_get_video_comment_replies_by_comment_id`
172
187
 
173
188
  Use the automatically listed MCP tools above as the source of truth for tool names. Pick the narrowest tool for the user's platform and task.
174
- Continue pagination only when a returned next-page value is present, and keep the user's original topic, URL, creator target, or platform stable while changing only the page cursor or page number.
189
+ Use numeric `page` only for `xhs_search_notes`. Do not pass `page` to `douyin_search_videos` or `kuaishou_search_videos`; omit `page_token` on the first request and pass only the complete returned `next_page_token` when continuing the same chain.
175
190
 
176
191
  ## Output Guidance
177
192