@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
@@ -0,0 +1,79 @@
1
+ // Shared helpers for the bbc adapters that hit BBC's public RSS feeds.
2
+ import { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';
3
+
4
+ export const BBC_FEED_BASE = 'https://feeds.bbci.co.uk/news';
5
+ const UA = 'opencli-bbc-adapter (+https://github.com/jackwener/opencli)';
6
+
7
+ const HTML_ENTITIES = {
8
+ '&amp;': '&', '&lt;': '<', '&gt;': '>', '&quot;': '"', '&apos;': "'", '&#39;': "'", '&nbsp;': ' ',
9
+ };
10
+
11
+ export function decodeHtmlEntities(value) {
12
+ return String(value ?? '')
13
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, h) => String.fromCodePoint(parseInt(h, 16)))
14
+ .replace(/&#(\d+);/g, (_, d) => String.fromCodePoint(parseInt(d, 10)))
15
+ .replace(/&(amp|lt|gt|quot|apos|#39|nbsp);/g, (m) => HTML_ENTITIES[m] || m);
16
+ }
17
+
18
+ /** Extract `<tag>…</tag>` (CDATA-aware) from a block. */
19
+ export function extractRssTag(block, tag) {
20
+ const cdata = block.match(new RegExp(`<${tag}[^>]*>\\s*<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>\\s*<\\/${tag}>`));
21
+ if (cdata) return cdata[1];
22
+ const plain = block.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`));
23
+ return plain ? plain[1] : '';
24
+ }
25
+
26
+ export function parseRssItems(xml) {
27
+ const out = [];
28
+ const re = /<item[^>]*>([\s\S]*?)<\/item>/g;
29
+ let m;
30
+ while ((m = re.exec(String(xml || ''))) !== null) {
31
+ const block = m[1];
32
+ out.push({
33
+ title: decodeHtmlEntities(extractRssTag(block, 'title')).trim(),
34
+ description: decodeHtmlEntities(extractRssTag(block, 'description')).trim(),
35
+ link: decodeHtmlEntities(extractRssTag(block, 'link')).trim(),
36
+ pubDate: decodeHtmlEntities(extractRssTag(block, 'pubDate')).trim(),
37
+ guid: decodeHtmlEntities(extractRssTag(block, 'guid')).trim(),
38
+ });
39
+ }
40
+ return out;
41
+ }
42
+
43
+ export function requireBoundedInt(value, defaultValue, maxValue, label = 'limit') {
44
+ const raw = value ?? defaultValue;
45
+ const n = typeof raw === 'number' ? raw : Number(raw);
46
+ if (!Number.isInteger(n) || n <= 0) {
47
+ throw new ArgumentError(`bbc ${label} must be a positive integer`);
48
+ }
49
+ if (n > maxValue) {
50
+ throw new ArgumentError(`bbc ${label} must be <= ${maxValue}`);
51
+ }
52
+ return n;
53
+ }
54
+
55
+ export async function bbcFetchRss(path, label) {
56
+ const url = `${BBC_FEED_BASE}/${path}`;
57
+ let resp;
58
+ try {
59
+ resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/rss+xml, application/xml' } });
60
+ }
61
+ catch (err) {
62
+ throw new CommandExecutionError(
63
+ `${label} request failed: ${err?.message ?? err}`,
64
+ 'Check that feeds.bbci.co.uk is reachable from this network.',
65
+ );
66
+ }
67
+ if (!resp.ok) {
68
+ throw new CommandExecutionError(`${label} returned HTTP ${resp.status} (${url})`);
69
+ }
70
+ return resp.text();
71
+ }
72
+
73
+ /** Convert RFC-822 pubDate to ISO `YYYY-MM-DD`; empty string on parse failure. */
74
+ export function pubDateToIso(value) {
75
+ if (!value) return '';
76
+ const d = new Date(value);
77
+ if (Number.isNaN(d.getTime())) return '';
78
+ return d.toISOString().slice(0, 10);
79
+ }
@@ -8,7 +8,6 @@ cli({
8
8
  description: '学习通作业列表',
9
9
  domain: 'mooc2-ans.chaoxing.com',
10
10
  strategy: Strategy.COOKIE,
11
- timeoutSeconds: 90,
12
11
  args: [
13
12
  { name: 'course', type: 'string', help: '按课程名过滤(模糊匹配)' },
14
13
  {
@@ -19,6 +18,7 @@ cli({
19
18
  help: '按状态过滤',
20
19
  },
21
20
  { name: 'limit', type: 'int', default: 20, help: '最大返回数量' },
21
+ { name: 'timeout', type: 'int', required: false, default: 90, help: 'Max seconds for the overall command (default: 90)' },
22
22
  ],
23
23
  columns: ['rank', 'course', 'title', 'deadline', 'status', 'score'],
24
24
  func: async (page, kwargs) => {
@@ -7,7 +7,6 @@ cli({
7
7
  description: '学习通考试列表',
8
8
  domain: 'mooc2-ans.chaoxing.com',
9
9
  strategy: Strategy.COOKIE,
10
- timeoutSeconds: 90,
11
10
  args: [
12
11
  { name: 'course', type: 'string', help: '按课程名过滤(模糊匹配)' },
13
12
  {
@@ -18,6 +17,7 @@ cli({
18
17
  help: '按状态过滤',
19
18
  },
20
19
  { name: 'limit', type: 'int', default: 20, help: '最大返回数量' },
20
+ { name: 'timeout', type: 'int', required: false, default: 90, help: 'Max seconds for the overall command (default: 90)' },
21
21
  ],
22
22
  columns: ['rank', 'course', 'title', 'start', 'end', 'status', 'score'],
23
23
  func: async (page, kwargs) => {
@@ -0,0 +1,57 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { CommandExecutionError } from '@jackwener/opencli/errors';
3
+ import {
4
+ CHATGPT_DOMAIN,
5
+ CHATGPT_URL,
6
+ ensureChatGPTComposer,
7
+ ensureOnChatGPT,
8
+ getBubbleCount,
9
+ normalizeBooleanFlag,
10
+ requireNonEmptyPrompt,
11
+ requirePositiveInt,
12
+ sendChatGPTMessage,
13
+ startNewChat,
14
+ waitForChatGPTResponse,
15
+ } from './utils.js';
16
+
17
+ export const askCommand = cli({
18
+ site: 'chatgpt',
19
+ name: 'ask',
20
+ access: 'write',
21
+ description: 'Send a prompt to ChatGPT web and wait for the response',
22
+ domain: CHATGPT_DOMAIN,
23
+ strategy: Strategy.COOKIE,
24
+ browser: true,
25
+ browserSession: { reuse: 'site' },
26
+ navigateBefore: false,
27
+ args: [
28
+ { name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
29
+ { name: 'timeout', type: 'int', default: 120, help: 'Max seconds to wait for response' },
30
+ { name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
31
+ ],
32
+ columns: ['response'],
33
+ func: async (page, kwargs) => {
34
+ const prompt = requireNonEmptyPrompt(kwargs.prompt, 'chatgpt ask');
35
+ const timeout = requirePositiveInt(
36
+ Number(kwargs.timeout ?? 120),
37
+ 'chatgpt ask --timeout',
38
+ 'Example: opencli chatgpt ask "hello" --timeout 120',
39
+ );
40
+
41
+ if (normalizeBooleanFlag(kwargs.new)) {
42
+ await startNewChat(page);
43
+ } else {
44
+ await ensureOnChatGPT(page);
45
+ }
46
+ await page.wait(2);
47
+ await ensureChatGPTComposer(page, 'ChatGPT ask requires a logged-in ChatGPT session with a visible composer.');
48
+
49
+ const baseline = await getBubbleCount(page);
50
+ const sent = await sendChatGPTMessage(page, prompt);
51
+ if (!sent) {
52
+ throw new CommandExecutionError('Failed to send message to ChatGPT', `Open ${CHATGPT_URL} and verify the composer is ready.`);
53
+ }
54
+
55
+ return [{ response: await waitForChatGPTResponse(page, baseline, prompt, timeout) }];
56
+ },
57
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getRegistry } from '@jackwener/opencli/registry';
3
+ import './ask.js';
4
+ import './send.js';
5
+ import './read.js';
6
+ import './history.js';
7
+ import './detail.js';
8
+ import './new.js';
9
+ import './status.js';
10
+ import './image.js';
11
+
12
+ describe('chatgpt browser command registration', () => {
13
+ it('registers the baseline web chat commands with site-level reuse', () => {
14
+ const expectedAccess = {
15
+ ask: 'write',
16
+ send: 'write',
17
+ read: 'read',
18
+ history: 'read',
19
+ detail: 'read',
20
+ new: 'read',
21
+ status: 'read',
22
+ image: 'write',
23
+ };
24
+
25
+ for (const [name, access] of Object.entries(expectedAccess)) {
26
+ const cmd = getRegistry().get(`chatgpt/${name}`);
27
+ expect(cmd, `chatgpt/${name}`).toBeDefined();
28
+ expect(cmd.site).toBe('chatgpt');
29
+ expect(cmd.domain).toBe('chatgpt.com');
30
+ expect(cmd.strategy).toBe('cookie');
31
+ expect(cmd.browser).toBe(true);
32
+ expect(cmd.browserSession).toEqual({ reuse: 'site' });
33
+ expect(cmd.navigateBefore).toBe(false);
34
+ expect(cmd.access).toBe(access);
35
+ }
36
+ });
37
+
38
+ it('keeps ask timeout as the runtime-visible integer timeout arg', () => {
39
+ const ask = getRegistry().get('chatgpt/ask');
40
+ expect(ask.args).toEqual(expect.arrayContaining([
41
+ expect.objectContaining({ name: 'timeout', type: 'int', default: 120 }),
42
+ expect.objectContaining({ name: 'new', type: 'boolean', default: false }),
43
+ ]));
44
+ });
45
+ });
@@ -0,0 +1,46 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { EmptyResultError } from '@jackwener/opencli/errors';
3
+ import {
4
+ CHATGPT_DOMAIN,
5
+ CHATGPT_URL,
6
+ ensureChatGPTLogin,
7
+ getVisibleMessages,
8
+ messageHtmlToMarkdown,
9
+ normalizeBooleanFlag,
10
+ parseChatGPTConversationId,
11
+ } from './utils.js';
12
+
13
+ export const detailCommand = cli({
14
+ site: 'chatgpt',
15
+ name: 'detail',
16
+ access: 'read',
17
+ description: 'Open a ChatGPT web conversation by ID and read its messages',
18
+ domain: CHATGPT_DOMAIN,
19
+ strategy: Strategy.COOKIE,
20
+ browser: true,
21
+ browserSession: { reuse: 'site' },
22
+ navigateBefore: false,
23
+ args: [
24
+ { name: 'id', positional: true, required: true, help: 'Conversation ID or full /c/<id> URL' },
25
+ { name: 'markdown', type: 'boolean', default: false, help: 'Emit assistant replies as markdown' },
26
+ ],
27
+ columns: ['Index', 'Role', 'Text'],
28
+ func: async (page, kwargs) => {
29
+ const id = parseChatGPTConversationId(kwargs.id);
30
+ const wantMarkdown = normalizeBooleanFlag(kwargs.markdown, false);
31
+ await page.goto(`${CHATGPT_URL}/c/${id}`, { settleMs: 2000 });
32
+ await page.wait(4);
33
+ await ensureChatGPTLogin(page, 'ChatGPT detail requires a logged-in ChatGPT session.');
34
+ const messages = await getVisibleMessages(page);
35
+ if (!messages.length) {
36
+ throw new EmptyResultError('chatgpt detail', `No visible ChatGPT messages were found for conversation ${id}.`);
37
+ }
38
+ return messages.map((message) => ({
39
+ Index: message.Index,
40
+ Role: message.Role,
41
+ Text: wantMarkdown && message.Role === 'Assistant' && message.Html
42
+ ? (messageHtmlToMarkdown(message.Html) || message.Text)
43
+ : message.Text,
44
+ }));
45
+ },
46
+ });
@@ -0,0 +1,39 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { EmptyResultError } from '@jackwener/opencli/errors';
3
+ import {
4
+ CHATGPT_DOMAIN,
5
+ ensureChatGPTLogin,
6
+ ensureOnChatGPT,
7
+ getConversationList,
8
+ requirePositiveInt,
9
+ } from './utils.js';
10
+
11
+ export const historyCommand = cli({
12
+ site: 'chatgpt',
13
+ name: 'history',
14
+ access: 'read',
15
+ description: 'List visible ChatGPT web conversation history from the sidebar',
16
+ domain: CHATGPT_DOMAIN,
17
+ strategy: Strategy.COOKIE,
18
+ browser: true,
19
+ browserSession: { reuse: 'site' },
20
+ navigateBefore: false,
21
+ args: [
22
+ { name: 'limit', type: 'int', default: 20, help: 'Max conversations to show' },
23
+ ],
24
+ columns: ['Index', 'Id', 'Title', 'Url'],
25
+ func: async (page, kwargs) => {
26
+ const limit = requirePositiveInt(
27
+ Number(kwargs.limit ?? 20),
28
+ 'chatgpt history --limit',
29
+ 'Example: opencli chatgpt history --limit 20',
30
+ );
31
+ await ensureOnChatGPT(page);
32
+ await ensureChatGPTLogin(page, 'ChatGPT history requires a logged-in ChatGPT session.');
33
+ const conversations = await getConversationList(page);
34
+ if (!conversations.length) {
35
+ throw new EmptyResultError('chatgpt history', 'No ChatGPT conversation links were visible in the sidebar.');
36
+ }
37
+ return conversations.slice(0, limit);
38
+ },
39
+ });
@@ -3,8 +3,8 @@ import * as path from 'node:path';
3
3
  import * as fs from 'node:fs';
4
4
  import { cli, Strategy } from '@jackwener/opencli/registry';
5
5
  import { saveBase64ToFile } from '@jackwener/opencli/utils';
6
- import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
7
- import { getChatGPTVisibleImageUrls, sendChatGPTMessage, waitForChatGPTImages, getChatGPTImageAssets } from './utils.js';
6
+ import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
7
+ import { getChatGPTVisibleImageUrls, normalizeBooleanFlag, sendChatGPTMessage, waitForChatGPTImages, getChatGPTImageAssets } from './utils.js';
8
8
 
9
9
  const CHATGPT_DOMAIN = 'chatgpt.com';
10
10
 
@@ -15,12 +15,6 @@ function extFromMime(mime) {
15
15
  return '.jpg';
16
16
  }
17
17
 
18
- function normalizeBooleanFlag(value) {
19
- if (typeof value === 'boolean') return value;
20
- const normalized = String(value ?? '').trim().toLowerCase();
21
- return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
22
- }
23
-
24
18
  function displayPath(filePath) {
25
19
  const home = os.homedir();
26
20
  return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath;
@@ -55,13 +49,14 @@ export const imageCommand = cli({
55
49
  domain: CHATGPT_DOMAIN,
56
50
  strategy: Strategy.COOKIE,
57
51
  browser: true,
52
+ browserSession: { reuse: 'site' },
58
53
  navigateBefore: false,
59
54
  defaultFormat: 'plain',
60
- timeoutSeconds: 240,
61
55
  args: [
62
56
  { name: 'prompt', positional: true, required: true, help: 'Image prompt to send to ChatGPT' },
63
57
  { name: 'op', help: 'Output directory (default: ~/Pictures/chatgpt)' },
64
58
  { name: 'sd', type: 'boolean', default: false, help: 'Skip download shorthand; only show ChatGPT link' },
59
+ { name: 'timeout', type: 'int', required: false, default: 240, help: 'Max seconds for the overall command (default: 240)' },
65
60
  ],
66
61
  columns: ['status', 'file', 'link'],
67
62
  func: async (page, kwargs) => {
@@ -69,7 +64,10 @@ export const imageCommand = cli({
69
64
  const outputDir = resolveOutputDir(kwargs.op);
70
65
  const skipDownloadRaw = kwargs.sd;
71
66
  const skipDownload = skipDownloadRaw === '' || skipDownloadRaw === true || normalizeBooleanFlag(skipDownloadRaw);
72
- const timeout = 120;
67
+ const timeout = kwargs.timeout;
68
+ if (!Number.isInteger(timeout) || timeout < 1) {
69
+ throw new ArgumentError('--timeout must be a positive integer (seconds)');
70
+ }
73
71
 
74
72
  // Navigate to chatgpt.com/new with full reload to clear React sidebar state
75
73
  await page.goto(`https://${CHATGPT_DOMAIN}/new`, { settleMs: 2000 });
@@ -79,7 +77,10 @@ export const imageCommand = cli({
79
77
  // Send the image generation prompt - must be explicit
80
78
  const sent = await sendChatGPTMessage(page, `Generate an image of: ${prompt}`);
81
79
  if (!sent) {
82
- return [{ status: '⚠️ send-failed', file: '📁 -', link: `🔗 ${await currentChatGPTLink(page)}` }];
80
+ throw new CommandExecutionError(
81
+ 'Failed to send image prompt to ChatGPT',
82
+ `Open ${await currentChatGPTLink(page)} and verify the composer is ready.`,
83
+ );
83
84
  }
84
85
 
85
86
  // ChatGPT briefly navigates to /c/{id} after sending, then may
@@ -12,6 +12,12 @@ const mocks = vi.hoisted(() => ({
12
12
 
13
13
  vi.mock('./utils.js', () => ({
14
14
  getChatGPTVisibleImageUrls: mocks.getChatGPTVisibleImageUrls,
15
+ normalizeBooleanFlag: (value, fallback = false) => {
16
+ if (typeof value === 'boolean') return value;
17
+ if (value == null || value === '') return fallback;
18
+ const normalized = String(value).trim().toLowerCase();
19
+ return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
20
+ },
15
21
  sendChatGPTMessage: mocks.sendChatGPTMessage,
16
22
  waitForChatGPTImages: mocks.waitForChatGPTImages,
17
23
  getChatGPTImageAssets: mocks.getChatGPTImageAssets,
@@ -63,6 +69,21 @@ describe('chatgpt image output paths', () => {
63
69
  });
64
70
 
65
71
  describe('chatgpt image failure contracts', () => {
72
+ it('fails fast when the image prompt cannot be sent', async () => {
73
+ mocks.sendChatGPTMessage.mockResolvedValue(false);
74
+
75
+ await expect(imageCommand.func(createPage(), {
76
+ prompt: 'cat',
77
+ op: '',
78
+ sd: false,
79
+ timeout: 240,
80
+ })).rejects.toMatchObject({
81
+ code: 'COMMAND_EXEC',
82
+ message: expect.stringContaining('Failed to send image prompt to ChatGPT'),
83
+ });
84
+ expect(mocks.waitForChatGPTImages).not.toHaveBeenCalled();
85
+ });
86
+
66
87
  it('fails fast when image generation detection finds no new images', async () => {
67
88
  mocks.waitForChatGPTImages.mockResolvedValue([]);
68
89
 
@@ -70,6 +91,7 @@ describe('chatgpt image failure contracts', () => {
70
91
  prompt: 'cat',
71
92
  op: '',
72
93
  sd: false,
94
+ timeout: 240,
73
95
  })).rejects.toMatchObject({
74
96
  code: 'EMPTY_RESULT',
75
97
  message: expect.stringContaining('chatgpt image returned no data'),
@@ -84,6 +106,7 @@ describe('chatgpt image failure contracts', () => {
84
106
  prompt: 'cat',
85
107
  op: '',
86
108
  sd: false,
109
+ timeout: 240,
87
110
  })).rejects.toMatchObject({
88
111
  code: 'COMMAND_EXEC',
89
112
  message: expect.stringContaining('Failed to export generated ChatGPT image assets'),
@@ -0,0 +1,25 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import {
3
+ CHATGPT_DOMAIN,
4
+ ensureChatGPTComposer,
5
+ startNewChat,
6
+ } from './utils.js';
7
+
8
+ export const newCommand = cli({
9
+ site: 'chatgpt',
10
+ name: 'new',
11
+ access: 'read',
12
+ description: 'Start a new ChatGPT web conversation',
13
+ domain: CHATGPT_DOMAIN,
14
+ strategy: Strategy.COOKIE,
15
+ browser: true,
16
+ browserSession: { reuse: 'site' },
17
+ navigateBefore: false,
18
+ args: [],
19
+ columns: ['Status'],
20
+ func: async (page) => {
21
+ await startNewChat(page);
22
+ await ensureChatGPTComposer(page, 'ChatGPT new requires a logged-in ChatGPT session with a visible composer.');
23
+ return [{ Status: 'New chat started' }];
24
+ },
25
+ });
@@ -0,0 +1,43 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { EmptyResultError } from '@jackwener/opencli/errors';
3
+ import {
4
+ CHATGPT_DOMAIN,
5
+ ensureChatGPTLogin,
6
+ ensureOnChatGPT,
7
+ getVisibleMessages,
8
+ messageHtmlToMarkdown,
9
+ normalizeBooleanFlag,
10
+ } from './utils.js';
11
+
12
+ export const readCommand = cli({
13
+ site: 'chatgpt',
14
+ name: 'read',
15
+ access: 'read',
16
+ description: 'Read messages in the current ChatGPT web conversation',
17
+ domain: CHATGPT_DOMAIN,
18
+ strategy: Strategy.COOKIE,
19
+ browser: true,
20
+ browserSession: { reuse: 'site' },
21
+ navigateBefore: false,
22
+ args: [
23
+ { name: 'markdown', type: 'boolean', default: false, help: 'Emit assistant replies as markdown' },
24
+ ],
25
+ columns: ['Index', 'Role', 'Text'],
26
+ func: async (page, kwargs) => {
27
+ const wantMarkdown = normalizeBooleanFlag(kwargs.markdown, false);
28
+ await ensureOnChatGPT(page);
29
+ await page.wait(2);
30
+ await ensureChatGPTLogin(page, 'ChatGPT read requires a logged-in ChatGPT session.');
31
+ const messages = await getVisibleMessages(page);
32
+ if (!messages.length) {
33
+ throw new EmptyResultError('chatgpt read', 'No visible ChatGPT messages were found in the current conversation.');
34
+ }
35
+ return messages.map((message) => ({
36
+ Index: message.Index,
37
+ Role: message.Role,
38
+ Text: wantMarkdown && message.Role === 'Assistant' && message.Html
39
+ ? (messageHtmlToMarkdown(message.Html) || message.Text)
40
+ : message.Text,
41
+ }));
42
+ },
43
+ });
@@ -0,0 +1,46 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { CommandExecutionError } from '@jackwener/opencli/errors';
3
+ import {
4
+ CHATGPT_DOMAIN,
5
+ CHATGPT_URL,
6
+ ensureChatGPTComposer,
7
+ ensureOnChatGPT,
8
+ normalizeBooleanFlag,
9
+ requireNonEmptyPrompt,
10
+ sendChatGPTMessage,
11
+ startNewChat,
12
+ } from './utils.js';
13
+
14
+ export const sendCommand = cli({
15
+ site: 'chatgpt',
16
+ name: 'send',
17
+ access: 'write',
18
+ description: 'Send a prompt to ChatGPT web without waiting for the response',
19
+ domain: CHATGPT_DOMAIN,
20
+ strategy: Strategy.COOKIE,
21
+ browser: true,
22
+ browserSession: { reuse: 'site' },
23
+ navigateBefore: false,
24
+ args: [
25
+ { name: 'prompt', positional: true, required: true, help: 'Prompt to send' },
26
+ { name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
27
+ ],
28
+ columns: ['Status', 'InjectedText'],
29
+ func: async (page, kwargs) => {
30
+ const prompt = requireNonEmptyPrompt(kwargs.prompt, 'chatgpt send');
31
+
32
+ if (normalizeBooleanFlag(kwargs.new)) {
33
+ await startNewChat(page);
34
+ } else {
35
+ await ensureOnChatGPT(page);
36
+ }
37
+ await page.wait(2);
38
+ await ensureChatGPTComposer(page, 'ChatGPT send requires a logged-in ChatGPT session with a visible composer.');
39
+
40
+ const sent = await sendChatGPTMessage(page, prompt);
41
+ if (!sent) {
42
+ throw new CommandExecutionError('Failed to send message to ChatGPT', `Open ${CHATGPT_URL} and verify the composer is ready.`);
43
+ }
44
+ return [{ Status: 'Success', InjectedText: prompt }];
45
+ },
46
+ });
@@ -0,0 +1,29 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import {
3
+ CHATGPT_DOMAIN,
4
+ ensureOnChatGPT,
5
+ getPageState,
6
+ } from './utils.js';
7
+
8
+ export const statusCommand = cli({
9
+ site: 'chatgpt',
10
+ name: 'status',
11
+ access: 'read',
12
+ description: 'Check ChatGPT web page availability and login state',
13
+ domain: CHATGPT_DOMAIN,
14
+ strategy: Strategy.COOKIE,
15
+ browser: true,
16
+ browserSession: { reuse: 'site' },
17
+ navigateBefore: false,
18
+ args: [],
19
+ columns: ['Status', 'Login', 'Url'],
20
+ func: async (page) => {
21
+ await ensureOnChatGPT(page);
22
+ const state = await getPageState(page);
23
+ return [{
24
+ Status: state.hasComposer ? 'Connected' : 'Page not ready',
25
+ Login: state.isLoggedIn && !state.hasLoginGate ? 'Yes' : 'No',
26
+ Url: state.url,
27
+ }];
28
+ },
29
+ });