@jackwener/opencli 1.7.12 → 1.7.14

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 (419) hide show
  1. package/README.md +8 -7
  2. package/README.zh-CN.md +9 -8
  3. package/cli-manifest.json +12128 -6665
  4. package/clis/1point3acres/digest.js +35 -0
  5. package/clis/1point3acres/forum.js +51 -0
  6. package/clis/1point3acres/forums.js +44 -0
  7. package/clis/1point3acres/hot.js +35 -0
  8. package/clis/1point3acres/latest.js +35 -0
  9. package/clis/1point3acres/notifications.js +64 -0
  10. package/clis/1point3acres/search.js +71 -0
  11. package/clis/1point3acres/thread.js +117 -0
  12. package/clis/1point3acres/user.js +77 -0
  13. package/clis/1point3acres/utils.js +247 -0
  14. package/clis/_shared/desktop-commands.js +4 -0
  15. package/clis/aibase/news.js +110 -0
  16. package/clis/aibase/news.test.js +59 -0
  17. package/clis/amazon/discussion.test.js +1 -28
  18. package/clis/antigravity/watch.js +3 -2
  19. package/clis/arxiv/author.js +44 -0
  20. package/clis/baidu-scholar/search.js +0 -1
  21. package/clis/bbc/topic.js +57 -0
  22. package/clis/bbc/utils.js +79 -0
  23. package/clis/chaoxing/assignments.js +1 -1
  24. package/clis/chaoxing/exams.js +1 -1
  25. package/clis/chatgpt/ask.js +57 -0
  26. package/clis/chatgpt/commands.test.js +45 -0
  27. package/clis/chatgpt/detail.js +46 -0
  28. package/clis/chatgpt/history.js +39 -0
  29. package/clis/chatgpt/image.js +12 -11
  30. package/clis/chatgpt/image.test.js +23 -0
  31. package/clis/chatgpt/new.js +25 -0
  32. package/clis/chatgpt/read.js +43 -0
  33. package/clis/chatgpt/send.js +46 -0
  34. package/clis/chatgpt/status.js +29 -0
  35. package/clis/chatgpt/utils.js +294 -4
  36. package/clis/chatgpt/utils.test.js +13 -0
  37. package/clis/chatgpt-app/ask.js +6 -3
  38. package/clis/chatwise/ask.js +16 -43
  39. package/clis/chatwise/composer.test.js +186 -0
  40. package/clis/chatwise/send.js +2 -24
  41. package/clis/chatwise/utils.js +143 -0
  42. package/clis/claude/ask.js +1 -1
  43. package/clis/claude/detail.js +1 -0
  44. package/clis/claude/history.js +1 -0
  45. package/clis/claude/new.js +1 -0
  46. package/clis/claude/read.js +1 -0
  47. package/clis/claude/send.js +1 -0
  48. package/clis/claude/status.js +1 -0
  49. package/clis/codex/ask.js +15 -9
  50. package/clis/codex/history.js +16 -33
  51. package/clis/codex/projects.js +28 -0
  52. package/clis/codex/read.js +10 -4
  53. package/clis/codex/send.js +10 -3
  54. package/clis/codex/sidebar.js +356 -0
  55. package/clis/codex/sidebar.test.js +329 -0
  56. package/clis/coingecko/categories.js +75 -0
  57. package/clis/coingecko/coin.js +107 -0
  58. package/clis/coingecko/coingecko.test.js +109 -0
  59. package/clis/coingecko/derivatives.js +84 -0
  60. package/clis/coingecko/exchanges.js +74 -0
  61. package/clis/coingecko/global.js +71 -0
  62. package/clis/coingecko/top.js +64 -0
  63. package/clis/coingecko/trending.js +55 -0
  64. package/clis/coupang/add-to-cart.js +21 -13
  65. package/clis/coupang/coupang.test.js +159 -0
  66. package/clis/coupang/product.js +257 -0
  67. package/clis/coupang/search.js +38 -16
  68. package/clis/coupang/utils.js +55 -1
  69. package/clis/crates/crate.js +62 -0
  70. package/clis/crates/search.js +44 -0
  71. package/clis/crates/utils.js +72 -0
  72. package/clis/ctrip/ctrip.test.js +234 -0
  73. package/clis/ctrip/hotel-suggest.js +45 -0
  74. package/clis/ctrip/search.js +22 -68
  75. package/clis/ctrip/utils.js +175 -0
  76. package/clis/cursor/ask.js +6 -3
  77. package/clis/dblp/author.js +133 -0
  78. package/clis/dblp/venue.js +64 -0
  79. package/clis/deepseek/ask.js +12 -7
  80. package/clis/deepseek/ask.test.js +13 -13
  81. package/clis/deepseek/detail.js +38 -0
  82. package/clis/deepseek/detail.test.js +81 -0
  83. package/clis/deepseek/history.js +1 -0
  84. package/clis/deepseek/new.js +1 -0
  85. package/clis/deepseek/read.js +1 -0
  86. package/clis/deepseek/send.js +140 -0
  87. package/clis/deepseek/send.test.js +107 -0
  88. package/clis/deepseek/status.js +1 -0
  89. package/clis/deepseek/utils.js +66 -0
  90. package/clis/deepseek/utils.test.js +107 -1
  91. package/clis/defillama/defillama.test.js +99 -0
  92. package/clis/defillama/protocol.js +84 -0
  93. package/clis/defillama/protocols.js +55 -0
  94. package/clis/defillama/utils.js +99 -0
  95. package/clis/devto/latest.js +74 -0
  96. package/clis/dockerhub/image.js +52 -0
  97. package/clis/dockerhub/search.js +47 -0
  98. package/clis/dockerhub/utils.js +100 -0
  99. package/clis/doubao/ask.js +7 -3
  100. package/clis/doubao/detail.js +1 -0
  101. package/clis/doubao/history.js +1 -0
  102. package/clis/doubao/meeting-summary.js +1 -0
  103. package/clis/doubao/meeting-transcript.js +1 -0
  104. package/clis/doubao/new.js +1 -0
  105. package/clis/doubao/read.js +1 -0
  106. package/clis/doubao/send.js +1 -0
  107. package/clis/doubao/status.js +1 -0
  108. package/clis/douyin/draft.test.js +1 -30
  109. package/clis/endoflife/endoflife.test.js +51 -0
  110. package/clis/endoflife/product.js +55 -0
  111. package/clis/endoflife/utils.js +89 -0
  112. package/clis/facebook/__fixtures__/notifications-page.html +13 -0
  113. package/clis/facebook/notifications.js +326 -30
  114. package/clis/facebook/notifications.test.js +458 -0
  115. package/clis/flathub/app.js +71 -0
  116. package/clis/flathub/flathub.test.js +90 -0
  117. package/clis/flathub/search.js +80 -0
  118. package/clis/flathub/utils.js +114 -0
  119. package/clis/gemini/ask.js +7 -3
  120. package/clis/gemini/ask.test.js +2 -2
  121. package/clis/gemini/deep-research-result.js +6 -2
  122. package/clis/gemini/deep-research-result.test.js +15 -14
  123. package/clis/gemini/deep-research.js +8 -4
  124. package/clis/gemini/deep-research.test.js +15 -18
  125. package/clis/gemini/image.js +7 -2
  126. package/clis/gemini/new.js +1 -0
  127. package/clis/gemini/utils.js +0 -4
  128. package/clis/google-scholar/cite.js +0 -1
  129. package/clis/google-scholar/profile.js +0 -1
  130. package/clis/google-scholar/search.js +0 -1
  131. package/clis/goproxy/goproxy.test.js +103 -0
  132. package/clis/goproxy/module.js +47 -0
  133. package/clis/goproxy/utils.js +165 -0
  134. package/clis/goproxy/versions.js +59 -0
  135. package/clis/gov-law/recent.js +0 -1
  136. package/clis/gov-law/search.js +0 -1
  137. package/clis/gov-policy/__fixtures__/recent.html +16 -0
  138. package/clis/gov-policy/__fixtures__/search.html +41 -0
  139. package/clis/gov-policy/gov-policy.test.js +224 -0
  140. package/clis/gov-policy/recent.js +66 -24
  141. package/clis/gov-policy/search.js +65 -23
  142. package/clis/gov-policy/utils.js +54 -0
  143. package/clis/grok/ask.js +49 -265
  144. package/clis/grok/ask.test.js +21 -46
  145. package/clis/grok/detail.js +60 -0
  146. package/clis/grok/history.js +48 -0
  147. package/clis/grok/{image.ts → image.js} +56 -70
  148. package/clis/grok/image.test.ts +20 -0
  149. package/clis/grok/new.js +20 -0
  150. package/clis/grok/read.js +39 -0
  151. package/clis/grok/send.js +50 -0
  152. package/clis/grok/status.js +41 -0
  153. package/clis/grok/utils.js +326 -0
  154. package/clis/grok/utils.test.js +103 -0
  155. package/clis/hf/datasets.js +88 -0
  156. package/clis/hf/hf.test.js +16 -0
  157. package/clis/hf/models.js +91 -0
  158. package/clis/hf/paper.js +79 -0
  159. package/clis/hf/spaces.js +101 -0
  160. package/clis/hf/top.js +1 -0
  161. package/clis/homebrew/cask.js +39 -0
  162. package/clis/homebrew/formula.js +41 -0
  163. package/clis/homebrew/popular.js +54 -0
  164. package/clis/homebrew/utils.js +100 -0
  165. package/clis/hupu/__fixtures__/hot-home.html +64 -0
  166. package/clis/hupu/detail.js +0 -1
  167. package/clis/hupu/hot.js +156 -35
  168. package/clis/hupu/hot.test.js +224 -0
  169. package/clis/hupu/search.js +0 -1
  170. package/clis/instagram/note.js +1 -1
  171. package/clis/instagram/note.test.js +1 -29
  172. package/clis/instagram/post.js +1 -1
  173. package/clis/instagram/post.test.js +1 -1
  174. package/clis/instagram/reel.js +1 -1
  175. package/clis/instagram/story.js +1 -1
  176. package/clis/instagram/story.test.js +1 -34
  177. package/clis/jd/commands.test.js +1 -24
  178. package/clis/lichess/lichess.test.js +85 -0
  179. package/clis/lichess/top.js +46 -0
  180. package/clis/lichess/user.js +91 -0
  181. package/clis/lichess/utils.js +97 -0
  182. package/clis/linkedin/search.js +107 -10
  183. package/clis/linkedin/search.test.js +222 -0
  184. package/clis/linux-do/feed.js +2 -5
  185. package/clis/linux-do/feed.test.js +35 -0
  186. package/clis/lobsters/domain.js +92 -0
  187. package/clis/maven/artifact.js +49 -0
  188. package/clis/maven/search.js +51 -0
  189. package/clis/maven/utils.js +110 -0
  190. package/clis/mdn/search.js +97 -0
  191. package/clis/medium/tag.js +135 -0
  192. package/clis/npm/downloads.js +59 -0
  193. package/clis/npm/package.js +70 -0
  194. package/clis/npm/search.js +49 -0
  195. package/clis/npm/utils.js +76 -0
  196. package/clis/nuget/nuget.test.js +111 -0
  197. package/clis/nuget/package.js +101 -0
  198. package/clis/nuget/search.js +69 -0
  199. package/clis/nuget/utils.js +87 -0
  200. package/clis/nvd/cve.js +121 -0
  201. package/clis/oeis/oeis.test.js +88 -0
  202. package/clis/oeis/search.js +63 -0
  203. package/clis/oeis/sequence.js +71 -0
  204. package/clis/oeis/utils.js +88 -0
  205. package/clis/openalex/search.js +69 -0
  206. package/clis/openalex/utils.js +160 -0
  207. package/clis/openalex/work.js +65 -0
  208. package/clis/openfda/drug-label.js +74 -0
  209. package/clis/openfda/food-recall.js +65 -0
  210. package/clis/openfda/openfda.test.js +114 -0
  211. package/clis/openfda/utils.js +67 -0
  212. package/clis/osv/osv.test.js +97 -0
  213. package/clis/osv/query.js +72 -0
  214. package/clis/osv/utils.js +169 -0
  215. package/clis/osv/vulnerability.js +54 -0
  216. package/clis/packagist/package.js +49 -0
  217. package/clis/packagist/search.js +43 -0
  218. package/clis/packagist/utils.js +113 -0
  219. package/clis/paperreview/feedback.js +1 -1
  220. package/clis/paperreview/review.js +1 -1
  221. package/clis/paperreview/submit.js +1 -1
  222. package/clis/pixiv/download.test.js +1 -1
  223. package/clis/pixiv/illusts.test.js +1 -1
  224. package/clis/pixiv/search.test.js +1 -1
  225. package/clis/pubmed/article.js +50 -0
  226. package/clis/pubmed/author.js +64 -0
  227. package/clis/pubmed/citations.js +36 -0
  228. package/clis/pubmed/pubmed.test.js +276 -0
  229. package/clis/pubmed/related.js +45 -0
  230. package/clis/pubmed/search.js +75 -0
  231. package/clis/pubmed/utils.js +309 -0
  232. package/clis/pypi/downloads.js +66 -0
  233. package/clis/pypi/package.js +79 -0
  234. package/clis/pypi/utils.js +55 -0
  235. package/clis/quark/mv.js +1 -1
  236. package/clis/quark/save.js +1 -1
  237. package/clis/qwen/ask.js +85 -0
  238. package/clis/qwen/detail.js +62 -0
  239. package/clis/qwen/history.js +61 -0
  240. package/clis/qwen/image.js +179 -0
  241. package/clis/qwen/new.js +23 -0
  242. package/clis/qwen/read.js +41 -0
  243. package/clis/qwen/send.js +55 -0
  244. package/clis/qwen/status.js +37 -0
  245. package/clis/qwen/utils.js +409 -0
  246. package/clis/qwen/utils.test.js +45 -0
  247. package/clis/rest-countries/country.js +65 -0
  248. package/clis/rest-countries/region.js +64 -0
  249. package/clis/rest-countries/rest-countries.test.js +83 -0
  250. package/clis/rest-countries/utils.js +126 -0
  251. package/clis/reuters/article-detail.js +53 -0
  252. package/clis/reuters/reuters.test.js +299 -0
  253. package/clis/reuters/search.js +45 -34
  254. package/clis/reuters/utils.js +159 -0
  255. package/clis/rfc/rfc.js +52 -0
  256. package/clis/rfc/rfc.test.js +74 -0
  257. package/clis/rfc/utils.js +72 -0
  258. package/clis/rubygems/gem.js +42 -0
  259. package/clis/rubygems/search.js +47 -0
  260. package/clis/rubygems/utils.js +86 -0
  261. package/clis/stackoverflow/related.js +66 -0
  262. package/clis/stackoverflow/stackoverflow.test.js +58 -0
  263. package/clis/stackoverflow/tag.js +60 -0
  264. package/clis/stackoverflow/user.js +50 -0
  265. package/clis/stackoverflow/utils.js +118 -0
  266. package/clis/steam/app.js +67 -0
  267. package/clis/steam/search.js +58 -0
  268. package/clis/steam/steam.test.js +46 -0
  269. package/clis/steam/utils.js +107 -0
  270. package/clis/taobao/commands.test.js +1 -24
  271. package/clis/test-utils.js +61 -0
  272. package/clis/tieba/hot.js +0 -1
  273. package/clis/tiktok/comment.js +128 -41
  274. package/clis/tiktok/creator-videos.js +270 -0
  275. package/clis/tiktok/creator-videos.test.js +113 -0
  276. package/clis/tiktok/explore.js +137 -29
  277. package/clis/tiktok/follow.js +115 -33
  278. package/clis/tiktok/following.js +157 -36
  279. package/clis/tiktok/friends.js +139 -37
  280. package/clis/tiktok/live.js +137 -41
  281. package/clis/tiktok/notifications.js +141 -38
  282. package/clis/tiktok/refactor.test.js +389 -0
  283. package/clis/tiktok/unfollow.js +124 -38
  284. package/clis/tiktok/user.js +203 -29
  285. package/clis/tiktok/utils.js +505 -0
  286. package/clis/tiktok/write-refactor.test.js +370 -0
  287. package/clis/toutiao/articles.js +36 -62
  288. package/clis/toutiao/hot.js +63 -0
  289. package/clis/toutiao/toutiao.test.js +378 -0
  290. package/clis/toutiao/utils.js +161 -0
  291. package/clis/tvmaze/search.js +61 -0
  292. package/clis/tvmaze/show.js +60 -0
  293. package/clis/tvmaze/tvmaze.test.js +93 -0
  294. package/clis/tvmaze/utils.js +110 -0
  295. package/clis/twitter/accept.js +1 -1
  296. package/clis/twitter/followers.js +134 -69
  297. package/clis/twitter/quote.js +139 -0
  298. package/clis/twitter/quote.test.js +106 -0
  299. package/clis/twitter/reply-dm.js +1 -1
  300. package/clis/twitter/reply.test.js +1 -29
  301. package/clis/twitter/retweet.js +99 -0
  302. package/clis/twitter/retweet.test.js +69 -0
  303. package/clis/twitter/shared.js +38 -0
  304. package/clis/twitter/shared.test.js +28 -1
  305. package/clis/twitter/unlike.js +87 -0
  306. package/clis/twitter/unlike.test.js +72 -0
  307. package/clis/twitter/unretweet.js +99 -0
  308. package/clis/twitter/unretweet.test.js +69 -0
  309. package/clis/uisdc/news.js +105 -0
  310. package/clis/uisdc/news.test.js +66 -0
  311. package/clis/wanfang/search.js +0 -1
  312. package/clis/web/read.js +47 -17
  313. package/clis/web/read.test.js +101 -1
  314. package/clis/weixin/create-draft.js +1 -1
  315. package/clis/weixin/drafts.js +1 -1
  316. package/clis/weixin/drafts.test.js +5 -1
  317. package/clis/weixin/search.js +157 -0
  318. package/clis/weixin/search.test.js +227 -0
  319. package/clis/wikidata/entity.js +60 -0
  320. package/clis/wikidata/search.js +50 -0
  321. package/clis/wikidata/utils.js +117 -0
  322. package/clis/wikidata/wikidata.test.js +83 -0
  323. package/clis/wikipedia/page.js +95 -0
  324. package/clis/wttr/current.js +63 -0
  325. package/clis/wttr/forecast.js +71 -0
  326. package/clis/wttr/utils.js +50 -0
  327. package/clis/wttr/wttr.test.js +84 -0
  328. package/clis/xianyu/chat.js +16 -4
  329. package/clis/xianyu/chat.test.js +64 -0
  330. package/clis/xianyu/publish.js +485 -0
  331. package/clis/xianyu/publish.test.js +220 -0
  332. package/clis/xiaoe/catalog.js +105 -40
  333. package/clis/xiaoe/content.js +164 -29
  334. package/clis/xiaoe/courses.js +86 -29
  335. package/clis/xiaoe/xiaoe.test.js +486 -0
  336. package/clis/xiaohongshu/creator-notes-summary.js +1 -1
  337. package/clis/xiaohongshu/publish.js +16 -3
  338. package/clis/xiaohongshu/publish.test.js +46 -1
  339. package/clis/youtube/transcript.js +13 -19
  340. package/clis/youtube/transcript.test.js +17 -0
  341. package/clis/yuanbao/ask.js +17 -66
  342. package/clis/yuanbao/ask.test.js +5 -5
  343. package/clis/yuanbao/detail.js +65 -0
  344. package/clis/yuanbao/history.js +51 -0
  345. package/clis/yuanbao/new.js +1 -0
  346. package/clis/yuanbao/read.js +38 -0
  347. package/clis/yuanbao/send.js +57 -0
  348. package/clis/yuanbao/shared.js +297 -5
  349. package/clis/yuanbao/shared.test.js +80 -0
  350. package/clis/yuanbao/status.js +44 -0
  351. package/clis/zlibrary/commands.test.js +1 -11
  352. package/dist/src/browser/base-page.d.ts +9 -0
  353. package/dist/src/browser/base-page.js +44 -1
  354. package/dist/src/browser/base-page.test.js +66 -0
  355. package/dist/src/browser/bridge.js +47 -45
  356. package/dist/src/browser/cdp.d.ts +1 -0
  357. package/dist/src/browser/cdp.js +51 -9
  358. package/dist/src/browser/daemon-client.d.ts +4 -0
  359. package/dist/src/browser/errors.js +1 -1
  360. package/dist/src/browser/page.d.ts +1 -1
  361. package/dist/src/browser/page.js +3 -1
  362. package/dist/src/browser/page.test.js +29 -0
  363. package/dist/src/browser/target-errors.d.ts +2 -1
  364. package/dist/src/browser/target-errors.js +1 -0
  365. package/dist/src/browser/target-resolver.d.ts +25 -0
  366. package/dist/src/browser/target-resolver.js +43 -0
  367. package/dist/src/browser.test.js +18 -0
  368. package/dist/src/build-manifest.js +9 -4
  369. package/dist/src/build-manifest.test.js +2 -8
  370. package/dist/src/capabilityRouting.d.ts +16 -1
  371. package/dist/src/capabilityRouting.js +24 -1
  372. package/dist/src/capabilityRouting.test.js +19 -1
  373. package/dist/src/cli.js +76 -11
  374. package/dist/src/cli.test.js +241 -1
  375. package/dist/src/commanderAdapter.js +23 -9
  376. package/dist/src/commanderAdapter.test.js +0 -1
  377. package/dist/src/discovery.js +2 -5
  378. package/dist/src/errors.js +1 -1
  379. package/dist/src/execution.d.ts +1 -1
  380. package/dist/src/execution.js +111 -27
  381. package/dist/src/execution.test.js +326 -17
  382. package/dist/src/help.d.ts +27 -2
  383. package/dist/src/help.js +196 -23
  384. package/dist/src/help.test.d.ts +1 -0
  385. package/dist/src/help.test.js +54 -0
  386. package/dist/src/main.js +14 -1
  387. package/dist/src/manifest-types.d.ts +5 -3
  388. package/dist/src/pipeline/executor.js +1 -1
  389. package/dist/src/pipeline/executor.test.js +8 -0
  390. package/dist/src/pipeline/registry.d.ts +9 -0
  391. package/dist/src/pipeline/registry.js +13 -1
  392. package/dist/src/pipeline/steps/browser.d.ts +1 -0
  393. package/dist/src/pipeline/steps/browser.js +10 -0
  394. package/dist/src/pipeline/steps/download.test.js +1 -0
  395. package/dist/src/registry-api.d.ts +1 -1
  396. package/dist/src/registry.d.ts +12 -11
  397. package/dist/src/registry.js +16 -6
  398. package/dist/src/registry.test.js +2 -2
  399. package/dist/src/runtime.d.ts +2 -1
  400. package/dist/src/runtime.js +1 -1
  401. package/dist/src/serialization.d.ts +2 -2
  402. package/dist/src/serialization.js +4 -6
  403. package/dist/src/serialization.test.js +17 -0
  404. package/dist/src/types.d.ts +17 -0
  405. package/dist/src/validate.js +15 -11
  406. package/dist/src/validate.test.d.ts +9 -0
  407. package/dist/src/validate.test.js +90 -0
  408. package/package.json +1 -1
  409. package/scripts/fetch-adapters.js +1 -1
  410. package/scripts/typed-error-lint-baseline.json +5 -77
  411. package/clis/ctrip/search.test.js +0 -64
  412. package/clis/gov-policy/commands.test.js +0 -27
  413. package/clis/linux-do/category.js +0 -37
  414. package/clis/linux-do/hot.js +0 -26
  415. package/clis/linux-do/latest.js +0 -19
  416. package/clis/pixiv/test-utils.js +0 -23
  417. package/clis/toutiao/articles.test.js +0 -30
  418. package/dist/src/analysis.d.ts +0 -40
  419. package/dist/src/analysis.js +0 -172
