@jackwener/opencli 1.4.0 → 1.5.0

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 (514) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/workflows/build-extension.yml +2 -6
  3. package/.github/workflows/ci.yml +37 -3
  4. package/.github/workflows/e2e-headed.yml +16 -3
  5. package/CHANGELOG.md +23 -0
  6. package/PRIVACY.md +57 -0
  7. package/README.md +36 -7
  8. package/README.zh-CN.md +13 -6
  9. package/SKILL.md +103 -2
  10. package/dist/browser/cdp.d.ts +2 -1
  11. package/dist/browser/discover.d.ts +4 -1
  12. package/dist/browser/discover.js +6 -2
  13. package/dist/browser/errors.d.ts +2 -2
  14. package/dist/browser/errors.js +4 -12
  15. package/dist/browser/mcp.d.ts +2 -1
  16. package/dist/build-manifest.d.ts +2 -0
  17. package/dist/build-manifest.js +39 -14
  18. package/dist/build-manifest.test.js +21 -0
  19. package/dist/capabilityRouting.d.ts +2 -0
  20. package/dist/capabilityRouting.js +2 -1
  21. package/dist/cli-manifest.json +1838 -151
  22. package/dist/cli.js +34 -3
  23. package/dist/clis/36kr/article.d.ts +1 -0
  24. package/dist/clis/36kr/article.js +62 -0
  25. package/dist/clis/36kr/hot.d.ts +3 -0
  26. package/dist/clis/36kr/hot.js +80 -0
  27. package/dist/clis/36kr/hot.test.d.ts +1 -0
  28. package/dist/clis/36kr/hot.test.js +15 -0
  29. package/dist/clis/36kr/news.d.ts +1 -0
  30. package/dist/clis/36kr/news.js +51 -0
  31. package/dist/clis/36kr/news.test.d.ts +1 -0
  32. package/dist/clis/36kr/news.test.js +85 -0
  33. package/dist/clis/36kr/search.d.ts +1 -0
  34. package/dist/clis/36kr/search.js +72 -0
  35. package/dist/clis/apple-podcasts/search.js +2 -1
  36. package/dist/clis/arxiv/search.js +2 -2
  37. package/dist/clis/bbc/news.js +0 -1
  38. package/dist/clis/bilibili/comments.d.ts +5 -0
  39. package/dist/clis/bilibili/comments.js +40 -0
  40. package/dist/clis/bilibili/comments.test.d.ts +1 -0
  41. package/dist/clis/bilibili/comments.test.js +82 -0
  42. package/dist/clis/chatgpt/ask.js +29 -14
  43. package/dist/clis/chatgpt/ax.d.ts +6 -0
  44. package/dist/clis/chatgpt/ax.js +172 -1
  45. package/dist/clis/chatgpt/model.d.ts +1 -0
  46. package/dist/clis/chatgpt/model.js +24 -0
  47. package/dist/clis/chatgpt/send.js +12 -3
  48. package/dist/clis/ctrip/search.js +0 -1
  49. package/dist/clis/douban/download.d.ts +1 -0
  50. package/dist/clis/douban/download.js +67 -0
  51. package/dist/clis/douban/download.test.d.ts +1 -0
  52. package/dist/clis/douban/download.test.js +170 -0
  53. package/dist/clis/douban/photos.d.ts +1 -0
  54. package/dist/clis/douban/photos.js +34 -0
  55. package/dist/clis/douban/utils.d.ts +25 -0
  56. package/dist/clis/douban/utils.js +190 -1
  57. package/dist/clis/douban/utils.test.d.ts +1 -0
  58. package/dist/clis/douban/utils.test.js +64 -0
  59. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  60. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  61. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  62. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  63. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  64. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  65. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  66. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  67. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  68. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  69. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  70. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  71. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  72. package/dist/clis/douyin/_shared/sts2.js +15 -0
  73. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  74. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  75. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  76. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  77. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  78. package/dist/clis/douyin/_shared/timing.js +22 -0
  79. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  80. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  81. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  82. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  83. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  84. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  85. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  86. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  87. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  88. package/dist/clis/douyin/_shared/transcode.js +45 -0
  89. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  90. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  91. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  92. package/dist/clis/douyin/_shared/types.js +1 -0
  93. package/dist/clis/douyin/activities.d.ts +1 -0
  94. package/dist/clis/douyin/activities.js +20 -0
  95. package/dist/clis/douyin/activities.test.d.ts +1 -0
  96. package/dist/clis/douyin/activities.test.js +22 -0
  97. package/dist/clis/douyin/collections.d.ts +1 -0
  98. package/dist/clis/douyin/collections.js +22 -0
  99. package/dist/clis/douyin/collections.test.d.ts +1 -0
  100. package/dist/clis/douyin/collections.test.js +23 -0
  101. package/dist/clis/douyin/delete.d.ts +1 -0
  102. package/dist/clis/douyin/delete.js +18 -0
  103. package/dist/clis/douyin/delete.test.d.ts +1 -0
  104. package/dist/clis/douyin/delete.test.js +11 -0
  105. package/dist/clis/douyin/draft.d.ts +14 -0
  106. package/dist/clis/douyin/draft.js +237 -0
  107. package/dist/clis/douyin/draft.test.d.ts +1 -0
  108. package/dist/clis/douyin/draft.test.js +11 -0
  109. package/dist/clis/douyin/drafts.d.ts +1 -0
  110. package/dist/clis/douyin/drafts.js +23 -0
  111. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  112. package/dist/clis/douyin/drafts.test.js +11 -0
  113. package/dist/clis/douyin/hashtag.d.ts +1 -0
  114. package/dist/clis/douyin/hashtag.js +45 -0
  115. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  116. package/dist/clis/douyin/hashtag.test.js +25 -0
  117. package/dist/clis/douyin/location.d.ts +1 -0
  118. package/dist/clis/douyin/location.js +24 -0
  119. package/dist/clis/douyin/location.test.d.ts +1 -0
  120. package/dist/clis/douyin/location.test.js +23 -0
  121. package/dist/clis/douyin/profile.d.ts +1 -0
  122. package/dist/clis/douyin/profile.js +28 -0
  123. package/dist/clis/douyin/profile.test.d.ts +1 -0
  124. package/dist/clis/douyin/profile.test.js +11 -0
  125. package/dist/clis/douyin/publish.d.ts +14 -0
  126. package/dist/clis/douyin/publish.js +288 -0
  127. package/dist/clis/douyin/publish.test.d.ts +1 -0
  128. package/dist/clis/douyin/publish.test.js +38 -0
  129. package/dist/clis/douyin/stats.d.ts +1 -0
  130. package/dist/clis/douyin/stats.js +27 -0
  131. package/dist/clis/douyin/stats.test.d.ts +1 -0
  132. package/dist/clis/douyin/stats.test.js +22 -0
  133. package/dist/clis/douyin/update.d.ts +1 -0
  134. package/dist/clis/douyin/update.js +31 -0
  135. package/dist/clis/douyin/update.test.d.ts +1 -0
  136. package/dist/clis/douyin/update.test.js +11 -0
  137. package/dist/clis/douyin/videos.d.ts +1 -0
  138. package/dist/clis/douyin/videos.js +34 -0
  139. package/dist/clis/douyin/videos.test.d.ts +1 -0
  140. package/dist/clis/douyin/videos.test.js +11 -0
  141. package/dist/clis/hackernews/search.yaml +1 -1
  142. package/dist/clis/imdb/person.d.ts +1 -0
  143. package/dist/clis/imdb/person.js +203 -0
  144. package/dist/clis/imdb/reviews.d.ts +1 -0
  145. package/dist/clis/imdb/reviews.js +88 -0
  146. package/dist/clis/imdb/search.d.ts +1 -0
  147. package/dist/clis/imdb/search.js +161 -0
  148. package/dist/clis/imdb/title.d.ts +1 -0
  149. package/dist/clis/imdb/title.js +93 -0
  150. package/dist/clis/imdb/top.d.ts +1 -0
  151. package/dist/clis/imdb/top.js +53 -0
  152. package/dist/clis/imdb/trending.d.ts +1 -0
  153. package/dist/clis/imdb/trending.js +52 -0
  154. package/dist/clis/imdb/utils.d.ts +46 -0
  155. package/dist/clis/imdb/utils.js +285 -0
  156. package/dist/clis/imdb/utils.test.d.ts +1 -0
  157. package/dist/clis/imdb/utils.test.js +88 -0
  158. package/dist/clis/instagram/search.yaml +2 -1
  159. package/dist/clis/jd/item.d.ts +4 -0
  160. package/dist/clis/jd/item.js +16 -15
  161. package/dist/clis/jd/item.test.js +16 -1
  162. package/dist/clis/linux-do/categories.yaml +38 -9
  163. package/dist/clis/linux-do/category.d.ts +1 -0
  164. package/dist/clis/linux-do/category.js +36 -0
  165. package/dist/clis/linux-do/feed.d.ts +45 -0
  166. package/dist/clis/linux-do/feed.js +397 -0
  167. package/dist/clis/linux-do/feed.test.d.ts +1 -0
  168. package/dist/clis/linux-do/feed.test.js +118 -0
  169. package/dist/clis/linux-do/hot.d.ts +1 -0
  170. package/dist/clis/linux-do/hot.js +25 -0
  171. package/dist/clis/linux-do/latest.d.ts +1 -0
  172. package/dist/clis/linux-do/latest.js +18 -0
  173. package/dist/clis/linux-do/search.yaml +3 -1
  174. package/dist/clis/linux-do/tags.yaml +41 -0
  175. package/dist/clis/linux-do/topic.yaml +41 -3
  176. package/dist/clis/linux-do/user-posts.yaml +67 -0
  177. package/dist/clis/linux-do/user-topics.yaml +54 -0
  178. package/dist/clis/medium/search.js +1 -1
  179. package/dist/clis/paperreview/commands.test.d.ts +3 -0
  180. package/dist/clis/paperreview/commands.test.js +243 -0
  181. package/dist/clis/paperreview/feedback.d.ts +1 -0
  182. package/dist/clis/paperreview/feedback.js +52 -0
  183. package/dist/clis/paperreview/review.d.ts +1 -0
  184. package/dist/clis/paperreview/review.js +37 -0
  185. package/dist/clis/paperreview/submit.d.ts +1 -0
  186. package/dist/clis/paperreview/submit.js +85 -0
  187. package/dist/clis/paperreview/utils.d.ts +46 -0
  188. package/dist/clis/paperreview/utils.js +197 -0
  189. package/dist/clis/paperreview/utils.test.d.ts +1 -0
  190. package/dist/clis/paperreview/utils.test.js +49 -0
  191. package/dist/clis/producthunt/browse.d.ts +1 -0
  192. package/dist/clis/producthunt/browse.js +99 -0
  193. package/dist/clis/producthunt/hot.d.ts +1 -0
  194. package/dist/clis/producthunt/hot.js +110 -0
  195. package/dist/clis/producthunt/posts.d.ts +1 -0
  196. package/dist/clis/producthunt/posts.js +28 -0
  197. package/dist/clis/producthunt/today.d.ts +1 -0
  198. package/dist/clis/producthunt/today.js +35 -0
  199. package/dist/clis/producthunt/utils.d.ts +29 -0
  200. package/dist/clis/producthunt/utils.js +99 -0
  201. package/dist/clis/producthunt/utils.test.d.ts +1 -0
  202. package/dist/clis/producthunt/utils.test.js +64 -0
  203. package/dist/clis/reuters/search.js +0 -1
  204. package/dist/clis/twitter/article.js +4 -28
  205. package/dist/clis/twitter/likes.d.ts +24 -0
  206. package/dist/clis/twitter/likes.js +217 -0
  207. package/dist/clis/twitter/likes.test.d.ts +1 -0
  208. package/dist/clis/twitter/likes.test.js +85 -0
  209. package/dist/clis/twitter/profile.js +4 -28
  210. package/dist/clis/twitter/search.js +7 -4
  211. package/dist/clis/twitter/search.test.js +56 -2
  212. package/dist/clis/twitter/shared.d.ts +6 -0
  213. package/dist/clis/twitter/shared.js +35 -0
  214. package/dist/clis/twitter/timeline.js +2 -13
  215. package/dist/clis/weibo/comments.d.ts +1 -0
  216. package/dist/clis/weibo/comments.js +53 -0
  217. package/dist/clis/weibo/feed.d.ts +1 -0
  218. package/dist/clis/weibo/feed.js +56 -0
  219. package/dist/clis/weibo/hot.js +0 -1
  220. package/dist/clis/weibo/me.d.ts +1 -0
  221. package/dist/clis/weibo/me.js +76 -0
  222. package/dist/clis/weibo/post.d.ts +1 -0
  223. package/dist/clis/weibo/post.js +75 -0
  224. package/dist/clis/weibo/user.d.ts +1 -0
  225. package/dist/clis/weibo/user.js +63 -0
  226. package/dist/clis/weibo/utils.d.ts +6 -0
  227. package/dist/clis/weibo/utils.js +30 -0
  228. package/dist/clis/weixin/download.d.ts +17 -0
  229. package/dist/clis/weixin/download.js +88 -20
  230. package/dist/clis/weread/book.js +2 -2
  231. package/dist/clis/weread/commands.test.d.ts +3 -0
  232. package/dist/clis/weread/commands.test.js +43 -0
  233. package/dist/clis/weread/highlights.js +2 -2
  234. package/dist/clis/weread/notebooks.js +2 -2
  235. package/dist/clis/weread/notes.js +3 -3
  236. package/dist/clis/weread/search.js +3 -2
  237. package/dist/clis/weread/shelf.js +2 -2
  238. package/dist/clis/weread/utils.d.ts +4 -4
  239. package/dist/clis/weread/utils.js +32 -14
  240. package/dist/clis/weread/utils.test.js +1 -28
  241. package/dist/clis/xiaohongshu/comments.d.ts +5 -0
  242. package/dist/clis/xiaohongshu/comments.js +74 -0
  243. package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
  244. package/dist/clis/xiaohongshu/comments.test.js +79 -0
  245. package/dist/clis/xiaohongshu/publish.js +114 -18
  246. package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
  247. package/dist/clis/xiaohongshu/publish.test.js +119 -0
  248. package/dist/clis/xueqiu/search.yaml +2 -1
  249. package/dist/clis/yahoo-finance/quote.js +0 -1
  250. package/dist/clis/youtube/channel.d.ts +1 -0
  251. package/dist/clis/youtube/channel.js +150 -0
  252. package/dist/clis/youtube/comments.d.ts +1 -0
  253. package/dist/clis/youtube/comments.js +95 -0
  254. package/dist/clis/youtube/search.js +0 -1
  255. package/dist/clis/zhihu/search.yaml +2 -1
  256. package/dist/commanderAdapter.d.ts +1 -0
  257. package/dist/commanderAdapter.js +176 -29
  258. package/dist/commanderAdapter.test.d.ts +1 -0
  259. package/dist/commanderAdapter.test.js +62 -0
  260. package/dist/daemon.js +17 -1
  261. package/dist/discovery.js +8 -14
  262. package/dist/doctor.d.ts +1 -0
  263. package/dist/doctor.js +9 -2
  264. package/dist/download/index.js +63 -51
  265. package/dist/download/index.test.js +17 -4
  266. package/dist/errors.d.ts +3 -1
  267. package/dist/errors.js +15 -32
  268. package/dist/execution.d.ts +1 -3
  269. package/dist/execution.js +21 -1
  270. package/dist/external-clis.yaml +0 -17
  271. package/dist/hooks.js +2 -0
  272. package/dist/main.js +5 -0
  273. package/dist/output.js +5 -1
  274. package/dist/pipeline/executor.js +3 -4
  275. package/dist/plugin-manifest.d.ts +70 -0
  276. package/dist/plugin-manifest.js +160 -0
  277. package/dist/plugin-manifest.test.d.ts +4 -0
  278. package/dist/plugin-manifest.test.js +179 -0
  279. package/dist/plugin.d.ts +38 -5
  280. package/dist/plugin.js +267 -33
  281. package/dist/plugin.test.js +220 -3
  282. package/dist/registry.d.ts +4 -0
  283. package/dist/registry.js +2 -0
  284. package/dist/runtime-detect.d.ts +21 -0
  285. package/dist/runtime-detect.js +32 -0
  286. package/dist/runtime-detect.test.d.ts +1 -0
  287. package/dist/runtime-detect.test.js +27 -0
  288. package/dist/runtime.js +1 -1
  289. package/dist/serialization.d.ts +2 -0
  290. package/dist/serialization.js +6 -0
  291. package/dist/types.d.ts +1 -0
  292. package/dist/update-check.d.ts +22 -0
  293. package/dist/update-check.js +112 -0
  294. package/dist/weixin-download.test.d.ts +1 -0
  295. package/dist/weixin-download.test.js +30 -0
  296. package/dist/weread-private-api-regression.test.d.ts +1 -0
  297. package/dist/weread-private-api-regression.test.js +122 -0
  298. package/dist/weread-search-regression.test.d.ts +1 -0
  299. package/dist/weread-search-regression.test.js +39 -0
  300. package/dist/yaml-schema.d.ts +3 -0
  301. package/dist/yaml-schema.js +18 -1
  302. package/docs/.vitepress/config.mts +17 -0
  303. package/docs/adapters/browser/36kr.md +47 -0
  304. package/docs/adapters/browser/douban.md +14 -0
  305. package/docs/adapters/browser/douyin.md +75 -0
  306. package/docs/adapters/browser/imdb.md +47 -0
  307. package/docs/adapters/browser/jd.md +2 -2
  308. package/docs/adapters/browser/linux-do.md +181 -20
  309. package/docs/adapters/browser/paperreview.md +43 -0
  310. package/docs/adapters/browser/producthunt.md +49 -0
  311. package/docs/adapters/browser/twitter.md +6 -0
  312. package/docs/adapters/desktop/chatgpt.md +5 -0
  313. package/docs/adapters/index.md +12 -3
  314. package/docs/advanced/download.md +4 -0
  315. package/docs/advanced/rate-limiter-plugin.md +99 -0
  316. package/docs/guide/electron-app-cli.md +200 -0
  317. package/docs/guide/getting-started.md +1 -0
  318. package/docs/guide/plugins.md +87 -0
  319. package/docs/zh/guide/electron-app-cli.md +188 -0
  320. package/docs/zh/guide/getting-started.md +1 -0
  321. package/docs/zh/guide/plugins.md +65 -0
  322. package/extension/dist/background.js +508 -518
  323. package/extension/manifest.json +6 -2
  324. package/extension/package.json +2 -1
  325. package/extension/popup.html +84 -0
  326. package/extension/popup.js +25 -0
  327. package/extension/scripts/package-release.mjs +179 -0
  328. package/extension/src/background.ts +22 -1
  329. package/package.json +4 -1
  330. package/scripts/postinstall.js +10 -0
  331. package/src/browser/cdp.ts +2 -1
  332. package/src/browser/discover.ts +8 -3
  333. package/src/browser/errors.ts +13 -14
  334. package/src/browser/mcp.ts +2 -1
  335. package/src/build-manifest.test.ts +23 -0
  336. package/src/build-manifest.ts +40 -15
  337. package/src/capabilityRouting.ts +2 -1
  338. package/src/cli.ts +35 -3
  339. package/src/clis/36kr/article.ts +69 -0
  340. package/src/clis/36kr/hot.test.ts +19 -0
  341. package/src/clis/36kr/hot.ts +100 -0
  342. package/src/clis/36kr/news.test.ts +90 -0
  343. package/src/clis/36kr/news.ts +54 -0
  344. package/src/clis/36kr/search.ts +78 -0
  345. package/src/clis/apple-podcasts/search.ts +2 -1
  346. package/src/clis/arxiv/search.ts +2 -2
  347. package/src/clis/bbc/news.ts +0 -1
  348. package/src/clis/bilibili/comments.test.ts +102 -0
  349. package/src/clis/bilibili/comments.ts +44 -0
  350. package/src/clis/chatgpt/ask.ts +28 -14
  351. package/src/clis/chatgpt/ax.ts +180 -1
  352. package/src/clis/chatgpt/model.ts +27 -0
  353. package/src/clis/chatgpt/send.ts +16 -6
  354. package/src/clis/ctrip/search.ts +0 -1
  355. package/src/clis/douban/download.test.ts +196 -0
  356. package/src/clis/douban/download.ts +78 -0
  357. package/src/clis/douban/photos.ts +36 -0
  358. package/src/clis/douban/utils.test.ts +97 -0
  359. package/src/clis/douban/utils.ts +232 -1
  360. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  361. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  362. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  363. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  364. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  365. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  366. package/src/clis/douyin/_shared/sts2.ts +20 -0
  367. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  368. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  369. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  370. package/src/clis/douyin/_shared/timing.ts +22 -0
  371. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  372. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  373. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  374. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  375. package/src/clis/douyin/_shared/transcode.ts +78 -0
  376. package/src/clis/douyin/_shared/types.ts +29 -0
  377. package/src/clis/douyin/activities.test.ts +25 -0
  378. package/src/clis/douyin/activities.ts +23 -0
  379. package/src/clis/douyin/collections.test.ts +26 -0
  380. package/src/clis/douyin/collections.ts +25 -0
  381. package/src/clis/douyin/delete.test.ts +12 -0
  382. package/src/clis/douyin/delete.ts +20 -0
  383. package/src/clis/douyin/draft.test.ts +12 -0
  384. package/src/clis/douyin/draft.ts +282 -0
  385. package/src/clis/douyin/drafts.test.ts +12 -0
  386. package/src/clis/douyin/drafts.ts +27 -0
  387. package/src/clis/douyin/hashtag.test.ts +28 -0
  388. package/src/clis/douyin/hashtag.ts +56 -0
  389. package/src/clis/douyin/location.test.ts +26 -0
  390. package/src/clis/douyin/location.ts +27 -0
  391. package/src/clis/douyin/profile.test.ts +12 -0
  392. package/src/clis/douyin/profile.ts +37 -0
  393. package/src/clis/douyin/publish.test.ts +45 -0
  394. package/src/clis/douyin/publish.ts +340 -0
  395. package/src/clis/douyin/stats.test.ts +25 -0
  396. package/src/clis/douyin/stats.ts +30 -0
  397. package/src/clis/douyin/update.test.ts +12 -0
  398. package/src/clis/douyin/update.ts +43 -0
  399. package/src/clis/douyin/videos.test.ts +12 -0
  400. package/src/clis/douyin/videos.ts +49 -0
  401. package/src/clis/hackernews/search.yaml +1 -1
  402. package/src/clis/imdb/person.ts +232 -0
  403. package/src/clis/imdb/reviews.ts +111 -0
  404. package/src/clis/imdb/search.ts +179 -0
  405. package/src/clis/imdb/title.ts +121 -0
  406. package/src/clis/imdb/top.ts +67 -0
  407. package/src/clis/imdb/trending.ts +66 -0
  408. package/src/clis/imdb/utils.test.ts +117 -0
  409. package/src/clis/imdb/utils.ts +305 -0
  410. package/src/clis/instagram/search.yaml +2 -1
  411. package/src/clis/jd/item.test.ts +18 -1
  412. package/src/clis/jd/item.ts +18 -15
  413. package/src/clis/linux-do/categories.yaml +38 -9
  414. package/src/clis/linux-do/category.ts +37 -0
  415. package/src/clis/linux-do/feed.test.ts +132 -0
  416. package/src/clis/linux-do/feed.ts +501 -0
  417. package/src/clis/linux-do/hot.ts +26 -0
  418. package/src/clis/linux-do/latest.ts +19 -0
  419. package/src/clis/linux-do/search.yaml +3 -1
  420. package/src/clis/linux-do/tags.yaml +41 -0
  421. package/src/clis/linux-do/topic.yaml +41 -3
  422. package/src/clis/linux-do/user-posts.yaml +67 -0
  423. package/src/clis/linux-do/user-topics.yaml +54 -0
  424. package/src/clis/medium/search.ts +1 -1
  425. package/src/clis/paperreview/commands.test.ts +283 -0
  426. package/src/clis/paperreview/feedback.ts +64 -0
  427. package/src/clis/paperreview/review.ts +47 -0
  428. package/src/clis/paperreview/submit.ts +119 -0
  429. package/src/clis/paperreview/utils.test.ts +68 -0
  430. package/src/clis/paperreview/utils.ts +276 -0
  431. package/src/clis/producthunt/browse.ts +109 -0
  432. package/src/clis/producthunt/hot.ts +127 -0
  433. package/src/clis/producthunt/posts.ts +29 -0
  434. package/src/clis/producthunt/today.ts +37 -0
  435. package/src/clis/producthunt/utils.test.ts +72 -0
  436. package/src/clis/producthunt/utils.ts +122 -0
  437. package/src/clis/reuters/search.ts +0 -1
  438. package/src/clis/twitter/article.ts +5 -28
  439. package/src/clis/twitter/likes.test.ts +91 -0
  440. package/src/clis/twitter/likes.ts +256 -0
  441. package/src/clis/twitter/profile.ts +5 -28
  442. package/src/clis/twitter/search.test.ts +71 -2
  443. package/src/clis/twitter/search.ts +8 -4
  444. package/src/clis/twitter/shared.ts +45 -0
  445. package/src/clis/twitter/timeline.ts +2 -13
  446. package/src/clis/weibo/comments.ts +54 -0
  447. package/src/clis/weibo/feed.ts +57 -0
  448. package/src/clis/weibo/hot.ts +0 -1
  449. package/src/clis/weibo/me.ts +77 -0
  450. package/src/clis/weibo/post.ts +77 -0
  451. package/src/clis/weibo/user.ts +64 -0
  452. package/src/clis/weibo/utils.ts +32 -0
  453. package/src/clis/weixin/download.ts +114 -20
  454. package/src/clis/weread/book.ts +2 -2
  455. package/src/clis/weread/commands.test.ts +57 -0
  456. package/src/clis/weread/highlights.ts +2 -2
  457. package/src/clis/weread/notebooks.ts +2 -2
  458. package/src/clis/weread/notes.ts +3 -3
  459. package/src/clis/weread/search.ts +3 -2
  460. package/src/clis/weread/shelf.ts +2 -2
  461. package/src/clis/weread/utils.test.ts +1 -32
  462. package/src/clis/weread/utils.ts +41 -16
  463. package/src/clis/xiaohongshu/comments.test.ts +96 -0
  464. package/src/clis/xiaohongshu/comments.ts +81 -0
  465. package/src/clis/xiaohongshu/publish.test.ts +137 -0
  466. package/src/clis/xiaohongshu/publish.ts +129 -18
  467. package/src/clis/xueqiu/search.yaml +2 -1
  468. package/src/clis/yahoo-finance/quote.ts +0 -1
  469. package/src/clis/youtube/channel.ts +155 -0
  470. package/src/clis/youtube/comments.ts +97 -0
  471. package/src/clis/youtube/search.ts +0 -1
  472. package/src/clis/zhihu/search.yaml +2 -1
  473. package/src/commanderAdapter.test.ts +78 -0
  474. package/src/commanderAdapter.ts +188 -24
  475. package/src/daemon.ts +19 -1
  476. package/src/discovery.ts +8 -15
  477. package/src/doctor.ts +13 -2
  478. package/src/download/index.test.ts +14 -4
  479. package/src/download/index.ts +67 -55
  480. package/src/errors.ts +25 -66
  481. package/src/execution.ts +28 -3
  482. package/src/external-clis.yaml +0 -17
  483. package/src/hooks.ts +1 -0
  484. package/src/main.ts +6 -0
  485. package/src/output.ts +3 -1
  486. package/src/pipeline/executor.ts +4 -6
  487. package/src/plugin-manifest.test.ts +223 -0
  488. package/src/plugin-manifest.ts +206 -0
  489. package/src/plugin.test.ts +246 -2
  490. package/src/plugin.ts +338 -36
  491. package/src/registry.ts +6 -1
  492. package/src/runtime-detect.test.ts +30 -0
  493. package/src/runtime-detect.ts +36 -0
  494. package/src/runtime.ts +1 -1
  495. package/src/serialization.ts +4 -0
  496. package/src/types.ts +1 -0
  497. package/src/update-check.ts +114 -0
  498. package/src/weixin-download.test.ts +64 -0
  499. package/src/weread-private-api-regression.test.ts +150 -0
  500. package/src/weread-search-regression.test.ts +44 -0
  501. package/src/yaml-schema.ts +20 -0
  502. package/tests/e2e/browser-auth.test.ts +13 -9
  503. package/tests/e2e/browser-public-extended.test.ts +162 -0
  504. package/tests/e2e/browser-public.test.ts +55 -136
  505. package/tests/e2e/helpers.ts +2 -1
  506. package/tests/e2e/public-commands.test.ts +37 -3
  507. package/tests/smoke/api-health.test.ts +1 -1
  508. package/vitest.config.ts +34 -17
  509. package/dist/clis/linux-do/category.yaml +0 -51
  510. package/dist/clis/linux-do/hot.yaml +0 -50
  511. package/dist/clis/linux-do/latest.yaml +0 -40
  512. package/src/clis/linux-do/category.yaml +0 -51
  513. package/src/clis/linux-do/hot.yaml +0 -50
  514. package/src/clis/linux-do/latest.yaml +0 -40
