specific-ai-cli 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +177 -0
  3. package/README.ja.md +177 -0
  4. package/README.ko.md +177 -0
  5. package/README.md +174 -2
  6. package/README.zh-TW.md +177 -0
  7. package/assets/banner.txt +3 -0
  8. package/dist/cli/argv.js +98 -0
  9. package/dist/cli/commands/auth.js +11 -0
  10. package/dist/cli/commands/config.js +23 -0
  11. package/dist/cli/commands/doctor.js +134 -0
  12. package/dist/cli/commands/platforms.js +24 -0
  13. package/dist/cli/commands/version.js +4 -0
  14. package/dist/cli/confirm.js +22 -0
  15. package/dist/cli/dispatch.js +440 -0
  16. package/dist/cli/help.js +223 -0
  17. package/dist/cli/hints.js +60 -0
  18. package/dist/cli/main.js +18 -0
  19. package/dist/cli/output.js +66 -0
  20. package/dist/core/auth-handlers.js +41 -0
  21. package/dist/core/auth-store.js +119 -0
  22. package/dist/core/capabilities.js +163 -0
  23. package/dist/core/config.js +121 -0
  24. package/dist/core/cookies.js +164 -0
  25. package/dist/core/errors.js +37 -0
  26. package/dist/core/files.js +89 -0
  27. package/dist/core/fsutil.js +52 -0
  28. package/dist/core/hash.js +4 -0
  29. package/dist/core/http.js +331 -0
  30. package/dist/core/image.js +280 -0
  31. package/dist/core/log.js +40 -0
  32. package/dist/core/login.js +191 -0
  33. package/dist/core/mp4.js +130 -0
  34. package/dist/core/normalize.js +207 -0
  35. package/dist/core/options.js +110 -0
  36. package/dist/core/paths.js +38 -0
  37. package/dist/core/pb.js +59 -0
  38. package/dist/core/py.js +181 -0
  39. package/dist/core/rand.js +131 -0
  40. package/dist/core/registry.js +129 -0
  41. package/dist/core/schemas.js +41 -0
  42. package/dist/core/stream.js +104 -0
  43. package/dist/core/toolkit.js +35 -0
  44. package/dist/core/vm.js +73 -0
  45. package/dist/core/vocab.js +157 -0
  46. package/dist/platforms/_shared/impaas.js +410 -0
  47. package/dist/platforms/bilibili/index.js +216 -0
  48. package/dist/platforms/bilibili/web/api.js +806 -0
  49. package/dist/platforms/bilibili/web/client.js +311 -0
  50. package/dist/platforms/bilibili/web/commands.js +783 -0
  51. package/dist/platforms/bilibili/web/ddddocr.js +342 -0
  52. package/dist/platforms/bilibili/web/gaia.js +130 -0
  53. package/dist/platforms/bilibili/web/geetest-manual.js +154 -0
  54. package/dist/platforms/bilibili/web/geetest-vision.js +232 -0
  55. package/dist/platforms/bilibili/web/geetest.js +450 -0
  56. package/dist/platforms/bilibili/web/normalize.js +234 -0
  57. package/dist/platforms/bilibili/web/profile.js +135 -0
  58. package/dist/platforms/bilibili/web/resolve.js +112 -0
  59. package/dist/platforms/bilibili/web/sign.js +223 -0
  60. package/dist/platforms/bilibili/web/upos.js +102 -0
  61. package/dist/platforms/douyin/index.js +142 -0
  62. package/dist/platforms/douyin/web/api.js +863 -0
  63. package/dist/platforms/douyin/web/client.js +413 -0
  64. package/dist/platforms/douyin/web/commands.js +712 -0
  65. package/dist/platforms/douyin/web/creator.js +601 -0
  66. package/dist/platforms/douyin/web/crypto.js +247 -0
  67. package/dist/platforms/douyin/web/dtrait.js +190 -0
  68. package/dist/platforms/douyin/web/im.js +502 -0
  69. package/dist/platforms/douyin/web/image.js +5 -0
  70. package/dist/platforms/douyin/web/live.js +225 -0
  71. package/dist/platforms/douyin/web/mssdk.js +148 -0
  72. package/dist/platforms/douyin/web/normalize.js +326 -0
  73. package/dist/platforms/douyin/web/passport.js +1247 -0
  74. package/dist/platforms/douyin/web/profile.js +221 -0
  75. package/dist/platforms/douyin/web/proto.js +26 -0
  76. package/dist/platforms/douyin/web/resolve.js +88 -0
  77. package/dist/platforms/douyin/web/sign.js +436 -0
  78. package/dist/platforms/douyin/web/tos.js +78 -0
  79. package/dist/platforms/index.js +17 -0
  80. package/dist/platforms/jd/index.js +67 -0
  81. package/dist/platforms/jd/web/aks.js +115 -0
  82. package/dist/platforms/jd/web/api.js +549 -0
  83. package/dist/platforms/jd/web/chat.js +132 -0
  84. package/dist/platforms/jd/web/client.js +470 -0
  85. package/dist/platforms/jd/web/commands.js +557 -0
  86. package/dist/platforms/jd/web/jcap/helper.js +90 -0
  87. package/dist/platforms/jd/web/jcap/host.js +107 -0
  88. package/dist/platforms/jd/web/jcap/lsd.js +300 -0
  89. package/dist/platforms/jd/web/jcap/solver.js +995 -0
  90. package/dist/platforms/jd/web/jcap.js +150 -0
  91. package/dist/platforms/jd/web/js.js +420 -0
  92. package/dist/platforms/jd/web/login.js +775 -0
  93. package/dist/platforms/jd/web/normalize.js +297 -0
  94. package/dist/platforms/jd/web/profile.js +256 -0
  95. package/dist/platforms/jd/web/util.js +175 -0
  96. package/dist/platforms/jd/web/webm.js +133 -0
  97. package/dist/platforms/kuaishou/index.js +98 -0
  98. package/dist/platforms/kuaishou/web/api.js +694 -0
  99. package/dist/platforms/kuaishou/web/captcha-crypto.js +134 -0
  100. package/dist/platforms/kuaishou/web/captcha-fp.js +180 -0
  101. package/dist/platforms/kuaishou/web/captcha.js +273 -0
  102. package/dist/platforms/kuaishou/web/client.js +487 -0
  103. package/dist/platforms/kuaishou/web/commands.js +863 -0
  104. package/dist/platforms/kuaishou/web/gap.js +144 -0
  105. package/dist/platforms/kuaishou/web/gdfp.js +376 -0
  106. package/dist/platforms/kuaishou/web/gql.js +11 -0
  107. package/dist/platforms/kuaishou/web/live.js +125 -0
  108. package/dist/platforms/kuaishou/web/normalize.js +241 -0
  109. package/dist/platforms/kuaishou/web/oracle.js +329 -0
  110. package/dist/platforms/kuaishou/web/profile.js +259 -0
  111. package/dist/platforms/kuaishou/web/resolve.js +84 -0
  112. package/dist/platforms/kuaishou/web/session.js +592 -0
  113. package/dist/platforms/kuaishou/web/sign.js +568 -0
  114. package/dist/platforms/taobao/index.js +56 -0
  115. package/dist/platforms/taobao/web/api.js +96 -0
  116. package/dist/platforms/taobao/web/client.js +102 -0
  117. package/dist/platforms/taobao/web/commands.js +172 -0
  118. package/dist/platforms/taobao/web/im.js +68 -0
  119. package/dist/platforms/taobao/web/normalize.js +103 -0
  120. package/dist/platforms/taobao/web/profile.js +85 -0
  121. package/dist/platforms/taobao/web/resolve.js +51 -0
  122. package/dist/platforms/taobao/web/sign.js +35 -0
  123. package/dist/platforms/tiktok/index.js +120 -0
  124. package/dist/platforms/tiktok/web/api.js +1234 -0
  125. package/dist/platforms/tiktok/web/client.js +362 -0
  126. package/dist/platforms/tiktok/web/commands.js +821 -0
  127. package/dist/platforms/tiktok/web/im.js +133 -0
  128. package/dist/platforms/tiktok/web/jsrun.js +126 -0
  129. package/dist/platforms/tiktok/web/normalize.js +286 -0
  130. package/dist/platforms/tiktok/web/profile.js +169 -0
  131. package/dist/platforms/tiktok/web/resolve.js +156 -0
  132. package/dist/platforms/tiktok/web/sign.js +585 -0
  133. package/dist/platforms/tiktok/web/upload.js +805 -0
  134. package/dist/platforms/tiktok/web/wire.js +490 -0
  135. package/dist/platforms/weibo/index.js +91 -0
  136. package/dist/platforms/weibo/web/api.js +206 -0
  137. package/dist/platforms/weibo/web/client.js +155 -0
  138. package/dist/platforms/weibo/web/commands.js +202 -0
  139. package/dist/platforms/weibo/web/normalize.js +137 -0
  140. package/dist/platforms/weibo/web/profile.js +246 -0
  141. package/dist/platforms/weibo/web/resolve.js +60 -0
  142. package/dist/platforms/weibo/web/sign.js +85 -0
  143. package/dist/platforms/x/index.js +107 -0
  144. package/dist/platforms/x/web/api.js +443 -0
  145. package/dist/platforms/x/web/article.js +212 -0
  146. package/dist/platforms/x/web/client.js +202 -0
  147. package/dist/platforms/x/web/commands.js +363 -0
  148. package/dist/platforms/x/web/graphql.js +108 -0
  149. package/dist/platforms/x/web/normalize.js +235 -0
  150. package/dist/platforms/x/web/profile.js +99 -0
  151. package/dist/platforms/x/web/resolve.js +44 -0
  152. package/dist/platforms/x/web/transaction.js +171 -0
  153. package/dist/platforms/xhs/index.js +101 -0
  154. package/dist/platforms/xhs/web/api.js +278 -0
  155. package/dist/platforms/xhs/web/client.js +540 -0
  156. package/dist/platforms/xhs/web/commands.js +1046 -0
  157. package/dist/platforms/xhs/web/creator-api.js +609 -0
  158. package/dist/platforms/xhs/web/creator.js +203 -0
  159. package/dist/platforms/xhs/web/js.js +194 -0
  160. package/dist/platforms/xhs/web/login.js +230 -0
  161. package/dist/platforms/xhs/web/normalize.js +166 -0
  162. package/dist/platforms/xhs/web/profile.js +127 -0
  163. package/dist/platforms/xhs/web/push.js +361 -0
  164. package/dist/platforms/xhs/web/resolve.js +69 -0
  165. package/dist/platforms/xhs/web/state.js +368 -0
  166. package/dist/platforms/xianyu/index.js +59 -0
  167. package/dist/platforms/xianyu/web/api.js +312 -0
  168. package/dist/platforms/xianyu/web/client.js +134 -0
  169. package/dist/platforms/xianyu/web/commands.js +305 -0
  170. package/dist/platforms/xianyu/web/im.js +73 -0
  171. package/dist/platforms/xianyu/web/normalize.js +123 -0
  172. package/dist/platforms/xianyu/web/profile.js +146 -0
  173. package/dist/platforms/xianyu/web/resolve.js +48 -0
  174. package/dist/platforms/xianyu/web/sign.js +79 -0
  175. package/package.json +77 -4
  176. package/static/douyin/Live.proto +194 -0
  177. package/static/douyin/Request.proto +89 -0
  178. package/static/douyin/Response.proto +46 -0
  179. package/static/douyin/acrawler_runtime/ac_vm.js +2 -0
  180. package/static/douyin/acrawler_runtime/browser_window_shape.json +1 -0
  181. package/static/douyin/acrawler_runtime/canvas_actual_exact.json +1 -0
  182. package/static/douyin/acrawler_runtime/run_ac_node.js +1005 -0
  183. package/static/douyin/acrawler_runtime/runner_window_keys_baseline.txt +240 -0
  184. package/static/douyin/challenge_profile.json +801 -0
  185. package/static/douyin/challenge_template_runner.js +217 -0
  186. package/static/douyin/dtrait_profile.json +66 -0
  187. package/static/douyin/mstoken_common_profile.json +1 -0
  188. package/static/douyin/mstoken_profile.json +214 -0
  189. package/static/jd/h5st5_env.js +750 -0
  190. package/static/jd/h5st5_lib.js +5 -0
  191. package/static/jd/h5st5_server.js +49 -0
  192. package/static/jd/jcap/env/env_core.js +512 -0
  193. package/static/jd/jcap/env/run.js +1307 -0
  194. package/static/jd/jcap/run/jcap_ujb96b.js +39 -0
  195. package/static/jd/package.json +4 -0
  196. package/static/jd/pc_tk_lib.js +1 -0
  197. package/static/jd/pc_tk_server.js +63 -0
  198. package/static/jd/summer_cryptico_runner.js +50 -0
  199. package/static/jd/webm/env/run.js +453 -0
  200. package/static/jd/webm/run/jdwebm-riskhandle.js +49 -0
  201. package/static/kuaishou/bundles/weapon/kwf-0.0.2.2cee19b4b7dec496.js +1 -0
  202. package/static/kuaishou/bundles/weapon/kws-0-0.0.1-obfuscated.4b7f3de1ab0ddcc2.js +1 -0
  203. package/static/kuaishou/bundles/weapon/kws-1-0.0.1-obfuscated.346ebdb1c78df316.js +1 -0
  204. package/static/kuaishou/bundles/weapon/kws-10-0.0.1-obfuscated.a33db41897b983af.js +1 -0
  205. package/static/kuaishou/bundles/weapon/kws-11-0.0.1-obfuscated.5e0a90af726d8a7e.js +1 -0
  206. package/static/kuaishou/bundles/weapon/kws-12-0.0.1-obfuscated.20ea29345706ec8c.js +1 -0
  207. package/static/kuaishou/bundles/weapon/kws-13-0.0.1-obfuscated.6b74e9640ff18648.js +1 -0
  208. package/static/kuaishou/bundles/weapon/kws-14-0.0.1-obfuscated.551e4429d156bf66.js +1 -0
  209. package/static/kuaishou/bundles/weapon/kws-15-0.0.1-obfuscated.8625b087aaba4a60.js +1 -0
  210. package/static/kuaishou/bundles/weapon/kws-16-0.0.1-obfuscated.d5ffa4020f3be99a.js +1 -0
  211. package/static/kuaishou/bundles/weapon/kws-17-0.0.1-obfuscated.6d13e19b22f50a80.js +1 -0
  212. package/static/kuaishou/bundles/weapon/kws-18-0.0.1-obfuscated.6d31405044166289.js +1 -0
  213. package/static/kuaishou/bundles/weapon/kws-19-0.0.1-obfuscated.a9a8d925942f19e4.js +1 -0
  214. package/static/kuaishou/bundles/weapon/kws-2-0.0.1-obfuscated.b849a136ae4a6b3e.js +1 -0
  215. package/static/kuaishou/bundles/weapon/kws-3-0.0.1-obfuscated.f9e77ebf6186e2ac.js +1 -0
  216. package/static/kuaishou/bundles/weapon/kws-4-0.0.1-obfuscated.2c3c155aa52ceb0e.js +1 -0
  217. package/static/kuaishou/bundles/weapon/kws-5-0.0.1-obfuscated.4e553d17691b3c5a.js +1 -0
  218. package/static/kuaishou/bundles/weapon/kws-6-0.0.1-obfuscated.f73ddd3ff13e1217.js +1 -0
  219. package/static/kuaishou/bundles/weapon/kws-7-0.0.1-obfuscated.1628b8729beaab7e.js +1 -0
  220. package/static/kuaishou/bundles/weapon/kws-8-0.0.1-obfuscated.6da6baef7aa81fff.js +1 -0
  221. package/static/kuaishou/bundles/weapon/kws-9-0.0.1-obfuscated.8645d55e5323b743.js +1 -0
  222. package/static/kuaishou/fixtures/current_follow_script_813.network-response +2 -0
  223. package/static/kuaishou/fixtures/live_ws_maps.json +61 -0
  224. package/static/kuaishou/fixtures/live_ws_proto.json +1342 -0
  225. package/static/kuaishou/js/cp-kwf.js +1 -0
  226. package/static/kuaishou/tools/kwf_oracle.js +799 -0
  227. package/static/kuaishou/tools/kws_oracle.js +885 -0
  228. package/static/kuaishou/tools/like_token_oracle.js +70 -0
  229. package/static/taobao/taobao_js_20260407.js +570 -0
  230. package/static/tiktok/Tiktok_Request.json +597 -0
  231. package/static/tiktok/package.json +3 -0
  232. package/static/tiktok/shop_bsid/core.js +1 -0
  233. package/static/tiktok/shop_bsid/env_core.js +512 -0
  234. package/static/tiktok/shop_bsid/loader.js +1 -0
  235. package/static/tiktok/shop_bsid/run.js +753 -0
  236. package/static/tiktok/shop_bsid/sign.js +100 -0
  237. package/static/tiktok/shop_bsid/webmssdk.js +1 -0
  238. package/static/tiktok/shop_bsid/webmssdk_ex.js +1 -0
  239. package/static/tiktok/signing/env/env_core.js +512 -0
  240. package/static/tiktok/signing/env/sign.js +455 -0
  241. package/static/tiktok/signing/sdk_config.json +230 -0
  242. package/static/tiktok/signing/source/webmssdk.js +1 -0
  243. package/static/tiktok/signing/source/webmssdk_ex.js +1 -0
  244. package/static/tiktok/signing/source/webmssdk_legacy.js +1 -0
  245. package/static/x/graphql.json +7777 -0
  246. package/static/x/transaction_l1.json +12 -0
  247. package/static/xhs/package.json +3 -0
  248. package/static/xhs/xhs_core/js/b1.js +210 -0
  249. package/static/xhs/xhs_core/js/mns.js +340 -0
  250. package/static/xhs/xhs_core/js/mns_0101_keystream.json +5 -0
  251. package/static/xhs/xhs_core/js/mns_keystreams.json +1 -0
  252. package/static/xhs/xhs_core/js/sign.js +225 -0
  253. package/static/xhs/xhs_core/js/websectiga_cli.js +220 -0
  254. package/static/xhs/xhs_core/js/websectiga_env.js +512 -0
  255. package/static/xhs/xhs_core/js/xs_common.js +79 -0
  256. package/static/xhs/xhs_creator/js/profile.js +115 -0
  257. package/static/xhs/xhs_creator/js/rap_fingerprint_creator.json +6 -0
  258. package/static/xhs/xhs_creator/js/reference_profile.json +282 -0
  259. package/static/xhs/xhs_creator/js/xhs_creator_sign.js +29 -0
  260. package/static/xhs/xhs_creator/js/xhs_creator_signature.js +23 -0
  261. package/static/xhs/xhs_pc/js/aes_decrypt.js +22 -0
  262. package/static/xhs/xhs_pc/js/aes_encrypt.js +30 -0
  263. package/static/xhs/xhs_pc/js/b1.js +9 -0
  264. package/static/xhs/xhs_pc/js/deflate.js +320 -0
  265. package/static/xhs/xhs_pc/js/mns.js +4 -0
  266. package/static/xhs/xhs_pc/js/profile.js +141 -0
  267. package/static/xhs/xhs_pc/js/rap.js +173 -0
  268. package/static/xhs/xhs_pc/js/rap_cli.js +30 -0
  269. package/static/xhs/xhs_pc/js/rap_crypto.js +73 -0
  270. package/static/xhs/xhs_pc/js/rap_fingerprint_template.json +8 -0
  271. package/static/xhs/xhs_pc/js/reference_profile.json +262 -0
  272. package/static/xhs/xhs_pc/js/sign.js +6 -0
  273. package/static/xhs/xhs_pc/js/web_ssk.js +82 -0
  274. package/static/xhs/xhs_pc/js/xs_common.js +4 -0
  275. package/static/xianyu/et_f.js +2 -0
  276. package/static/xianyu/gen_tfstk.js +410 -0
  277. package/static/xianyu/goofish_js_version_2.js +568 -0
