socialdatax-skills 0.2.36 → 0.2.38

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/CATALOG.md CHANGED
@@ -6,7 +6,7 @@ Official website and API Key access: <https://socialdatax.com/ai?from=github>
6
6
 
7
7
  ## Quick Start
8
8
 
9
- Set your API Key in the environment:
9
+ For installed AI clients, persist `SOCIALDATAX_API_KEY` in the client Secret or user environment. The `export` below is only for a one-off shell run:
10
10
 
11
11
  ```bash
12
12
  export SOCIALDATAX_API_KEY="<your_api_key>"
package/README.md CHANGED
@@ -89,7 +89,7 @@ Common search phrases for this skill package:
89
89
  - Reserved future SocialDataX namespace names for existing platform listings without draft files yet: `com.socialdatax/kuaishou-insights`, `com.socialdatax/weibo-insights`, `com.socialdatax/wechat-channels-insights`, and `com.socialdatax/instagram-insights`.
90
90
  - Hosted endpoints without repo-tracked standalone listing materials yet: Bilibili, Zhihu, X / Twitter, YouTube, TikTok, and Sensitive Words Check.
91
91
  - Unified MCP registry name: none; this package installs skills and calls explicit hosted MCP entries.
92
- - Current public capability version: `0.2.36`
92
+ - Current public capability version: `0.2.38`
93
93
 
94
94
  ## Direct CLI
95
95
 
@@ -245,7 +245,7 @@ npx -y socialdatax-skills@latest tiktok user-posts --profile-url "<profile_url_o
245
245
  npx -y socialdatax-skills@latest sensitive-check text --text "<content>" --platform xhs --pretty
246
246
  ```
247
247
 
248
- Most direct CLI commands print a JSON envelope with `platform`, `tool`, `arguments`, and `data`. `sensitive-check` prints `platform`, `tool`, and `data` only; it does not echo the original text in CLI output. `wechat decrypt-media` is a local save command: pass the `video.video_url` returned by WeChat detail and an `--output` file path. It saves the media locally, decrypts when needed, and does not require `SOCIALDATAX_API_KEY`. `xhs/douyin/kuaishou/weibo download-media` is also local: pass one media URL returned by detail and either `--output <file>` or `--output-dir <directory>`. `x download-media` accepts one X media URL returned by search or detail. These local commands write through a `.part` file, resume partial downloads when the server supports range requests, skip an already existing output file, and infer common image/video/audio extensions in `--output-dir` mode. X / Twitter media URLs are served from overseas CDN domains such as `pbs.twimg.com` and `video.twimg.com`; if the local download times out, enable a global/TUN/VPN proxy for the download process, then retry.
248
+ Most direct CLI commands print a JSON envelope with `platform`, `tool`, `arguments`, and `data`. `sensitive-check` prints `platform`, `tool`, and `data` only; it does not echo the original text in CLI output. `wechat decrypt-media` is a local save command: pass the `video.video_url` returned by WeChat detail and an `--output` file path. It saves the media locally, decrypts when needed, and does not require `SOCIALDATAX_API_KEY`. `xhs/douyin/kuaishou/weibo download-media` is also local: pass one media URL returned by detail and either `--output <file>` or `--output-dir <directory>`. `x download-media` accepts one X media URL returned by search or detail. These local commands write through a `.part` file, resume partial downloads when the server supports range requests, skip an already existing output file, and infer common image/video/audio extensions in `--output-dir` mode. X / Twitter media URLs are served from overseas CDN domains such as `pbs.twimg.com` and `video.twimg.com`; if the local download times out, make the CLI process use a proxy with `--proxy "http://127.0.0.1:7890"` or `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`, then retry.
249
249
 
250
250
  For transcript commands, the direct CLI tries to deliver the final result in one run: submit may wait up to 240 seconds, and if `data.is_terminal` is not `true`, the CLI automatically continues matching get-job requests for up to 1200 seconds by default, with each get-job request waiting up to 240 seconds. Use positive `--max-wait-seconds <seconds>` to tune that follow-up window. Do not submit a duplicate transcript job just to poll status.
251
251
 
@@ -333,7 +333,7 @@ npx -y socialdatax-skills@latest doctor
333
333
  npx -y socialdatax-skills@latest doctor --json
