@jackwener/opencli 1.3.3 → 1.4.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 (680) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/pull_request_template.md +3 -1
  3. package/.github/workflows/build-extension.yml +7 -1
  4. package/.github/workflows/ci.yml +46 -6
  5. package/.github/workflows/docs.yml +1 -1
  6. package/.github/workflows/e2e-headed.yml +36 -3
  7. package/.github/workflows/release.yml +1 -1
  8. package/.github/workflows/security.yml +0 -3
  9. package/CHANGELOG.md +78 -0
  10. package/CONTRIBUTING.md +6 -3
  11. package/PRIVACY.md +57 -0
  12. package/README.md +31 -4
  13. package/README.zh-CN.md +31 -4
  14. package/SKILL.md +107 -2
  15. package/TESTING.md +1 -0
  16. package/chatwise-opencli.ps1 +82 -0
  17. package/dist/analysis.d.ts +38 -0
  18. package/dist/analysis.js +166 -0
  19. package/dist/browser/cdp.d.ts +0 -4
  20. package/dist/browser/cdp.js +53 -41
  21. package/dist/browser/cdp.test.d.ts +1 -0
  22. package/dist/browser/cdp.test.js +52 -0
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/index.d.ts +2 -2
  27. package/dist/browser/index.js +1 -1
  28. package/dist/browser/mcp.d.ts +0 -2
  29. package/dist/browser/mcp.js +2 -3
  30. package/dist/browser/page.d.ts +4 -3
  31. package/dist/browser/page.js +34 -37
  32. package/dist/browser/stealth.d.ts +0 -2
  33. package/dist/browser/stealth.js +24 -9
  34. package/dist/browser.test.js +2 -2
  35. package/dist/build-manifest.js +15 -9
  36. package/dist/build-manifest.test.js +12 -0
  37. package/dist/cascade.js +4 -2
  38. package/dist/cli-manifest.json +1325 -256
  39. package/dist/cli.js +57 -29
  40. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  41. package/dist/clis/_shared/desktop-commands.js +108 -0
  42. package/dist/clis/antigravity/serve.js +5 -2
  43. package/dist/clis/apple-podcasts/search.js +2 -1
  44. package/dist/clis/arxiv/search.js +3 -3
  45. package/dist/clis/bbc/news.js +0 -1
  46. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  47. package/dist/clis/bilibili/dynamic.test.js +68 -0
  48. package/dist/clis/bilibili/favorite.js +4 -2
  49. package/dist/clis/bilibili/following.js +3 -2
  50. package/dist/clis/bilibili/subtitle.js +8 -7
  51. package/dist/clis/bilibili/utils.js +2 -2
  52. package/dist/clis/boss/batchgreet.js +1 -1
  53. package/dist/clis/boss/chatlist.js +1 -1
  54. package/dist/clis/boss/chatmsg.js +1 -1
  55. package/dist/clis/boss/detail.js +1 -1
  56. package/dist/clis/boss/exchange.js +1 -1
  57. package/dist/clis/boss/greet.js +1 -1
  58. package/dist/clis/boss/invite.js +1 -1
  59. package/dist/clis/boss/joblist.js +1 -1
  60. package/dist/clis/boss/mark.js +4 -3
  61. package/dist/clis/boss/recommend.js +1 -1
  62. package/dist/clis/boss/resume.js +1 -1
  63. package/dist/clis/boss/search.js +1 -1
  64. package/dist/clis/boss/send.js +5 -4
  65. package/dist/clis/boss/stats.js +1 -1
  66. package/dist/clis/chatgpt/ask.js +4 -0
  67. package/dist/clis/chatgpt/new.js +5 -1
  68. package/dist/clis/chatgpt/read.js +5 -1
  69. package/dist/clis/chatgpt/send.js +2 -1
  70. package/dist/clis/chatgpt/status.js +5 -1
  71. package/dist/clis/chatwise/ask.js +8 -2
  72. package/dist/clis/chatwise/export.js +2 -0
  73. package/dist/clis/chatwise/history.js +2 -0
  74. package/dist/clis/chatwise/model.js +8 -3
  75. package/dist/clis/chatwise/new.js +3 -18
  76. package/dist/clis/chatwise/read.js +2 -0
  77. package/dist/clis/chatwise/screenshot.js +3 -27
  78. package/dist/clis/chatwise/send.js +8 -2
  79. package/dist/clis/chatwise/shared.d.ts +2 -0
  80. package/dist/clis/chatwise/shared.js +6 -0
  81. package/dist/clis/chatwise/status.js +3 -22
  82. package/dist/clis/codex/ask.js +6 -2
  83. package/dist/clis/codex/dump.js +2 -25
  84. package/dist/clis/codex/new.js +2 -25
  85. package/dist/clis/codex/screenshot.js +2 -27
  86. package/dist/clis/codex/send.js +6 -4
  87. package/dist/clis/codex/status.js +2 -22
  88. package/dist/clis/ctrip/search.js +0 -1
  89. package/dist/clis/cursor/ask.js +2 -1
  90. package/dist/clis/cursor/composer.js +2 -1
  91. package/dist/clis/cursor/dump.js +2 -25
  92. package/dist/clis/cursor/new.js +2 -18
  93. package/dist/clis/cursor/read.js +2 -1
  94. package/dist/clis/cursor/screenshot.js +1 -30
  95. package/dist/clis/cursor/send.js +2 -1
  96. package/dist/clis/cursor/status.js +2 -21
  97. package/dist/clis/dictionary/examples.yaml +25 -0
  98. package/dist/clis/dictionary/search.yaml +27 -0
  99. package/dist/clis/dictionary/synonyms.yaml +25 -0
  100. package/dist/clis/douban/book-hot.js +1 -1
  101. package/dist/clis/douban/movie-hot.js +1 -1
  102. package/dist/clis/douban/search.js +1 -1
  103. package/dist/clis/douban/utils.d.ts +4 -1
  104. package/dist/clis/douban/utils.js +156 -1
  105. package/dist/clis/doubao/ask.js +1 -1
  106. package/dist/clis/doubao/new.js +1 -1
  107. package/dist/clis/doubao/read.js +1 -1
  108. package/dist/clis/doubao/send.js +1 -1
  109. package/dist/clis/doubao/status.js +1 -1
  110. package/dist/clis/doubao-app/ask.js +1 -1
  111. package/dist/clis/doubao-app/new.js +1 -1
  112. package/dist/clis/doubao-app/read.js +1 -1
  113. package/dist/clis/doubao-app/send.js +1 -1
  114. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  115. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  116. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  117. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  118. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  119. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  120. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  121. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  122. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  123. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  124. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  125. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  126. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  127. package/dist/clis/douyin/_shared/sts2.js +15 -0
  128. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  129. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  130. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  131. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  132. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  133. package/dist/clis/douyin/_shared/timing.js +22 -0
  134. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  135. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  136. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  137. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  138. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  139. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  140. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  141. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  142. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  143. package/dist/clis/douyin/_shared/transcode.js +45 -0
  144. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  145. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  146. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  147. package/dist/clis/douyin/_shared/types.js +1 -0
  148. package/dist/clis/douyin/activities.d.ts +1 -0
  149. package/dist/clis/douyin/activities.js +20 -0
  150. package/dist/clis/douyin/activities.test.d.ts +1 -0
  151. package/dist/clis/douyin/activities.test.js +22 -0
  152. package/dist/clis/douyin/collections.d.ts +1 -0
  153. package/dist/clis/douyin/collections.js +22 -0
  154. package/dist/clis/douyin/collections.test.d.ts +1 -0
  155. package/dist/clis/douyin/collections.test.js +23 -0
  156. package/dist/clis/douyin/delete.d.ts +1 -0
  157. package/dist/clis/douyin/delete.js +18 -0
  158. package/dist/clis/douyin/delete.test.d.ts +1 -0
  159. package/dist/clis/douyin/delete.test.js +11 -0
  160. package/dist/clis/douyin/draft.d.ts +14 -0
  161. package/dist/clis/douyin/draft.js +237 -0
  162. package/dist/clis/douyin/draft.test.d.ts +1 -0
  163. package/dist/clis/douyin/draft.test.js +11 -0
  164. package/dist/clis/douyin/drafts.d.ts +1 -0
  165. package/dist/clis/douyin/drafts.js +23 -0
  166. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  167. package/dist/clis/douyin/drafts.test.js +11 -0
  168. package/dist/clis/douyin/hashtag.d.ts +1 -0
  169. package/dist/clis/douyin/hashtag.js +45 -0
  170. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  171. package/dist/clis/douyin/hashtag.test.js +25 -0
  172. package/dist/clis/douyin/location.d.ts +1 -0
  173. package/dist/clis/douyin/location.js +24 -0
  174. package/dist/clis/douyin/location.test.d.ts +1 -0
  175. package/dist/clis/douyin/location.test.js +23 -0
  176. package/dist/clis/douyin/profile.d.ts +1 -0
  177. package/dist/clis/douyin/profile.js +28 -0
  178. package/dist/clis/douyin/profile.test.d.ts +1 -0
  179. package/dist/clis/douyin/profile.test.js +11 -0
  180. package/dist/clis/douyin/publish.d.ts +14 -0
  181. package/dist/clis/douyin/publish.js +288 -0
  182. package/dist/clis/douyin/publish.test.d.ts +1 -0
  183. package/dist/clis/douyin/publish.test.js +38 -0
  184. package/dist/clis/douyin/stats.d.ts +1 -0
  185. package/dist/clis/douyin/stats.js +27 -0
  186. package/dist/clis/douyin/stats.test.d.ts +1 -0
  187. package/dist/clis/douyin/stats.test.js +22 -0
  188. package/dist/clis/douyin/update.d.ts +1 -0
  189. package/dist/clis/douyin/update.js +31 -0
  190. package/dist/clis/douyin/update.test.d.ts +1 -0
  191. package/dist/clis/douyin/update.test.js +11 -0
  192. package/dist/clis/douyin/videos.d.ts +1 -0
  193. package/dist/clis/douyin/videos.js +34 -0
  194. package/dist/clis/douyin/videos.test.d.ts +1 -0
  195. package/dist/clis/douyin/videos.test.js +11 -0
  196. package/dist/clis/grok/ask.d.ts +4 -0
  197. package/dist/clis/grok/ask.js +28 -10
  198. package/dist/clis/grok/ask.test.js +18 -0
  199. package/dist/clis/hackernews/search.yaml +1 -1
  200. package/dist/clis/instagram/search.yaml +2 -1
  201. package/dist/clis/jd/item.d.ts +1 -0
  202. package/dist/clis/jd/item.js +96 -0
  203. package/dist/clis/jd/item.test.d.ts +1 -0
  204. package/dist/clis/jd/item.test.js +28 -0
  205. package/dist/clis/jike/feed.js +1 -1
  206. package/dist/clis/jike/search.js +1 -1
  207. package/dist/clis/linkedin/search.js +5 -4
  208. package/dist/clis/linkedin/timeline.d.ts +21 -0
  209. package/dist/clis/linkedin/timeline.js +503 -0
  210. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  211. package/dist/clis/linkedin/timeline.test.js +81 -0
  212. package/dist/clis/linux-do/search.yaml +3 -1
  213. package/dist/clis/medium/feed.js +1 -1
  214. package/dist/clis/medium/search.js +2 -2
  215. package/dist/clis/medium/user.js +1 -1
  216. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  217. package/dist/clis/pixiv/detail.yaml +49 -0
  218. package/dist/clis/pixiv/download.d.ts +7 -0
  219. package/dist/clis/pixiv/download.js +78 -0
  220. package/dist/clis/pixiv/download.test.d.ts +1 -0
  221. package/dist/clis/pixiv/download.test.js +87 -0
  222. package/dist/clis/pixiv/illusts.d.ts +8 -0
  223. package/dist/clis/pixiv/illusts.js +65 -0
  224. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  225. package/dist/clis/pixiv/illusts.test.js +99 -0
  226. package/dist/clis/pixiv/ranking.yaml +53 -0
  227. package/dist/clis/pixiv/search.d.ts +6 -0
  228. package/dist/clis/pixiv/search.js +43 -0
  229. package/dist/clis/pixiv/search.test.d.ts +1 -0
  230. package/dist/clis/pixiv/search.test.js +83 -0
  231. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  232. package/dist/clis/pixiv/test-utils.js +23 -0
  233. package/dist/clis/pixiv/user.yaml +46 -0
  234. package/dist/clis/pixiv/utils.d.ts +27 -0
  235. package/dist/clis/pixiv/utils.js +49 -0
  236. package/dist/clis/reddit/comment.js +2 -1
  237. package/dist/clis/reddit/read.js +4 -3
  238. package/dist/clis/reddit/read.test.d.ts +1 -0
  239. package/dist/clis/reddit/read.test.js +28 -0
  240. package/dist/clis/reddit/save.js +2 -1
  241. package/dist/clis/reddit/saved.js +7 -3
  242. package/dist/clis/reddit/subscribe.js +2 -1
  243. package/dist/clis/reddit/upvote.js +2 -1
  244. package/dist/clis/reddit/upvoted.js +7 -3
  245. package/dist/clis/reuters/search.js +0 -1
  246. package/dist/clis/sinablog/article.js +1 -1
  247. package/dist/clis/sinablog/hot.js +1 -1
  248. package/dist/clis/sinablog/user.js +1 -1
  249. package/dist/clis/substack/feed.js +1 -1
  250. package/dist/clis/substack/publication.js +1 -1
  251. package/dist/clis/substack/search.js +3 -2
  252. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  253. package/dist/clis/tiktok/search.yaml +2 -1
  254. package/dist/clis/twitter/accept.js +2 -1
  255. package/dist/clis/twitter/article.js +4 -1
  256. package/dist/clis/twitter/block.js +2 -1
  257. package/dist/clis/twitter/bookmark.js +2 -1
  258. package/dist/clis/twitter/bookmarks.js +3 -2
  259. package/dist/clis/twitter/delete.js +2 -1
  260. package/dist/clis/twitter/follow.js +2 -1
  261. package/dist/clis/twitter/followers.js +3 -2
  262. package/dist/clis/twitter/following.js +3 -2
  263. package/dist/clis/twitter/hide-reply.js +2 -1
  264. package/dist/clis/twitter/like.js +2 -1
  265. package/dist/clis/twitter/notifications.js +2 -1
  266. package/dist/clis/twitter/post.js +2 -1
  267. package/dist/clis/twitter/profile.js +5 -2
  268. package/dist/clis/twitter/reply-dm.js +2 -1
  269. package/dist/clis/twitter/reply.js +2 -1
  270. package/dist/clis/twitter/search.js +32 -13
  271. package/dist/clis/twitter/search.test.d.ts +1 -0
  272. package/dist/clis/twitter/search.test.js +156 -0
  273. package/dist/clis/twitter/thread.js +2 -2
  274. package/dist/clis/twitter/timeline.js +3 -2
  275. package/dist/clis/twitter/trending.js +3 -2
  276. package/dist/clis/twitter/unblock.js +2 -1
  277. package/dist/clis/twitter/unbookmark.js +2 -1
  278. package/dist/clis/twitter/unfollow.js +2 -1
  279. package/dist/clis/v2ex/daily.js +3 -2
  280. package/dist/clis/v2ex/me.js +3 -2
  281. package/dist/clis/v2ex/notifications.js +4 -4
  282. package/dist/clis/web/read.d.ts +16 -0
  283. package/dist/clis/web/read.js +202 -0
  284. package/dist/clis/weibo/comments.d.ts +1 -0
  285. package/dist/clis/weibo/comments.js +53 -0
  286. package/dist/clis/weibo/feed.d.ts +1 -0
  287. package/dist/clis/weibo/feed.js +56 -0
  288. package/dist/clis/weibo/hot.js +0 -1
  289. package/dist/clis/weibo/me.d.ts +1 -0
  290. package/dist/clis/weibo/me.js +76 -0
  291. package/dist/clis/weibo/post.d.ts +1 -0
  292. package/dist/clis/weibo/post.js +75 -0
  293. package/dist/clis/weibo/user.d.ts +1 -0
  294. package/dist/clis/weibo/user.js +63 -0
  295. package/dist/clis/weibo/utils.d.ts +6 -0
  296. package/dist/clis/weibo/utils.js +30 -0
  297. package/dist/clis/weread/search.js +3 -2
  298. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  299. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  300. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  301. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  302. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  303. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  304. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  305. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  306. package/dist/clis/xueqiu/search.yaml +2 -1
  307. package/dist/clis/yahoo-finance/quote.js +0 -1
  308. package/dist/clis/youtube/channel.d.ts +1 -0
  309. package/dist/clis/youtube/channel.js +150 -0
  310. package/dist/clis/youtube/comments.d.ts +1 -0
  311. package/dist/clis/youtube/comments.js +95 -0
  312. package/dist/clis/youtube/search.js +0 -1
  313. package/dist/clis/youtube/transcript.js +5 -4
  314. package/dist/clis/youtube/video.js +3 -2
  315. package/dist/clis/zhihu/search.yaml +2 -1
  316. package/dist/daemon.js +7 -3
  317. package/dist/discovery.js +11 -10
  318. package/dist/doctor.js +2 -1
  319. package/dist/download/index.d.ts +4 -12
  320. package/dist/download/index.js +33 -12
  321. package/dist/download/index.test.js +79 -2
  322. package/dist/download/media-download.js +4 -2
  323. package/dist/engine.test.js +76 -4
  324. package/dist/execution.d.ts +1 -9
  325. package/dist/execution.js +56 -46
  326. package/dist/explore.js +12 -111
  327. package/dist/external-clis.yaml +0 -25
  328. package/dist/external.js +7 -5
  329. package/dist/external.test.js +4 -0
  330. package/dist/generate.d.ts +0 -9
  331. package/dist/generate.js +4 -20
  332. package/dist/hooks.d.ts +46 -0
  333. package/dist/hooks.js +56 -0
  334. package/dist/hooks.test.d.ts +4 -0
  335. package/dist/hooks.test.js +92 -0
  336. package/dist/interceptor.js +70 -23
  337. package/dist/main.js +2 -0
  338. package/dist/output.js +12 -6
  339. package/dist/pipeline/executor.js +1 -1
  340. package/dist/pipeline/steps/browser.js +1 -3
  341. package/dist/pipeline/steps/download.js +42 -26
  342. package/dist/pipeline/steps/download.test.d.ts +1 -0
  343. package/dist/pipeline/steps/download.test.js +101 -0
  344. package/dist/pipeline/steps/fetch.js +40 -22
  345. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  346. package/dist/pipeline/steps/fetch.test.js +123 -0
  347. package/dist/pipeline/steps/transform.js +2 -6
  348. package/dist/pipeline/template.js +66 -52
  349. package/dist/pipeline/template.test.js +28 -0
  350. package/dist/pipeline/transform.test.js +18 -0
  351. package/dist/plugin.d.ts +40 -1
  352. package/dist/plugin.js +214 -17
  353. package/dist/plugin.test.d.ts +1 -1
  354. package/dist/plugin.test.js +219 -3
  355. package/dist/record.js +6 -98
  356. package/dist/registry-api.d.ts +2 -0
  357. package/dist/registry-api.js +1 -0
  358. package/dist/registry.d.ts +5 -2
  359. package/dist/registry.js +1 -2
  360. package/dist/runtime.d.ts +0 -1
  361. package/dist/runtime.js +14 -4
  362. package/dist/snapshotFormatter.d.ts +7 -14
  363. package/dist/snapshotFormatter.js +38 -78
  364. package/dist/utils.d.ts +9 -0
  365. package/dist/utils.js +29 -0
  366. package/dist/validate.js +3 -5
  367. package/dist/weread-search-regression.test.d.ts +1 -0
  368. package/dist/weread-search-regression.test.js +39 -0
  369. package/dist/yaml-schema.d.ts +26 -0
  370. package/dist/yaml-schema.js +5 -0
  371. package/docs/.vitepress/config.mts +16 -0
  372. package/docs/adapters/browser/dictionary.md +27 -0
  373. package/docs/adapters/browser/douyin.md +75 -0
  374. package/docs/adapters/browser/jd.md +27 -0
  375. package/docs/adapters/browser/linkedin.md +6 -0
  376. package/docs/adapters/browser/pixiv.md +92 -0
  377. package/docs/adapters/browser/twitter.md +6 -0
  378. package/docs/adapters/browser/web.md +30 -0
  379. package/docs/adapters/browser/xueqiu.md +27 -9
  380. package/docs/adapters/index.md +9 -2
  381. package/docs/comparison.md +125 -0
  382. package/docs/developer/contributing.md +21 -2
  383. package/docs/developer/testing.md +14 -8
  384. package/docs/developer/ts-adapter.md +18 -0
  385. package/docs/developer/yaml-adapter.md +16 -0
  386. package/docs/guide/plugins.md +10 -0
  387. package/docs/zh/guide/plugins.md +10 -0
  388. package/extension/dist/background.js +100 -35
  389. package/extension/manifest.json +6 -2
  390. package/extension/package.json +1 -1
  391. package/extension/popup.html +84 -0
  392. package/extension/popup.js +25 -0
  393. package/extension/src/background.test.ts +46 -1
  394. package/extension/src/background.ts +128 -34
  395. package/extension/src/cdp.ts +9 -9
  396. package/package.json +3 -2
  397. package/scripts/check-doc-coverage.sh +2 -0
  398. package/src/analysis.ts +170 -0
  399. package/src/browser/cdp.test.ts +66 -0
  400. package/src/browser/cdp.ts +59 -44
  401. package/src/browser/dom-snapshot.test.ts +42 -0
  402. package/src/browser/dom-snapshot.ts +56 -3
  403. package/src/browser/index.ts +2 -2
  404. package/src/browser/mcp.ts +2 -4
  405. package/src/browser/page.ts +34 -37
  406. package/src/browser/stealth.ts +24 -10
  407. package/src/browser.test.ts +2 -2
  408. package/src/build-manifest.test.ts +14 -0
  409. package/src/build-manifest.ts +13 -31
  410. package/src/cascade.ts +5 -3
  411. package/src/cli.ts +66 -34
  412. package/src/clis/_shared/desktop-commands.ts +121 -0
  413. package/src/clis/antigravity/serve.ts +6 -3
  414. package/src/clis/apple-podcasts/search.ts +2 -1
  415. package/src/clis/arxiv/search.ts +3 -3
  416. package/src/clis/bbc/news.ts +0 -1
  417. package/src/clis/bilibili/dynamic.test.ts +79 -0
  418. package/src/clis/bilibili/favorite.ts +5 -2
  419. package/src/clis/bilibili/following.ts +3 -2
  420. package/src/clis/bilibili/subtitle.ts +8 -7
  421. package/src/clis/bilibili/utils.ts +2 -2
  422. package/src/clis/boss/batchgreet.ts +1 -1
  423. package/src/clis/boss/chatlist.ts +1 -1
  424. package/src/clis/boss/chatmsg.ts +1 -1
  425. package/src/clis/boss/detail.ts +1 -1
  426. package/src/clis/boss/exchange.ts +1 -1
  427. package/src/clis/boss/greet.ts +1 -1
  428. package/src/clis/boss/invite.ts +1 -1
  429. package/src/clis/boss/joblist.ts +1 -1
  430. package/src/clis/boss/mark.ts +4 -3
  431. package/src/clis/boss/recommend.ts +1 -1
  432. package/src/clis/boss/resume.ts +1 -1
  433. package/src/clis/boss/search.ts +1 -1
  434. package/src/clis/boss/send.ts +5 -4
  435. package/src/clis/boss/stats.ts +1 -1
  436. package/src/clis/chatgpt/ask.ts +5 -0
  437. package/src/clis/chatgpt/new.ts +7 -2
  438. package/src/clis/chatgpt/read.ts +7 -2
  439. package/src/clis/chatgpt/send.ts +3 -2
  440. package/src/clis/chatgpt/status.ts +6 -1
  441. package/src/clis/chatwise/ask.ts +7 -2
  442. package/src/clis/chatwise/export.ts +2 -0
  443. package/src/clis/chatwise/history.ts +2 -0
  444. package/src/clis/chatwise/model.ts +7 -3
  445. package/src/clis/chatwise/new.ts +3 -20
  446. package/src/clis/chatwise/read.ts +2 -0
  447. package/src/clis/chatwise/screenshot.ts +3 -32
  448. package/src/clis/chatwise/send.ts +7 -2
  449. package/src/clis/chatwise/shared.ts +8 -0
  450. package/src/clis/chatwise/status.ts +3 -24
  451. package/src/clis/codex/ask.ts +5 -2
  452. package/src/clis/codex/dump.ts +2 -27
  453. package/src/clis/codex/new.ts +2 -28
  454. package/src/clis/codex/screenshot.ts +2 -32
  455. package/src/clis/codex/send.ts +5 -4
  456. package/src/clis/codex/status.ts +2 -24
  457. package/src/clis/ctrip/search.ts +0 -1
  458. package/src/clis/cursor/ask.ts +2 -1
  459. package/src/clis/cursor/composer.ts +2 -1
  460. package/src/clis/cursor/dump.ts +2 -27
  461. package/src/clis/cursor/new.ts +2 -20
  462. package/src/clis/cursor/read.ts +2 -1
  463. package/src/clis/cursor/screenshot.ts +1 -36
  464. package/src/clis/cursor/send.ts +2 -1
  465. package/src/clis/cursor/status.ts +2 -22
  466. package/src/clis/dictionary/examples.yaml +25 -0
  467. package/src/clis/dictionary/search.yaml +27 -0
  468. package/src/clis/dictionary/synonyms.yaml +25 -0
  469. package/src/clis/douban/book-hot.ts +1 -1
  470. package/src/clis/douban/movie-hot.ts +1 -1
  471. package/src/clis/douban/search.ts +1 -1
  472. package/src/clis/douban/utils.ts +165 -1
  473. package/src/clis/doubao/ask.ts +1 -1
  474. package/src/clis/doubao/new.ts +1 -1
  475. package/src/clis/doubao/read.ts +1 -1
  476. package/src/clis/doubao/send.ts +1 -1
  477. package/src/clis/doubao/status.ts +1 -1
  478. package/src/clis/doubao-app/ask.ts +1 -1
  479. package/src/clis/doubao-app/new.ts +1 -1
  480. package/src/clis/doubao-app/read.ts +1 -1
  481. package/src/clis/doubao-app/send.ts +1 -1
  482. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  483. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  484. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  485. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  486. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  487. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  488. package/src/clis/douyin/_shared/sts2.ts +20 -0
  489. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  490. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  491. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  492. package/src/clis/douyin/_shared/timing.ts +22 -0
  493. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  494. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  495. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  496. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  497. package/src/clis/douyin/_shared/transcode.ts +78 -0
  498. package/src/clis/douyin/_shared/types.ts +29 -0
  499. package/src/clis/douyin/activities.test.ts +25 -0
  500. package/src/clis/douyin/activities.ts +23 -0
  501. package/src/clis/douyin/collections.test.ts +26 -0
  502. package/src/clis/douyin/collections.ts +25 -0
  503. package/src/clis/douyin/delete.test.ts +12 -0
  504. package/src/clis/douyin/delete.ts +20 -0
  505. package/src/clis/douyin/draft.test.ts +12 -0
  506. package/src/clis/douyin/draft.ts +282 -0
  507. package/src/clis/douyin/drafts.test.ts +12 -0
  508. package/src/clis/douyin/drafts.ts +27 -0
  509. package/src/clis/douyin/hashtag.test.ts +28 -0
  510. package/src/clis/douyin/hashtag.ts +56 -0
  511. package/src/clis/douyin/location.test.ts +26 -0
  512. package/src/clis/douyin/location.ts +27 -0
  513. package/src/clis/douyin/profile.test.ts +12 -0
  514. package/src/clis/douyin/profile.ts +37 -0
  515. package/src/clis/douyin/publish.test.ts +45 -0
  516. package/src/clis/douyin/publish.ts +340 -0
  517. package/src/clis/douyin/stats.test.ts +25 -0
  518. package/src/clis/douyin/stats.ts +30 -0
  519. package/src/clis/douyin/update.test.ts +12 -0
  520. package/src/clis/douyin/update.ts +43 -0
  521. package/src/clis/douyin/videos.test.ts +12 -0
  522. package/src/clis/douyin/videos.ts +49 -0
  523. package/src/clis/grok/ask.test.ts +25 -0
  524. package/src/clis/grok/ask.ts +25 -12
  525. package/src/clis/hackernews/search.yaml +1 -1
  526. package/src/clis/instagram/search.yaml +2 -1
  527. package/src/clis/jd/item.test.ts +35 -0
  528. package/src/clis/jd/item.ts +101 -0
  529. package/src/clis/jike/feed.ts +1 -1
  530. package/src/clis/jike/search.ts +1 -1
  531. package/src/clis/linkedin/search.ts +5 -4
  532. package/src/clis/linkedin/timeline.test.ts +99 -0
  533. package/src/clis/linkedin/timeline.ts +532 -0
  534. package/src/clis/linux-do/search.yaml +3 -1
  535. package/src/clis/medium/feed.ts +1 -1
  536. package/src/clis/medium/search.ts +2 -2
  537. package/src/clis/medium/user.ts +1 -1
  538. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  539. package/src/clis/pixiv/detail.yaml +49 -0
  540. package/src/clis/pixiv/download.test.ts +114 -0
  541. package/src/clis/pixiv/download.ts +91 -0
  542. package/src/clis/pixiv/illusts.test.ts +115 -0
  543. package/src/clis/pixiv/illusts.ts +78 -0
  544. package/src/clis/pixiv/ranking.yaml +53 -0
  545. package/src/clis/pixiv/search.test.ts +97 -0
  546. package/src/clis/pixiv/search.ts +53 -0
  547. package/src/clis/pixiv/test-utils.ts +29 -0
  548. package/src/clis/pixiv/user.yaml +46 -0
  549. package/src/clis/pixiv/utils.ts +62 -0
  550. package/src/clis/reddit/comment.ts +2 -1
  551. package/src/clis/reddit/read.test.ts +34 -0
  552. package/src/clis/reddit/read.ts +4 -3
  553. package/src/clis/reddit/save.ts +2 -1
  554. package/src/clis/reddit/saved.ts +6 -2
  555. package/src/clis/reddit/subscribe.ts +2 -1
  556. package/src/clis/reddit/upvote.ts +2 -1
  557. package/src/clis/reddit/upvoted.ts +6 -2
  558. package/src/clis/reuters/search.ts +0 -1
  559. package/src/clis/sinablog/article.ts +1 -1
  560. package/src/clis/sinablog/hot.ts +1 -1
  561. package/src/clis/sinablog/user.ts +1 -1
  562. package/src/clis/substack/feed.ts +1 -1
  563. package/src/clis/substack/publication.ts +1 -1
  564. package/src/clis/substack/search.ts +3 -2
  565. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  566. package/src/clis/tiktok/search.yaml +2 -1
  567. package/src/clis/twitter/accept.ts +2 -1
  568. package/src/clis/twitter/article.ts +3 -1
  569. package/src/clis/twitter/block.ts +2 -1
  570. package/src/clis/twitter/bookmark.ts +2 -1
  571. package/src/clis/twitter/bookmarks.ts +3 -2
  572. package/src/clis/twitter/delete.ts +2 -1
  573. package/src/clis/twitter/follow.ts +2 -1
  574. package/src/clis/twitter/followers.ts +3 -2
  575. package/src/clis/twitter/following.ts +3 -2
  576. package/src/clis/twitter/hide-reply.ts +2 -1
  577. package/src/clis/twitter/like.ts +2 -1
  578. package/src/clis/twitter/notifications.ts +2 -1
  579. package/src/clis/twitter/post.ts +2 -1
  580. package/src/clis/twitter/profile.ts +4 -2
  581. package/src/clis/twitter/reply-dm.ts +2 -1
  582. package/src/clis/twitter/reply.ts +2 -1
  583. package/src/clis/twitter/search.test.ts +180 -0
  584. package/src/clis/twitter/search.ts +40 -14
  585. package/src/clis/twitter/thread.ts +2 -2
  586. package/src/clis/twitter/timeline.ts +3 -2
  587. package/src/clis/twitter/trending.ts +3 -2
  588. package/src/clis/twitter/unblock.ts +2 -1
  589. package/src/clis/twitter/unbookmark.ts +2 -1
  590. package/src/clis/twitter/unfollow.ts +2 -1
  591. package/src/clis/v2ex/daily.ts +3 -2
  592. package/src/clis/v2ex/me.ts +3 -2
  593. package/src/clis/v2ex/notifications.ts +3 -4
  594. package/src/clis/web/read.ts +210 -0
  595. package/src/clis/weibo/comments.ts +54 -0
  596. package/src/clis/weibo/feed.ts +57 -0
  597. package/src/clis/weibo/hot.ts +0 -1
  598. package/src/clis/weibo/me.ts +77 -0
  599. package/src/clis/weibo/post.ts +77 -0
  600. package/src/clis/weibo/user.ts +64 -0
  601. package/src/clis/weibo/utils.ts +32 -0
  602. package/src/clis/weread/search.ts +3 -2
  603. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  604. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  605. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  606. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  607. package/src/clis/xueqiu/search.yaml +2 -1
  608. package/src/clis/yahoo-finance/quote.ts +0 -1
  609. package/src/clis/youtube/channel.ts +155 -0
  610. package/src/clis/youtube/comments.ts +97 -0
  611. package/src/clis/youtube/search.ts +0 -1
  612. package/src/clis/youtube/transcript.ts +5 -4
  613. package/src/clis/youtube/video.ts +3 -2
  614. package/src/clis/zhihu/search.yaml +2 -1
  615. package/src/daemon.ts +5 -4
  616. package/src/discovery.ts +12 -34
  617. package/src/doctor.ts +3 -2
  618. package/src/download/index.test.ts +93 -2
  619. package/src/download/index.ts +44 -23
  620. package/src/download/media-download.ts +5 -3
  621. package/src/engine.test.ts +84 -3
  622. package/src/execution.ts +62 -46
  623. package/src/explore.ts +21 -90
  624. package/src/external-clis.yaml +0 -25
  625. package/src/external.test.ts +9 -0
  626. package/src/external.ts +12 -10
  627. package/src/generate.ts +4 -41
  628. package/src/hooks.test.ts +126 -0
  629. package/src/hooks.ts +90 -0
  630. package/src/interceptor.ts +73 -23
  631. package/src/main.ts +2 -0
  632. package/src/output.ts +14 -6
  633. package/src/pipeline/executor.ts +1 -1
  634. package/src/pipeline/steps/browser.ts +1 -3
  635. package/src/pipeline/steps/download.test.ts +136 -0
  636. package/src/pipeline/steps/download.ts +47 -34
  637. package/src/pipeline/steps/fetch.test.ts +179 -0
  638. package/src/pipeline/steps/fetch.ts +39 -23
  639. package/src/pipeline/steps/transform.ts +2 -6
  640. package/src/pipeline/template.test.ts +28 -0
  641. package/src/pipeline/template.ts +67 -79
  642. package/src/pipeline/transform.test.ts +20 -0
  643. package/src/plugin.test.ts +251 -3
  644. package/src/plugin.ts +265 -21
  645. package/src/record.ts +12 -84
  646. package/src/registry-api.ts +2 -0
  647. package/src/registry.ts +7 -4
  648. package/src/runtime.ts +14 -4
  649. package/src/snapshotFormatter.ts +43 -121
  650. package/src/utils.ts +39 -0
  651. package/src/validate.ts +3 -5
  652. package/src/weread-search-regression.test.ts +44 -0
  653. package/src/yaml-schema.ts +28 -0
  654. package/tests/e2e/browser-auth.test.ts +25 -0
  655. package/tests/e2e/browser-public-extended.test.ts +162 -0
  656. package/tests/e2e/browser-public.test.ts +7 -146
  657. package/tests/e2e/plugin-management.test.ts +137 -0
  658. package/tests/e2e/public-commands.test.ts +34 -1
  659. package/vitest.config.ts +33 -8
  660. package/.github/workflows/pkg-pr-new.yml +0 -30
  661. package/dist/clis/douban/shared.d.ts +0 -4
  662. package/dist/clis/douban/shared.js +0 -155
  663. package/src/clis/douban/shared.ts +0 -165
  664. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  665. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  666. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  667. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  668. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  669. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  670. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  671. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  672. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  673. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  674. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  675. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  676. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  677. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  678. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  679. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  680. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -0,0 +1,295 @@