@@ -0,0 +1,223 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { describeOption, flagName } from '../core/options.js';
4
+ import { PACKAGE_ROOT } from '../core/paths.js';
5
+ import { ENDPOINTS, sortCommands } from '../core/registry.js';
6
+ import { PLATFORMS } from '../platforms/index.js';
7
+ /** 帮助(AGENTS 4.10):内容由注册表生成,写到 stdout。 */
8
+ const WIDE = /[\u1100-\u115f\u2e80-\ua4cf\uac00-\ud7a3\uf900-\ufaff\ufe30-\ufe4f\uff00-\uff60\uffe0-\uffe6]/;
9
+ function width(s) {
10
+ let w = 0;
11
+ for (const ch of s)
12
+ w += WIDE.test(ch) ? 2 : 1;
13
+ return w;
14
+ }
15
+ /** 按显示宽度对齐的多列文本,最后一列不补空格。 */
16
+ function table(rows, indent = ' ') {
17
+ const widths = [];
18
+ for (const row of rows)
19
+ row.forEach((cell, i) => (widths[i] = Math.max(widths[i] ?? 0, width(cell))));
20
+ return rows
21
+ .map((row) => {
22
+ const cells = row.map((cell, i) => (i === row.length - 1 ? cell : cell + ' '.repeat(widths[i] - width(cell))));
23
+ return (indent + cells.join(' ')).trimEnd();
24
+ })
25
+ .join('\n');
26
+ }
27
+ const GLOBAL_OPTIONS = [
28
+ ['-a, --account <name>', '使用哪个账号'],
29
+ ['-e, --endpoint <端>', 'web | app | pc,默认 web'],
30
+ ['-o, --output <格式>', 'json | jsonl,默认 json'],
31
+ [' --raw', '用平台原始对象代替归一化对象'],
32
+ [' --proxy <url>', '本次调用使用的代理'],
33
+ ['-v, --verbose', '在 stderr 输出调试日志(已脱敏)'],
34
+ ['-q, --quiet', '不输出提示和进度,只保留错误'],
35
+ ['-y, --yes', '跳过危险操作的确认'],
36
+ ['-h, --help', '帮助'],
37
+ [' --version', '版本信息'],
38
+ ];
39
+ export function rootHelp() {
40
+ const banner = readFileSync(join(PACKAGE_ROOT, 'assets', 'banner.txt'), 'utf8').trimEnd();
41
+ const platforms = PLATFORMS.map((p) => [p.id, p.name, p.aliases.length ? `别名 ${p.aliases.join('、')}` : '']);
42
+ return [
43
+ banner,
44
+ '',
45
+ '用法:',
46
+ ' specific-ai <platform> <resource> <action> [参数...] [选项...]',
47
+ ' specific-ai <全局命令>',
48
+ '',
49
+ '平台:',
50
+ table(platforms),
51
+ '',
52
+ '全局命令:',
53
+ table([
54
+ ['platforms [platform]', '能力矩阵:平台 × 端 × 命令的状态'],
55
+ ['doctor', '检查运行环境'],
56
+ ['auth list', '所有平台、所有端的账号'],
57
+ ['config get|set|unset|list', '读写配置(~/.specific-ai/config.toml)'],
58
+ ['version', '版本、Node 版本、系统与架构'],
59
+ ]),
60
+ '',
61
+ '全局选项:',
62
+ table(GLOBAL_OPTIONS),
63
+ '',
64
+ '示例:',
65
+ ' specific-ai xhs item get "https://www.xiaohongshu.com/explore/<id>?xsec_token=..."',
66
+ ' specific-ai bilibili item get BV1xx411c7mD',
67
+ ' specific-ai douyin comment list <item> --limit 100',
68
+ ' specific-ai xhs auth login',
69
+ '',
70
+ '平台的命令:specific-ai <platform> --help',
71
+ ].join('\n');
72
+ }
73
+ function endpointsLine(platform) {
74
+ return ENDPOINTS.map((e) => (platform.endpoints[e] === 'planned' ? `${e} ○ planned` : `${e} ✓`)).join(' · ');
75
+ }
76
+ function title(platform) {
77
+ const aliases = platform.aliases.length ? ` 别名:${platform.aliases.join('、')}` : '';
78
+ return `${platform.name} (${platform.id})${aliases}`;
79
+ }
80
+ /** 请求的端还是 planned 时,列出 web 端的命令作参考。 */
81
+ function listedEndpoint(platform, endpoint) {
82
+ const ep = platform.endpoints[endpoint];
83
+ if (ep !== 'planned')
84
+ return { ep, notice: null };
85
+ return {
86
+ ep: platform.endpoints.web,
87
+ notice: `${endpoint} 端尚未实现,所有命令都返回 NOT_IMPLEMENTED。下面是 web 端的命令。`,
88
+ };
89
+ }
90
+ export function platformHelp(platform, endpoint) {
91
+ const { ep, notice } = listedEndpoint(platform, endpoint);
92
+ const login = ep.login.methods.map((m) => (m === ep.login.default ? `${m}(默认)` : m)).join(' · ');
93
+ const scopes = ep.login.scopes?.length ? `;子站点:${ep.login.scopes.join('、')}` : '';
94
+ const groups = new Map();
95
+ for (const c of sortCommands(ep.commands.values())) {
96
+ const list = groups.get(c.resource) ?? groups.set(c.resource, []).get(c.resource);
97
+ list.push(c.status === 'planned' ? `${c.action}○` : c.action);
98
+ }
99
+ return [
100
+ title(platform),
101
+ `端: ${endpointsLine(platform)}`,
102
+ `登录: ${login}${scopes}`,
103
+ '',
104
+ `用法: specific-ai ${platform.id} <resource> <action> [参数...] [选项...]`,
105
+ '',
106
+ ...(notice ? [notice, ''] : []),
107
+ '命令(带 ○ 的是规划中,执行时返回 NOT_IMPLEMENTED):',
108
+ table([...groups].map(([r, actions]) => [r, actions.join(' ')])),
109
+ '',
110
+ `命令详情:specific-ai ${platform.id} <resource> --help`,
111
+ ].join('\n');
112
+ }
113
+ export function resourceHelp(platform, endpoint, resource) {
114
+ const { ep, notice } = listedEndpoint(platform, endpoint);
115
+ const commands = sortCommands([...ep.commands.values()].filter((c) => c.resource === resource));
116
+ const label = resource === 'item' ? `item(${platform.item})` : resource;
117
+ return [
118
+ `${platform.name} (${platform.id}) · ${label}`,
119
+ '',
120
+ ...(notice ? [notice, ''] : []),
121
+ table(commands.map((c) => [c.action, c.summary, c.status === 'planned' ? '○ 规划中' : ''])),
122
+ '',
123
+ `用法: specific-ai ${platform.id} ${resource} <action> [参数...] [选项...]`,
124
+ `命令详情:specific-ai ${platform.id} ${resource} <action> --help`,
125
+ ].join('\n');
126
+ }
127
+ const UPSTREAM_TEXT = { full: '✓ 上游已有', partial: '◐ 上游部分支持', none: '○ 上游没有,规划中' };
128
+ function optionUsage(key, command) {
129
+ const info = describeOption(command.options[key]);
130
+ const flag = `--${flagName(key)}`;
131
+ if (info.kind === 'boolean')
132
+ return flag;
133
+ if (info.values)
134
+ return `${flag} <${info.values.join('|')}>`;
135
+ return `${flag} <${info.kind === 'number' ? 'n' : '值'}>`;
136
+ }
137
+ export function commandHelp(platform, endpoint, command) {
138
+ const p = platform.id;
139
+ const e = endpoint === 'web' ? '' : ` -e ${endpoint}`;
140
+ const argUsage = command.args.map((a) => (a.optional ? `[${a.name}]` : `<${a.name}>`));
141
+ const optionKeys = Object.keys(command.options);
142
+ const requiredOptions = optionKeys.filter((k) => describeOption(command.options[k]).required);
143
+ const endpoints = ENDPOINTS.map((name) => {
144
+ const ep = platform.endpoints[name];
145
+ if (ep === 'planned')
146
+ return `${name} ○ planned`;
147
+ const c = ep.commands.get(command.key);
148
+ return !c ? `${name} —` : c.status === 'planned' ? `${name} ○ planned` : `${name} ✓`;
149
+ });
150
+ const confirm = command.confirm === true ? '危险操作,执行前需要确认;-y 跳过' : command.confirm ? '部分情况需要确认(见选项);-y 跳过' : null;
151
+ const lines = [
152
+ `用法: specific-ai ${p} ${command.key}${argUsage.map((a) => ' ' + a).join('')}${optionKeys.length ? ' [选项...]' : ''}`,
153
+ '',
154
+ command.summary,
155
+ '',
156
+ `端: ${endpoints.join(' · ')}`,
157
+ `登录: ${command.auth === 'required' ? '需要' : '可选'}`,
158
+ `上游: ${command.upstream ? UPSTREAM_TEXT[command.upstream] : 'core 实现'}`,
159
+ ...(command.note ? [`说明: ${command.note}`] : []),
160
+ ...(confirm ? [`确认: ${confirm}`] : []),
161
+ `输出: ${command.output}${command.paged ? ',分页' : ''}${command.stream ? ',持续输出 jsonl' : ''}`,
162
+ ];
163
+ if (command.args.length) {
164
+ lines.push('', '参数:');
165
+ lines.push(table(command.args.map((a) => [a.optional ? `[${a.name}]` : `<${a.name}>`, a.summary])));
166
+ }
167
+ lines.push('', '选项:');
168
+ if (optionKeys.length) {
169
+ lines.push(table(optionKeys.map((k) => {
170
+ const info = describeOption(command.options[k]);
171
+ const notes = [
172
+ info.required ? '必填' : null,
173
+ info.default !== undefined ? `默认 ${String(info.default)}` : null,
174
+ info.kind === 'array' ? '可重复' : null,
175
+ ].filter(Boolean);
176
+ return [optionUsage(k, command), `${info.description ?? ''}${notes.length ? `(${notes.join(',')})` : ''}`];
177
+ })));
178
+ }
179
+ lines.push(' 全局选项见 specific-ai --help');
180
+ const example = [`specific-ai ${p} ${command.key}`, ...command.args.filter((a) => !a.optional).map((a) => `<${a.name}>`)];
181
+ for (const k of requiredOptions)
182
+ example.push(optionUsage(k, command));
183
+ lines.push('', '示例:', ` ${example.join(' ')}${e}`);
184
+ return lines.join('\n');
185
+ }
186
+ export const GLOBAL_HELP = {
187
+ platforms: [
188
+ '用法: specific-ai platforms [platform]',
189
+ '',
190
+ '能力矩阵:每个平台的端、命令、登录要求、状态(implemented / planned)和上游支持程度。',
191
+ '带平台参数时只输出这个平台。',
192
+ ].join('\n'),
193
+ doctor: [
194
+ '用法: specific-ai doctor',
195
+ '',
196
+ '逐项检查运行环境:Node 版本、~/.specific-ai 权限、签名 vm、HTTP 库、canvas、onnx、京东模型包、OCR 模型包。',
197
+ '有检查项不通过时退出码为 1,data 里仍是完整的检查结果。',
198
+ ].join('\n'),
199
+ auth: [
200
+ '用法: specific-ai auth list',
201
+ '',
202
+ '列出所有平台、所有端的账号。',
203
+ '登录、切换、登出请在平台下操作:',
204
+ ' specific-ai <platform> auth login|status|logout|list|use',
205
+ ].join('\n'),
206
+ config: [
207
+ '用法:',
208
+ ' specific-ai config get <key>',
209
+ ' specific-ai config set <key> <value>',
210
+ ' specific-ai config unset <key>',
211
+ ' specific-ai config list',
212
+ '',
213
+ '配置项:',
214
+ table([
215
+ ['proxy', '全局代理,例如 http://127.0.0.1:7890、socks5://127.0.0.1:1080'],
216
+ ['timeout', '单次请求超时(秒),默认 30'],
217
+ ['<platform>.proxy', '平台代理,例如 xhs.proxy'],
218
+ ]),
219
+ '',
220
+ '代理的优先级:--proxy > <platform>.proxy > proxy。不读取 HTTP(S)_PROXY 环境变量。',
221
+ ].join('\n'),
222
+ version: ['用法: specific-ai version(或 specific-ai --version)', '', '输出 specific-ai 版本、Node 版本、系统与架构。'].join('\n'),
223
+ };
@@ -0,0 +1,60 @@
1
+ /** 平台原生叫法 → 规范词(AGENTS 4.6)。原生叫法不做别名,只在 UNSUPPORTED 的 hint 里提示。 */
2
+ const RESOURCE_WORDS = {
3
+ note: 'item',
4
+ aweme: 'item',
5
+ video: 'item',
6
+ photo: 'item',
7
+ work: 'item',
8
+ post: 'item',
9
+ status: 'item',
10
+ tweet: 'item',
11
+ goods: 'item',
12
+ sku: 'item',
13
+ board: 'folder',
14
+ collection: (p) => (p === 'tiktok' ? 'folder' : p === 'kuaishou' ? 'series' : 'folder 或 series'),
15
+ mix: 'series',
16
+ playlist: 'series',
17
+ reply: 'comment',
18
+ 评价: 'comment',
19
+ dm: 'msg',
20
+ im: 'msg',
21
+ 咚咚: 'msg',
22
+ location: 'poi',
23
+ playurl: 'item media',
24
+ };
25
+ const ACTION_WORDS = {
26
+ digg: 'like',
27
+ favorite: (p) => (p === 'x' ? 'like' : 'collect'),
28
+ favour: 'collect',
29
+ bookmark: 'collect',
30
+ 想要: 'collect',
31
+ 关注商品: 'collect',
32
+ retweet: 'repost',
33
+ watch: 'listen',
34
+ playurl: 'media',
35
+ };
36
+ function lookup(words, word, platform) {
37
+ // 只认自己的键:valueOf、constructor 这类原型上的名字不算
38
+ const key = word.toLowerCase();
39
+ const c = Object.hasOwn(words, key) ? words[key] : undefined;
40
+ return c == null ? null : typeof c === 'string' ? c : c(platform);
41
+ }
42
+ function canonicalAction(word, platform) {
43
+ // un- 形式:undigg → unlike、unretweet → unrepost
44
+ if (word.startsWith('un')) {
45
+ const base = lookup(ACTION_WORDS, word.slice(2), platform);
46
+ if (base)
47
+ return `un${base}`;
48
+ }
49
+ return lookup(ACTION_WORDS, word, platform);
50
+ }
51
+ /** 用了原生叫法时,给出改用规范词的建议;没用原生叫法时返回 null。 */
52
+ export function nativeHint(platform, resource, action) {
53
+ const r = lookup(RESOURCE_WORDS, resource, platform);
54
+ const a = action == null ? null : canonicalAction(action, platform);
55
+ if (!r && !a)
56
+ return null;
57
+ // playurl 作 resource 时已经带上 action:playurl → item media
58
+ const words = r?.includes(' ') ? r : [r ?? resource, ...(action == null ? [] : [a ?? action])].join(' ');
59
+ return words.includes('或') ? `规范词是 ${words}` : `用规范词:specific-ai ${platform} ${words}`;
60
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ // 代理只能显式配置(AGENTS 5.5):在加载 wreq-js 之前关掉它对 HTTP(S)_PROXY 和系统代理的读取。
3
+ process.env.NO_PROXY = process.env.no_proxy = '*,0.0.0.0/0,::/0';
4
+ process.stdout.on('error', (err) => {
5
+ if (err.code === 'EPIPE')
6
+ process.exit(0);
7
+ throw err;
8
+ });
9
+ const { run } = await import('./dispatch.js');
10
+ const { closeTransports } = await import('../core/http.js');
11
+ const code = await run(process.argv.slice(2));
12
+ await closeTransports();
13
+ // 原生连接(长连接的 WebSocket 等)可能还挂着句柄:输出写完后直接退出
14
+ const flush = (stream) => new Promise((resolve) => stream.write('', () => resolve()));
15
+ await flush(process.stdout);
16
+ await flush(process.stderr);
17
+ process.exit(code);
18
+ export {};
@@ -0,0 +1,66 @@
1
+ import { RAW } from '../core/schemas.js';
2
+ export const FORMATS = ['json', 'jsonl'];
3
+ /**
4
+ * 信封输出(AGENTS 6.1)。json:stdout 恰好一个信封;jsonl:stdout 每行一个 data 条目,
5
+ * 结束时在 stderr 写一行摘要信封(data 为 null)。
6
+ */
7
+ export class Output {
8
+ format = 'json';
9
+ raw = false;
10
+ meta = { platform: null, endpoint: null, resource: null, action: null, account: null };
11
+ /** jsonl / 流式命令:立即写一条。 */
12
+ item(value) {
13
+ process.stdout.write(JSON.stringify(this.unwrap(value) ?? null) + '\n');
14
+ }
15
+ /**
16
+ * 写结果。`streamed` 为 true 表示条目已经用 {@link item} 写过,这里只写摘要。
17
+ * doctor 这类命令失败时仍带 data,此时传入 error。
18
+ */
19
+ result(data, options = {}) {
20
+ const { page = null, streamed = false, error } = options;
21
+ if (this.format === 'jsonl') {
22
+ if (!streamed)
23
+ for (const v of Array.isArray(data) ? data : [data])
24
+ this.item(v);
25
+ this.summary(error ?? null, page);
26
+ }
27
+ else {
28
+ this.envelope(process.stdout, { ...this.base(error ?? null), data: this.unwrap(data) ?? null, page });
29
+ }
30
+ }
31
+ fail(error) {
32
+ if (this.format === 'jsonl')
33
+ this.summary(error, null);
34
+ else
35
+ this.envelope(process.stdout, { ...this.base(error), data: null, page: null });
36
+ }
37
+ summary(error, page) {
38
+ this.envelope(process.stderr, { ...this.base(error), data: null, page });
39
+ }
40
+ base(error) {
41
+ return { ok: error == null, ...this.meta, error: error?.toJSON() ?? null };
42
+ }
43
+ envelope(stream, env) {
44
+ const ordered = {
45
+ ok: env.ok,
46
+ platform: env.platform,
47
+ endpoint: env.endpoint,
48
+ resource: env.resource,
49
+ action: env.action,
50
+ account: env.account,
51
+ data: env.data,
52
+ page: env.page,
53
+ error: env.error,
54
+ };
55
+ const pretty = stream === process.stdout && stream.isTTY;
56
+ stream.write(JSON.stringify(ordered, null, pretty ? 2 : undefined) + '\n');
57
+ }
58
+ /** `--raw` 时用平台原始对象代替归一化对象。 */
59
+ unwrap(value) {
60
+ if (!this.raw || value == null || typeof value !== 'object')
61
+ return value;
62
+ if (Array.isArray(value))
63
+ return value.map((v) => this.unwrap(v));
64
+ return RAW in value ? value[RAW] : value;
65
+ }
66
+ }
@@ -0,0 +1,41 @@
1
+ import { checkAccountName, deleteCredential, endpointFlag, listAccounts, readCredential, setCurrent, toAccount, } from './auth-store.js';
2
+ import { SpecificAIError } from './errors.js';
3
+ /** core 自动注册到每个平台的 `auth list` / `use` / `logout`。 */
4
+ export async function list(ctx) {
5
+ return listAccounts(ctx.platform.id, ctx.endpoint);
6
+ }
7
+ export async function use(ctx) {
8
+ const p = ctx.platform.id;
9
+ const account = checkAccountName(ctx.args.account);
10
+ const credential = await readCredential(p, ctx.endpoint, account);
11
+ if (!credential) {
12
+ throw new SpecificAIError('AUTH_REQUIRED', `账号 ${account} 不存在`, {
13
+ hint: `specific-ai ${p} auth login -a ${account}${endpointFlag(ctx.endpoint)}`,
14
+ });
15
+ }
16
+ await setCurrent(p, ctx.endpoint, account);
17
+ return toAccount(credential, true);
18
+ }
19
+ export async function logout(ctx) {
20
+ const p = ctx.platform.id;
21
+ const hint = `specific-ai ${p} auth list${endpointFlag(ctx.endpoint)}`;
22
+ if (ctx.account == null)
23
+ throw new SpecificAIError('USAGE', '没有当前账号,请用 -a 指定要登出的账号', { hint });
24
+ const account = checkAccountName(ctx.account);
25
+ const found = (await listAccounts(p, ctx.endpoint)).find((a) => a.account === account);
26
+ if (!found)
27
+ throw new SpecificAIError('USAGE', `账号 ${account} 不存在`, { hint });
28
+ const ep = ctx.platform.endpoints[ctx.endpoint];
29
+ if (ep !== 'planned' && ep.logout && found.user) {
30
+ try {
31
+ await (await ep.logout())(ctx);
32
+ }
33
+ catch (err) {
34
+ ctx.log.warn(`服务端登出失败,只删除本地凭证:${err.message}`);
35
+ }
36
+ }
37
+ await deleteCredential(p, ctx.endpoint, account);
38
+ if (found.current)
39
+ await setCurrent(p, ctx.endpoint, null);
40
+ return { ...found, current: false };
41
+ }
@@ -0,0 +1,119 @@
1
+ import { readdir, rm } from 'node:fs/promises';
2
+ import { SpecificAIError } from './errors.js';
3
+ import { isoNow, readTextIfExists, writeFileAtomic } from './fsutil.js';
4
+ import { authDir } from './paths.js';
5
+ import { CredentialSchema } from './schemas.js';
6
+ /** 凭证只由 core 读写(AGENTS 5)。目录:auth/<platform>/<endpoint>/<account>.json、guest.json、_current。 */
7
+ export const GUEST = 'guest';
8
+ export const ACCOUNT_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
9
+ export function checkAccountName(name, options = {}) {
10
+ if (!ACCOUNT_RE.test(name)) {
11
+ throw new SpecificAIError('USAGE', `账号名不合法:${name}`, {
12
+ hint: '账号名只能用小写字母、数字、_ 和 -,以字母或数字开头,最长 32 位',
13
+ });
14
+ }
15
+ if (name === GUEST && !options.allowGuest)
16
+ throw new SpecificAIError('USAGE', 'guest 是保留名,表示游客身份');
17
+ return name;
18
+ }
19
+ /** 给用户看的命令后缀:非 web 端时带上 -e。 */
20
+ export function endpointFlag(endpoint) {
21
+ return endpoint === 'web' ? '' : ` -e ${endpoint}`;
22
+ }
23
+ function credentialFile(platform, endpoint, account) {
24
+ return authDir(platform, endpoint, `${account}.json`);
25
+ }
26
+ export async function readCredential(platform, endpoint, account) {
27
+ const file = credentialFile(platform, endpoint, account);
28
+ const text = await readTextIfExists(file);
29
+ if (text == null)
30
+ return null;
31
+ let parsed;
32
+ try {
33
+ parsed = CredentialSchema.safeParse(JSON.parse(text));
34
+ }
35
+ catch {
36
+ parsed = null;
37
+ }
38
+ if (!parsed?.success) {
39
+ throw new SpecificAIError('ERROR', `凭证文件已损坏:${file}`, {
40
+ hint: account === GUEST ? `删除该文件后重试` : `specific-ai ${platform} auth logout -a ${account}${endpointFlag(endpoint)}`,
41
+ });
42
+ }
43
+ return parsed.data;
44
+ }
45
+ export async function writeCredential(credential) {
46
+ const { platform, endpoint, account } = credential;
47
+ checkAccountName(account, { allowGuest: true });
48
+ credential.updated_at = isoNow();
49
+ await writeFileAtomic(credentialFile(platform, endpoint, account), JSON.stringify(credential, null, 2) + '\n');
50
+ }
51
+ export async function deleteCredential(platform, endpoint, account) {
52
+ await rm(credentialFile(platform, endpoint, account), { force: true });
53
+ }
54
+ export function newCredential(init) {
55
+ const now = isoNow();
56
+ return {
57
+ schema: 1,
58
+ platform: init.platform,
59
+ endpoint: init.endpoint,
60
+ account: init.account,
61
+ user: init.user ?? null,
62
+ method: init.method,
63
+ created_at: now,
64
+ updated_at: now,
65
+ scopes: { main: { cookies: [], tokens: {} } },
66
+ device: {},
67
+ extra: {},
68
+ };
69
+ }
70
+ /** 当前账号;`_current` 不存在、不合法或指向已删除的账号时为 null。 */
71
+ export async function getCurrent(platform, endpoint) {
72
+ const name = (await readTextIfExists(authDir(platform, endpoint, '_current')))?.trim();
73
+ if (!name || name === GUEST || !ACCOUNT_RE.test(name))
74
+ return null;
75
+ const exists = await readTextIfExists(credentialFile(platform, endpoint, name));
76
+ return exists == null ? null : name;
77
+ }
78
+ export async function setCurrent(platform, endpoint, account) {
79
+ const file = authDir(platform, endpoint, '_current');
80
+ if (account == null)
81
+ await rm(file, { force: true });
82
+ else
83
+ await writeFileAtomic(file, account + '\n');
84
+ }
85
+ export function toAccount(credential, current) {
86
+ return {
87
+ platform: credential.platform,
88
+ endpoint: credential.endpoint,
89
+ account: credential.account,
90
+ current,
91
+ user: credential.user,
92
+ method: credential.method,
93
+ updated_at: credential.updated_at,
94
+ };
95
+ }
96
+ /** 某平台某端的账号,按名字排序。损坏的凭证文件也列出来,user / method 为 null。 */
97
+ export async function listAccounts(platform, endpoint) {
98
+ let files;
99
+ try {
100
+ files = await readdir(authDir(platform, endpoint));
101
+ }
102
+ catch (err) {
103
+ if (err.code === 'ENOENT')
104
+ return [];
105
+ throw err;
106
+ }
107
+ const current = await getCurrent(platform, endpoint);
108
+ const accounts = [];
109
+ for (const file of files.sort()) {
110
+ const name = file.replace(/\.json$/, '');
111
+ if (name === file || name === GUEST || !ACCOUNT_RE.test(name))
112
+ continue;
113
+ const credential = await readCredential(platform, endpoint, name).catch(() => null);
114
+ accounts.push(credential
115
+ ? toAccount(credential, name === current)
116
+ : { platform, endpoint, account: name, current: name === current, user: null, method: null, updated_at: null });
117
+ }
118
+ return accounts;
119
+ }