334
334
  ```
335
335
 
336
- The public package declares no npm lifecycle scripts such as `preinstall`, `install`, or `postinstall`. The installer copies AgentSkills files only, does not save API keys, and does not change MCP server configuration. Authenticated data calls require `SOCIALDATAX_API_KEY` at runtime, do not read local browser data, and do not perform account actions.
336
+ The public package declares no npm lifecycle scripts such as `preinstall`, `install`, or `postinstall`. The installer copies Skill files only, does not save API keys, and does not change MCP server configuration. Authenticated data calls require `SOCIALDATAX_API_KEY` at runtime, do not read local browser data, and do not perform account actions.
337
337
 
338
338
  ## Platform Names
339
339
 
@@ -404,6 +404,8 @@ npx -y socialdatax-skills@latest install media-search --target codex
404
404
  npx -y socialdatax-skills@latest install media-search --target codex --scope workspace
405
405
  npx -y socialdatax-skills@latest install media-search --target claude-code
406
406
  npx -y socialdatax-skills@latest install media-search --target claude-code --scope workspace
407
+ npx -y socialdatax-skills@latest install --path ~/.workbuddy/skills/
408
+ npx -y socialdatax-skills@latest install media-search --path ~/.workbuddy/skills/media-search
407
409
  npx -y socialdatax-skills@latest xhs search --keyword "露营" --pretty
408
410
  npx -y socialdatax-skills@latest xhs hot-search --pretty
409
411
  npx -y socialdatax-skills@latest xhs transcript --url "<note_url_or_share_text>" --pretty
@@ -457,6 +459,7 @@ Default install locations:
457
459
  - Codex workspace scope: `./.codex/skills/<skill-name>`
458
460
  - Claude Code: `~/.claude/skills/<skill-name>`
459
461
  - Claude Code workspace scope: `./.claude/skills/<skill-name>`
462
+ - WorkBuddy: use `--path` with the Skills directory supported by your current WorkBuddy client, for example `npx -y socialdatax-skills@latest install --path ~/.workbuddy/skills/`
460
463
 
461
464
  If no skill name is provided, all skills are installed. If a destination already exists, re-run with `--force` to replace an existing directory for the same skill. Use `--path <directory>` to install one skill to a custom directory, or multiple skills under a custom parent directory. The `shared` scope is only meaningful for `--target hermes`; use `--target agents` for the shared AgentSkills directory directly.
462
465
 
@@ -572,7 +575,7 @@ Current X / Twitter workflows include:
572
575
  - Fetch paginated replies under a first-level comment by post_id and comment_id.
573
576
  - Read creator profile data by user_id, username, or profile URL/share text.
574
577
  - Fetch paginated public posts by creator user_id, username, or profile URL/share text.
575
- - Save returned `media_items[].cover_image_url` or `media_items[].video_url` media links locally with `x download-media`. Search results may already include these URLs; use detail as a fallback when search media fields are absent. If X CDN downloads time out, enable a global/TUN/VPN proxy for the download process and retry.
578
+ - Save returned `media_items[].cover_image_url` or `media_items[].video_url` media links locally with `x download-media`. Search results may already include these URLs; use detail as a fallback when search media fields are absent. If X CDN downloads time out, make the CLI process use a proxy with `--proxy "http://127.0.0.1:7890"` or `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`, then retry.
576
579
 
577
580
  Current YouTube workflows include:
578
581
 
@@ -804,6 +807,8 @@ Current Sensitive Words Check workflows include:
804
807
 
805
808
  For agents that can execute shell commands, use the direct CLI. This is the recommended default for installed skills:
806
809
 
810
+ For installed AI clients, persist `SOCIALDATAX_API_KEY` in the client Secret or user environment. The `export` below is only for a one-off shell run.
811
+
807
812
  ```bash
808
813
  export SOCIALDATAX_API_KEY="<SOCIALDATAX_API_KEY>"
809
814
  npx -y socialdatax-skills@latest xhs search --keyword "露营" --pretty
package/cli.mjs CHANGED
@@ -22,7 +22,7 @@ import { decryptWechatMediaCommand } from "./lib/media/wechat-decrypt.mjs";
22
22
  export { decryptWechatMediaCommand };
23
23
 
24
24
  const PACKAGE_NAME = "socialdatax-skills";
25
- const PACKAGE_VERSION = "0.2.36";
25
+ const PACKAGE_VERSION = "0.2.38";
26
26
  const PACKAGE_SPEC = `${PACKAGE_NAME}@latest`;
27
27
  const LOG_PREFIX = `[${PACKAGE_NAME}]`;
28
28
  const MIN_NODE_VERSION = "20.18.1";
@@ -172,7 +172,7 @@ const XHS_DIRECT_ACTION_OPTIONS = {
172
172
  "pretty",
173
173
  ],
174
174
  transcript: ["url", "noteId", "jobId", "maxWaitSeconds", "pretty"],
175
- "download-media": ["url", "output", "outputDir", "pretty"],
175
+ "download-media": ["url", "output", "outputDir", "proxy", "pretty"],
176
176
  };
177
177
  const XHS_DIRECT_ACTION_NAMES = Object.keys(XHS_DIRECT_ACTION_OPTIONS).join(", ");