1
+ /**
2
+ * TOS (ByteDance Object Storage) multipart uploader with resume support.
3
+ *
4
+ * Uses AWS Signature V4 (HMAC-SHA256) with STS2 temporary credentials.
5
+ * For the init multipart upload call, the pre-computed auth from TosUploadInfo is used.
6
+ * For PUT part uploads and the final complete call, AWS4 is computed from STS2 credentials.
7
+ */
8
+ import * as crypto from 'node:crypto';
9
+ import * as fs from 'node:fs';
10
+ import * as os from 'node:os';
11
+ import * as path from 'node:path';
12
+ import { CommandExecutionError } from '../../../errors.js';
13
+ const PART_SIZE = 5 * 1024 * 1024; // 5 MB minimum per TOS/S3 spec
14
+ const RESUME_DIR = path.join(os.homedir(), '.opencli', 'douyin-resume');
15
+ // ── Resume file helpers ──────────────────────────────────────────────────────
16
+ function getResumeFilePath(filePath) {
17
+ const hash = crypto.createHash('sha256').update(filePath).digest('hex');
18
+ return path.join(RESUME_DIR, `${hash}.json`);
19
+ }
20
+ function loadResumeState(resumePath, fileSize) {
21
+ try {
22
+ const raw = fs.readFileSync(resumePath, 'utf8');
23
+ const state = JSON.parse(raw);
24
+ if (state.fileSize === fileSize && state.uploadId && Array.isArray(state.parts)) {
25
+ return state;
26
+ }
27
+ }
28
+ catch {
29
+ // no valid resume state
30
+ }
31
+ return null;
32
+ }
33
+ function saveResumeState(resumePath, state) {
34
+ fs.mkdirSync(path.dirname(resumePath), { recursive: true });
35
+ fs.writeFileSync(resumePath, JSON.stringify(state, null, 2), 'utf8');
36
+ }
37
+ function deleteResumeState(resumePath) {
38
+ try {
39
+ fs.unlinkSync(resumePath);
40
+ }
41
+ catch {
42
+ // ignore if not found
43
+ }
44
+ }
45
+ // ── AWS Signature V4 ─────────────────────────────────────────────────────────
46
+ function hmacSha256(key, data) {
47
+ return crypto.createHmac('sha256', key).update(data, 'utf8').digest();
48
+ }
49
+ function sha256Hex(data) {
50
+ const hash = crypto.createHash('sha256');
51
+ if (typeof data === 'string') {
52
+ hash.update(data, 'utf8');
53
+ }
54
+ else {
55
+ hash.update(data);
56
+ }
57
+ return hash.digest('hex');
58
+ }
59
+ function extractRegionFromHost(host) {
60
+ // e.g. "tos-cn-i-alisg.volces.com" → "cn-i-alisg"
61
+ // e.g. "tos-cn-beijing.ivolces.com" → "cn-beijing"
62
+ const match = host.match(/^tos-([^.]+)\./);
63
+ if (match)
64
+ return match[1];
65
+ return 'cn-north-1'; // fallback
66
+ }
67
+ /**
68
+ * Compute AWS Signature V4 headers for a TOS request.
69
+ * Returns a Record of all headers to include (including Authorization, x-amz-date, etc.)
70
+ */
71
+ function computeAws4Headers(opts) {
72
+ const { method, url, credentials, service, region, datetime } = opts;
73
+ const date = datetime.slice(0, 8); // YYYYMMDD
74
+ const parsedUrl = new URL(url);
75
+ const canonicalUri = parsedUrl.pathname || '/';
76
+ // Canonical query string: sort by name, encode
77
+ const queryParams = [...parsedUrl.searchParams.entries()]
78
+ .sort(([a], [b]) => a.localeCompare(b))
79
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
80
+ .join('&');
81
+ const bodyHash = sha256Hex(opts.body);
82
+ // Merge in required headers and compute canonical headers
83
+ const allHeaders = {
84
+ ...opts.headers,
85
+ host: parsedUrl.host,
86
+ 'x-amz-content-sha256': bodyHash,
87
+ 'x-amz-date': datetime,
88
+ 'x-amz-security-token': credentials.session_token,
89
+ };
90
+ const sortedHeaderKeys = Object.keys(allHeaders).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
91
+ const canonicalHeaders = sortedHeaderKeys
92
+ .map(k => `${k.toLowerCase()}:${allHeaders[k].trim()}`)
93
+ .join('\n') + '\n';
94
+ const signedHeadersList = sortedHeaderKeys.map(k => k.toLowerCase()).join(';');
95
+ const canonicalRequest = [
96
+ method.toUpperCase(),
97
+ canonicalUri,
98
+ queryParams,
99
+ canonicalHeaders,
100
+ signedHeadersList,
101
+ bodyHash,
102
+ ].join('\n');
103
+ const credentialScope = `${date}/${region}/${service}/aws4_request`;
104
+ const stringToSign = [
105
+ 'AWS4-HMAC-SHA256',
106
+ datetime,
107
+ credentialScope,
108
+ sha256Hex(canonicalRequest),
109
+ ].join('\n');
110
+ // Signing key chain
111
+ const kDate = hmacSha256(`AWS4${credentials.secret_access_key}`, date);
112
+ const kRegion = hmacSha256(kDate, region);
113
+ const kService = hmacSha256(kRegion, service);
114
+ const kSigning = hmacSha256(kService, 'aws4_request');
115
+ const signature = hmacSha256(kSigning, stringToSign).toString('hex');
116
+ const authorization = `AWS4-HMAC-SHA256 Credential=${credentials.access_key_id}/${credentialScope}, SignedHeaders=${signedHeadersList}, Signature=${signature}`;
117
+ return {
118
+ ...allHeaders,
119
+ Authorization: authorization,
120
+ };
121
+ }
122
+ // ── HTTP helpers ─────────────────────────────────────────────────────────────
123
+ async function tosRequest(opts) {
124
+ const { method, url, headers, body } = opts;
125
+ const fetchBody = body == null ? null
126
+ : typeof body === 'string' ? body
127
+ : body;
128
+ const res = await fetch(url, {
129
+ method,
130
+ headers,
131
+ body: fetchBody,
132
+ });
133
+ const responseBody = await res.text();
134
+ const responseHeaders = {};
135
+ res.headers.forEach((value, key) => {
136
+ responseHeaders[key.toLowerCase()] = value;
137
+ });
138
+ return { status: res.status, headers: responseHeaders, body: responseBody };
139
+ }
140
+ function nowDatetime() {
141
+ return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, 'Z');
142
+ }
143
+ // ── Phase 1: Init multipart upload ───────────────────────────────────────────
144
+ async function initMultipartUpload(tosUrl, auth, credentials) {
145
+ const initUrl = `${tosUrl}?uploads`;
146
+ const datetime = nowDatetime();
147
+ // Use the pre-computed auth for INIT, as it comes from ApplyVideoUpload
148
+ const headers = {
149
+ Authorization: auth,
150
+ 'x-amz-date': datetime,
151
+ 'x-amz-security-token': credentials.session_token,
152
+ 'content-type': 'application/octet-stream',
153
+ };
154
+ const res = await tosRequest({ method: 'POST', url: initUrl, headers });
155
+ if (res.status !== 200) {
156
+ throw new CommandExecutionError(`TOS init multipart upload failed with status ${res.status}: ${res.body}`, 'Check that TOS credentials are valid and not expired.');
157
+ }
158
+ // Parse UploadId from XML: <UploadId>...</UploadId>
159
+ const match = res.body.match(/<UploadId>([^<]+)<\/UploadId>/);
160
+ if (!match) {
161
+ throw new CommandExecutionError(`TOS init response missing UploadId: ${res.body}`);
162
+ }
163
+ return match[1];
164
+ }
165
+ // ── Phase 2: Upload a single part ────────────────────────────────────────────
166
+ async function uploadPart(tosUrl, partNumber, uploadId, data, credentials, region) {
167
+ const parsedUrl = new URL(tosUrl);
168
+ parsedUrl.searchParams.set('partNumber', String(partNumber));
169
+ parsedUrl.searchParams.set('uploadId', uploadId);
170
+ const url = parsedUrl.toString();
171
+ const datetime = nowDatetime();
172
+ const headers = computeAws4Headers({
173
+ method: 'PUT',
174
+ url,
175
+ headers: { 'content-type': 'application/octet-stream' },
176
+ body: data,
177
+ credentials,
178
+ service: 'tos',
179
+ region,
180
+ datetime,
181
+ });
182
+ const res = await tosRequest({ method: 'PUT', url, headers, body: data });
183
+ if (res.status !== 200) {
184
+ throw new CommandExecutionError(`TOS upload part ${partNumber} failed with status ${res.status}: ${res.body}`, 'Check that STS2 credentials are valid and not expired.');
185
+ }
186
+ const etag = res.headers['etag'];
187
+ if (!etag) {
188
+ throw new CommandExecutionError(`TOS upload part ${partNumber} response missing ETag header`);
189
+ }
190
+ return etag;
191
+ }
192
+ // ── Phase 3: Complete multipart upload ───────────────────────────────────────
193
+ async function completeMultipartUpload(tosUrl, uploadId, parts, credentials, region) {
194
+ const parsedUrl = new URL(tosUrl);
195
+ parsedUrl.searchParams.set('uploadId', uploadId);
196
+ const url = parsedUrl.toString();
197
+ const xmlBody = '<CompleteMultipartUpload>' +
198
+ parts
199
+ .sort((a, b) => a.partNumber - b.partNumber)
200
+ .map(p => `<Part><PartNumber>${p.partNumber}</PartNumber><ETag>${p.etag}</ETag></Part>`)
201
+ .join('') +
202
+ '</CompleteMultipartUpload>';
203
+ const datetime = nowDatetime();
204
+ const headers = computeAws4Headers({
205
+ method: 'POST',
206
+ url,
207
+ headers: { 'content-type': 'application/xml' },
208
+ body: xmlBody,
209
+ credentials,
210
+ service: 'tos',
211
+ region,
212
+ datetime,
213
+ });
214
+ const res = await tosRequest({
215
+ method: 'POST',
216
+ url,
217
+ headers,
218
+ body: xmlBody,
219
+ });
220
+ if (res.status !== 200) {
221
+ throw new CommandExecutionError(`TOS complete multipart upload failed with status ${res.status}: ${res.body}`, 'Check that all parts were uploaded successfully.');
222
+ }
223
+ }
224
+ let _readSyncOverride = null;
225
+ /** @internal — for testing only */
226
+ export function setReadSyncOverride(fn) {
227
+ _readSyncOverride = fn;
228
+ }
229
+ // ── Public API ───────────────────────────────────────────────────────────────
230
+ export async function tosUpload(options) {
231
+ const { filePath, uploadInfo, credentials, onProgress } = options;
232
+ // Validate file exists
233
+ if (!fs.existsSync(filePath)) {
234
+ throw new CommandExecutionError(`Video file not found: ${filePath}`, 'Ensure the file path is correct and accessible.');
235
+ }
236
+ const { size: fileSize } = fs.statSync(filePath);
237
+ if (fileSize === 0) {
238
+ throw new CommandExecutionError(`Video file is empty: ${filePath}`);
239
+ }
240
+ const { tos_upload_url: tosUrl, auth } = uploadInfo;
241
+ const parsedTosUrl = new URL(tosUrl);
242
+ const region = extractRegionFromHost(parsedTosUrl.host);
243
+ const resumePath = getResumeFilePath(filePath);
244
+ let resumeState = loadResumeState(resumePath, fileSize);
245
+ let uploadId;
246
+ let completedParts;
247
+ if (resumeState) {
248
+ // Resume from previous state
249
+ uploadId = resumeState.uploadId;
250
+ completedParts = resumeState.parts;
251
+ }
252
+ else {
253
+ // Start fresh
254
+ uploadId = await initMultipartUpload(tosUrl, auth, credentials);
255
+ completedParts = [];
256
+ saveResumeState(resumePath, { uploadId, fileSize, parts: completedParts });
257
+ }
258
+ // Determine which parts are already done
259
+ const completedPartNumbers = new Set(completedParts.map(p => p.partNumber));
260
+ // Calculate total parts
261
+ const totalParts = Math.ceil(fileSize / PART_SIZE);
262
+ // Track uploaded bytes for progress
263
+ let uploadedBytes = completedParts.length * PART_SIZE;
264
+ if (onProgress)
265
+ onProgress(Math.min(uploadedBytes, fileSize), fileSize);
266
+ const fd = fs.openSync(filePath, 'r');
267
+ try {
268
+ for (let partNumber = 1; partNumber <= totalParts; partNumber++) {
269
+ if (completedPartNumbers.has(partNumber)) {
270
+ continue; // already uploaded
271
+ }
272
+ const offset = (partNumber - 1) * PART_SIZE;
273
+ const chunkSize = Math.min(PART_SIZE, fileSize - offset);
274
+ const buffer = Buffer.allocUnsafe(chunkSize);
275
+ const readFn = _readSyncOverride ?? fs.readSync;
276
+ const bytesRead = readFn(fd, buffer, 0, chunkSize, offset);
277
+ if (bytesRead !== chunkSize) {
278
+ throw new CommandExecutionError(`Short read on part ${partNumber}: expected ${chunkSize} bytes, got ${bytesRead}`);
279
+ }
280
+ const etag = await uploadPart(tosUrl, partNumber, uploadId, buffer, credentials, region);
281
+ completedParts.push({ partNumber, etag });
282
+ saveResumeState(resumePath, { uploadId, fileSize, parts: completedParts });
283
+ uploadedBytes = Math.min(offset + chunkSize, fileSize);
284
+ if (onProgress)
285
+ onProgress(uploadedBytes, fileSize);
286
+ }
287
+ }
288
+ finally {
289
+ fs.closeSync(fd);
290
+ }
291
+ await completeMultipartUpload(tosUrl, uploadId, completedParts, credentials, region);
292
+ deleteResumeState(resumePath);
293
+ }
294
+ // ── Internal exports for testing ─────────────────────────────────────────────
295
+ export { PART_SIZE, RESUME_DIR, extractRegionFromHost, getResumeFilePath, loadResumeState, saveResumeState, deleteResumeState, computeAws4Headers, };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,229 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
+ import { PART_SIZE, computeAws4Headers, deleteResumeState, extractRegionFromHost, getResumeFilePath, loadResumeState, saveResumeState, } from './tos-upload.js';
6
+ // ── extractRegionFromHost ────────────────────────────────────────────────────
7
+ describe('extractRegionFromHost', () => {
8
+ it('extracts region from standard TOS host', () => {
9
+ expect(extractRegionFromHost('tos-cn-i-alisg.volces.com')).toBe('cn-i-alisg');
10
+ });
11
+ it('extracts region from beijing TOS host', () => {
12
+ expect(extractRegionFromHost('tos-cn-beijing.ivolces.com')).toBe('cn-beijing');
13
+ });
14
+ it('falls back to cn-north-1 for unknown host', () => {
15
+ expect(extractRegionFromHost('unknown.example.com')).toBe('cn-north-1');
16
+ });
17
+ });
18
+ // ── Part chunking ────────────────────────────────────────────────────────────
19
+ describe('PART_SIZE and part chunking logic', () => {
20
+ it('PART_SIZE is exactly 5 MB', () => {
21
+ expect(PART_SIZE).toBe(5 * 1024 * 1024);
22
+ });
23
+ it('single file smaller than PART_SIZE fits in 1 part', () => {
24
+ const fileSize = 1 * 1024 * 1024; // 1 MB
25
+ const totalParts = Math.ceil(fileSize / PART_SIZE);
26
+ expect(totalParts).toBe(1);
27
+ const lastPartSize = fileSize - (totalParts - 1) * PART_SIZE;
28
+ expect(lastPartSize).toBe(fileSize);
29
+ });
30
+ it('exactly 5 MB file produces 1 part', () => {
31
+ const fileSize = PART_SIZE;
32
+ expect(Math.ceil(fileSize / PART_SIZE)).toBe(1);
33
+ });
34
+ it('5 MB + 1 byte produces 2 parts', () => {
35
+ const fileSize = PART_SIZE + 1;
36
+ expect(Math.ceil(fileSize / PART_SIZE)).toBe(2);
37
+ });
38
+ it('100 MB file produces 20 parts of 5 MB each', () => {
39
+ const fileSize = 100 * 1024 * 1024;
40
+ const totalParts = Math.ceil(fileSize / PART_SIZE);
41
+ expect(totalParts).toBe(20);
42
+ // Each part is exactly PART_SIZE
43
+ for (let i = 1; i <= totalParts; i++) {
44
+ const offset = (i - 1) * PART_SIZE;
45
+ const chunkSize = Math.min(PART_SIZE, fileSize - offset);
46
+ expect(chunkSize).toBe(PART_SIZE);
47
+ }
48
+ });
49
+ it('101 MB file produces 21 parts, last part is 1 MB', () => {
50
+ const fileSize = 101 * 1024 * 1024;
51
+ const totalParts = Math.ceil(fileSize / PART_SIZE);
52
+ expect(totalParts).toBe(21);
53
+ const lastOffset = (totalParts - 1) * PART_SIZE;
54
+ const lastPartSize = fileSize - lastOffset;
55
+ expect(lastPartSize).toBe(1 * 1024 * 1024);
56
+ });
57
+ });
58
+ // ── Resume file serialization/deserialization ─────────────────────────────────
59
+ describe('resume state read/write', () => {
60
+ let tmpDir;
61
+ let resumePath;
62
+ beforeEach(() => {
63
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tos-upload-test-'));
64
+ resumePath = path.join(tmpDir, 'resume.json');
65
+ });
66
+ afterEach(() => {
67
+ fs.rmSync(tmpDir, { recursive: true, force: true });
68
+ });
69
+ it('saves and loads resume state correctly', () => {
70
+ const state = {
71
+ uploadId: 'test-upload-id-123',
72
+ fileSize: 12345678,
73
+ parts: [
74
+ { partNumber: 1, etag: '"abc123"' },
75
+ { partNumber: 2, etag: '"def456"' },
76
+ ],
77
+ };
78
+ saveResumeState(resumePath, state);
79
+ const loaded = loadResumeState(resumePath, 12345678);
80
+ expect(loaded).not.toBeNull();
81
+ expect(loaded.uploadId).toBe('test-upload-id-123');
82
+ expect(loaded.fileSize).toBe(12345678);
83
+ expect(loaded.parts).toHaveLength(2);
84
+ expect(loaded.parts[0]).toEqual({ partNumber: 1, etag: '"abc123"' });
85
+ expect(loaded.parts[1]).toEqual({ partNumber: 2, etag: '"def456"' });
86
+ });
87
+ it('returns null when file does not exist', () => {
88
+ const result = loadResumeState('/nonexistent/path/resume.json', 12345678);
89
+ expect(result).toBeNull();
90
+ });
91
+ it('returns null when fileSize does not match', () => {
92
+ const state = {
93
+ uploadId: 'upload-id',
94
+ fileSize: 100,
95
+ parts: [],
96
+ };
97
+ saveResumeState(resumePath, state);
98
+ // Different file size — should not resume
99
+ const result = loadResumeState(resumePath, 999);
100
+ expect(result).toBeNull();
101
+ });
102
+ it('returns null when JSON is malformed', () => {
103
+ fs.writeFileSync(resumePath, 'not-valid-json', 'utf8');
104
+ const result = loadResumeState(resumePath, 100);
105
+ expect(result).toBeNull();
106
+ });
107
+ it('returns null when uploadId is missing', () => {
108
+ const broken = { fileSize: 100, parts: [] };
109
+ fs.writeFileSync(resumePath, JSON.stringify(broken), 'utf8');
110
+ const result = loadResumeState(resumePath, 100);
111
+ expect(result).toBeNull();
112
+ });
113
+ it('deletes resume file without throwing when file exists', () => {
114
+ fs.writeFileSync(resumePath, '{}', 'utf8');
115
+ expect(() => deleteResumeState(resumePath)).not.toThrow();
116
+ expect(fs.existsSync(resumePath)).toBe(false);
117
+ });
118
+ it('deleteResumeState does not throw when file does not exist', () => {
119
+ expect(() => deleteResumeState('/nonexistent/path/resume.json')).not.toThrow();
120
+ });
121
+ it('saveResumeState creates parent directories if missing', () => {
122
+ const nestedPath = path.join(tmpDir, 'nested', 'deep', 'resume.json');
123
+ const state = { uploadId: 'x', fileSize: 0, parts: [] };
124
+ expect(() => saveResumeState(nestedPath, state)).not.toThrow();
125
+ expect(fs.existsSync(nestedPath)).toBe(true);
126
+ });
127
+ });
128
+ // ── getResumeFilePath ────────────────────────────────────────────────────────
129
+ describe('getResumeFilePath', () => {
130
+ it('returns a path inside ~/.opencli/douyin-resume/', () => {
131
+ const result = getResumeFilePath('/some/video/file.mp4');
132
+ expect(result).toContain('douyin-resume');
133
+ expect(result).toMatch(/\.json$/);
134
+ });
135
+ it('produces same path for same input', () => {
136
+ const a = getResumeFilePath('/video.mp4');
137
+ const b = getResumeFilePath('/video.mp4');
138
+ expect(a).toBe(b);
139
+ });
140
+ it('produces different paths for different inputs', () => {
141
+ const a = getResumeFilePath('/video1.mp4');
142
+ const b = getResumeFilePath('/video2.mp4');
143
+ expect(a).not.toBe(b);
144
+ });
145
+ });
146
+ // ── computeAws4Headers ───────────────────────────────────────────────────────
147
+ describe('computeAws4Headers', () => {
148
+ const mockCredentials = {
149
+ access_key_id: 'AKIAIOSFODNN7EXAMPLE',
150
+ secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
151
+ session_token: 'FQoGZXIvYXdzEJr//////////test-session-token',
152
+ expired_time: Date.now() / 1000 + 3600,
153
+ };
154
+ it('returns Authorization header', () => {
155
+ const headers = computeAws4Headers({
156
+ method: 'PUT',
157
+ url: 'https://tos-cn-i-alisg.volces.com/bucket/object?partNumber=1&uploadId=abc',
158
+ headers: { 'content-type': 'application/octet-stream' },
159
+ body: Buffer.from('hello'),
160
+ credentials: mockCredentials,
161
+ service: 'tos',
162
+ region: 'cn-i-alisg',
163
+ datetime: '20260325T120000Z',
164
+ });
165
+ expect(headers['Authorization']).toMatch(/^AWS4-HMAC-SHA256 Credential=/);
166
+ expect(headers['Authorization']).toContain('AKIAIOSFODNN7EXAMPLE/20260325/cn-i-alisg/tos/aws4_request');
167
+ expect(headers['Authorization']).toContain('SignedHeaders=');
168
+ expect(headers['Authorization']).toContain('Signature=');
169
+ });
170
+ it('includes x-amz-date header', () => {
171
+ const headers = computeAws4Headers({
172
+ method: 'PUT',
173
+ url: 'https://tos-cn-i-alisg.volces.com/bucket/object?partNumber=1&uploadId=abc',
174
+ headers: {},
175
+ body: Buffer.alloc(0),
176
+ credentials: mockCredentials,
177
+ service: 'tos',
178
+ region: 'cn-i-alisg',
179
+ datetime: '20260325T120000Z',
180
+ });
181
+ expect(headers['x-amz-date']).toBe('20260325T120000Z');
182
+ });
183
+ it('includes x-amz-security-token with session token', () => {
184
+ const headers = computeAws4Headers({
185
+ method: 'PUT',
186
+ url: 'https://tos-cn-i-alisg.volces.com/bucket/object',
187
+ headers: {},
188
+ body: '',
189
+ credentials: mockCredentials,
190
+ service: 'tos',
191
+ region: 'cn-i-alisg',
192
+ datetime: '20260325T120000Z',
193
+ });
194
+ expect(headers['x-amz-security-token']).toBe(mockCredentials.session_token);
195
+ });
196
+ it('signed headers list is sorted', () => {
197
+ const headers = computeAws4Headers({
198
+ method: 'POST',
199
+ url: 'https://tos-cn-i-alisg.volces.com/bucket/object?uploadId=abc',
200
+ headers: { 'content-type': 'application/xml' },
201
+ body: '<xml/>',
202
+ credentials: mockCredentials,
203
+ service: 'tos',
204
+ region: 'cn-i-alisg',
205
+ datetime: '20260325T120000Z',
206
+ });
207
+ const authHeader = headers['Authorization'];
208
+ const signedHeadersMatch = authHeader.match(/SignedHeaders=([^,]+)/);
209
+ expect(signedHeadersMatch).not.toBeNull();
210
+ const signedHeadersList = signedHeadersMatch[1].split(';');
211
+ const sorted = [...signedHeadersList].sort((a, b) => a.localeCompare(b));
212
+ expect(signedHeadersList).toEqual(sorted);
213
+ });
214
+ it('produces deterministic signature for same inputs', () => {
215
+ const opts = {
216
+ method: 'PUT',
217
+ url: 'https://tos-cn-i-alisg.volces.com/bucket/key?partNumber=1&uploadId=xyz',
218
+ headers: { 'content-type': 'application/octet-stream' },
219
+ body: Buffer.from('test-data'),
220
+ credentials: mockCredentials,
221
+ service: 'tos',
222
+ region: 'cn-i-alisg',
223
+ datetime: '20260325T120000Z',
224
+ };
225
+ const h1 = computeAws4Headers(opts);
226
+ const h2 = computeAws4Headers(opts);
227
+ expect(h1['Authorization']).toBe(h2['Authorization']);
228
+ });
229
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Transcode poller for Douyin video processing.
3
+ *
4
+ * After a video is uploaded via TOS and the "confirm upload" API is called,
5
+ * Douyin transcodes the video asynchronously. This module polls the transcode
6
+ * status endpoint until encode=2 (complete) or a timeout is reached.
7
+ */
8
+ import type { IPage } from '../../../types.js';
9
+ import type { TranscodeResult } from './types.js';
10
+ type BrowserFetchFn = (page: IPage, method: 'GET' | 'POST', url: string) => Promise<unknown>;
11
+ /**
12
+ * Lower-level poll function that accepts an injected fetch function.
13
+ * Exported for testability.
14
+ */
15
+ export declare function pollTranscodeWithFetch(fetchFn: BrowserFetchFn, page: IPage, videoId: string, timeoutMs?: number): Promise<TranscodeResult>;
16
+ /**
17
+ * Poll Douyin's transcode status endpoint until the video is fully transcoded
18
+ * (encode=2) or the timeout expires.
19
+ *
20
+ * @param page - Browser page for making credentialed API calls
21
+ * @param videoId - The video_id returned from the confirm upload step
22
+ * @param timeoutMs - Maximum wait time in ms (default: 300 000 = 5 minutes)
23
+ * @returns TranscodeResult including duration, fps, dimensions, and poster info
24
+ * @throws TimeoutError if transcode does not complete within timeoutMs
25
+ */
26
+ export declare function pollTranscode(page: IPage, videoId: string, timeoutMs?: number): Promise<TranscodeResult>;
27
+ export {};
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Transcode poller for Douyin video processing.
3
+ *
4
+ * After a video is uploaded via TOS and the "confirm upload" API is called,
5
+ * Douyin transcodes the video asynchronously. This module polls the transcode
6
+ * status endpoint until encode=2 (complete) or a timeout is reached.
7
+ */
8
+ import { TimeoutError } from '../../../errors.js';
9
+ import { browserFetch } from './browser-fetch.js';
10
+ const POLL_INTERVAL_MS = 3_000;
11
+ const DEFAULT_TIMEOUT_MS = 300_000;
12
+ const TRANSCODE_URL_BASE = 'https://creator.douyin.com/web/api/media/video/transend/';
13
+ /**
14
+ * Lower-level poll function that accepts an injected fetch function.
15
+ * Exported for testability.
16
+ */
17
+ export async function pollTranscodeWithFetch(fetchFn, page, videoId, timeoutMs = DEFAULT_TIMEOUT_MS) {
18
+ const url = `${TRANSCODE_URL_BASE}?video_id=${encodeURIComponent(videoId)}&aid=1128`;
19
+ const deadline = Date.now() + timeoutMs;
20
+ while (Date.now() < deadline) {
21
+ const result = (await fetchFn(page, 'GET', url));
22
+ if (result.encode === 2) {
23
+ return result;
24
+ }
25
+ // Wait before next poll, but don't exceed the deadline
26
+ const remaining = deadline - Date.now();
27
+ if (remaining <= 0)
28
+ break;
29
+ await new Promise(resolve => setTimeout(resolve, Math.min(POLL_INTERVAL_MS, remaining)));
30
+ }
31
+ throw new TimeoutError(`Douyin transcode for video ${videoId}`, Math.round(timeoutMs / 1000));
32
+ }
33
+ /**
34
+ * Poll Douyin's transcode status endpoint until the video is fully transcoded
35
+ * (encode=2) or the timeout expires.
36
+ *
37
+ * @param page - Browser page for making credentialed API calls
38
+ * @param videoId - The video_id returned from the confirm upload step
39
+ * @param timeoutMs - Maximum wait time in ms (default: 300 000 = 5 minutes)
40
+ * @returns TranscodeResult including duration, fps, dimensions, and poster info
41
+ * @throws TimeoutError if transcode does not complete within timeoutMs
42
+ */
43
+ export async function pollTranscode(page, videoId, timeoutMs) {
44
+ return pollTranscodeWithFetch(browserFetch, page, videoId, timeoutMs);
45
+ }
@@ -0,0 +1 @@
1
+ export {};