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
package/README.md CHANGED
@@ -1,3 +1,175 @@
1
- # Temporary Holding Version
1
+ <div align="center">
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ <img src="assets/hero.svg" width="100%" alt="Specific AI — 一个命令,连接每个平台">
4
+
5
+ <br>
6
+
7
+ **一个命令,连接 10 个平台。**<br>
8
+ 小红书 · 抖音 · TikTok · B 站 · 快手 · 微博 · 闲鱼 · 淘宝 · 京东 · X
9
+ <br>
10
+ <sub>即将支持:Instagram · YouTube · Facebook · 知乎 · 微信公众号 · 今日头条 · 得物 · 拼多多 · 美团 · 大众点评</sub>
11
+
12
+ <br>
13
+
14
+ [![npm](https://img.shields.io/npm/v/specific-ai-cli?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/specific-ai-cli)
15
+ [![License: MIT](https://img.shields.io/badge/license-MIT-A6E3A1?style=for-the-badge)](LICENSE)
16
+ [![Node](https://img.shields.io/badge/node-22%20%7C%2024%20%7C%2026-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)](package.json)
17
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](tsconfig.json)
18
+ [![Platforms](https://img.shields.io/badge/platforms-10-F38BA8?style=for-the-badge)](docs/platforms/README.md)
19
+ [![Agent Ready](https://img.shields.io/badge/AI%20Agent-ready-CBA6F7?style=for-the-badge)](.claude/skills/specific-ai/SKILL.md)
20
+
21
+ **简体中文** · [English](README.en.md) · [繁體中文](README.zh-TW.md) · [日本語](README.ja.md) · [한국어](README.ko.md)
22
+
23
+ [快速上手](docs/guide/quick-start.md) · [能力矩阵](docs/capabilities.md) · [各平台](docs/platforms/README.md) · [示例](docs/examples/README.md) · [给 Agent 用](.claude/skills/specific-ai/SKILL.md)
24
+
25
+ </div>
26
+
27
+ <br>
28
+
29
+ > 每个平台都有自己的一套接口、签名、登录方式和数据格式。**Specific AI 把它们汇聚到同一个入口**:同一套命令、同一种 JSON、同一个登录态目录。换平台,只需要换一个词。
30
+
31
+
32
+ ## ✨ Specific AI 是什么
33
+
34
+ Specific AI 是一个命令行工具,用**统一的语法**调用 10 个中外主流平台的 web 端能力:搜索、详情、评论、用户、推荐流、下载、发布、私信、直播弹幕……
35
+
36
+ ```bash
37
+ specific-ai xhs item search 露营 --sort latest
38
+ specific-ai douyin item search 露营 --sort latest
39
+ specific-ai bilibili item search 露营 --sort latest
40
+ specific-ai x item search camping --sort latest
41
+ ```
42
+
43
+ 四个平台,四条几乎一样的命令,输出**同一种结构**的 JSON。笔记、作品、稿件、推文一律叫 `item`,搜索一律叫 `search`,点赞一律叫 `like`。学一次,十个平台都会用。
44
+
45
+ <p align="center">
46
+ <img src="assets/platforms.svg" width="100%" alt="已支持的 10 个平台与正在开发的 10 个平台">
47
+ </p>
48
+
49
+ ## 🚀 为什么是 Specific AI
50
+
51
+ <table>
52
+ <tr>
53
+ <td width="50%" valign="top">
54
+
55
+ ### 🧭 同义同名
56
+ 含义相同的能力,在所有平台上用**同一个命令、同一组参数、同一种输出**。用了平台原生叫法(`note`、`aweme`、`tweet`……)也没关系,specific-ai 会告诉你该用哪个词。
57
+
58
+ </td>
59
+ <td width="50%" valign="top">
60
+
61
+ ### 📦 只需要 Node
62
+ 不需要 Python、编译器或任何系统依赖。原生依赖全部带预编译包,覆盖 Windows、macOS、Linux 的 x64 / arm64(含 musl),**8 个目标系统**在 CI 上逐一冒烟。
63
+
64
+ </td>
65
+ </tr>
66
+ <tr>
67
+ <td valign="top">
68
+
69
+ ### 🤖 为 AI Agent 而生
70
+ stdout 只有 JSON,退出码稳定,错误里带着下一步该执行的命令;`specific-ai platforms` 输出完整的能力清单。自带 [Agent 技能](.claude/skills/specific-ai/SKILL.md) 与 [llms.txt](llms.txt)。
71
+
72
+ </td>
73
+ <td valign="top">
74
+
75
+ ### 🔐 登录态只在你的电脑上
76
+ 扫码、短信、cookie 导入都支持,凭证按 **平台 × 端 × 账号** 隔离在 `~/.specific-ai/`(0700 / 0600),多账号随时切换。不收集、不上传任何数据,没有遥测。
77
+
78
+ </td>
79
+ </tr>
80
+ <tr>
81
+ <td valign="top">
82
+
83
+ ### 🧪 与上游逐字节一致
84
+ 每个请求的 URL、header 顺序、cookie、body 和签名,都在固定的随机数与时钟下和上游实现**逐字节对拍**。560+ 份对拍数据、940+ 个离线测试守住每一次改动。
85
+
86
+ </td>
87
+ <td valign="top">
88
+
89
+ ### 🛡️ 浏览器级的网络栈
90
+ 与真 Chrome 一致的 TLS / HTTP2 指纹,上游签名 JS 原样在沙箱里运行,滑块、点选验证码用本地的 ONNX 与 OpenCV 自动识别。
91
+
92
+ </td>
93
+ </tr>
94
+ </table>
95
+
96
+ ## 📊 一些数字
97
+
98
+ <div align="center">
99
+
100
+ | 🚌 平台 | ⌨️ 已实现命令 | 🧪 对拍数据 | ✅ 离线测试 | 💻 目标系统 |
101
+ |:---:|:---:|:---:|:---:|:---:|
102
+ | **10** | **300+** | **560+** | **940+** | **8** |
103
+
104
+ </div>
105
+
106
+ ## 🏗️ 架构
107
+
108
+ <p align="center">
109
+ <img src="assets/architecture.svg" width="100%" alt="specific-ai 架构">
110
+ </p>
111
+
112
+ - **一个注册表驱动一切**:命令解析、四层 `--help`、`specific-ai platforms`、[能力矩阵](docs/capabilities.md) 都由同一份注册表生成,永远不会互相矛盾。
113
+ - **一个平台,一组文件**:`client` 管会话与签名,`api` 一一对应上游方法,`normalize` 把原始数据变成统一的类型,`resolve` 让 ID、链接、分享短链、`me` 都能当参数。
114
+ - **可扩展**:规范为 app / pc 端预留了位置;非 JS 的上游将通过 provider 协议接入。
115
+
116
+ ## 🤖 为 AI Agent 而生
117
+
118
+ Specific AI 从第一天起就按「被程序调用」来设计:
119
+
120
+ ```bash
121
+ specific-ai platforms douyin | jq '.data.commands[] | select(.status=="implemented") | .resource + " " + .action'
122
+ ```
123
+
124
+ - **自描述**:每个平台、每条命令的登录要求、实现状态、上游支持程度,一条命令就能查到。
125
+ - **可预期**:成功和失败都是同一种信封 `{ ok, data, page, error }`;`error.hint` 直接给出下一步命令。
126
+ - **能接力**:所有输出对象都带 `id` 和 `url`,上一条命令的结果可以直接喂给下一条。
127
+ - **有分寸**:危险操作必须显式 `-y`,发布可以设为仅自己可见,遇到风控明确报 `RISK_CONTROL`,不会悄悄重试。
128
+
129
+ 一条命令把 Specific AI 技能装进 Claude Code、Cursor、Codex 等 Agent:
130
+
131
+ ```bash
132
+ npx skills add Dearli666/specific-ai
133
+ ```
134
+
135
+ 技能按需加载:总纲只有一页,涉及哪个平台、做哪类事(采集、发布、直播私信、排错)才读对应的[子文件](.claude/skills/specific-ai/SKILL.md),并且内置了安全红线——登录交给你、写操作先确认、发布默认仅自己可见、遇到风控就停。
136
+
137
+ ## ⚡ 30 秒开始
138
+
139
+ ```bash
140
+ npm i -g specific-ai-cli
141
+
142
+ specific-ai doctor # 检查环境
143
+ specific-ai bilibili auth login # 扫码登录
144
+ specific-ai bilibili item search 猫 --limit 20
145
+ ```
146
+
147
+ 也可以用 [Docker](docs/guide/docker.md) 或从源码安装,完整步骤见 [快速上手](docs/guide/quick-start.md)。
148
+
149
+ ## 📚 文档
150
+
151
+ | | |
152
+ |---|---|
153
+ | 🚀 [快速上手](docs/guide/quick-start.md) | 从安装到导出第一批数据 |
154
+ | 🔑 [登录](docs/guide/login.md) · ⚙️ [配置](docs/guide/configuration.md) · 🐳 [Docker](docs/guide/docker.md) | 扫码 / cookie、多账号、代理、容器 |
155
+ | 📤 [输出与错误处理](docs/guide/output.md) | 信封、归一化类型、分页、退出码 |
156
+ | 🧭 [各平台说明](docs/platforms/README.md) · 🗂️ [能力矩阵](docs/capabilities.md) | 每个平台支持什么、怎么传参 |
157
+ | 🧪 [示例脚本](docs/examples/README.md) | 跨平台搜索、导出评论、备份作品、录直播弹幕 |
158
+ | 📐 [AGENTS.md](AGENTS.md) | specific-ai 的规范本身 |
159
+ | ❓ [常见问题](docs/guide/faq.md) | 为什么要登录、遇到风控怎么办…… |
160
+
161
+ ## 🤝 参与
162
+
163
+ 欢迎 issue 和 PR:新平台、新命令、平台接口变了、文档改进都可以。开始之前读一下 [CONTRIBUTING.md](.github/CONTRIBUTING.md) 和 [AGENTS.md](AGENTS.md)(规范先行,请求必须有对拍)。安全问题请按 [SECURITY.md](.github/SECURITY.md) 私下报告。
164
+
165
+
166
+ ## 📄 License
167
+
168
+ [MIT](LICENSE) © 2026 [cv-cat](https://github.com/cv-cat). Specific AI 基于原项目重命名并保持其 CLI 能力。
169
+
170
+ <div align="center">
171
+ <br>
172
+ <img src="assets/logo.svg" width="64" alt="specific-ai">
173
+ <br>
174
+ <sub>一个命令,连接每个平台 · One command. Every platform.</sub>
175
+ </div>
@@ -0,0 +1,177 @@
1
+ <div align="center">
2
+
3
+ <img src="assets/hero.svg" width="100%" alt="Specific AI — 一個指令,連接每個平台">
4
+
5
+ <br>
6
+
7
+ **一個指令,連接 10 個平台。**<br>
8
+ 小紅書 · 抖音 · TikTok · 嗶哩嗶哩 · 快手 · 微博 · 閒魚 · 淘寶 · 京東 · X
9
+ <br>
10
+ <sub>即將支援:Instagram · YouTube · Facebook · 知乎 · 微信公眾號 · 今日頭條 · 得物 · 拼多多 · 美團 · 大眾點評</sub>
11
+
12
+ <br>
13
+
14
+ [![npm](https://img.shields.io/npm/v/specific-ai-cli?style=for-the-badge&logo=npm&color=CB3837)](https://www.npmjs.com/package/specific-ai-cli)
15
+ [![License: MIT](https://img.shields.io/badge/license-MIT-A6E3A1?style=for-the-badge)](LICENSE)
16
+ [![Node](https://img.shields.io/badge/node-22%20%7C%2024%20%7C%2026-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)](package.json)
17
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](tsconfig.json)
18
+ [![Platforms](https://img.shields.io/badge/platforms-10-F38BA8?style=for-the-badge)](docs/platforms/README.md)
19
+ [![Agent Ready](https://img.shields.io/badge/AI%20Agent-ready-CBA6F7?style=for-the-badge)](.claude/skills/specific-ai/SKILL.md)
20
+
21
+ [简体中文](README.md) · [English](README.en.md) · **繁體中文** · [日本語](README.ja.md) · [한국어](README.ko.md)
22
+
23
+ [快速上手](docs/guide/quick-start.md) · [能力矩陣](docs/capabilities.md) · [各平台](docs/platforms/README.md) · [範例](docs/examples/README.md) · [給 Agent 用](.claude/skills/specific-ai/SKILL.md)
24
+
25
+ </div>
26
+
27
+ <br>
28
+
29
+ > 每個平台都有自己的一套 API、簽章、登入方式和資料格式。**Specific AI 把它們整合到同一個入口**:同一套指令、同一種 JSON、同一個登入狀態目錄。換平台,只需要換一個詞。
30
+
31
+
32
+ ## ✨ Specific AI 是什麼
33
+
34
+ Specific AI 是一個命令列工具,用**統一的語法**呼叫 10 個中國大陸及國際主流平台的 web 端功能:搜尋、詳細資料、留言、使用者、推薦動態、下載、發佈、私訊、直播彈幕……
35
+
36
+ ```bash
37
+ specific-ai xhs item search 露营 --sort latest
38
+ specific-ai douyin item search 露营 --sort latest
39
+ specific-ai bilibili item search 露营 --sort latest
40
+ specific-ai x item search camping --sort latest
41
+ ```
42
+
43
+ 四個平台,四條幾乎一樣的指令,輸出**同一種結構**的 JSON。筆記、作品、影片、推文一律叫 `item`,搜尋一律叫 `search`,按讚一律叫 `like`。學一次,十個平台都會用。
44
+
45
+ <p align="center">
46
+ <img src="assets/platforms.svg" width="100%" alt="已支援的 10 個平台與開發中的 10 個平台">
47
+ </p>
48
+
49
+ ## 🚀 為什麼選 Specific AI
50
+
51
+ <table>
52
+ <tr>
53
+ <td width="50%" valign="top">
54
+
55
+ ### 🧭 同義同名
56
+ 意義相同的功能,在所有平台上都用**同一個指令、同一組參數、同一種輸出**。就算用了平台原生的叫法(`note`、`aweme`、`tweet`……)也沒關係,specific-ai 會告訴你該用哪個詞。
57
+
58
+ </td>
59
+ <td width="50%" valign="top">
60
+
61
+ ### 📦 只需要 Node
62
+ 不需要 Python、編譯器或任何系統相依套件。原生相依套件全部附有預先編譯的版本,涵蓋 Windows、macOS、Linux 的 x64 / arm64(含 musl),**8 個目標系統**都在 CI 上逐一進行冒煙測試。
63
+
64
+ </td>
65
+ </tr>
66
+ <tr>
67
+ <td valign="top">
68
+
69
+ ### 🤖 為 AI Agent 而生
70
+ stdout 只有 JSON,結束代碼穩定,錯誤訊息裡直接附上下一步該執行的指令;`specific-ai platforms` 會輸出完整的功能清單。內建 [Agent 技能](.claude/skills/specific-ai/SKILL.md) 與 [llms.txt](llms.txt)。
71
+
72
+ </td>
73
+ <td valign="top">
74
+
75
+ ### 🔐 登入狀態只留在你的電腦上
76
+ QR Code 掃描、簡訊、cookie 匯入都支援,憑證依 **平台 × 端 × 帳號** 隔離存放在 `~/.specific-ai/`(0700 / 0600),多帳號隨時切換。不蒐集、不上傳任何資料,也沒有遙測。
77
+
78
+ </td>
79
+ </tr>
80
+ <tr>
81
+ <td valign="top">
82
+
83
+ ### 🧪 與上游逐位元組一致
84
+ 每個請求的 URL、header 順序、cookie、body 和簽章,都在固定的亂數與時鐘下和上游實作**逐位元組比對**。560+ 份比對資料、940+ 個離線測試,守住每一次修改。
85
+
86
+ </td>
87
+ <td valign="top">
88
+
89
+ ### 🛡️ 瀏覽器等級的網路堆疊
90
+ 與真正的 Chrome 一致的 TLS / HTTP2 指紋,上游簽章 JS 原封不動地在沙盒中執行,滑塊、點選驗證碼則用本機的 ONNX 與 OpenCV 自動辨識。
91
+
92
+ </td>
93
+ </tr>
94
+ </table>
95
+
96
+ ## 📊 一些數字
97
+
98
+ <div align="center">
99
+
100
+ | 🚌 平台 | ⌨️ 已實作指令 | 🧪 比對資料 | ✅ 離線測試 | 💻 目標系統 |
101
+ |:---:|:---:|:---:|:---:|:---:|
102
+ | **10** | **300+** | **560+** | **940+** | **8** |
103
+
104
+ </div>
105
+
106
+ ## 🏗️ 架構
107
+
108
+ <p align="center">
109
+ <img src="assets/architecture.svg" width="100%" alt="specific-ai 架構">
110
+ </p>
111
+
112
+ - **一個註冊表驅動一切**:指令解析、四層 `--help`、`specific-ai platforms`、[能力矩陣](docs/capabilities.md) 都由同一份註冊表產生,永遠不會互相矛盾。
113
+ - **一個平台,一組檔案**:`client` 負責工作階段與簽章,`api` 與上游方法一一對應,`normalize` 把原始資料轉成統一的型別,`resolve` 讓 ID、連結、分享短網址、`me` 都能當作參數。
114
+ - **可擴充**:規範為 app / pc 端預留了位置;非 JS 的上游將透過 provider 協定接入。
115
+
116
+ ## 🤖 為 AI Agent 而生
117
+
118
+ specific-ai 從第一天起,就是依照「被程式呼叫」來設計的:
119
+
120
+ ```bash
121
+ specific-ai platforms douyin | jq '.data.commands[] | select(.status=="implemented") | .resource + " " + .action'
122
+ ```
123
+
124
+ - **自我描述**:每個平台、每條指令的登入需求、實作狀態、上游支援程度,一條指令就能查到。
125
+ - **可預期**:成功和失敗都是同一種信封 `{ ok, data, page, error }`;`error.hint` 直接給出下一步的指令。
126
+ - **能接力**:所有輸出物件都帶有 `id` 和 `url`,上一條指令的結果可以直接餵給下一條。
127
+ - **有分寸**:危險操作必須明確加上 `-y`,發佈時可以設為僅自己可見,遇到風控會明確回報 `RISK_CONTROL`,不會偷偷重試。
128
+
129
+ 一行指令把 specific-ai 技能裝進 Claude Code、Cursor、Codex 等 Agent:
130
+
131
+ ```bash
132
+ npx skills add Dearli666/specific-ai
133
+ ```
134
+
135
+ 技能按需載入:總綱只有一頁,碰到哪個平台、做哪類事(採集、發布、直播私訊、排錯)才讀對應的[子檔案](.claude/skills/specific-ai/SKILL.md),並且內建安全紅線——登入交給你、寫入操作先確認、發布預設僅自己可見、遇到風控就停。
136
+
137
+ ## ⚡ 30 秒上車
138
+
139
+ ```bash
140
+ npm i -g specific-ai-cli
141
+
142
+ specific-ai doctor # 檢查環境
143
+ specific-ai bilibili auth login # 掃描 QR Code 登入
144
+ specific-ai bilibili item search 猫 --limit 20
145
+ ```
146
+
147
+ 也可以用 [Docker](docs/guide/docker.md) 或從原始碼安裝,完整步驟請見 [快速上手](docs/guide/quick-start.md)。
148
+
149
+ ## 📚 文件
150
+
151
+ 以下文件目前僅提供簡體中文版。
152
+
153
+ | | |
154
+ |---|---|
155
+ | 🚀 [快速上手](docs/guide/quick-start.md) | 從安裝到匯出第一批資料 |
156
+ | 🔑 [登入](docs/guide/login.md) · ⚙️ [設定](docs/guide/configuration.md) · 🐳 [Docker](docs/guide/docker.md) | 掃碼 / cookie、多帳號、代理伺服器、容器 |
157
+ | 📤 [輸出與錯誤處理](docs/guide/output.md) | 信封、正規化型別、分頁、結束代碼 |
158
+ | 🧭 [各平台說明](docs/platforms/README.md) · 🗂️ [能力矩陣](docs/capabilities.md) | 每個平台支援什麼、參數怎麼傳 |
159
+ | 🧪 [範例腳本](docs/examples/README.md) | 跨平台搜尋、匯出留言、備份作品、錄製直播彈幕 |
160
+ | 📐 [AGENTS.md](AGENTS.md) | specific-ai 的規範本身 |
161
+ | ❓ [常見問題](docs/guide/faq.md) | 為什麼要登入、遇到風控怎麼辦…… |
162
+
163
+ ## 🤝 參與貢獻
164
+
165
+ 歡迎 issue 和 PR:新平台、新指令、平台 API 變動、文件改進都可以。開始之前請先讀一下 [CONTRIBUTING.md](.github/CONTRIBUTING.md) 和 [AGENTS.md](AGENTS.md)(規範先行,請求必須有比對測試)。安全性問題請依照 [SECURITY.md](.github/SECURITY.md) 私下回報。
166
+
167
+
168
+ ## 📄 License
169
+
170
+ [MIT](LICENSE) © 2026 [cv-cat](https://github.com/cv-cat)
171
+
172
+ <div align="center">
173
+ <br>
174
+ <img src="assets/logo.svg" width="64" alt="specific-ai">
175
+ <br>
176
+ <sub>一個指令,連接每個平台 · One command. Every platform.</sub>
177
+ </div>
@@ -0,0 +1,3 @@
1
+ ╭─●──●─╮ Specific AI · 一个命令,连接每个平台
2
+ ● ❯_ ● specific-ai <platform> <resource> <action>
3
+ ╰─●──●─╯ One command. Every platform.
@@ -0,0 +1,98 @@
1
+ import { parseArgs } from 'node:util';
2
+ import { SpecificAIError } from '../core/errors.js';
3
+ import { describeOption, flagName, STANDARD_OPTIONS } from '../core/options.js';
4
+ import { VOCAB } from '../core/vocab.js';
5
+ export const GLOBAL_FLAGS = {
6
+ account: { type: 'string', short: 'a' },
7
+ endpoint: { type: 'string', short: 'e' },
8
+ output: { type: 'string', short: 'o' },
9
+ raw: { type: 'boolean' },
10
+ proxy: { type: 'string' },
11
+ verbose: { type: 'boolean', short: 'v' },
12
+ quiet: { type: 'boolean', short: 'q' },
13
+ yes: { type: 'boolean', short: 'y' },
14
+ help: { type: 'boolean', short: 'h' },
15
+ version: { type: 'boolean' },
16
+ };
17
+ /**
18
+ * 汇总标准选项、词表和所有平台声明的选项。同名选项在不同命令里的类型必须一致,
19
+ * 也不能与全局选项重名,否则启动时就报错。
20
+ */
21
+ export function collectOptionKinds(platforms) {
22
+ const kinds = new Map();
23
+ const add = (key, schema, where) => {
24
+ if (key in GLOBAL_FLAGS)
25
+ throw new Error(`选项 --${flagName(key)}(${where})与全局选项重名`);
26
+ const kind = describeOption(schema).kind;
27
+ const prev = kinds.get(key);
28
+ if (prev && prev !== kind)
29
+ throw new Error(`选项 --${flagName(key)}(${where})的类型 ${kind} 与别处的 ${prev} 不一致`);
30
+ kinds.set(key, kind);
31
+ };
32
+ for (const [k, s] of Object.entries(STANDARD_OPTIONS))
33
+ add(k, s, '标准选项');
34
+ for (const [key, spec] of Object.entries(VOCAB))
35
+ for (const [k, s] of Object.entries(spec.options))
36
+ add(k, s, key);
37
+ for (const p of platforms) {
38
+ for (const e of Object.values(p.endpoints)) {
39
+ if (e === 'planned')
40
+ continue;
41
+ for (const c of e.commands.values())
42
+ for (const [k, s] of Object.entries(c.options))
43
+ add(k, s, `${p.id} ${c.key}`);
44
+ }
45
+ }
46
+ return kinds;
47
+ }
48
+ export function parseArgv(argv, kinds) {
49
+ const config = { ...GLOBAL_FLAGS };
50
+ const keyOf = new Map();
51
+ for (const [key, kind] of kinds) {
52
+ const flag = flagName(key);
53
+ keyOf.set(flag, key);
54
+ config[flag] = { type: kind === 'boolean' ? 'boolean' : 'string', multiple: kind === 'array' };
55
+ }
56
+ let parsed;
57
+ try {
58
+ parsed = parseArgs({ args: argv, options: config, strict: true, allowPositionals: true });
59
+ }
60
+ catch (err) {
61
+ throw translate(err);
62
+ }
63
+ const global = {};
64
+ const options = {};
65
+ for (const [flag, value] of Object.entries(parsed.values)) {
66
+ if (flag in GLOBAL_FLAGS) {
67
+ global[flag] = value;
68
+ continue;
69
+ }
70
+ const key = keyOf.get(flag);
71
+ if (kinds.get(key) === 'number') {
72
+ const n = Number(value);
73
+ if (typeof value !== 'string' || value.trim() === '' || !Number.isFinite(n)) {
74
+ throw new SpecificAIError('USAGE', `选项 --${flag} 需要一个数字,收到的是 ${String(value)}`);
75
+ }
76
+ options[key] = n;
77
+ }
78
+ else
79
+ options[key] = value;
80
+ }
81
+ return { positionals: parsed.positionals, global, options };
82
+ }
83
+ function translate(err) {
84
+ const quoted = /'([^']+)'/.exec(err.message)?.[1] ?? '';
85
+ const flag = /--[\w-]+/.exec(quoted)?.[0] ?? /-\w/.exec(quoted)?.[0] ?? quoted;
86
+ switch (err.code) {
87
+ case 'ERR_PARSE_ARGS_UNKNOWN_OPTION':
88
+ return new SpecificAIError('USAGE', `未知选项 ${flag}`, {
89
+ hint: `以 - 开头的参数请放在 -- 之后,例如:-- "${flag}"`,
90
+ });
91
+ case 'ERR_PARSE_ARGS_INVALID_OPTION_VALUE':
92
+ return /does not take an argument/.test(err.message)
93
+ ? new SpecificAIError('USAGE', `选项 ${flag} 不接受值`)
94
+ : new SpecificAIError('USAGE', `选项 ${flag} 缺少值`, { hint: `值以 - 开头时写成 ${flag}=<值>` });
95
+ default:
96
+ return new SpecificAIError('USAGE', err.message);
97
+ }
98
+ }
@@ -0,0 +1,11 @@
1
+ import { listAccounts } from '../../core/auth-store.js';
2
+ import { ENDPOINTS } from '../../core/registry.js';
3
+ import { PLATFORMS } from '../../platforms/index.js';
4
+ /** 全局 `specific-ai auth list`:所有平台、所有端的账号。 */
5
+ export async function allAccounts() {
6
+ const accounts = [];
7
+ for (const p of PLATFORMS)
8
+ for (const e of ENDPOINTS)
9
+ accounts.push(...(await listAccounts(p.id, e)));
10
+ return accounts;
11
+ }
@@ -0,0 +1,23 @@
1
+ import { getConfig, listConfig, parseKey, setConfig, unsetConfig } from '../../core/config.js';
2
+ import { SpecificAIError } from '../../core/errors.js';
3
+ import { findPlatform } from '../../platforms/index.js';
4
+ const resolvePlatform = (name) => findPlatform(name)?.id;
5
+ const USAGE = { get: 1, set: 2, unset: 1, list: 0 };
6
+ export async function config(action, words) {
7
+ const arity = USAGE[action];
8
+ if (arity === undefined) {
9
+ throw new SpecificAIError('USAGE', `未知的 config 子命令:${action}`, { hint: 'specific-ai config --help' });
10
+ }
11
+ if (words.length !== arity) {
12
+ const usage = { get: 'get <key>', set: 'set <key> <value>', unset: 'unset <key>', list: 'list' }[action];
13
+ throw new SpecificAIError('USAGE', `参数个数不对,用法:specific-ai config ${usage}`, { hint: 'specific-ai config --help' });
14
+ }
15
+ if (action === 'list')
16
+ return listConfig();
17
+ const path = parseKey(words[0], resolvePlatform);
18
+ if (action === 'get')
19
+ return getConfig(path);
20
+ if (action === 'set')
21
+ return setConfig(path, words[1]);
22
+ return unsetConfig(path);
23
+ }
@@ -0,0 +1,134 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { rm, stat } from 'node:fs/promises';
4
+ import { createRequire } from 'node:module';
5
+ import { dirname, join } from 'node:path';
6
+ import vm from 'node:vm';
7
+ import { ensureDir, writeFileAtomic } from '../../core/fsutil.js';
8
+ import { specificAiHome, packageJson } from '../../core/paths.js';
9
+ import { createContext } from '../../core/vm.js';
10
+ const require = createRequire(import.meta.url);
11
+ /** onnx 冒烟用的最小模型:c = a + b,float32[2]。 */
12
+ const ADD_MODEL = 'CAg6SAoOCgFhCgFiEgFjIgNBZGQSA2FkZFoPCgFhEgoKCAgBEgQKAggCWg8KAWISCgoICAESBAoCCAJiDwoBYxIKCggIARIECgIIAkIECgAQDQ==';
13
+ async function check(name, run) {
14
+ try {
15
+ return { name, ok: true, message: await run() };
16
+ }
17
+ catch (err) {
18
+ return { name, ok: false, message: err instanceof Error ? err.message : String(err) };
19
+ }
20
+ }
21
+ /** 已安装依赖的版本:从入口文件向上找它的 package.json(有的包不导出 ./package.json)。 */
22
+ function installedVersion(name) {
23
+ let dir = dirname(require.resolve(name));
24
+ for (;;) {
25
+ try {
26
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
27
+ if (pkg.name === name)
28
+ return pkg.version;
29
+ }
30
+ catch { }
31
+ const parent = dirname(dir);
32
+ if (parent === dir)
33
+ return '?';
34
+ dir = parent;
35
+ }
36
+ }
37
+ /** 只支持 engines 里用到的写法:`^x.y.z`、`>=x.y.z`,用 `||` 连接。 */
38
+ export function satisfies(version, range) {
39
+ const v = version.split('.').map(Number);
40
+ return range.split('||').some((part) => {
41
+ const m = /^\s*(\^|>=)(\d+)\.(\d+)\.(\d+)\s*$/.exec(part);
42
+ if (!m)
43
+ return false;
44
+ const min = [Number(m[2]), Number(m[3]), Number(m[4])];
45
+ const i = v.findIndex((n, j) => n !== min[j]);
46
+ const atLeast = i === -1 || v[i] > min[i];
47
+ return atLeast && (m[1] === '>=' || v[0] === min[0]);
48
+ });
49
+ }
50
+ export async function doctor() {
51
+ const engines = packageJson().engines.node;
52
+ return [
53
+ await check('node', () => {
54
+ const v = process.versions.node;
55
+ if (!satisfies(v, engines))
56
+ throw new Error(`Node ${v} 不满足要求 ${engines}`);
57
+ return `Node ${v}(要求 ${engines})`;
58
+ }),
59
+ await check('home', async () => {
60
+ const home = specificAiHome();
61
+ await ensureDir(home);
62
+ const probe = join(home, '.doctor');
63
+ await writeFileAtomic(probe, 'ok');
64
+ await rm(probe, { force: true });
65
+ if (process.platform !== 'win32') {
66
+ const mode = (await stat(home)).mode & 0o777;
67
+ if (mode & 0o077) {
68
+ throw new Error(`${home} 的权限是 ${mode.toString(8).padStart(4, '0')},其他用户可以访问;执行 chmod 700 ${home}`);
69
+ }
70
+ }
71
+ return `${home} 可写`;
72
+ }),
73
+ await check('vm', () => {
74
+ const expected = createHash('md5').update('specific-ai').digest('hex');
75
+ const got = vm.runInContext(`require('node:crypto').createHash('md5').update('specific-ai').digest('hex')`, createContext());
76
+ if (got !== expected)
77
+ throw new Error(`签名 vm 的结果不对:${got}`);
78
+ return 'node:vm + require 正常';
79
+ }),
80
+ await check('http', async () => {
81
+ const { createSession } = await import('wreq-js');
82
+ const session = await createSession();
83
+ await session.close();
84
+ return `wreq-js ${installedVersion('wreq-js')}`;
85
+ }),
86
+ await check('canvas', async () => {
87
+ const { createCanvas } = await import('@napi-rs/canvas');
88
+ const canvas = createCanvas(4, 4);
89
+ const ctx = canvas.getContext('2d');
90
+ ctx.fillStyle = '#f5c2e7';
91
+ ctx.fillRect(0, 0, 4, 4);
92
+ const png = canvas.toBuffer('image/png');
93
+ if (png.subarray(1, 4).toString() !== 'PNG')
94
+ throw new Error('PNG 编码结果不对');
95
+ return `@napi-rs/canvas ${installedVersion('@napi-rs/canvas')}`;
96
+ }),
97
+ await check('onnx', async () => {
98
+ const ort = await import('onnxruntime-web');
99
+ ort.env.wasm.numThreads = 1;
100
+ const session = await ort.InferenceSession.create(Buffer.from(ADD_MODEL, 'base64'));
101
+ const input = (v) => new ort.Tensor('float32', Float32Array.from(v), [2]);
102
+ const { c } = await session.run({ a: input([1, 2]), b: input([3, 4]) });
103
+ await session.release();
104
+ const got = Array.from(c.data).join();
105
+ if (got !== '4,6')
106
+ throw new Error(`推理结果不对:${got}`);
107
+ return `onnxruntime-web ${installedVersion('onnxruntime-web')}(wasm)`;
108
+ }),
109
+ await check('assets-jd', async () => {
110
+ const name = 'specific-ai-assets-jd';
111
+ const { models } = await import('specific-ai-assets-jd').catch(() => {
112
+ throw new Error(`没有安装 ${name},请重新安装 specific-ai-cli`);
113
+ });
114
+ for (const file of Object.values(models)) {
115
+ const s = await stat(file).catch(() => null);
116
+ if (!s?.size)
117
+ throw new Error(`模型文件缺失:${file}`);
118
+ }
119
+ return `${name} ${installedVersion(name)}`;
120
+ }),
121
+ await check('assets-ocr', async () => {
122
+ const name = 'specific-ai-assets-ocr';
123
+ const { models } = await import('specific-ai-assets-ocr').catch(() => {
124
+ throw new Error(`没有安装 ${name},请重新安装 specific-ai-cli`);
125
+ });
126
+ for (const file of Object.values(models)) {
127
+ const s = await stat(file).catch(() => null);
128
+ if (!s?.size)
129
+ throw new Error(`模型文件缺失:${file}`);
130
+ }
131
+ return `${name} ${installedVersion(name)}`;
132
+ }),
133
+ ];
134
+ }
@@ -0,0 +1,24 @@
1
+ import { ENDPOINTS, sortCommands } from '../../core/registry.js';
2
+ /** `specific-ai platforms` 的一项:平台 × 端 × 命令的状态,数据来自注册表。 */
3
+ export function platformInfo(platform) {
4
+ return {
5
+ id: platform.id,
6
+ name: platform.name,
7
+ aliases: platform.aliases,
8
+ endpoints: Object.fromEntries(ENDPOINTS.map((e) => [e, platform.endpoints[e] === 'planned' ? 'planned' : 'available'])),
9
+ commands: ENDPOINTS.flatMap((endpoint) => {
10
+ const ep = platform.endpoints[endpoint];
11
+ if (ep === 'planned')
12
+ return [];
13
+ return sortCommands(ep.commands.values()).map((c) => ({
14
+ endpoint,
15
+ resource: c.resource,
16
+ action: c.action,
17
+ auth: c.auth,
18
+ status: c.status,
19
+ upstream: c.upstream,
20
+ note: c.note,
21
+ }));
22
+ }),
23
+ };
24
+ }
@@ -0,0 +1,4 @@
1
+ import { packageJson } from '../../core/paths.js';
2
+ export function version() {
3
+ return { version: packageJson().version, node: process.versions.node, platform: process.platform, arch: process.arch };
4
+ }