@@ -282,6 +282,48 @@ describe('xiaohongshu publish', () => {
282
282
  },
283
283
  ]);
284
284
  });
285
+ it('falls back to DataTransfer upload when CDP file injection is blocked by Chrome', async () => {
286
+ const cmd = getRegistry().get('xiaohongshu/publish');
287
+ expect(cmd?.func).toBeTypeOf('function');
288
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-xhs-publish-'));
289
+ const imagePath = path.join(tempDir, 'demo.jpg');
290
+ fs.writeFileSync(imagePath, Buffer.from([0xff, 0xd8, 0xff, 0xd9]));
291
+ const setFileInput = vi.fn().mockRejectedValue(new Error('Chrome Not allowed'));
292
+ const page = createPageMock([
293
+ 'https://creator.xiaohongshu.com/publish/publish?from=menu_left&target=image',
294
+ { ok: true, target: '上传图文', text: '上传图文' },
295
+ { state: 'editor_ready', hasTitleInput: true, hasImageInput: true, hasVideoSurface: false },
296
+ 'input[type="file"][accept*="image"],input[type="file"][accept*=".jpg"],input[type="file"][accept*=".jpeg"],input[type="file"][accept*=".png"],input[type="file"][accept*=".gif"],input[type="file"][accept*=".webp"]',
297
+ { ok: true, count: 1 },
298
+ false,
299
+ true,
300
+ { ok: true, sel: 'input[maxlength="20"]', kind: 'input' },
301
+ { ok: true, actual: 'CDP被拒后回退' },
302
+ { ok: true, sel: '[contenteditable="true"][class*="content"]', kind: 'contenteditable' },
303
+ { ok: true, actual: 'DataTransfer fallback path' },
304
+ true,
305
+ 'https://creator.xiaohongshu.com/publish/success',
306
+ '发布成功',
307
+ ], {
308
+ setFileInput,
309
+ });
310
+ const result = await cmd.func(page, {
311
+ title: 'CDP被拒后回退',
312
+ content: 'DataTransfer fallback path',
313
+ images: imagePath,
314
+ topics: '',
315
+ draft: false,
316
+ });
317
+ const evaluateCalls = page.evaluate.mock.calls.map((args) => String(args[0]));
318
+ expect(setFileInput).toHaveBeenCalledWith([imagePath], expect.stringContaining('input[type="file"][accept*="image"]'));
319
+ expect(evaluateCalls.some((code) => code.includes('dt.items.add(new File'))).toBe(true);
320
+ expect(result).toEqual([
321
+ {
322
+ status: '✅ 发布成功',
323
+ detail: '"CDP被拒后回退" · 1张图片 · 发布成功',
324
+ },
325
+ ]);
326
+ });
285
327
  it('fails fast when only a generic file input exists on the page', async () => {
286
328
  const cmd = getRegistry().get('xiaohongshu/publish');
287
329
  expect(cmd?.func).toBeTypeOf('function');
@@ -336,8 +378,11 @@ describe('xiaohongshu publish', () => {
336
378
  draft: false,
337
379
  });
338
380
  const evaluateCalls = page.evaluate.mock.calls.map((args) => String(args[0]));
339
- expect(evaluateCalls.some((code) => code.includes("const targets = ['上传图文', '图文', '图片']"))).toBe(true);
381
+ const tabSelectCode = evaluateCalls.find((code) => code.includes("const targets = ['上传图文', '图文', '图片']"));
382
+ expect(tabSelectCode).toBeTruthy();
383
+ expect(tabSelectCode.indexOf('if (text === target)')).toBeLessThan(tabSelectCode.indexOf('text.startsWith(target)'));
340
384
  expect(evaluateCalls.some((code) => code.includes("No image file input found on page"))).toBe(true);
385
+ expect(page.goto).toHaveBeenCalledWith(expect.stringContaining('target=image'));
341
386
  expect(result).toEqual([
342
387
  {
343
388
  status: '✅ 发布成功',
@@ -1,16 +1,16 @@
1
1
  /**
2
- * YouTube transcript — uses InnerTube player API with Android client context.
2
+ * YouTube transcript — extracts caption tracks from watch page bootstrap data.
3
3
  *
4
- * The Web client's caption URLs require a PoToken (proof of origin) generated
5
- * by BotGuard at runtime. The Android client returns caption URLs that work
6
- * without PoToken — same approach used by youtube-transcript-api (Python).
4
+ * The old Android InnerTube client path stopped reliably returning captions.
5
+ * We now match youtube/video.js: fetch watch HTML with the browser session and
6
+ * parse ytInitialPlayerResponse.captions.playerCaptionsTracklistRenderer.
7
7
  *
8
8
  * Modes:
9
9
  * --mode grouped (default): sentences merged, speaker detection, chapters
10
10
  * --mode raw: every caption segment as-is with precise timestamps
11
11
  */
12
12
  import { cli, Strategy } from '@jackwener/opencli/registry';
13
- import { parseVideoId, prepareYoutubeApiPage } from './utils.js';
13
+ import { extractJsonAssignmentFromHtml, parseVideoId, prepareYoutubeApiPage } from './utils.js';
14
14
  import { groupTranscriptSegments, formatGroupedTranscript, } from './transcript-group.js';
15
15
  import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
16
16
  cli({
@@ -32,27 +32,21 @@ cli({
32
32
  await prepareYoutubeApiPage(page);
33
33
  const lang = kwargs.lang || '';
34
34
  const mode = kwargs.mode || 'grouped';
35
- // Step 1: Get caption track URL via Android InnerTube API
35
+ // Step 1: Get caption track URL from watch page HTML
36
36
  const captionData = await page.evaluate(`
37
37
  (async () => {
38
- const cfg = window.ytcfg?.data_ || {};
39
- const apiKey = cfg.INNERTUBE_API_KEY;
40
- if (!apiKey) return { error: 'INNERTUBE_API_KEY not found on page' };
38
+ const extractJsonAssignmentFromHtml = ${extractJsonAssignmentFromHtml.toString()};
41
39
 
42
- const resp = await fetch('/youtubei/v1/player?key=' + apiKey + '&prettyPrint=false', {
43
- method: 'POST',
40
+ const watchResp = await fetch('/watch?v=' + encodeURIComponent(${JSON.stringify(videoId)}), {
44
41
  credentials: 'include',
45
- headers: { 'Content-Type': 'application/json' },
46
- body: JSON.stringify({
47
- context: { client: { clientName: 'ANDROID', clientVersion: '20.10.38' } },
48
- videoId: ${JSON.stringify(videoId)}
49
- })
50
42
  });
43
+ if (!watchResp.ok) return { error: 'Watch HTML returned HTTP ' + watchResp.status };
51
44
 
52
- if (!resp.ok) return { error: 'InnerTube player API returned HTTP ' + resp.status };
53
- const data = await resp.json();
45
+ const html = await watchResp.text();
46
+ const player = extractJsonAssignmentFromHtml(html, 'ytInitialPlayerResponse');
47
+ if (!player) return { error: 'ytInitialPlayerResponse not found in watch HTML' };
54
48
 
55
- const renderer = data.captions?.playerCaptionsTracklistRenderer;
49
+ const renderer = player.captions?.playerCaptionsTracklistRenderer;
56
50
  if (!renderer?.captionTracks?.length) {
57
51
  return { error: 'No captions available for this video' };
58
52
  }
@@ -0,0 +1,17 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const transcriptSource = readFileSync(resolve(__dirname, 'transcript.js'), 'utf8');
8
+
9
+ describe('youtube transcript source contract', () => {
10
+ it('gets caption tracks from watch page bootstrap data, not Android InnerTube', () => {
11
+ expect(transcriptSource).toContain("fetch('/watch?v='");
12
+ expect(transcriptSource).toContain("extractJsonAssignmentFromHtml(html, 'ytInitialPlayerResponse')");
13
+ expect(transcriptSource).toContain('playerCaptionsTracklistRenderer');
14
+ expect(transcriptSource).not.toContain('/youtubei/v1/player');
15
+ expect(transcriptSource).not.toContain("clientName: 'ANDROID'");
16
+ });
17
+ });
@@ -1,7 +1,16 @@
1
1
  import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  import { htmlToMarkdown } from '@jackwener/opencli/utils';
3
- import { CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
4
- import { YUANBAO_DOMAIN, IS_VISIBLE_JS, authRequired, isOnYuanbao, ensureYuanbaoPage, hasLoginGate } from './shared.js';
3
+ import { ArgumentError, CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
4
+ import {
5
+ YUANBAO_DOMAIN,
6
+ IS_VISIBLE_JS,
7
+ authRequired,
8
+ isOnYuanbao,
9
+ ensureYuanbaoPage,
10
+ hasLoginGate,
11
+ normalizeBooleanFlag,
12
+ sendYuanbaoMessage,
13
+ } from './shared.js';
5
14
  const YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS = 2;
6
15
  const YUANBAO_MIN_WAIT_MS = 8_000;
7
16
  const YUANBAO_STABLE_POLLS_REQUIRED = 3;
@@ -12,14 +21,6 @@ function sendFailure(reason, detail) {
12
21
  function normalizeText(value) {
13
22
  return typeof value === 'string' ? value.trim() : '';
14
23
  }
15
- function normalizeBooleanFlag(value, fallback) {
16
- if (typeof value === 'boolean')
17
- return value;
18
- if (value == null || value === '')
19
- return fallback;
20
- const normalized = String(value).trim().toLowerCase();
21
- return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
22
- }
23
24
  export function convertYuanbaoHtmlToMarkdown(value) {
24
25
  return htmlToMarkdown(value, (td) => {
25
26
  td.addRule('table', {
@@ -272,59 +273,6 @@ async function setYuanbaoDeepThink(page, enabled) {
272
273
  })()`);
273
274
  await page.wait(0.5);
274
275
  }
275
- async function sendYuanbaoMessage(page, prompt) {
276
- return await page.evaluate(`(async () => {
277
- const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
278
- ${IS_VISIBLE_JS}
279
-
280
- const composer = Array.from(document.querySelectorAll('.ql-editor[contenteditable="true"], .ql-editor, [contenteditable="true"]'))
281
- .find(isVisible);
282
-
283
- if (!(composer instanceof HTMLElement)) {
284
- return {
285
- ok: false,
286
- reason: 'Yuanbao composer was not found.',
287
- };
288
- }
289
-
290
- try {
291
- composer.focus();
292
- const selection = window.getSelection();
293
- const range = document.createRange();
294
- range.selectNodeContents(composer);
295
- range.collapse(false);
296
- selection?.removeAllRanges();
297
- selection?.addRange(range);
298
- composer.textContent = '';
299
- document.execCommand('insertText', false, ${JSON.stringify(prompt)});
300
- composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: ${JSON.stringify(prompt)}, inputType: 'insertText' }));
301
- await waitFor(200);
302
- } catch (error) {
303
- return {
304
- ok: false,
305
- reason: 'Failed to insert the prompt into the Yuanbao composer.',
306
- detail: error instanceof Error ? error.message : String(error),
307
- };
308
- }
309
-
310
- const submit = Array.from(document.querySelectorAll('a[class*="send-btn"], button[class*="send-btn"]'))
311
- .find((node) => {
312
- if (!(node instanceof HTMLElement) || !isVisible(node)) return false;
313
- const className = node.className || '';
314
- if (typeof className === 'string' && className.includes('disabled')) return false;
315
- return true;
316
- });
317
-
318
- if (submit instanceof HTMLElement) {
319
- submit.click();
320
- return { ok: true, action: 'click' };
321
- }
322
-
323
- composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
324
- composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
325
- return { ok: true, action: 'enter' };
326
- })()`);
327
- }
328
276
  async function waitForYuanbaoResponse(page, baselineAssistantCount, beforeLines, prompt, timeoutSeconds) {
329
277
  const startTime = Date.now();
330
278
  let previousText = '';
@@ -357,19 +305,22 @@ export const askCommand = cli({
357
305
  domain: YUANBAO_DOMAIN,
358
306
  strategy: Strategy.COOKIE,
359
307
  browser: true,
308
+ browserSession: { reuse: 'site' },
360
309
  navigateBefore: false,
361
310
  defaultFormat: 'plain',
362
- timeoutSeconds: 180,
363
311
  args: [
364
312
  { name: 'prompt', required: true, positional: true, help: 'Prompt to send' },
365
- { name: 'timeout', required: false, help: 'Max seconds to wait (default: 60)', default: '60' },
313
+ { name: 'timeout', type: 'int', required: false, help: 'Max seconds to wait (default: 60)', default: 60 },
366
314
  { name: 'search', type: 'boolean', required: false, help: 'Enable Yuanbao internet search (default: true)', default: true },
367
315
  { name: 'think', type: 'boolean', required: false, help: 'Enable Yuanbao deep thinking (default: false)', default: false },
368
316
  ],
369
317
  columns: ['Role', 'Text'],
370
318
  func: async (page, kwargs) => {
371
319
  const prompt = kwargs.prompt;
372
- const timeout = parseInt(kwargs.timeout, 10) || 60;
320
+ const timeout = kwargs.timeout;
321
+ if (!Number.isInteger(timeout) || timeout < 1) {
322
+ throw new ArgumentError('--timeout must be a positive integer (seconds)');
323
+ }
373
324
  const useSearch = normalizeBooleanFlag(kwargs.search, true);
374
325
  const useThink = normalizeBooleanFlag(kwargs.think, false);
375
326
  await ensureYuanbaoPage(page);
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it, vi } from 'vitest';
2
- import { AuthRequiredError, CommandExecutionError, TimeoutError } from '@jackwener/opencli/errors';
2
+ import { ArgumentError, AuthRequiredError, CommandExecutionError } from '@jackwener/opencli/errors';
3
3
  import { __test__ } from './ask.js';
4
4
  import { askCommand } from './ask.js';
5
5
  describe('yuanbao ask helpers', () => {
@@ -101,7 +101,7 @@ function createAskPageMock(overrides = {}) {
101
101
  describe('yuanbao ask command', () => {
102
102
  it('throws AuthRequiredError when Yuanbao shows a login gate before sending', async () => {
103
103
  const page = createAskPageMock({ hasLoginGate: true });
104
- await expect(askCommand.func(page, { prompt: '你好', timeout: '60', search: true, think: false }))
104
+ await expect(askCommand.func(page, { prompt: '你好', timeout: 60, search: true, think: false }))
105
105
  .rejects.toBeInstanceOf(AuthRequiredError);
106
106
  });
107
107
  it('throws CommandExecutionError when the prompt cannot be sent', async () => {
@@ -111,14 +111,14 @@ describe('yuanbao ask command', () => {
111
111
  reason: 'Yuanbao composer was not found.',
112
112
  },
113
113
  });
114
- await expect(askCommand.func(page, { prompt: '你好', timeout: '60', search: true, think: false }))
114
+ await expect(askCommand.func(page, { prompt: '你好', timeout: 60, search: true, think: false }))
115
115
  .rejects.toBeInstanceOf(CommandExecutionError);
116
116
  });
117
117
  it('throws TimeoutError when no response arrives before timeout', async () => {
118
118
  const page = createAskPageMock({
119
119
  sendResult: { ok: true, action: 'click' },
120
120
  });
121
- await expect(askCommand.func(page, { prompt: '你好', timeout: '-1', search: true, think: false }))
122
- .rejects.toBeInstanceOf(TimeoutError);
121
+ await expect(askCommand.func(page, { prompt: '你好', timeout: -1, search: true, think: false }))
122
+ .rejects.toBeInstanceOf(ArgumentError);
123
123
  });
124
124
  });
@@ -0,0 +1,65 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { EmptyResultError } from '@jackwener/opencli/errors';
3
+ import {
4
+ YUANBAO_DOMAIN,
5
+ YUANBAO_URL,
6
+ getYuanbaoMessageBubbles,
7
+ parseYuanbaoSessionId,
8
+ hasLoginGate,
9
+ authRequired,
10
+ } from './shared.js';
11
+ import { convertYuanbaoHtmlToMarkdown } from './ask.js';
12
+
13
+ cli({
14
+ site: 'yuanbao',
15
+ name: 'detail',
16
+ access: 'read',
17
+ description: 'Open a Yuanbao conversation by ID and read its messages',
18
+ domain: YUANBAO_DOMAIN,
19
+ strategy: Strategy.COOKIE,
20
+ browser: true,
21
+ browserSession: { reuse: 'site' },
22
+ navigateBefore: false,
23
+ args: [
24
+ {
25
+ name: 'id',
26
+ positional: true,
27
+ required: true,
28
+ help: 'Full https://yuanbao.tencent.com/chat/<agentId>/<convId> URL or "<agentId>/<convId>" pair (a UUID alone is not enough — Yuanbao requires the agent slug)',
29
+ },
30
+ ],
31
+ columns: ['Role', 'Text'],
32
+ func: async (page, kwargs) => {
33
+ const { agentId, convId } = parseYuanbaoSessionId(kwargs.id);
34
+ await page.goto(`${YUANBAO_URL}chat/${agentId}/${convId}`, { waitUntil: 'load', settleMs: 2500 });
35
+ await page.wait(2);
36
+ if (await hasLoginGate(page)) {
37
+ throw authRequired('Yuanbao opened a login gate when navigating to the conversation.');
38
+ }
39
+
40
+ // Poll up to ~20s for the transcript to render. The page shell loads
41
+ // before history is fetched, so a fixed wait races the empty render.
42
+ let bubbles = [];
43
+ const POLL_DEADLINE_MS = 20_000;
44
+ const POLL_INTERVAL_S = 1;
45
+ const startedAt = Date.now();
46
+ while (Date.now() - startedAt < POLL_DEADLINE_MS) {
47
+ bubbles = await getYuanbaoMessageBubbles(page);
48
+ if (bubbles.length > 0) break;
49
+ await page.wait(POLL_INTERVAL_S);
50
+ }
51
+
52
+ if (!bubbles.length) {
53
+ throw new EmptyResultError(
54
+ 'yuanbao detail',
55
+ `No visible messages found for conversation ${agentId}/${convId}. Verify the IDs are correct and that the session belongs to the current login.`,
56
+ );
57
+ }
58
+ return bubbles.map((b) => ({
59
+ Role: b.role,
60
+ Text: b.role === 'Assistant' && b.html
61
+ ? (convertYuanbaoHtmlToMarkdown(b.html).trim() || b.text)
62
+ : b.text,
63
+ }));
64
+ },
65
+ });
@@ -0,0 +1,51 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { ArgumentError, EmptyResultError } from '@jackwener/opencli/errors';
3
+ import {
4
+ YUANBAO_DOMAIN,
5
+ YUANBAO_URL,
6
+ ensureYuanbaoPage,
7
+ getYuanbaoSessionList,
8
+ hasLoginGate,
9
+ authRequired,
10
+ } from './shared.js';
11
+
12
+ cli({
13
+ site: 'yuanbao',
14
+ name: 'history',
15
+ access: 'read',
16
+ description: 'List recent Yuanbao conversations from the sidebar (requires login)',
17
+ domain: YUANBAO_DOMAIN,
18
+ strategy: Strategy.COOKIE,
19
+ browser: true,
20
+ browserSession: { reuse: 'site' },
21
+ navigateBefore: false,
22
+ args: [
23
+ { name: 'limit', type: 'int', default: 20, help: 'Max conversations to list (sidebar virtual scroll caps actual count)' },
24
+ ],
25
+ columns: ['Index', 'Title', 'AgentId', 'SessionId', 'Url'],
26
+ func: async (page, kwargs) => {
27
+ const limit = Number(kwargs.limit ?? 20);
28
+ if (!Number.isInteger(limit) || limit <= 0) {
29
+ throw new ArgumentError('limit', 'must be a positive integer');
30
+ }
31
+ await ensureYuanbaoPage(page);
32
+ if (await hasLoginGate(page)) {
33
+ throw authRequired('Yuanbao opened a login gate when reading the sidebar.');
34
+ }
35
+ await page.wait(1.5);
36
+ const sessions = await getYuanbaoSessionList(page, limit);
37
+ if (!sessions.length) {
38
+ throw new EmptyResultError(
39
+ 'yuanbao history',
40
+ 'No Yuanbao conversations found in the sidebar. Either the account is logged out, the sidebar is collapsed, or the user truly has no chat history yet.',
41
+ );
42
+ }
43
+ return sessions.map((s, i) => ({
44
+ Index: i + 1,
45
+ Title: s.title || '(untitled)',
46
+ AgentId: s.agentId,
47
+ SessionId: s.cid,
48
+ Url: `${YUANBAO_URL}chat/${s.agentId}/${s.cid}`,
49
+ }));
50
+ },
51
+ });
@@ -55,6 +55,7 @@ export const newCommand = cli({
55
55
  domain: YUANBAO_DOMAIN,
56
56
  strategy: Strategy.COOKIE,
57
57
  browser: true,
58
+ browserSession: { reuse: 'site' },
58
59
  navigateBefore: false,
59
60
  args: [],
60
61
  columns: ['Status', 'Action'],
@@ -0,0 +1,38 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import {
3
+ YUANBAO_DOMAIN,
4
+ ensureYuanbaoPage,
5
+ getYuanbaoMessageBubbles,
6
+ } from './shared.js';
7
+ import { convertYuanbaoHtmlToMarkdown } from './ask.js';
8
+
9
+ cli({
10
+ site: 'yuanbao',
11
+ name: 'read',
12
+ access: 'read',
13
+ description: 'Read messages in the current Yuanbao conversation',
14
+ domain: YUANBAO_DOMAIN,
15
+ strategy: Strategy.COOKIE,
16
+ browser: true,
17
+ browserSession: { reuse: 'site' },
18
+ navigateBefore: false,
19
+ args: [],
20
+ columns: ['Role', 'Text'],
21
+ func: async (page) => {
22
+ await ensureYuanbaoPage(page);
23
+ await page.wait(1.5);
24
+ const bubbles = await getYuanbaoMessageBubbles(page);
25
+ if (!bubbles.length) {
26
+ return [{ Role: 'system', Text: 'No visible messages in the current Yuanbao conversation.' }];
27
+ }
28
+ return bubbles.map((b) => ({
29
+ Role: b.role,
30
+ // Assistant turns render markdown HTML; convert to markdown so the
31
+ // text column carries usable structure (lists, code, tables) rather
32
+ // than collapsed innerText.
33
+ Text: b.role === 'Assistant' && b.html
34
+ ? (convertYuanbaoHtmlToMarkdown(b.html).trim() || b.text)
35
+ : b.text,
36
+ }));
37
+ },
38
+ });
@@ -0,0 +1,57 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';
3
+ import {
4
+ YUANBAO_DOMAIN,
5
+ authRequired,
6
+ ensureYuanbaoPage,
7
+ hasLoginGate,
8
+ normalizeBooleanFlag,
9
+ sendYuanbaoMessage,
10
+ startNewYuanbaoChat,
11
+ } from './shared.js';
12
+
13
+ cli({
14
+ site: 'yuanbao',
15
+ name: 'send',
16
+ access: 'write',
17
+ description: 'Fire-and-forget: send a prompt to Yuanbao without waiting for the reply',
18
+ domain: YUANBAO_DOMAIN,
19
+ strategy: Strategy.COOKIE,
20
+ browser: true,
21
+ browserSession: { reuse: 'site' },
22
+ navigateBefore: false,
23
+ args: [
24
+ { name: 'prompt', positional: true, required: true, help: 'Prompt to send to Yuanbao' },
25
+ { name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
26
+ ],
27
+ columns: ['Status', 'Prompt'],
28
+ func: async (page, kwargs) => {
29
+ const prompt = String(kwargs.prompt || '').trim();
30
+ if (!prompt) throw new ArgumentError('prompt', 'is required');
31
+ const startFresh = normalizeBooleanFlag(kwargs.new, false);
32
+
33
+ await ensureYuanbaoPage(page);
34
+ if (await hasLoginGate(page)) {
35
+ throw authRequired('Yuanbao opened a login gate before sending the prompt.');
36
+ }
37
+ if (startFresh) {
38
+ const action = await startNewYuanbaoChat(page);
39
+ if (action === 'blocked') {
40
+ throw authRequired('Yuanbao opened a login gate while starting a new chat.');
41
+ }
42
+ }
43
+ const send = await sendYuanbaoMessage(page, prompt);
44
+ if (!send?.ok) {
45
+ if (await hasLoginGate(page)) {
46
+ throw authRequired('Yuanbao opened a login gate instead of accepting the prompt.');
47
+ }
48
+ throw new CommandExecutionError(
49
+ send?.reason || 'Failed to send Yuanbao prompt',
50
+ send?.detail
51
+ ? `Detail: ${send.detail}`
52
+ : 'Make sure the Yuanbao chat composer is visible and not in a disabled state.',
53
+ );
54
+ }
55
+ return [{ Status: 'sent', Prompt: prompt }];
56
+ },
57
+ });