@@ -0,0 +1,22 @@
1
+ const MIN_OFFSET = 7200; // 2 hours
2
+ const MAX_OFFSET = 14 * 86400; // 14 days
3
+ export function validateTiming(unixSeconds) {
4
+ if (!Number.isFinite(unixSeconds))
5
+ throw new Error(`无效的时间戳: ${unixSeconds}`);
6
+ const now = Math.floor(Date.now() / 1000);
7
+ if (unixSeconds < now + MIN_OFFSET)
8
+ throw new Error(`定时发布时间必须在至少 2 小时后`);
9
+ if (unixSeconds > now + MAX_OFFSET)
10
+ throw new Error(`定时发布时间不能超过 14 天`);
11
+ }
12
+ export function toUnixSeconds(input) {
13
+ if (typeof input === 'number')
14
+ return input;
15
+ if (/^\d+$/.test(input)) {
16
+ return Number(input);
17
+ }
18
+ const ms = new Date(input).getTime();
19
+ if (isNaN(ms))
20
+ throw new Error(`无效的时间格式: "${input}"`);
21
+ return Math.floor(ms / 1000);
22
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { validateTiming, toUnixSeconds } from './timing.js';
3
+ describe('validateTiming', () => {
4
+ const now = () => Math.floor(Date.now() / 1000);
5
+ it('accepts a time 3 hours from now', () => {
6
+ expect(() => validateTiming(now() + 3 * 3600)).not.toThrow();
7
+ });
8
+ it('rejects a time less than 2 hours from now', () => {
9
+ expect(() => validateTiming(now() + 3600)).toThrow('至少 2 小时后');
10
+ });
11
+ it('rejects a time more than 14 days from now', () => {
12
+ expect(() => validateTiming(now() + 15 * 86400)).toThrow('不能超过 14 天');
13
+ });
14
+ });
15
+ describe('toUnixSeconds', () => {
16
+ it('passes through a numeric unix timestamp', () => {
17
+ expect(toUnixSeconds(1744070400)).toBe(1744070400);
18
+ });
19
+ it('parses a numeric unix timestamp string', () => {
20
+ expect(toUnixSeconds('1744070400')).toBe(1744070400);
21
+ });
22
+ it('parses ISO8601 string', () => {
23
+ expect(toUnixSeconds('2026-04-08T12:00:00Z')).toBe(Math.floor(new Date('2026-04-08T12:00:00Z').getTime() / 1000));
24
+ });
25
+ it('throws on invalid input', () => {
26
+ expect(() => toUnixSeconds('not-a-date')).toThrow();
27
+ });
28
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Tests for the fs.readSync short-read guard in tosUpload.
3
+ *
4
+ * This file is separate from tos-upload.test.ts because vi.mock is hoisted and
5
+ * would interfere with the real-fs tests there.
6
+ *
7
+ * Strategy:
8
+ * - Use setReadSyncOverride (exported testing seam) to force readSync to return 0
9
+ * - Mock global fetch to satisfy initMultipartUpload so the code path reaches readSync
10
+ */
11
+ export {};
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Tests for the fs.readSync short-read guard in tosUpload.
3
+ *
4
+ * This file is separate from tos-upload.test.ts because vi.mock is hoisted and
5
+ * would interfere with the real-fs tests there.
6
+ *
7
+ * Strategy:
8
+ * - Use setReadSyncOverride (exported testing seam) to force readSync to return 0
9
+ * - Mock global fetch to satisfy initMultipartUpload so the code path reaches readSync
10
+ */
11
+ import * as actualFs from 'node:fs';
12
+ import * as os from 'node:os';
13
+ import * as path from 'node:path';
14
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
15
+ import { CommandExecutionError } from '../../../errors.js';
16
+ import { setReadSyncOverride, tosUpload } from './tos-upload.js';
17
+ /** Build a minimal fetch mock that satisfies initMultipartUpload (POST ?uploads → 200 + UploadId XML). */
18
+ function makeFetchMock() {
19
+ return vi.fn().mockResolvedValue({
20
+ status: 200,
21
+ text: async () => '<InitiateMultipartUploadResult><UploadId>mock-upload-id</UploadId></InitiateMultipartUploadResult>',
22
+ headers: { forEach: (_cb) => { } },
23
+ });
24
+ }
25
+ describe('tosUpload short-read guard', () => {
26
+ let tmpDir;
27
+ let tmpFile;
28
+ let originalFetch;
29
+ beforeEach(() => {
30
+ originalFetch = globalThis.fetch;
31
+ tmpDir = actualFs.mkdtempSync(path.join(os.tmpdir(), 'tos-upload-shortread-'));
32
+ tmpFile = path.join(tmpDir, 'video.mp4');
33
+ // 100-byte file — fits in a single part
34
+ actualFs.writeFileSync(tmpFile, Buffer.alloc(100, 0xff));
35
+ });
36
+ afterEach(() => {
37
+ setReadSyncOverride(null);
38
+ globalThis.fetch = originalFetch;
39
+ actualFs.rmSync(tmpDir, { recursive: true, force: true });
40
+ });
41
+ it('throws CommandExecutionError on short read', async () => {
42
+ // Mock fetch so initMultipartUpload succeeds and code reaches readSync
43
+ globalThis.fetch = makeFetchMock();
44
+ // Override readSync to return 0 (fewer bytes than requested)
45
+ setReadSyncOverride(() => 0);
46
+ const mockCredentials = {
47
+ access_key_id: 'AKIAIOSFODNN7EXAMPLE',
48
+ secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
49
+ session_token: 'test-session-token',
50
+ expired_time: Date.now() / 1000 + 3600,
51
+ };
52
+ const uploadInfo = {
53
+ tos_upload_url: 'https://tos-cn-i-alisg.volces.com/bucket/key',
54
+ auth: 'AWS4-HMAC-SHA256 Credential=test',
55
+ video_id: 'test-video-id',
56
+ };
57
+ await expect(tosUpload({
58
+ filePath: tmpFile,
59
+ uploadInfo,
60
+ credentials: mockCredentials,
61
+ })).rejects.toThrow(CommandExecutionError);
62
+ });
63
+ it('error message identifies the part number and byte counts', async () => {
64
+ globalThis.fetch = makeFetchMock();
65
+ setReadSyncOverride(() => 0);
66
+ const mockCredentials = {
67
+ access_key_id: 'AKIAIOSFODNN7EXAMPLE',
68
+ secret_access_key: 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
69
+ session_token: 'test-session-token',
70
+ expired_time: Date.now() / 1000 + 3600,
71
+ };
72
+ const uploadInfo = {
73
+ tos_upload_url: 'https://tos-cn-i-alisg.volces.com/bucket/key',
74
+ auth: 'AWS4-HMAC-SHA256 Credential=test',
75
+ video_id: 'test-video-id',
76
+ };
77
+ await expect(tosUpload({
78
+ filePath: tmpFile,
79
+ uploadInfo,
80
+ credentials: mockCredentials,
81
+ })).rejects.toThrow(/Short read on part 1: expected 100 bytes, got 0/);
82
+ });
83
+ });
@@ -0,0 +1,53 @@
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 type { Sts2Credentials, TosUploadInfo } from './types.js';
9
+ export interface TosUploadOptions {
10
+ filePath: string;
11
+ uploadInfo: TosUploadInfo;
12
+ credentials: Sts2Credentials;
13
+ onProgress?: (uploaded: number, total: number) => void;
14
+ }
15
+ interface ResumePart {
16
+ partNumber: number;
17
+ etag: string;
18
+ }
19
+ interface ResumeState {
20
+ uploadId: string;
21
+ fileSize: number;
22
+ parts: ResumePart[];
23
+ }
24
+ declare const PART_SIZE: number;
25
+ declare const RESUME_DIR: string;
26
+ declare function getResumeFilePath(filePath: string): string;
27
+ declare function loadResumeState(resumePath: string, fileSize: number): ResumeState | null;
28
+ declare function saveResumeState(resumePath: string, state: ResumeState): void;
29
+ declare function deleteResumeState(resumePath: string): void;
30
+ declare function extractRegionFromHost(host: string): string;
31
+ interface SignedHeaders {
32
+ [key: string]: string;
33
+ }
34
+ /**
35
+ * Compute AWS Signature V4 headers for a TOS request.
36
+ * Returns a Record of all headers to include (including Authorization, x-amz-date, etc.)
37
+ */
38
+ declare function computeAws4Headers(opts: {
39
+ method: string;
40
+ url: string;
41
+ headers: Record<string, string>;
42
+ body: Buffer | string;
43
+ credentials: Sts2Credentials;
44
+ service: string;
45
+ region: string;
46
+ datetime: string;
47
+ }): SignedHeaders;
48
+ type ReadSyncFn = (fd: number, buffer: Buffer, offset: number, length: number, position: number) => number;
49
+ /** @internal — for testing only */
50
+ export declare function setReadSyncOverride(fn: ReadSyncFn | null): void;
51
+ export declare function tosUpload(options: TosUploadOptions): Promise<void>;
52
+ export { PART_SIZE, RESUME_DIR, extractRegionFromHost, getResumeFilePath, loadResumeState, saveResumeState, deleteResumeState, computeAws4Headers, };
53
+ export type { ResumeState, ResumePart };
@@ -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 {};