178
178
  const XHS_OPTION_DISPLAY_NAMES = {
@@ -195,6 +195,7 @@ const XHS_OPTION_DISPLAY_NAMES = {
195
195
  maxWaitSeconds: "--max-wait-seconds",
196
196
  output: "--output",
197
197
  outputDir: "--output-dir",
198
+ proxy: "--proxy",
198
199
  };
199
200
  const XHS_SEARCH_SORT_TYPES = [
200
201
  "general",
@@ -262,7 +263,7 @@ const DOUYIN_DIRECT_ACTION_OPTIONS = {
262
263
  ],
263
264
  "user-series": ["secUserId", "profileUrl", "pageToken", "pages", "all", "maxItems", "pretty"],
264
265
  transcript: ["url", "awemeId", "jobId", "maxWaitSeconds", "pretty"],
265
- "download-media": ["url", "output", "outputDir", "pretty"],
266
+ "download-media": ["url", "output", "outputDir", "proxy", "pretty"],
266
267
  };
267
268
  const DOUYIN_DIRECT_ACTION_NAMES = Object.keys(DOUYIN_DIRECT_ACTION_OPTIONS).join(", ");
268
269
  const DOUYIN_SEARCH_SORT_TYPES = ["general", "time_descending", "like_count_descending"];
@@ -295,6 +296,7 @@ const DOUYIN_OPTION_DISPLAY_NAMES = {
295
296
  maxWaitSeconds: "--max-wait-seconds",
296
297
  output: "--output",
297
298
  outputDir: "--output-dir",
299
+ proxy: "--proxy",
298
300
  };
299
301
  const KUAISHOU_DIRECT_ACTION_OPTIONS = {
300
302
  "hot-search": ["pretty"],
@@ -333,7 +335,7 @@ const KUAISHOU_DIRECT_ACTION_OPTIONS = {
333
335
  "pretty",
334
336
  ],
335
337
  transcript: ["url", "photoId", "jobId", "maxWaitSeconds", "pretty"],
336
- "download-media": ["url", "output", "outputDir", "pretty"],
338
+ "download-media": ["url", "output", "outputDir", "proxy", "pretty"],
337
339
  };
338
340
  const KUAISHOU_DIRECT_ACTION_NAMES = Object.keys(KUAISHOU_DIRECT_ACTION_OPTIONS).join(", ");
339
341
  const KUAISHOU_OPTION_DISPLAY_NAMES = {
@@ -353,6 +355,7 @@ const KUAISHOU_OPTION_DISPLAY_NAMES = {
353
355
  maxWaitSeconds: "--max-wait-seconds",
354
356
  output: "--output",
355
357
  outputDir: "--output-dir",
358
+ proxy: "--proxy",
356
359
  };
357
360
  const BILIBILI_DIRECT_ACTION_OPTIONS = {
358
361
  "search-videos": [
@@ -578,7 +581,7 @@ const X_DIRECT_ACTION_OPTIONS = {
578
581
  "maxItems",
579
582
  "pretty",
580
583
  ],
581
- "download-media": ["url", "output", "outputDir", "pretty"],
584
+ "download-media": ["url", "output", "outputDir", "proxy", "pretty"],
582
585
  };
583
586
  const X_DIRECT_ACTION_NAMES = Object.keys(X_DIRECT_ACTION_OPTIONS).join(", ");
584
587
  const X_OPTION_DISPLAY_NAMES = {
@@ -597,6 +600,7 @@ const X_OPTION_DISPLAY_NAMES = {
597
600
  url: "--url",
598
601
  output: "--output",
599
602
  outputDir: "--output-dir",
603
+ proxy: "--proxy",
600
604
  };
601
605
  const X_SEARCH_SORT_TYPES = ["hot", "time_descending"];
602
606
  const YOUTUBE_DIRECT_ACTION_OPTIONS = {
@@ -724,7 +728,7 @@ const WEIBO_DIRECT_ACTION_OPTIONS = {
724
728
  "pretty",
725
729
  ],
726
730
  transcript: ["postUrl", "postId", "jobId", "maxWaitSeconds", "pretty"],
727
- "download-media": ["url", "output", "outputDir", "pretty"],
731
+ "download-media": ["url", "output", "outputDir", "proxy", "pretty"],
728
732
  };
729
733
  const WEIBO_DIRECT_ACTION_NAMES = Object.keys(WEIBO_DIRECT_ACTION_OPTIONS).join(", ");
730
734
  const WEIBO_OPTION_DISPLAY_NAMES = {
@@ -745,6 +749,7 @@ const WEIBO_OPTION_DISPLAY_NAMES = {
745
749
  url: "--url",
746
750
  output: "--output",
747
751
  outputDir: "--output-dir",
752
+ proxy: "--proxy",
748
753
  };
749
754
  const WECHAT_DIRECT_ACTION_OPTIONS = {
750
755
  "hot-search": ["pretty"],
@@ -1741,11 +1746,66 @@ async function main() {
1741
1746
  process.exitCode = 1;
1742
1747
  }
1743
1748
  } catch (error) {
1744
- console.error(`${LOG_PREFIX} ${error.message}`);
1749
+ console.error(`${LOG_PREFIX} ${formatCliErrorMessage(error)}`);
1745
1750
  process.exit(1);
1746
1751
  }
1747
1752
  }
1748
1753
 
1754
+ export function formatCliErrorMessage(error) {
1755
+ const message = error?.message || String(error);
1756
+ if (error?.structuredContent) {
1757
+ return message;
1758
+ }
1759
+ if (isLocalDependencyInstallError(message)) {
1760
+ return `${message}
1761
+ Local dependency/runtime/network/authorization issue, not a SocialDataX API key or business data error: install or restore the required CLI dependencies or Node.js runtime when missing, allow node/npm/npx and npm registry access, allow required local file or directory permissions, then retry the same command. 本地依赖/运行环境/网络/授权问题,不是 SocialDataX API Key 或业务数据返回错误:缺少依赖或 node/npm/npx 时请安装或启用当前 CLI 所需依赖/运行时;已有环境时允许当前 Agent 运行 node/npm/npx、访问 npm registry 和目标文件或目录权限,然后重试原命令;不要改用公开网页搜索替代 SocialDataX 数据。`;
1762
+ }
1763
+ return message;
1764
+ }
1765
+
1766
+ function isLocalDependencyInstallError(message) {
1767
+ if (isWrappedSocialDataxDirectCallError(message)) {
1768
+ return false;
1769
+ }
1770
+
1771
+ return (
1772
+ isMissingNodeDependencyError(message) ||
1773
+ /Node\.js \d+\.\d+\.\d+ or newer is required/i.test(message) ||
1774
+ isLocalFilesystemPermissionError(message) ||
1775
+ /npm ERR!.*\bENOTFOUND\b/i.test(message) ||
1776
+ (/\bENOSPC\b/i.test(message) &&
1777
+ /(?:npm|npx|socialdatax-skills|node_modules)/i.test(message)) ||
1778
+ (/\b(?:ENOTFOUND|EAI_AGAIN|ECONNRESET|ECONNREFUSED|ETIMEDOUT)\b/.test(message) &&
1779
+ /(?:npm|registry\.npmjs\.org|npm registry)/i.test(message)) ||
1780
+ (/(?:npm|registry\.npmjs\.org|npm registry)/i.test(message) &&
1781
+ /\b(?:E401|E403|401|403|SELF_SIGNED_CERT_IN_CHAIN|UNABLE_TO_VERIFY_LEAF_SIGNATURE|CERT_HAS_EXPIRED|certificate)\b/i.test(message)) ||
1782
+ /\bnpx\b.*(?:EACCES|EPERM|ENOENT|permission|denied|not found|被拒绝)/i.test(message) ||
1783
+ /(?:EACCES|EPERM|ENOENT|permission|denied|not found|被拒绝).*\bnpx\b/i.test(message) ||
1784
+ /\bnpm\b.*(?:EACCES|EPERM|permission|denied|被拒绝)/i.test(message)
1785
+ );
1786
+ }
1787
+
1788
+ function isLocalFilesystemPermissionError(message) {
1789
+ return (
1790
+ /\b(?:EACCES|EPERM)\b/i.test(message) &&
1791
+ /(?:permission|denied|operation not permitted|mkdir|open|copyfile|cp|scandir|rename|unlink|rmdir)/i.test(message) &&
1792
+ !/https?:\/\//i.test(message)
1793
+ );
1794
+ }
1795
+
1796
+ function isWrappedSocialDataxDirectCallError(message) {
1797
+ return /Direct CLI call failed[\s\S]* at https?:\/\/\S+/i.test(message);
1798
+ }
1799
+
1800
+ function isMissingNodeDependencyError(message) {
1801
+ return (
1802
+ /Cannot find (?:package|module)/i.test(message) &&
1803
+ (/(?:imported from|ERR_MODULE_NOT_FOUND)[\s\S]*(?:cli\.mjs|socialdatax-skills|node_modules)/i.test(message) ||
1804
+ /Cannot find module ['"][^'"]*node_modules[\\/]/i.test(message) ||
1805
+ /Require stack:[\s\S]*(?:cli\.mjs|socialdatax-skills)/i.test(message))
1806
+ );
1807
+ }
1808
+
1749
1809
  function parseCommandArgs(args) {
1750
1810
  const options = {};
1751
1811
  const positional = [];
@@ -2348,6 +2408,15 @@ function expandHome(path) {
2348
2408
  if (path.startsWith("~/")) {
2349
2409
  return join(homedir(), path.slice(2));
2350
2410
  }
2411
+ if (path.startsWith("~\\")) {
2412
+ return join(
2413
+ homedir(),
2414
+ ...path
2415
+ .slice(2)
2416
+ .split(/[\\/]+/)
2417
+ .filter(Boolean)
2418
+ );
2419
+ }
2351
2420
  return path;
2352
2421
  }
2353
2422
 
@@ -2493,11 +2562,13 @@ async function installSkills(args) {
2493
2562
  console.log("No MCP server setup is required for the bundled skills.");
2494
2563
  console.log("No API key was stored by this installer.");
2495
2564
  console.log("No MCP server configuration was changed.");
2496
- console.log("Installed files are AgentSkills files only.");
2565
+ console.log("Installed files are Skill files only.");
2497
2566
  console.log(`Authenticated data calls require ${PRIMARY_API_KEY_ENV} at runtime.`);
2498
2567
  console.log("Data calls do not perform login, posting, editing, liking, commenting, or account actions.");
2499
2568
  console.log("Configure your API Key before making authenticated calls:");
2500
- console.log(` export ${PRIMARY_API_KEY_ENV}="<${PRIMARY_API_KEY_ENV}>"`);
2569
+ console.log(
2570
+ ` Persist ${PRIMARY_API_KEY_ENV} in the target AI client Secret or user environment; do not rely on a temporary shell export.`
2571
+ );
2501
2572
  printInstalledSkillAttributionNote();
2502
2573
  }
2503
2574
 
@@ -2516,7 +2587,7 @@ function resolveInstallDestination({
2516
2587
  path,
2517
2588
  usePathAsParent,
2518
2589
  }) {
2519
- const customPath = path && usePathAsParent ? join(path, skillName) : path;
2590
+ const customPath = path && usePathAsParent ? join(expandHome(path), skillName) : path;
2520
2591
  return resolveInstallDir({
2521
2592
  target,
2522
2593
  scope,
@@ -2652,7 +2723,7 @@ function buildDoctorReport() {
2652
2723
  recommendedNode: "22 LTS or newer",
2653
2724
  },
2654
2725
  install: {
2655
- writes: "AgentSkills directories only",
2726
+ writes: "Skill files only",
2656
2727
  apiKeyStored: false,
2657
2728
  mcpConfigChanged: false,
2658
2729
  supportsDryRun: true,
@@ -2696,7 +2767,7 @@ function printDoctor(args) {
2696
2767
  console.log("");
2697
2768
  console.log("Install safety:");
2698
2769
  console.log("- npm lifecycle scripts: none declared by this package.");
2699
- console.log("- install writes AgentSkills files only.");
2770
+ console.log("- install writes Skill files only.");
2700
2771
  console.log("- install does not store API keys.");
2701
2772
  console.log("- install does not change MCP server configuration.");
2702
2773
  console.log("- install --dry-run previews destinations without writing files.");
@@ -3144,6 +3215,14 @@ function printHelp() {
3144
3215
  );
3145
3216
  console.log(" Install one skill to ./.claude/skills.");
3146
3217
  console.log("");
3218
+ console.log(` npx -y ${PACKAGE_SPEC} install --path ~/.workbuddy/skills/`);
3219
+ console.log(" Install all skills under a custom WorkBuddy Skills parent directory.");
3220
+ console.log("");
3221
+ console.log(
3222
+ ` npx -y ${PACKAGE_SPEC} install media-search --path ~/.workbuddy/skills/media-search`
3223
+ );
3224
+ console.log(" Install one skill to a direct custom Skills directory.");
3225
+ console.log("");
3147
3226
  console.log("Available skills:");
3148
3227
  console.log(` ${AVAILABLE_SKILL_NAMES.join(", ")}`);
3149
3228
  console.log("");
@@ -3160,6 +3239,8 @@ function printHelp() {
3160
3239
  console.log(" Output file path for local decrypt-media, download-media, or Bilibili download.");
3161
3240
  console.log(" --output-dir <directory>");
3162
3241
  console.log(" Directory for download-media or Bilibili download output when --output is omitted.");
3242
+ console.log(" --proxy <http-or-https-proxy-url>");
3243
+ console.log(" Proxy for local download-media requests; otherwise HTTPS_PROXY, HTTP_PROXY, or ALL_PROXY is used when set.");
3163
3244
  console.log(" --ffmpeg-path <path>");
3164
3245
  console.log(" ffmpeg executable path for Bilibili download; defaults to ffmpeg.");
3165
3246
  console.log(" --keep-tracks");
@@ -3273,7 +3354,8 @@ function printHelp() {
3273
3354
  console.log(" --target <openclaw|hermes|agents|codex|claude-code|claude>");
3274
3355
  console.log(" For install.");
3275
3356
  console.log(" --scope <user|workspace|shared> shared is only for --target hermes.");
3276
- console.log(" --path <directory> Override install destination.");
3357
+ console.log(" --path <directory>");
3358
+ console.log(" For install. Multiple/all skills: parent directory; one skill: skill destination directory.");
3277
3359
  console.log(" --dry-run Preview install without writing files.");
3278
3360
  console.log(" --force Replace an existing directory for the same skill.");
3279
3361
  }
@@ -3346,7 +3428,9 @@ async function runXhsDirectCommand(args) {
3346
3428
  validateXhsDirectActionOptions(action, options);
3347
3429
 
3348
3430
  if (action === "download-media") {
3349
- const data = await downloadPlatformMediaFromUrl("xhs", options.url, options);
3431
+ const data = await downloadPlatformMediaFromUrl("xhs", options.url, options, {
3432
+ env: process.env,
3433
+ });
3350
3434
  process.stdout.write(JSON.stringify(data, null, options.pretty ? 2 : 0));
3351
3435
  process.stdout.write("\n");
3352
3436
  return;
@@ -3382,7 +3466,9 @@ async function runDouyinDirectCommand(args) {
3382
3466
  validateDouyinDirectActionOptions(action, options);
3383
3467
 
3384
3468
  if (action === "download-media") {
3385
- const data = await downloadPlatformMediaFromUrl("douyin", options.url, options);
3469
+ const data = await downloadPlatformMediaFromUrl("douyin", options.url, options, {
3470
+ env: process.env,
3471
+ });
3386
3472
  process.stdout.write(JSON.stringify(data, null, options.pretty ? 2 : 0));
3387
3473
  process.stdout.write("\n");
3388
3474
  return;
@@ -3418,7 +3504,9 @@ async function runKuaishouDirectCommand(args) {
3418
3504
  validateKuaishouDirectActionOptions(action, options);
3419
3505
 
3420
3506
  if (action === "download-media") {
3421
- const data = await downloadPlatformMediaFromUrl("kuaishou", options.url, options);
3507
+ const data = await downloadPlatformMediaFromUrl("kuaishou", options.url, options, {
3508
+ env: process.env,
3509
+ });
3422
3510
  process.stdout.write(JSON.stringify(data, null, options.pretty ? 2 : 0));
3423
3511
  process.stdout.write("\n");
3424
3512
  return;
@@ -3564,7 +3652,9 @@ async function runWeiboDirectCommand(args) {
3564
3652
  validateWeiboDirectActionOptions(action, options);
3565
3653
 
3566
3654
  if (action === "download-media") {
3567
- const data = await downloadPlatformMediaFromUrl("weibo", options.url, options);
3655
+ const data = await downloadPlatformMediaFromUrl("weibo", options.url, options, {
3656
+ env: process.env,
3657
+ });
3568
3658
  process.stdout.write(JSON.stringify(data, null, options.pretty ? 2 : 0));
3569
3659
  process.stdout.write("\n");
3570
3660
  return;
@@ -3682,7 +3772,9 @@ async function runXDirectCommand(args) {
3682
3772
  validateXDirectActionOptions(action, options);
3683
3773
 
3684
3774
  if (action === "download-media") {
3685
- const data = await downloadPlatformMediaFromUrl("x", options.url, options);
3775
+ const data = await downloadPlatformMediaFromUrl("x", options.url, options, {
3776
+ env: process.env,
3777
+ });
3686
3778
  process.stdout.write(JSON.stringify(data, null, options.pretty ? 2 : 0));
3687
3779
  process.stdout.write("\n");
3688
3780
  return;
@@ -10,6 +10,8 @@ import {
10
10
  import { homedir } from "node:os";
11
11
  import { basename, dirname, extname, join, resolve } from "node:path";
12
12
 
13
+ import { ProxyAgent } from "undici";
14
+
13
15
  import {
14
16
  MEDIA_DOWNLOAD_MAX_BYTES,
15
17
  MEDIA_DOWNLOAD_TIMEOUT_MS,
@@ -82,6 +84,10 @@ const MEDIA_URL_FORMAT_EXTENSIONS = new Map([
82
84
  ["mp4", "mp4"],
83
85
  ["m4a", "m4a"],
84
86
  ]);
87
+ const MEDIA_DOWNLOAD_PROXY_ENV_BY_PROTOCOL = {
88
+ "http:": ["HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"],
89
+ "https:": ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"],
90
+ };
85
91
  export async function downloadPlatformMediaFromUrl(
86
92
  platform,
87
93
  url,
@@ -94,6 +100,7 @@ export async function downloadPlatformMediaFromUrl(
94
100
  now = () => new Date(),
95
101
  linkFile = linkPath,
96
102
  copyFile = copyFilePath,
103
+ env = {},
97
104
  } = {}
98
105
  ) {
99
106
  const config = resolvePlatformMediaDownloadConfig(platform);
@@ -111,16 +118,27 @@ export async function downloadPlatformMediaFromUrl(
111
118
  existingOutput.size
112
119
  );
113
120
  }
114
-
115
- return downloadWithPartResume(parsedUrl, target, {
121
+ const proxyDispatcher = createMediaDownloadProxyDispatcher(parsedUrl, options, {
116
122
  config,
117
- fetchMedia,
118
- downloadTimeoutMs,
119
- maxDownloadBytes,
120
- retryDelayMs,
121
- linkFile,
122
- copyFile,
123
+ env,
123
124
  });
125
+
126
+ try {
127
+ return await downloadWithPartResume(parsedUrl, target, {
128
+ config,
129
+ fetchMedia,
130
+ downloadTimeoutMs,
131
+ maxDownloadBytes,
132
+ retryDelayMs,
133
+ proxyDispatcher,
134
+ linkFile,
135
+ copyFile,
136
+ });
137
+ } finally {
138
+ if (proxyDispatcher) {
139
+ await proxyDispatcher.close().catch(ignoreError);
140
+ }
141
+ }
124
142
  }
125
143
 
126
144
  async function resolvePlatformMediaTarget(parsedUrl, options = {}, now, config) {
@@ -177,6 +195,7 @@ async function downloadWithPartResume(
177
195
  downloadTimeoutMs,
178
196
  maxDownloadBytes,
179
197
  retryDelayMs,
198
+ proxyDispatcher,
180
199
  linkFile,
181
200
  copyFile,
182
201
  }
@@ -189,6 +208,7 @@ async function downloadWithPartResume(
189
208
  fetchMedia,
190
209
  downloadTimeoutMs,
191
210
  maxDownloadBytes,
211
+ proxyDispatcher,
192
212
  linkFile,
193
213
  copyFile,
194
214
  });
@@ -242,6 +262,7 @@ async function downloadOnce(
242
262
  fetchMedia,
243
263
  downloadTimeoutMs,
244
264
  maxDownloadBytes,
265
+ proxyDispatcher,
245
266
  linkFile,
246
267
  copyFile,
247
268
  }
@@ -260,6 +281,7 @@ async function downloadOnce(
260
281
  const response = await fetchMedia(parsedUrl.href, {
261
282
  headers,
262
283
  ...(signal ? { signal } : {}),
284
+ ...(proxyDispatcher ? { dispatcher: proxyDispatcher } : {}),
263
285
  });
264
286
 
265
287
  if (MEDIA_EXPIRED_HTTP_STATUSES.has(response.status)) {
@@ -544,6 +566,53 @@ function parseHttpUrl(value, config) {
544
566
  return parsed;
545
567
  }
546
568
 
569
+ function createMediaDownloadProxyDispatcher(parsedUrl, options, { config, env }) {
570
+ const proxyUrl = resolveMediaDownloadProxyUrl(parsedUrl, options, {
571
+ config,
572
+ env,
573
+ });
574
+ return proxyUrl ? new ProxyAgent(proxyUrl) : undefined;
575
+ }
576
+
577
+ function resolveMediaDownloadProxyUrl(parsedUrl, options = {}, { config, env }) {
578
+ if (options.proxy !== undefined) {
579
+ return normalizeMediaDownloadProxyUrl(options.proxy, "--proxy", config);
580
+ }
581
+ const envName = MEDIA_DOWNLOAD_PROXY_ENV_BY_PROTOCOL[parsedUrl.protocol]?.find(
582
+ (name) => normalizeOptionalString(env?.[name])
583
+ );
584
+ if (!envName) {
585
+ return "";
586
+ }
587
+ return normalizeMediaDownloadProxyUrl(env[envName], envName, config);
588
+ }
589
+
590
+ function normalizeMediaDownloadProxyUrl(value, displayName, config) {
591
+ const normalized = normalizeOptionalString(value);
592
+ if (!normalized) {
593
+ return "";
594
+ }
595
+ let parsed;
596
+ try {
597
+ parsed = new URL(normalized);
598
+ } catch {
599
+ throw new Error(
600
+ `${displayName} must be an HTTP or HTTPS proxy URL for ${config.platform} download-media.`
601
+ );
602
+ }
603
+ if (!["http:", "https:"].includes(parsed.protocol) || !parsed.hostname) {
604
+ throw new Error(
605
+ `${displayName} must be an HTTP or HTTPS proxy URL for ${config.platform} download-media.`
606
+ );
607
+ }
608
+ return parsed.href;
609
+ }
610
+
611
+ function normalizeOptionalString(value) {
612
+ const normalized = String(value ?? "").trim();
613
+ return normalized === "" ? "" : normalized;
614
+ }
615
+
547
616
  function normalizePlatformDownloadHeaders(config) {
548
617
  return {
549
618
  "User-Agent": MEDIA_DOWNLOAD_USER_AGENT,
@@ -671,7 +740,7 @@ function formatPlatformMediaTimeoutMessage(config) {
671
740
  return [
672
741
  "X / Twitter media download timed out.",
673
742
  "X media is served from overseas CDN domains such as pbs.twimg.com and video.twimg.com;",
674
- "enable a global/TUN/VPN proxy for the download process, then retry.",
743
+ "make the CLI process use a proxy with --proxy \"http://127.0.0.1:7890\" or HTTP_PROXY / HTTPS_PROXY / ALL_PROXY, then retry.",
675
744
  ].join(" ");
676
745
  }
677
746
  return `${config.displayName} media download timed out.`;
@@ -682,7 +751,7 @@ function formatPlatformMediaNetworkFailureMessage(config) {
682
751
  return [
683
752
  "X / Twitter media download could not reach the media CDN.",
684
753
  "X media is served from overseas CDN domains such as pbs.twimg.com and video.twimg.com;",
685
- "enable a global/TUN/VPN proxy for the download process, then retry.",
754
+ "make the CLI process use a proxy with --proxy \"http://127.0.0.1:7890\" or HTTP_PROXY / HTTPS_PROXY / ALL_PROXY, then retry.",
686
755
  ].join(" ");
687
756
  }
688
757
  return `${config.displayName} media download failed before completion.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "socialdatax-skills",
3
- "version": "0.2.36",
3
+ "version": "0.2.38",
4
4
  "description": "SocialDataX / 社媒数据助手 skill installer and direct CLI helper for hosted MCP services.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -95,7 +95,8 @@
95
95
  "node": ">=20.18.1"
96
96
  },
97
97
  "dependencies": {
98
- "@modelcontextprotocol/sdk": "^1.29.0"
98
+ "@modelcontextprotocol/sdk": "^1.29.0",
99
+ "undici": "^7.29.0"
99
100
  },
100
101
  "overrides": {
101
102
  "hono": "4.12.27",
@@ -320,6 +320,7 @@ For Weibo and WeChat Channels / 视频号 comments, preserve returned content ID
320
320
 
321
321
  ## Troubleshooting
322
322
 
323
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
323
324
  - For non-balance network or API errors, preserve the error message, check `SOCIALDATAX_API_KEY`, parameters, and link or ID format, then retry once when appropriate.
324
325
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not retry repeatedly. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
325
326
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.
@@ -225,7 +225,7 @@ When the user wants to save Kuaishou media after detail, pass each returned `ima
225
225
  For Bilibili detail, preserve returned content IDs and URLs because comments, replies, and article/dynamic reactions may need them.
226
226
  For Zhihu, Instagram, X / Twitter, YouTube, and TikTok detail results, keep the returned URL and public content ID values exactly as returned for follow-up comments or creator lookups.
227
227
  When the user wants to save X / Twitter media after search or detail, pass each returned `media_items[].cover_image_url` or `media_items[].video_url` to `npx -y socialdatax-skills@latest x download-media --url "<media_url>" --output-dir <directory> --pretty`; this local save command does not require `SOCIALDATAX_API_KEY`. Search results may already include these media URLs; use detail as a fallback when search media fields are absent.
228
- If an X / Twitter local media download times out, explain that X media is served from overseas CDN domains such as `pbs.twimg.com` and `video.twimg.com`; ask the user to enable a global/TUN/VPN proxy for the download process, then retry the same `x download-media` command.
228
+ If an X / Twitter local media download times out, explain that X media is served from overseas CDN domains such as `pbs.twimg.com` and `video.twimg.com`; ask the user to make the download process use their proxy, for example by adding `--proxy "http://127.0.0.1:7890"` or setting `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`, then retry the same `x download-media` command.
229
229
  Detail access is read-only and does not provide account actions.
230
230
  For Weibo detail, include `post_id`, content, author, media, interaction counts, publish time, and post URL when available.
231
231
  When the user wants to save Weibo media after detail, pass each returned `image_urls[]` or `video.video_url` to `npx -y socialdatax-skills@latest weibo download-media --url "<media_url>" --output-dir <directory> --pretty`; this local save command does not require `SOCIALDATAX_API_KEY`.
@@ -235,6 +235,7 @@ For WeChat Official Account / 微信公众号 article detail, include article ti
235
235
 
236
236
  ## Troubleshooting
237
237
 
238
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
238
239
  - For non-balance network or API errors, preserve the error message, check `SOCIALDATAX_API_KEY`, parameters, and link or ID format, then retry once when appropriate.
239
240
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not retry repeatedly. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
240
241
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.
@@ -351,12 +351,13 @@ For Kuaishou search results, include `photo_id`, `share_url`, author facts, and
351
351
  For Bilibili search results, include content IDs, URLs, titles or descriptions, creator facts, and visible interaction counts when the user needs traceability.
352
352
  For Zhihu search results, include content URLs, titles or summaries, creator facts, and visible interaction counts when the user needs traceability.
353
353
  For Instagram, X / Twitter, YouTube, and TikTok search results, include returned post or video URLs, public IDs, author facts, and visible interaction counts when the user needs traceability.
354
- For X / Twitter search results, preserve returned `media_items[].cover_image_url` and `media_items[].video_url` values when present. When the user wants to save X media, pass one returned media URL at a time to `npx -y socialdatax-skills@latest x download-media --url "<media_url>" --output-dir <directory> --pretty`; this local save command does not require `SOCIALDATAX_API_KEY`. If search media fields are absent, use X detail as the fallback before downloading.
354
+ For X / Twitter search results, preserve returned `media_items[].cover_image_url` and `media_items[].video_url` values when present. When the user wants to save X media, pass one returned media URL at a time to `npx -y socialdatax-skills@latest x download-media --url "<media_url>" --output-dir <directory> --pretty`; this local save command does not require `SOCIALDATAX_API_KEY`. If the CLI process does not inherit the user's local proxy, add `--proxy "http://127.0.0.1:7890"` or set `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY`. If search media fields are absent, use X detail as the fallback before downloading.
355
355
  For Weibo search results, include `post_id`, `post_url`, author facts, interaction counts, and publish time when the user needs traceability.
356
356
  For WeChat Channels / 视频号 search results, include `encrypted_object_id`, author facts, interaction counts, publish time, and duration when the user needs traceability.
357
357
 
358
358
  ## Troubleshooting
359
359
 
360
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
360
361
  - For non-balance network or API errors, preserve the error message, check `SOCIALDATAX_API_KEY`, parameters, and link or ID format, then retry once when appropriate.
361
362
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not retry repeatedly. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
362
363
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.
@@ -166,6 +166,7 @@ If the result says no processable video resource is available or the job failed,
166
166
 
167
167
  ## Troubleshooting
168
168
 
169
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
169
170
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not submit another job or keep polling. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
170
171
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.
171
172
  - If a transcript `job_id` already exists, only check that same job; do not submit the video again.
@@ -227,6 +227,7 @@ For YouTube, report channel profile facts separately from video-list evidence.
227
227
 
228
228
  ## Troubleshooting
229
229
 
230
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
230
231
  - For non-balance network or API errors, preserve the error message, check `SOCIALDATAX_API_KEY`, parameters, and link or ID format, then retry once when appropriate.
231
232
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not retry repeatedly. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
232
233
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.
@@ -315,6 +315,7 @@ For WeChat Channels / 视频号 creator videos, report object IDs, descriptions,
315
315
 
316
316
  ## Troubleshooting
317
317
 
318
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
318
319
  - For non-balance network or API errors, preserve the error message, check `SOCIALDATAX_API_KEY`, parameters, and link or ID format, then retry once when appropriate.
319
320
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not retry repeatedly. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
320
321
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.
@@ -79,6 +79,7 @@ Do not echo the full original text back unless the user explicitly asks; prefer
79
79
 
80
80
  ## Troubleshooting
81
81
 
82
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
82
83
  - For non-balance network or API errors, preserve the error message, check `SOCIALDATAX_API_KEY`, parameters, and link or ID format, then retry once when appropriate.
83
84
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not retry repeatedly. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
84
85
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.
@@ -345,6 +345,7 @@ For hot-search, report ranking signals separately from keyword search results.
345
345
 
346
346
  ## Troubleshooting
347
347
 
348
+ - If an SDK/dependency, npm network, Node.js/npm/npx availability, permission, or missing runtime error appears, treat it as a local runtime, dependency installation, network, or agent authorization issue, not a SocialDataX API key or business data error. If the current environment has permission, install or restore automatically. When network or execution authorization is needed, ask the user to approve or finish authorization, then continue the same command; do not use public web search as a substitute for SocialDataX data.
348
349
  - For non-balance, non-rate-limit network or API errors, preserve the error message, check `SOCIALDATAX_API_KEY`, parameters, and link or ID format, then retry once when appropriate.
349
350
  - If the response returns `insufficient_balance` or says the balance/credits are insufficient, do not retry repeatedly. Show the recharge URL from the error exactly as returned, then continue the same command after the user recharges.
350
351
  - If the user has recharged but still sees insufficient balance, confirm `SOCIALDATAX_API_KEY` belongs to the same account that was recharged; if needed, copy a fresh API Key from the official dashboard.