@jackwener/opencli 1.7.11 → 1.7.12

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 (730) hide show
  1. package/README.zh-CN.md +2 -1
  2. package/cli-manifest.json +1417 -24
  3. package/clis/1688/assets.js +1 -0
  4. package/clis/1688/download.js +1 -0
  5. package/clis/1688/item.js +1 -0
  6. package/clis/1688/search.js +2 -1
  7. package/clis/1688/store.js +1 -0
  8. package/clis/36kr/article.js +1 -0
  9. package/clis/36kr/hot.js +1 -0
  10. package/clis/36kr/news.js +1 -0
  11. package/clis/36kr/search.js +1 -0
  12. package/clis/51job/company.js +1 -0
  13. package/clis/51job/detail.js +1 -0
  14. package/clis/51job/hot.js +1 -0
  15. package/clis/51job/search.js +1 -0
  16. package/clis/amazon/bestsellers.js +1 -0
  17. package/clis/amazon/discussion.js +1 -0
  18. package/clis/amazon/movers-shakers.js +1 -0
  19. package/clis/amazon/new-releases.js +1 -0
  20. package/clis/amazon/offer.js +1 -0
  21. package/clis/amazon/product.js +1 -0
  22. package/clis/amazon/rankings.js +1 -0
  23. package/clis/amazon/search.js +1 -0
  24. package/clis/antigravity/dump.js +1 -0
  25. package/clis/antigravity/extract-code.js +1 -0
  26. package/clis/antigravity/model.js +1 -0
  27. package/clis/antigravity/new.js +1 -0
  28. package/clis/antigravity/read.js +1 -0
  29. package/clis/antigravity/send.js +1 -0
  30. package/clis/antigravity/status.js +1 -0
  31. package/clis/antigravity/watch.js +1 -0
  32. package/clis/apple-podcasts/episodes.js +1 -0
  33. package/clis/apple-podcasts/search.js +1 -0
  34. package/clis/apple-podcasts/top.js +1 -0
  35. package/clis/arxiv/arxiv.test.js +112 -0
  36. package/clis/arxiv/paper.js +4 -3
  37. package/clis/arxiv/recent.js +33 -0
  38. package/clis/arxiv/search.js +19 -7
  39. package/clis/arxiv/utils.js +68 -5
  40. package/clis/baidu-scholar/search.js +1 -0
  41. package/clis/band/bands.js +1 -0
  42. package/clis/band/mentions.js +1 -0
  43. package/clis/band/post.js +1 -0
  44. package/clis/band/posts.js +1 -0
  45. package/clis/barchart/flow.js +1 -0
  46. package/clis/barchart/greeks.js +1 -0
  47. package/clis/barchart/options.js +1 -0
  48. package/clis/barchart/quote.js +1 -0
  49. package/clis/bbc/news.js +1 -0
  50. package/clis/bilibili/comments.js +1 -0
  51. package/clis/bilibili/download.js +1 -0
  52. package/clis/bilibili/dynamic.js +1 -0
  53. package/clis/bilibili/favorite.js +1 -0
  54. package/clis/bilibili/feed.js +2 -0
  55. package/clis/bilibili/following.js +1 -0
  56. package/clis/bilibili/history.js +1 -0
  57. package/clis/bilibili/hot.js +6 -1
  58. package/clis/bilibili/hot.test.js +17 -0
  59. package/clis/bilibili/me.js +1 -1
  60. package/clis/bilibili/ranking.js +1 -0
  61. package/clis/bilibili/search.js +1 -1
  62. package/clis/bilibili/subtitle.js +1 -0
  63. package/clis/bilibili/user-videos.js +1 -0
  64. package/clis/bilibili/video.js +1 -0
  65. package/clis/binance/asks.js +1 -0
  66. package/clis/binance/depth.js +1 -0
  67. package/clis/binance/gainers.js +1 -0
  68. package/clis/binance/klines.js +1 -0
  69. package/clis/binance/losers.js +1 -0
  70. package/clis/binance/pairs.js +1 -0
  71. package/clis/binance/price.js +1 -0
  72. package/clis/binance/prices.js +1 -0
  73. package/clis/binance/ticker.js +1 -0
  74. package/clis/binance/top.js +1 -0
  75. package/clis/binance/trades.js +1 -0
  76. package/clis/bloomberg/businessweek.js +1 -0
  77. package/clis/bloomberg/economics.js +1 -0
  78. package/clis/bloomberg/feeds.js +1 -0
  79. package/clis/bloomberg/industries.js +1 -0
  80. package/clis/bloomberg/main.js +1 -0
  81. package/clis/bloomberg/markets.js +1 -0
  82. package/clis/bloomberg/news.js +1 -0
  83. package/clis/bloomberg/opinions.js +1 -0
  84. package/clis/bloomberg/politics.js +1 -0
  85. package/clis/bloomberg/tech.js +1 -0
  86. package/clis/bluesky/feeds.js +1 -0
  87. package/clis/bluesky/followers.js +1 -0
  88. package/clis/bluesky/following.js +1 -0
  89. package/clis/bluesky/profile.js +1 -0
  90. package/clis/bluesky/search.js +1 -0
  91. package/clis/bluesky/starter-packs.js +1 -0
  92. package/clis/bluesky/thread.js +1 -0
  93. package/clis/bluesky/trending.js +1 -0
  94. package/clis/bluesky/user.js +3 -1
  95. package/clis/boss/batchgreet.js +1 -0
  96. package/clis/boss/chatlist.js +1 -0
  97. package/clis/boss/chatmsg.js +1 -0
  98. package/clis/boss/detail.js +1 -0
  99. package/clis/boss/exchange.js +1 -0
  100. package/clis/boss/greet.js +1 -0
  101. package/clis/boss/invite.js +1 -0
  102. package/clis/boss/joblist.js +1 -0
  103. package/clis/boss/mark.js +1 -0
  104. package/clis/boss/recommend.js +1 -0
  105. package/clis/boss/resume.js +1 -0
  106. package/clis/boss/search.js +1 -0
  107. package/clis/boss/send.js +1 -0
  108. package/clis/boss/stats.js +1 -0
  109. package/clis/chaoxing/assignments.js +1 -0
  110. package/clis/chaoxing/exams.js +1 -0
  111. package/clis/chatgpt/image.js +1 -0
  112. package/clis/chatgpt-app/ask.js +1 -0
  113. package/clis/chatgpt-app/model.js +1 -0
  114. package/clis/chatgpt-app/new.js +1 -0
  115. package/clis/chatgpt-app/read.js +1 -0
  116. package/clis/chatgpt-app/send.js +1 -0
  117. package/clis/chatgpt-app/status.js +1 -0
  118. package/clis/chatwise/ask.js +1 -0
  119. package/clis/chatwise/export.js +1 -0
  120. package/clis/chatwise/history.js +1 -0
  121. package/clis/chatwise/model.js +1 -0
  122. package/clis/chatwise/read.js +1 -0
  123. package/clis/chatwise/send.js +1 -0
  124. package/clis/claude/ask.js +1 -0
  125. package/clis/claude/detail.js +1 -0
  126. package/clis/claude/history.js +1 -0
  127. package/clis/claude/new.js +1 -0
  128. package/clis/claude/read.js +1 -0
  129. package/clis/claude/send.js +1 -0
  130. package/clis/claude/status.js +1 -0
  131. package/clis/cnki/search.js +1 -0
  132. package/clis/codex/ask.js +1 -0
  133. package/clis/codex/export.js +1 -0
  134. package/clis/codex/extract-diff.js +1 -0
  135. package/clis/codex/history.js +1 -0
  136. package/clis/codex/model.js +1 -0
  137. package/clis/codex/read.js +1 -0
  138. package/clis/codex/send.js +1 -0
  139. package/clis/coupang/add-to-cart.js +1 -0
  140. package/clis/coupang/search.js +1 -0
  141. package/clis/ctrip/search.js +1 -0
  142. package/clis/cursor/ask.js +1 -0
  143. package/clis/cursor/composer.js +1 -0
  144. package/clis/cursor/export.js +1 -0
  145. package/clis/cursor/extract-code.js +1 -0
  146. package/clis/cursor/history.js +1 -0
  147. package/clis/cursor/model.js +1 -0
  148. package/clis/cursor/read.js +1 -0
  149. package/clis/cursor/send.js +1 -0
  150. package/clis/dblp/dblp.test.js +397 -0
  151. package/clis/dblp/paper.js +40 -0
  152. package/clis/dblp/search.js +45 -0
  153. package/clis/dblp/utils.js +290 -0
  154. package/clis/deepseek/ask.js +1 -0
  155. package/clis/deepseek/history.js +1 -0
  156. package/clis/deepseek/new.js +1 -0
  157. package/clis/deepseek/read.js +1 -0
  158. package/clis/deepseek/status.js +1 -0
  159. package/clis/devto/devto.test.js +236 -0
  160. package/clis/devto/read.js +103 -0
  161. package/clis/devto/tag.js +5 -1
  162. package/clis/devto/top.js +5 -1
  163. package/clis/devto/user.js +5 -1
  164. package/clis/dianping/__fixtures__/search.html +168 -0
  165. package/clis/dianping/__fixtures__/shop.html +6 -0
  166. package/clis/dianping/dianping.test.js +424 -0
  167. package/clis/dianping/search.js +154 -0
  168. package/clis/dianping/shop.js +173 -0
  169. package/clis/dianping/utils.js +157 -0
  170. package/clis/dictionary/examples.js +1 -0
  171. package/clis/dictionary/search.js +1 -0
  172. package/clis/dictionary/synonyms.js +1 -0
  173. package/clis/discord-app/channels.js +1 -0
  174. package/clis/discord-app/delete.js +1 -0
  175. package/clis/discord-app/members.js +1 -0
  176. package/clis/discord-app/read.js +1 -0
  177. package/clis/discord-app/search.js +1 -0
  178. package/clis/discord-app/send.js +1 -0
  179. package/clis/discord-app/servers.js +1 -0
  180. package/clis/discord-app/status.js +1 -0
  181. package/clis/douban/book-hot.js +1 -0
  182. package/clis/douban/download.js +1 -0
  183. package/clis/douban/marks.js +1 -0
  184. package/clis/douban/movie-hot.js +2 -1
  185. package/clis/douban/movie-hot.test.js +14 -0
  186. package/clis/douban/photos.js +2 -1
  187. package/clis/douban/reviews.js +1 -0
  188. package/clis/douban/search.js +1 -0
  189. package/clis/douban/subject.js +1 -0
  190. package/clis/douban/top250.js +1 -0
  191. package/clis/douban/utils.js +11 -13
  192. package/clis/douban/utils.test.js +79 -0
  193. package/clis/doubao/ask.js +1 -0
  194. package/clis/doubao/detail.js +1 -0
  195. package/clis/doubao/history.js +1 -0
  196. package/clis/doubao/meeting-summary.js +1 -0
  197. package/clis/doubao/meeting-transcript.js +1 -0
  198. package/clis/doubao/new.js +1 -0
  199. package/clis/doubao/read.js +1 -0
  200. package/clis/doubao/send.js +1 -0
  201. package/clis/doubao/status.js +1 -0
  202. package/clis/doubao-app/ask.js +1 -0
  203. package/clis/doubao-app/dump.js +1 -0
  204. package/clis/doubao-app/new.js +1 -0
  205. package/clis/doubao-app/read.js +1 -0
  206. package/clis/doubao-app/screenshot.js +1 -0
  207. package/clis/doubao-app/send.js +1 -0
  208. package/clis/doubao-app/status.js +1 -0
  209. package/clis/douyin/activities.js +1 -0
  210. package/clis/douyin/collections.js +1 -0
  211. package/clis/douyin/delete.js +1 -0
  212. package/clis/douyin/draft.js +1 -0
  213. package/clis/douyin/drafts.js +1 -0
  214. package/clis/douyin/hashtag.js +1 -0
  215. package/clis/douyin/location.js +1 -0
  216. package/clis/douyin/profile.js +1 -0
  217. package/clis/douyin/publish.js +1 -0
  218. package/clis/douyin/stats.js +1 -0
  219. package/clis/douyin/update.js +1 -0
  220. package/clis/douyin/user-videos.js +1 -0
  221. package/clis/douyin/videos.js +1 -0
  222. package/clis/eastmoney/announcement.js +1 -0
  223. package/clis/eastmoney/convertible.js +1 -0
  224. package/clis/eastmoney/etf.js +1 -0
  225. package/clis/eastmoney/holders.js +1 -0
  226. package/clis/eastmoney/hot-rank.js +1 -0
  227. package/clis/eastmoney/index-board.js +1 -0
  228. package/clis/eastmoney/kline.js +1 -0
  229. package/clis/eastmoney/kuaixun.js +1 -0
  230. package/clis/eastmoney/longhu.js +1 -0
  231. package/clis/eastmoney/money-flow.js +1 -0
  232. package/clis/eastmoney/northbound.js +1 -0
  233. package/clis/eastmoney/quote.js +1 -0
  234. package/clis/eastmoney/rank.js +1 -0
  235. package/clis/eastmoney/sectors.js +1 -0
  236. package/clis/facebook/add-friend.js +1 -0
  237. package/clis/facebook/events.js +1 -0
  238. package/clis/facebook/feed.js +1 -0
  239. package/clis/facebook/friends.js +1 -0
  240. package/clis/facebook/groups.js +1 -0
  241. package/clis/facebook/join-group.js +1 -0
  242. package/clis/facebook/marketplace-inbox.js +1 -0
  243. package/clis/facebook/marketplace-listings.js +1 -0
  244. package/clis/facebook/memories.js +1 -0
  245. package/clis/facebook/notifications.js +1 -0
  246. package/clis/facebook/profile.js +1 -0
  247. package/clis/facebook/search.js +1 -0
  248. package/clis/gemini/ask.js +1 -0
  249. package/clis/gemini/deep-research-result.js +1 -0
  250. package/clis/gemini/deep-research.js +1 -0
  251. package/clis/gemini/image.js +1 -0
  252. package/clis/gemini/new.js +1 -0
  253. package/clis/gitee/search.js +1 -0
  254. package/clis/gitee/trending.js +1 -0
  255. package/clis/gitee/user.js +1 -0
  256. package/clis/google/news.js +1 -0
  257. package/clis/google/search.js +1 -0
  258. package/clis/google/suggest.js +1 -0
  259. package/clis/google/trends.js +1 -0
  260. package/clis/google-scholar/cite.js +1 -0
  261. package/clis/google-scholar/profile.js +1 -0
  262. package/clis/google-scholar/search.js +1 -0
  263. package/clis/gov-law/recent.js +1 -0
  264. package/clis/gov-law/search.js +1 -0
  265. package/clis/gov-policy/recent.js +1 -0
  266. package/clis/gov-policy/search.js +1 -0
  267. package/clis/grok/ask.js +1 -0
  268. package/clis/grok/image.ts +1 -0
  269. package/clis/hackernews/ask.js +3 -1
  270. package/clis/hackernews/best.js +3 -1
  271. package/clis/hackernews/hackernews.test.js +132 -0
  272. package/clis/hackernews/jobs.js +3 -1
  273. package/clis/hackernews/new.js +3 -1
  274. package/clis/hackernews/read.js +188 -0
  275. package/clis/hackernews/search.js +3 -1
  276. package/clis/hackernews/show.js +3 -1
  277. package/clis/hackernews/top.js +3 -1
  278. package/clis/hackernews/user.js +1 -0
  279. package/clis/hf/top.js +1 -0
  280. package/clis/hupu/detail.js +1 -0
  281. package/clis/hupu/hot.js +3 -1
  282. package/clis/hupu/like.js +1 -0
  283. package/clis/hupu/mentions.js +2 -1
  284. package/clis/hupu/reply.js +1 -0
  285. package/clis/hupu/search.js +3 -1
  286. package/clis/hupu/unlike.js +1 -0
  287. package/clis/imdb/person.js +1 -0
  288. package/clis/imdb/reviews.js +1 -0
  289. package/clis/imdb/search.js +1 -0
  290. package/clis/imdb/title.js +1 -0
  291. package/clis/imdb/top.js +1 -0
  292. package/clis/imdb/trending.js +1 -0
  293. package/clis/indeed/indeed.test.js +375 -0
  294. package/clis/indeed/job.js +86 -0
  295. package/clis/indeed/search.js +110 -0
  296. package/clis/indeed/utils.js +152 -0
  297. package/clis/instagram/collection-create.js +1 -0
  298. package/clis/instagram/collection-delete.js +1 -0
  299. package/clis/instagram/comment.js +1 -0
  300. package/clis/instagram/download.js +1 -0
  301. package/clis/instagram/explore.js +1 -0
  302. package/clis/instagram/follow.js +1 -0
  303. package/clis/instagram/followers.js +1 -0
  304. package/clis/instagram/following.js +1 -0
  305. package/clis/instagram/like.js +1 -0
  306. package/clis/instagram/note.js +1 -0
  307. package/clis/instagram/post.js +1 -0
  308. package/clis/instagram/profile.js +1 -0
  309. package/clis/instagram/reel.js +1 -0
  310. package/clis/instagram/save.js +1 -0
  311. package/clis/instagram/saved.js +1 -0
  312. package/clis/instagram/search.js +1 -0
  313. package/clis/instagram/story.js +1 -0
  314. package/clis/instagram/unfollow.js +1 -0
  315. package/clis/instagram/unlike.js +1 -0
  316. package/clis/instagram/unsave.js +1 -0
  317. package/clis/instagram/user.js +1 -0
  318. package/clis/jd/add-cart.js +1 -0
  319. package/clis/jd/cart.js +1 -0
  320. package/clis/jd/detail.js +1 -0
  321. package/clis/jd/item.js +1 -0
  322. package/clis/jd/reviews.js +1 -0
  323. package/clis/jd/search.js +1 -0
  324. package/clis/jianyu/detail.js +1 -0
  325. package/clis/jianyu/search.js +1 -0
  326. package/clis/jike/comment.js +1 -0
  327. package/clis/jike/create.js +1 -0
  328. package/clis/jike/feed.js +3 -1
  329. package/clis/jike/like.js +1 -0
  330. package/clis/jike/notifications.js +1 -0
  331. package/clis/jike/post.js +1 -0
  332. package/clis/jike/repost.js +1 -0
  333. package/clis/jike/search.js +3 -1
  334. package/clis/jike/topic.js +1 -0
  335. package/clis/jike/user.js +3 -1
  336. package/clis/jimeng/generate.js +1 -0
  337. package/clis/jimeng/history.js +1 -0
  338. package/clis/jimeng/new.js +1 -0
  339. package/clis/jimeng/workspaces.js +1 -0
  340. package/clis/ke/chengjiao.js +1 -0
  341. package/clis/ke/ershoufang.js +1 -0
  342. package/clis/ke/xiaoqu.js +1 -0
  343. package/clis/ke/zufang.js +1 -0
  344. package/clis/lesswrong/comments.js +1 -0
  345. package/clis/lesswrong/curated.js +1 -0
  346. package/clis/lesswrong/frontpage.js +1 -0
  347. package/clis/lesswrong/new.js +1 -0
  348. package/clis/lesswrong/read.js +1 -0
  349. package/clis/lesswrong/sequences.js +1 -0
  350. package/clis/lesswrong/shortform.js +1 -0
  351. package/clis/lesswrong/tag.js +1 -0
  352. package/clis/lesswrong/tags.js +1 -0
  353. package/clis/lesswrong/top-month.js +1 -0
  354. package/clis/lesswrong/top-week.js +1 -0
  355. package/clis/lesswrong/top-year.js +1 -0
  356. package/clis/lesswrong/top.js +1 -0
  357. package/clis/lesswrong/user-posts.js +1 -0
  358. package/clis/lesswrong/user.js +1 -0
  359. package/clis/linkedin/search.js +1 -0
  360. package/clis/linkedin/timeline.js +1 -0
  361. package/clis/linux-do/categories.js +1 -0
  362. package/clis/linux-do/category.js +1 -0
  363. package/clis/linux-do/feed.js +1 -0
  364. package/clis/linux-do/hot.js +1 -0
  365. package/clis/linux-do/latest.js +1 -0
  366. package/clis/linux-do/search.js +1 -0
  367. package/clis/linux-do/tags.js +2 -1
  368. package/clis/linux-do/topic-content.js +1 -0
  369. package/clis/linux-do/topic.js +1 -0
  370. package/clis/linux-do/user-posts.js +1 -0
  371. package/clis/linux-do/user-topics.js +1 -0
  372. package/clis/lobsters/active.js +4 -1
  373. package/clis/lobsters/hot.js +4 -1
  374. package/clis/lobsters/lobsters.test.js +169 -0
  375. package/clis/lobsters/newest.js +4 -1
  376. package/clis/lobsters/read.js +196 -0
  377. package/clis/lobsters/tag.js +4 -1
  378. package/clis/maimai/search-talents.js +1 -0
  379. package/clis/medium/feed.js +1 -0
  380. package/clis/medium/search.js +1 -0
  381. package/clis/medium/user.js +1 -0
  382. package/clis/mubu/doc.js +1 -0
  383. package/clis/mubu/docs.js +1 -0
  384. package/clis/mubu/notes.js +1 -0
  385. package/clis/mubu/recent.js +1 -0
  386. package/clis/mubu/search.js +1 -0
  387. package/clis/notebooklm/current.js +1 -0
  388. package/clis/notebooklm/get.js +1 -0
  389. package/clis/notebooklm/history.js +1 -0
  390. package/clis/notebooklm/list.js +1 -0
  391. package/clis/notebooklm/note-list.js +1 -0
  392. package/clis/notebooklm/notes-get.js +1 -0
  393. package/clis/notebooklm/open.js +1 -0
  394. package/clis/notebooklm/source-fulltext.js +1 -0
  395. package/clis/notebooklm/source-get.js +1 -0
  396. package/clis/notebooklm/source-guide.js +1 -0
  397. package/clis/notebooklm/source-list.js +1 -0
  398. package/clis/notebooklm/status.js +1 -0
  399. package/clis/notebooklm/summary.js +1 -0
  400. package/clis/notion/export.js +1 -0
  401. package/clis/notion/favorites.js +1 -0
  402. package/clis/notion/new.js +1 -0
  403. package/clis/notion/read.js +1 -0
  404. package/clis/notion/search.js +1 -0
  405. package/clis/notion/sidebar.js +1 -0
  406. package/clis/notion/status.js +1 -0
  407. package/clis/notion/write.js +1 -0
  408. package/clis/nowcoder/companies.js +1 -0
  409. package/clis/nowcoder/creators.js +1 -0
  410. package/clis/nowcoder/detail.js +1 -0
  411. package/clis/nowcoder/experience.js +1 -0
  412. package/clis/nowcoder/hot.js +1 -0
  413. package/clis/nowcoder/jobs.js +1 -0
  414. package/clis/nowcoder/notifications.js +1 -0
  415. package/clis/nowcoder/papers.js +1 -0
  416. package/clis/nowcoder/practice.js +1 -0
  417. package/clis/nowcoder/recommend.js +1 -0
  418. package/clis/nowcoder/referral.js +1 -0
  419. package/clis/nowcoder/salary.js +1 -0
  420. package/clis/nowcoder/search.js +1 -0
  421. package/clis/nowcoder/suggest.js +1 -0
  422. package/clis/nowcoder/topics.js +1 -0
  423. package/clis/nowcoder/trending.js +1 -0
  424. package/clis/ones/login.js +1 -0
  425. package/clis/ones/logout.js +1 -0
  426. package/clis/ones/me.js +1 -0
  427. package/clis/ones/my-tasks.js +1 -0
  428. package/clis/ones/task.js +1 -0
  429. package/clis/ones/tasks.js +1 -0
  430. package/clis/ones/token-info.js +1 -0
  431. package/clis/ones/worklog.js +1 -0
  432. package/clis/openreview/openreview.test.js +345 -0
  433. package/clis/openreview/paper.js +43 -0
  434. package/clis/openreview/reviews.js +131 -0
  435. package/clis/openreview/search.js +46 -0
  436. package/clis/openreview/utils.js +158 -0
  437. package/clis/openreview/venue.js +63 -0
  438. package/clis/paperreview/feedback.js +1 -0
  439. package/clis/paperreview/review.js +1 -0
  440. package/clis/paperreview/submit.js +1 -0
  441. package/clis/pixiv/detail.js +1 -0
  442. package/clis/pixiv/download.js +1 -0
  443. package/clis/pixiv/illusts.js +2 -1
  444. package/clis/pixiv/ranking.js +2 -1
  445. package/clis/pixiv/search.js +2 -1
  446. package/clis/pixiv/user.js +2 -0
  447. package/clis/powerchina/search.js +1 -0
  448. package/clis/producthunt/browse.js +1 -0
  449. package/clis/producthunt/hot.js +1 -0
  450. package/clis/producthunt/posts.js +1 -0
  451. package/clis/producthunt/today.js +1 -0
  452. package/clis/quark/ls.js +1 -0
  453. package/clis/quark/mkdir.js +1 -0
  454. package/clis/quark/mv.js +1 -0
  455. package/clis/quark/rename.js +1 -0
  456. package/clis/quark/rm.js +1 -0
  457. package/clis/quark/save.js +1 -0
  458. package/clis/quark/share-tree.js +1 -0
  459. package/clis/reddit/comment.js +1 -0
  460. package/clis/reddit/frontpage.js +1 -0
  461. package/clis/reddit/hot.js +6 -1
  462. package/clis/reddit/hot.test.js +18 -0
  463. package/clis/reddit/popular.js +1 -0
  464. package/clis/reddit/read.js +1 -0
  465. package/clis/reddit/save.js +1 -0
  466. package/clis/reddit/saved.js +1 -0
  467. package/clis/reddit/search.js +1 -0
  468. package/clis/reddit/subreddit.js +1 -0
  469. package/clis/reddit/subscribe.js +1 -0
  470. package/clis/reddit/upvote.js +1 -0
  471. package/clis/reddit/upvoted.js +1 -0
  472. package/clis/reddit/user-comments.js +1 -0
  473. package/clis/reddit/user-posts.js +1 -0
  474. package/clis/reddit/user.js +1 -0
  475. package/clis/reuters/search.js +1 -0
  476. package/clis/sinablog/article.js +1 -0
  477. package/clis/sinablog/hot.js +1 -0
  478. package/clis/sinablog/search.js +1 -0
  479. package/clis/sinablog/user.js +1 -0
  480. package/clis/sinafinance/news.js +1 -0
  481. package/clis/sinafinance/rolling-news.js +1 -0
  482. package/clis/sinafinance/stock-rank.js +1 -0
  483. package/clis/sinafinance/stock.js +1 -0
  484. package/clis/smzdm/search.js +1 -0
  485. package/clis/spotify/spotify.js +11 -0
  486. package/clis/stackoverflow/bounties.js +11 -3
  487. package/clis/stackoverflow/hot.js +10 -2
  488. package/clis/stackoverflow/read.js +314 -0
  489. package/clis/stackoverflow/search.js +10 -2
  490. package/clis/stackoverflow/stackoverflow.test.js +346 -0
  491. package/clis/stackoverflow/unanswered.js +9 -2
  492. package/clis/steam/top-sellers.js +1 -0
  493. package/clis/substack/feed.js +1 -0
  494. package/clis/substack/publication.js +1 -0
  495. package/clis/substack/search.js +1 -0
  496. package/clis/taobao/add-cart.js +1 -0
  497. package/clis/taobao/cart.js +1 -0
  498. package/clis/taobao/detail.js +1 -0
  499. package/clis/taobao/reviews.js +1 -0
  500. package/clis/taobao/search.js +1 -0
  501. package/clis/tdx/hot-rank.js +1 -0
  502. package/clis/ths/hot-rank.js +1 -0
  503. package/clis/tieba/hot.js +2 -1
  504. package/clis/tieba/posts.js +1 -0
  505. package/clis/tieba/read.js +1 -0
  506. package/clis/tieba/search.js +2 -1
  507. package/clis/tiktok/comment.js +1 -0
  508. package/clis/tiktok/explore.js +1 -0
  509. package/clis/tiktok/follow.js +1 -0
  510. package/clis/tiktok/following.js +1 -0
  511. package/clis/tiktok/friends.js +1 -0
  512. package/clis/tiktok/like.js +1 -0
  513. package/clis/tiktok/live.js +1 -0
  514. package/clis/tiktok/notifications.js +1 -0
  515. package/clis/tiktok/profile.js +1 -0
  516. package/clis/tiktok/save.js +1 -0
  517. package/clis/tiktok/search.js +1 -0
  518. package/clis/tiktok/unfollow.js +1 -0
  519. package/clis/tiktok/unlike.js +1 -0
  520. package/clis/tiktok/unsave.js +1 -0
  521. package/clis/tiktok/user.js +1 -0
  522. package/clis/toutiao/articles.js +1 -0
  523. package/clis/twitter/accept.js +1 -0
  524. package/clis/twitter/article.js +1 -0
  525. package/clis/twitter/block.js +1 -0
  526. package/clis/twitter/bookmark.js +1 -0
  527. package/clis/twitter/bookmarks.js +2 -1
  528. package/clis/twitter/delete.js +1 -0
  529. package/clis/twitter/download.js +1 -0
  530. package/clis/twitter/follow.js +1 -0
  531. package/clis/twitter/followers.js +1 -0
  532. package/clis/twitter/following.js +1 -0
  533. package/clis/twitter/hide-reply.js +1 -0
  534. package/clis/twitter/like.js +1 -0
  535. package/clis/twitter/likes.js +2 -1
  536. package/clis/twitter/list-add.js +1 -0
  537. package/clis/twitter/list-remove.js +1 -0
  538. package/clis/twitter/list-tweets.js +1 -0
  539. package/clis/twitter/lists.js +1 -0
  540. package/clis/twitter/notifications.js +1 -0
  541. package/clis/twitter/post.js +1 -0
  542. package/clis/twitter/profile.js +1 -0
  543. package/clis/twitter/reply-dm.js +1 -0
  544. package/clis/twitter/reply.js +1 -0
  545. package/clis/twitter/search.js +1 -0
  546. package/clis/twitter/thread.js +1 -0
  547. package/clis/twitter/timeline.js +1 -0
  548. package/clis/twitter/trending.js +11 -12
  549. package/clis/twitter/trending.test.js +15 -0
  550. package/clis/twitter/tweets.js +2 -1
  551. package/clis/twitter/tweets.test.js +2 -2
  552. package/clis/twitter/unblock.js +1 -0
  553. package/clis/twitter/unbookmark.js +1 -0
  554. package/clis/twitter/unfollow.js +1 -0
  555. package/clis/uiverse/code.js +1 -0
  556. package/clis/uiverse/preview.js +1 -0
  557. package/clis/v2ex/daily.js +1 -0
  558. package/clis/v2ex/hot.js +1 -0
  559. package/clis/v2ex/latest.js +1 -0
  560. package/clis/v2ex/me.js +1 -0
  561. package/clis/v2ex/member.js +1 -0
  562. package/clis/v2ex/node.js +1 -0
  563. package/clis/v2ex/nodes.js +1 -0
  564. package/clis/v2ex/notifications.js +1 -0
  565. package/clis/v2ex/replies.js +1 -0
  566. package/clis/v2ex/topic.js +1 -0
  567. package/clis/v2ex/user.js +1 -0
  568. package/clis/wanfang/search.js +1 -0
  569. package/clis/web/read.js +1 -0
  570. package/clis/weibo/comments.js +1 -0
  571. package/clis/weibo/favorites.js +1 -0
  572. package/clis/weibo/feed.js +3 -1
  573. package/clis/weibo/hot.js +1 -0
  574. package/clis/weibo/me.js +1 -0
  575. package/clis/weibo/post.js +1 -0
  576. package/clis/weibo/publish.js +1 -0
  577. package/clis/weibo/search.js +8 -2
  578. package/clis/weibo/user.js +1 -0
  579. package/clis/weixin/create-draft.js +1 -0
  580. package/clis/weixin/download.js +1 -0
  581. package/clis/weixin/drafts.js +1 -0
  582. package/clis/weread/ai-outline.js +1 -0
  583. package/clis/weread/book.js +1 -0
  584. package/clis/weread/highlights.js +1 -0
  585. package/clis/weread/notebooks.js +1 -0
  586. package/clis/weread/notes.js +1 -0
  587. package/clis/weread/ranking.js +1 -0
  588. package/clis/weread/search.js +1 -0
  589. package/clis/weread/shelf.js +1 -0
  590. package/clis/wikipedia/random.js +1 -0
  591. package/clis/wikipedia/search.js +1 -0
  592. package/clis/wikipedia/summary.js +1 -0
  593. package/clis/wikipedia/trending.js +1 -0
  594. package/clis/xianyu/chat.js +1 -0
  595. package/clis/xianyu/item.js +1 -0
  596. package/clis/xianyu/search.js +1 -0
  597. package/clis/xiaoe/catalog.js +2 -1
  598. package/clis/xiaoe/content.js +1 -0
  599. package/clis/xiaoe/courses.js +1 -0
  600. package/clis/xiaoe/detail.js +1 -0
  601. package/clis/xiaoe/play-url.js +1 -0
  602. package/clis/xiaohongshu/comments.js +1 -0
  603. package/clis/xiaohongshu/creator-note-detail.js +1 -0
  604. package/clis/xiaohongshu/creator-notes-summary.js +1 -0
  605. package/clis/xiaohongshu/creator-notes.js +1 -0
  606. package/clis/xiaohongshu/creator-profile.js +1 -0
  607. package/clis/xiaohongshu/creator-stats.js +1 -0
  608. package/clis/xiaohongshu/download.js +1 -0
  609. package/clis/xiaohongshu/feed.js +2 -1
  610. package/clis/xiaohongshu/note.js +1 -0
  611. package/clis/xiaohongshu/notifications.js +1 -0
  612. package/clis/xiaohongshu/publish.js +1 -0
  613. package/clis/xiaohongshu/search.js +1 -0
  614. package/clis/xiaohongshu/user.js +1 -0
  615. package/clis/xiaoyuzhou/download.js +1 -0
  616. package/clis/xiaoyuzhou/episode.js +1 -0
  617. package/clis/xiaoyuzhou/podcast-episodes.js +1 -0
  618. package/clis/xiaoyuzhou/podcast.js +1 -0
  619. package/clis/xiaoyuzhou/transcript.js +1 -0
  620. package/clis/xueqiu/comments.js +1 -0
  621. package/clis/xueqiu/earnings-date.js +1 -0
  622. package/clis/xueqiu/feed.js +1 -0
  623. package/clis/xueqiu/fund-holdings.js +1 -0
  624. package/clis/xueqiu/fund-snapshot.js +1 -0
  625. package/clis/xueqiu/groups.js +1 -0
  626. package/clis/xueqiu/hot-stock.js +1 -0
  627. package/clis/xueqiu/hot.js +1 -0
  628. package/clis/xueqiu/kline.js +1 -0
  629. package/clis/xueqiu/search.js +1 -0
  630. package/clis/xueqiu/stock.js +1 -0
  631. package/clis/xueqiu/watchlist.js +1 -0
  632. package/clis/yahoo-finance/quote.js +1 -0
  633. package/clis/yollomi/background.js +1 -0
  634. package/clis/yollomi/edit.js +1 -0
  635. package/clis/yollomi/face-swap.js +1 -0
  636. package/clis/yollomi/generate.js +1 -0
  637. package/clis/yollomi/models.js +1 -0
  638. package/clis/yollomi/object-remover.js +1 -0
  639. package/clis/yollomi/remove-bg.js +1 -0
  640. package/clis/yollomi/restore.js +1 -0
  641. package/clis/yollomi/try-on.js +1 -0
  642. package/clis/yollomi/upload.js +1 -0
  643. package/clis/yollomi/upscale.js +1 -0
  644. package/clis/yollomi/video.js +1 -0
  645. package/clis/youtube/channel.js +1 -0
  646. package/clis/youtube/comments.js +1 -0
  647. package/clis/youtube/feed.js +8 -7
  648. package/clis/youtube/feed.test.js +131 -0
  649. package/clis/youtube/history.js +1 -0
  650. package/clis/youtube/like.js +1 -0
  651. package/clis/youtube/playlist.js +1 -0
  652. package/clis/youtube/search.js +1 -0
  653. package/clis/youtube/subscribe.js +1 -0
  654. package/clis/youtube/subscriptions.js +1 -0
  655. package/clis/youtube/transcript.js +1 -0
  656. package/clis/youtube/unlike.js +1 -0
  657. package/clis/youtube/unsubscribe.js +1 -0
  658. package/clis/youtube/video.js +1 -0
  659. package/clis/youtube/watch-later.js +1 -0
  660. package/clis/yuanbao/ask.js +1 -0
  661. package/clis/yuanbao/new.js +1 -0
  662. package/clis/zhihu/answer.js +1 -0
  663. package/clis/zhihu/collection.js +1 -0
  664. package/clis/zhihu/collections.js +1 -0
  665. package/clis/zhihu/comment.js +1 -0
  666. package/clis/zhihu/download.js +1 -0
  667. package/clis/zhihu/favorite.js +1 -0
  668. package/clis/zhihu/follow.js +1 -0
  669. package/clis/zhihu/hot.js +1 -0
  670. package/clis/zhihu/like.js +1 -0
  671. package/clis/zhihu/question.js +1 -0
  672. package/clis/zhihu/search.js +1 -0
  673. package/clis/zlibrary/info.js +1 -0
  674. package/clis/zlibrary/search.js +1 -0
  675. package/clis/zsxq/dynamics.js +1 -0
  676. package/clis/zsxq/groups.js +1 -0
  677. package/clis/zsxq/search.js +1 -0
  678. package/clis/zsxq/topic.js +1 -0
  679. package/clis/zsxq/topics.js +1 -0
  680. package/dist/src/adapter-source.test.js +1 -1
  681. package/dist/src/browser/analyze.test.js +2 -0
  682. package/dist/src/browser/base-page.d.ts +9 -0
  683. package/dist/src/browser/base-page.js +72 -3
  684. package/dist/src/browser/base-page.test.js +42 -0
  685. package/dist/src/browser/cdp.js +6 -0
  686. package/dist/src/browser/cdp.test.js +3 -0
  687. package/dist/src/browser/page.d.ts +1 -0
  688. package/dist/src/browser/page.js +6 -0
  689. package/dist/src/browser/page.test.js +17 -0
  690. package/dist/src/browser/target-resolver.d.ts +10 -3
  691. package/dist/src/browser/target-resolver.js +16 -10
  692. package/dist/src/browser/verify-fixture.d.ts +6 -1
  693. package/dist/src/browser/verify-fixture.js +87 -0
  694. package/dist/src/browser/verify-fixture.test.js +44 -1
  695. package/dist/src/build-manifest.js +3 -0
  696. package/dist/src/build-manifest.test.js +18 -12
  697. package/dist/src/capabilityRouting.test.js +1 -1
  698. package/dist/src/cli.js +141 -5
  699. package/dist/src/cli.test.js +179 -0
  700. package/dist/src/commanderAdapter.d.ts +1 -1
  701. package/dist/src/commanderAdapter.js +17 -7
  702. package/dist/src/commanderAdapter.test.js +7 -6
  703. package/dist/src/convention-audit.d.ts +50 -0
  704. package/dist/src/convention-audit.js +546 -0
  705. package/dist/src/convention-audit.test.d.ts +1 -0
  706. package/dist/src/convention-audit.test.js +226 -0
  707. package/dist/src/discovery.js +2 -0
  708. package/dist/src/doctor.js +0 -1
  709. package/dist/src/doctor.test.js +1 -1
  710. package/dist/src/engine.test.js +10 -10
  711. package/dist/src/execution.js +1 -1
  712. package/dist/src/execution.test.js +9 -9
  713. package/dist/src/help.d.ts +15 -0
  714. package/dist/src/help.js +149 -0
  715. package/dist/src/main.js +13 -7
  716. package/dist/src/manifest-types.d.ts +2 -0
  717. package/dist/src/plugin.test.js +26 -26
  718. package/dist/src/registry.d.ts +5 -0
  719. package/dist/src/registry.js +8 -0
  720. package/dist/src/registry.test.js +27 -20
  721. package/dist/src/serialization.d.ts +4 -0
  722. package/dist/src/serialization.js +27 -1
  723. package/dist/src/serialization.test.js +24 -2
  724. package/dist/src/types.d.ts +2 -0
  725. package/package.json +4 -1
  726. package/scripts/check-listing-id-pairing.mjs +193 -0
  727. package/scripts/check-silent-column-drop.mjs +105 -0
  728. package/scripts/check-typed-error-lint.mjs +118 -0
  729. package/scripts/silent-column-drop-baseline.json +962 -0
  730. package/scripts/typed-error-lint-baseline.json +1586 -0
@@ -3,6 +3,7 @@ import { loadSinaBlogHot } from './utils.js';
3
3
  cli({
4
4
  site: 'sinablog',
5
5
  name: 'hot',
6
+ access: 'read',
6
7
  description: '获取新浪博客热门文章/推荐',
7
8
  domain: 'blog.sina.com.cn',
8
9
  strategy: Strategy.COOKIE,
@@ -38,6 +38,7 @@ async function searchSinaBlog(keyword, limit) {
38
38
  cli({
39
39
  site: 'sinablog',
40
40
  name: 'search',
41
+ access: 'read',
41
42
  description: '搜索新浪博客文章(通过新浪搜索)',
42
43
  domain: 'blog.sina.com.cn',
43
44
  strategy: Strategy.PUBLIC,
@@ -3,6 +3,7 @@ import { loadSinaBlogUser } from './utils.js';
3
3
  cli({
4
4
  site: 'sinablog',
5
5
  name: 'user',
6
+ access: 'read',
6
7
  description: '获取新浪博客用户的文章列表',
7
8
  domain: 'blog.sina.com.cn',
8
9
  strategy: Strategy.COOKIE,
@@ -25,6 +25,7 @@ function stripHtml(html) {
25
25
  cli({
26
26
  site: 'sinafinance',
27
27
  name: 'news',
28
+ access: 'read',
28
29
  description: '新浪财经 7x24 小时实时快讯',
29
30
  domain: 'app.cj.sina.com.cn',
30
31
  strategy: Strategy.PUBLIC,
@@ -5,6 +5,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
5
5
  cli({
6
6
  site: 'sinafinance',
7
7
  name: 'rolling-news',
8
+ access: 'read',
8
9
  description: '新浪财经滚动新闻',
9
10
  domain: 'finance.sina.com.cn/roll',
10
11
  strategy: Strategy.COOKIE,
@@ -5,6 +5,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
5
5
  cli({
6
6
  site: 'sinafinance',
7
7
  name: 'stock-rank',
8
+ access: 'read',
8
9
  description: '新浪财经热搜榜',
9
10
  domain: 'finance.sina.cn',
10
11
  strategy: Strategy.COOKIE,
@@ -53,6 +53,7 @@ function fmtMktCap(val) {
53
53
  cli({
54
54
  site: 'sinafinance',
55
55
  name: 'stock',
56
+ access: 'read',
56
57
  description: '新浪财经行情(A股/港股/美股)',
57
58
  domain: 'suggest3.sinajs.cn,hq.sinajs.cn',
58
59
  strategy: Strategy.PUBLIC,
@@ -9,6 +9,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
9
9
  cli({
10
10
  site: 'smzdm',
11
11
  name: 'search',
12
+ access: 'read',
12
13
  description: '什么值得买搜索好价',
13
14
  domain: 'www.smzdm.com',
14
15
  strategy: Strategy.COOKIE,
@@ -106,6 +106,7 @@ function openBrowser(url) {
106
106
  cli({
107
107
  site: 'spotify',
108
108
  name: 'auth',
109
+ access: 'write',
109
110
  description: 'Authenticate with Spotify (OAuth — run once)',
110
111
  strategy: Strategy.PUBLIC,
111
112
  browser: false,
@@ -172,6 +173,7 @@ cli({
172
173
  cli({
173
174
  site: 'spotify',
174
175
  name: 'status',
176
+ access: 'read',
175
177
  description: 'Show current playback status',
176
178
  strategy: Strategy.PUBLIC,
177
179
  browser: false,
@@ -193,6 +195,7 @@ cli({
193
195
  cli({
194
196
  site: 'spotify',
195
197
  name: 'play',
198
+ access: 'write',
196
199
  description: 'Resume playback or search and play a track/artist',
197
200
  strategy: Strategy.PUBLIC,
198
201
  browser: false,
@@ -211,6 +214,7 @@ cli({
211
214
  cli({
212
215
  site: 'spotify',
213
216
  name: 'pause',
217
+ access: 'write',
214
218
  description: 'Pause playback',
215
219
  strategy: Strategy.PUBLIC,
216
220
  browser: false,
@@ -221,6 +225,7 @@ cli({
221
225
  cli({
222
226
  site: 'spotify',
223
227
  name: 'next',
228
+ access: 'write',
224
229
  description: 'Skip to next track',
225
230
  strategy: Strategy.PUBLIC,
226
231
  browser: false,
@@ -231,6 +236,7 @@ cli({
231
236
  cli({
232
237
  site: 'spotify',
233
238
  name: 'prev',
239
+ access: 'write',
234
240
  description: 'Skip to previous track',
235
241
  strategy: Strategy.PUBLIC,
236
242
  browser: false,
@@ -241,6 +247,7 @@ cli({
241
247
  cli({
242
248
  site: 'spotify',
243
249
  name: 'volume',
250
+ access: 'write',
244
251
  description: 'Set playback volume (0-100)',
245
252
  strategy: Strategy.PUBLIC,
246
253
  browser: false,
@@ -257,6 +264,7 @@ cli({
257
264
  cli({
258
265
  site: 'spotify',
259
266
  name: 'search',
267
+ access: 'read',
260
268
  description: 'Search for tracks',
261
269
  strategy: Strategy.PUBLIC,
262
270
  browser: false,
@@ -277,6 +285,7 @@ cli({
277
285
  cli({
278
286
  site: 'spotify',
279
287
  name: 'queue',
288
+ access: 'write',
280
289
  description: 'Add a track to the playback queue',
281
290
  strategy: Strategy.PUBLIC,
282
291
  browser: false,
@@ -291,6 +300,7 @@ cli({
291
300
  cli({
292
301
  site: 'spotify',
293
302
  name: 'shuffle',
303
+ access: 'write',
294
304
  description: 'Toggle shuffle on/off',
295
305
  strategy: Strategy.PUBLIC,
296
306
  browser: false,
@@ -304,6 +314,7 @@ cli({
304
314
  cli({
305
315
  site: 'spotify',
306
316
  name: 'repeat',
317
+ access: 'write',
307
318
  description: 'Set repeat mode (off / track / context)',
308
319
  strategy: Strategy.PUBLIC,
309
320
  browser: false,
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  cli({
3
3
  site: 'stackoverflow',
4
4
  name: 'bounties',
5
+ access: 'read',
5
6
  description: 'Active bounties on Stack Overflow',
6
7
  domain: 'stackoverflow.com',
7
8
  strategy: Strategy.PUBLIC,
@@ -9,17 +10,24 @@ cli({
9
10
  args: [
10
11
  { name: 'limit', type: 'int', default: 10, help: 'Max number of results' },
11
12
  ],
12
- columns: ['bounty', 'title', 'score', 'answers', 'url'],
13
+ columns: ['rank', 'id', 'bounty', 'title', 'score', 'answers', 'views', 'is_answered', 'tags', 'author', 'creation_date', 'url'],
13
14
  pipeline: [
14
15
  { fetch: {
15
- url: 'https://api.stackexchange.com/2.3/questions/featured?order=desc&sort=activity&site=stackoverflow',
16
+ url: 'https://api.stackexchange.com/2.3/questions/featured?order=desc&sort=activity&site=stackoverflow&pagesize=${{ args.limit }}',
16
17
  } },
17
18
  { select: 'items' },
18
19
  { map: {
19
- title: '${{ item.title }}',
20
+ rank: '${{ index + 1 }}',
21
+ id: '${{ item.question_id }}',
20
22
  bounty: '${{ item.bounty_amount }}',
23
+ title: '${{ item.title }}',
21
24
  score: '${{ item.score }}',
22
25
  answers: '${{ item.answer_count }}',
26
+ views: '${{ item.view_count }}',
27
+ is_answered: '${{ item.is_answered }}',
28
+ tags: `\${{ item.tags | join(', ') }}`,
29
+ author: '${{ item.owner.display_name }}',
30
+ creation_date: '${{ item.creation_date }}',
23
31
  url: '${{ item.link }}',
24
32
  } },
25
33
  { limit: '${{ args.limit }}' },
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  cli({
3
3
  site: 'stackoverflow',
4
4
  name: 'hot',
5
+ access: 'read',
5
6
  description: 'Hot Stack Overflow questions',
6
7
  domain: 'stackoverflow.com',
7
8
  strategy: Strategy.PUBLIC,
@@ -9,14 +10,21 @@ cli({
9
10
  args: [
10
11
  { name: 'limit', type: 'int', default: 10, help: 'Max number of results' },
11
12
  ],
12
- columns: ['title', 'score', 'answers', 'url'],
13
+ columns: ['rank', 'id', 'title', 'score', 'answers', 'views', 'is_answered', 'tags', 'author', 'creation_date', 'url'],
13
14
  pipeline: [
14
- { fetch: { url: 'https://api.stackexchange.com/2.3/questions?order=desc&sort=hot&site=stackoverflow' } },
15
+ { fetch: { url: 'https://api.stackexchange.com/2.3/questions?order=desc&sort=hot&site=stackoverflow&pagesize=${{ args.limit }}' } },
15
16
  { select: 'items' },
16
17
  { map: {
18
+ rank: '${{ index + 1 }}',
19
+ id: '${{ item.question_id }}',
17
20
  title: '${{ item.title }}',
18
21
  score: '${{ item.score }}',
19
22
  answers: '${{ item.answer_count }}',
23
+ views: '${{ item.view_count }}',
24
+ is_answered: '${{ item.is_answered }}',
25
+ tags: `\${{ item.tags | join(', ') }}`,
26
+ author: '${{ item.owner.display_name }}',
27
+ creation_date: '${{ item.creation_date }}',
20
28
  url: '${{ item.link }}',
21
29
  } },
22
30
  { limit: '${{ args.limit }}' },
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Stack Overflow question reader.
3
+ *
4
+ * Hits the public Stack Exchange API:
5
+ * GET /questions/{id}?site=stackoverflow&filter=withbody
6
+ * GET /questions/{id}/answers?site=stackoverflow&filter=withbody
7
+ * GET /questions/{id}/comments?site=stackoverflow&filter=withbody
8
+ * GET /answers/{a1;a2;...}/comments?site=stackoverflow&filter=withbody
9
+ *
10
+ * Three calls are needed because comments under answers are not bundled in
11
+ * the answers payload. We batch all answer-comment fetches into a single
12
+ * semicolon-joined call. SO has its own quota (300/day for unauthenticated
13
+ * IP), but a `read` consumes at most 4 quota units, or 5 when the accepted
14
+ * answer is missing from the requested answer page and must be fetched by id.
15
+ *
16
+ * Output rows mirror `hackernews read` and `lobsters read`:
17
+ * - first row is the question itself (`type=POST`)
18
+ * - one row per top-level question comment (`type=Q-COMMENT`)
19
+ * - per answer: an `ANSWER` row plus its `A-COMMENT` rows indented under it
20
+ * - the accepted answer (if any) is surfaced first and tagged `accepted=true`
21
+ */
22
+ import { cli, Strategy } from '@jackwener/opencli/registry';
23
+ import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
24
+
25
+ const SE_API_BASE = 'https://api.stackexchange.com/2.3';
26
+ const SE_SITE = 'stackoverflow';
27
+ const SE_MAX_PAGE_SIZE = 100;
28
+
29
+ async function fetchJson(url, label) {
30
+ let res;
31
+ try {
32
+ res = await fetch(url);
33
+ } catch (e) {
34
+ const detail = e instanceof Error ? e.message : String(e);
35
+ throw new CommandExecutionError(
36
+ `Network failure fetching ${label}: ${detail}`,
37
+ 'Check connectivity to api.stackexchange.com',
38
+ );
39
+ }
40
+ if (res.status === 404) {
41
+ throw new EmptyResultError(label, `${label} not found`);
42
+ }
43
+ if (!res.ok) {
44
+ throw new CommandExecutionError(
45
+ `Stack Exchange API HTTP ${res.status} for ${label}`,
46
+ 'Check the question id and quota (300/day per IP)',
47
+ );
48
+ }
49
+ let json;
50
+ try {
51
+ json = await res.json();
52
+ } catch (e) {
53
+ const detail = e instanceof Error ? e.message : String(e);
54
+ throw new CommandExecutionError(
55
+ `Malformed JSON from Stack Exchange API for ${label}: ${detail}`,
56
+ 'The API returned a non-JSON body — likely a transient outage',
57
+ );
58
+ }
59
+ if (json && json.error_id) {
60
+ throw new CommandExecutionError(
61
+ `Stack Exchange API error ${json.error_id} (${json.error_name}) for ${label}: ${json.error_message || ''}`,
62
+ 'Common causes: invalid filter, throttled, or quota exhausted',
63
+ );
64
+ }
65
+ return json;
66
+ }
67
+
68
+ /**
69
+ * CLI args may arrive as strings (`--limit 5` → `'5'`) when not coerced by the
70
+ * arg type system. Coerce-then-validate so `Number.isInteger` actually catches
71
+ * the bad cases, and reject NaN explicitly.
72
+ */
73
+ function coerceInt(value) {
74
+ if (value === undefined || value === null || value === '') return NaN;
75
+ const n = typeof value === 'number' ? value : Number(value);
76
+ return Number.isFinite(n) && Number.isInteger(n) ? n : NaN;
77
+ }
78
+
79
+ function requireMinInt(value, min, label) {
80
+ const n = coerceInt(value);
81
+ if (!Number.isInteger(n) || n < min) {
82
+ throw new ArgumentError(`${label} must be an integer >= ${min}, got ${JSON.stringify(value)}`);
83
+ }
84
+ return n;
85
+ }
86
+
87
+ function requireBoundedInt(value, min, max, label) {
88
+ const n = coerceInt(value);
89
+ if (!Number.isInteger(n) || n < min || n > max) {
90
+ throw new ArgumentError(`${label} must be an integer between ${min} and ${max}, got ${JSON.stringify(value)}`);
91
+ }
92
+ return n;
93
+ }
94
+
95
+ function byAcceptedThenScoreDesc(question, answers) {
96
+ const acceptedAnswerId = question.accepted_answer_id;
97
+ return answers
98
+ .slice()
99
+ .sort((a, b) => {
100
+ const aAccepted = a.is_accepted || (acceptedAnswerId && a.answer_id === acceptedAnswerId);
101
+ const bAccepted = b.is_accepted || (acceptedAnswerId && b.answer_id === acceptedAnswerId);
102
+ if (aAccepted !== bAccepted) return aAccepted ? -1 : 1;
103
+ return (b.score ?? 0) - (a.score ?? 0);
104
+ });
105
+ }
106
+
107
+ async function fetchMissingAcceptedAnswer(question, answers, label) {
108
+ const acceptedAnswerId = question.accepted_answer_id;
109
+ if (!acceptedAnswerId || answers.some((answer) => answer.answer_id === acceptedAnswerId)) {
110
+ return answers;
111
+ }
112
+ const acceptedData = await fetchJson(
113
+ `${SE_API_BASE}/answers/${acceptedAnswerId}?site=${SE_SITE}&filter=withbody`,
114
+ `${label}/accepted-answer`,
115
+ );
116
+ const accepted = (acceptedData.items || [])[0];
117
+ return accepted ? answers.concat(accepted) : answers;
118
+ }
119
+
120
+ async function fetchAnswerCommentsByAnswerId(answers, commentsLimit, label) {
121
+ const answerCommentsByAnswerId = new Map();
122
+ if (answers.length === 0) return answerCommentsByAnswerId;
123
+
124
+ const ids = answers.map((a) => a.answer_id).join(';');
125
+ const pageSize = Math.min(SE_MAX_PAGE_SIZE, answers.length * commentsLimit);
126
+ const ansCommentsData = await fetchJson(
127
+ `${SE_API_BASE}/answers/${ids}/comments?site=${SE_SITE}&filter=withbody&order=asc&sort=creation&pagesize=${pageSize}`,
128
+ `${label}/answer-comments`,
129
+ );
130
+ for (const c of ansCommentsData.items || []) {
131
+ if (!c.post_id) continue;
132
+ if (!answerCommentsByAnswerId.has(c.post_id)) {
133
+ answerCommentsByAnswerId.set(c.post_id, []);
134
+ }
135
+ answerCommentsByAnswerId.get(c.post_id).push(c);
136
+ }
137
+
138
+ if (ansCommentsData.has_more) {
139
+ const missingForSelectedAnswer = answers.some((answer) => {
140
+ const comments = answerCommentsByAnswerId.get(answer.answer_id) || [];
141
+ return comments.length < commentsLimit;
142
+ });
143
+ if (missingForSelectedAnswer) {
144
+ throw new CommandExecutionError(
145
+ `Stack Exchange answer comments for ${label} exceed one API page`,
146
+ 'Lower --answers-limit or --comments-limit; refusing to return a partial answer-comment set.',
147
+ );
148
+ }
149
+ }
150
+
151
+ return answerCommentsByAnswerId;
152
+ }
153
+
154
+ const NAMED_ENTITIES = {
155
+ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ',
156
+ hellip: '…', mdash: '—', ndash: '–', laquo: '«', raquo: '»',
157
+ copy: '©', reg: '®', trade: '™', euro: '€', pound: '£', yen: '¥',
158
+ rsquo: '’', lsquo: '‘', rdquo: '”', ldquo: '“',
159
+ };
160
+
161
+ /** Decode named/numeric HTML entities. Used on both body HTML and display names. */
162
+ function decodeEntities(text) {
163
+ if (!text) return '';
164
+ return String(text)
165
+ .replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => {
166
+ const code = parseInt(hex, 16);
167
+ return Number.isFinite(code) ? String.fromCodePoint(code) : '';
168
+ })
169
+ .replace(/&#(\d+);/g, (_, dec) => {
170
+ const code = parseInt(dec, 10);
171
+ return Number.isFinite(code) ? String.fromCodePoint(code) : '';
172
+ })
173
+ .replace(/&([a-zA-Z]+);/g, (match, name) => NAMED_ENTITIES[name] ?? match);
174
+ }
175
+
176
+ /** SO renders bodies as HTML — convert to plain text similar to HN/lobsters. */
177
+ function htmlToText(html) {
178
+ if (!html) return '';
179
+ const stripped = String(html)
180
+ .replace(/<pre[^>]*><code[^>]*>([\s\S]*?)<\/code><\/pre>/gi, '\n$1\n')
181
+ .replace(/<code[^>]*>(.*?)<\/code>/gi, '`$1`')
182
+ .replace(/<p[^>]*>/gi, '\n\n')
183
+ .replace(/<\/p>/gi, '')
184
+ .replace(/<br\s*\/?>/gi, '\n')
185
+ .replace(/<li[^>]*>/gi, '\n- ')
186
+ .replace(/<\/li>/gi, '')
187
+ .replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, '$2 ($1)')
188
+ .replace(/<[^>]+>/g, '');
189
+ return decodeEntities(stripped)
190
+ .replace(/\n{3,}/g, '\n\n')
191
+ .trim();
192
+ }
193
+
194
+ function authorName(owner) {
195
+ return decodeEntities(owner?.display_name || '') || '[deleted]';
196
+ }
197
+
198
+ function truncate(text, maxLength) {
199
+ if (!text || text.length <= maxLength) return text || '';
200
+ return text.slice(0, maxLength) + ' ... [truncated]';
201
+ }
202
+
203
+ function indentLines(text, depth) {
204
+ if (depth === 0) return text;
205
+ const indent = ' '.repeat(depth);
206
+ const prefix = `${indent}> `;
207
+ return text.split('\n').map((line) => prefix + line).join('\n');
208
+ }
209
+
210
+ cli({
211
+ site: 'stackoverflow',
212
+ name: 'read',
213
+ access: 'read',
214
+ description: 'Read a Stack Overflow question with answers and comments',
215
+ domain: 'stackoverflow.com',
216
+ strategy: Strategy.PUBLIC,
217
+ browser: false,
218
+ args: [
219
+ { name: 'id', required: true, positional: true, help: 'Stack Overflow question id (numeric, e.g. 79935770)' },
220
+ { name: 'answers-limit', type: 'int', default: 10, help: 'Max answers to include (1-100; accepted answer always included first)' },
221
+ { name: 'comments-limit', type: 'int', default: 5, help: 'Max comments per question/answer (1-100)' },
222
+ { name: 'max-length', type: 'int', default: 4000, help: 'Max characters per body / answer / comment (min 100)' },
223
+ ],
224
+ columns: ['type', 'author', 'score', 'accepted', 'text'],
225
+ func: async (args) => {
226
+ const id = String(args.id || '').trim();
227
+ if (!/^\d+$/.test(id)) {
228
+ throw new ArgumentError(`Invalid Stack Overflow question id: ${args.id}`, 'Pass a numeric id like 79935770');
229
+ }
230
+ const answersLimit = requireBoundedInt(args['answers-limit'] ?? 10, 1, SE_MAX_PAGE_SIZE, 'stackoverflow read --answers-limit');
231
+ const commentsLimit = requireBoundedInt(args['comments-limit'] ?? 5, 1, SE_MAX_PAGE_SIZE, 'stackoverflow read --comments-limit');
232
+ const maxLength = requireMinInt(args['max-length'] ?? 4000, 100, 'stackoverflow read --max-length');
233
+
234
+ const label = `stackoverflow/${id}`;
235
+ const qUrl = `${SE_API_BASE}/questions/${id}?site=${SE_SITE}&filter=withbody`;
236
+ const qData = await fetchJson(qUrl, label);
237
+ const question = (qData.items || [])[0];
238
+ if (!question) {
239
+ throw new EmptyResultError(label, 'Question not found');
240
+ }
241
+
242
+ // Fetch question comments and answers in parallel.
243
+ const [qCommentsData, answersData] = await Promise.all([
244
+ fetchJson(
245
+ `${SE_API_BASE}/questions/${id}/comments?site=${SE_SITE}&filter=withbody&order=asc&sort=creation&pagesize=${commentsLimit}`,
246
+ `${label}/comments`,
247
+ ),
248
+ fetchJson(
249
+ `${SE_API_BASE}/questions/${id}/answers?site=${SE_SITE}&filter=withbody&order=desc&sort=votes&pagesize=${answersLimit}`,
250
+ `${label}/answers`,
251
+ ),
252
+ ]);
253
+
254
+ const allAnswers = await fetchMissingAcceptedAnswer(question, answersData.items || [], label);
255
+ // Surface accepted answer first, then by score order.
256
+ const orderedAnswers = byAcceptedThenScoreDesc(question, allAnswers).slice(0, answersLimit);
257
+
258
+ const answerCommentsByAnswerId = await fetchAnswerCommentsByAnswerId(orderedAnswers, commentsLimit, label);
259
+
260
+ const rows = [];
261
+
262
+ // POST row: question
263
+ const qBody = htmlToText(question.body || '');
264
+ const qTextParts = [
265
+ question.title || '',
266
+ qBody,
267
+ question.link || '',
268
+ ].filter(Boolean);
269
+ rows.push({
270
+ type: 'POST',
271
+ author: authorName(question.owner),
272
+ score: question.score ?? 0,
273
+ accepted: '',
274
+ text: truncate(qTextParts.join('\n\n'), maxLength),
275
+ });
276
+
277
+ // Q-COMMENT rows
278
+ const qComments = (qCommentsData.items || []).slice(0, commentsLimit);
279
+ for (const c of qComments) {
280
+ const text = indentLines(htmlToText(c.body || ''), 1);
281
+ rows.push({
282
+ type: 'Q-COMMENT',
283
+ author: authorName(c.owner),
284
+ score: c.score ?? 0,
285
+ accepted: '',
286
+ text: truncate(text, maxLength),
287
+ });
288
+ }
289
+
290
+ // ANSWER + A-COMMENT rows
291
+ for (const ans of orderedAnswers) {
292
+ rows.push({
293
+ type: 'ANSWER',
294
+ author: authorName(ans.owner),
295
+ score: ans.score ?? 0,
296
+ accepted: ans.is_accepted ? 'true' : '',
297
+ text: truncate(htmlToText(ans.body || ''), maxLength),
298
+ });
299
+ const ansComments = (answerCommentsByAnswerId.get(ans.answer_id) || []).slice(0, commentsLimit);
300
+ for (const c of ansComments) {
301
+ const text = indentLines(htmlToText(c.body || ''), 1);
302
+ rows.push({
303
+ type: 'A-COMMENT',
304
+ author: authorName(c.owner),
305
+ score: c.score ?? 0,
306
+ accepted: '',
307
+ text: truncate(text, maxLength),
308
+ });
309
+ }
310
+ }
311
+
312
+ return rows;
313
+ },
314
+ });
@@ -2,6 +2,7 @@ import { cli, Strategy } from '@jackwener/opencli/registry';
2
2
  cli({
3
3
  site: 'stackoverflow',
4
4
  name: 'search',
5
+ access: 'read',
5
6
  description: 'Search Stack Overflow questions',
6
7
  domain: 'stackoverflow.com',
7
8
  strategy: Strategy.PUBLIC,
@@ -10,16 +11,23 @@ cli({
10
11
  { name: 'query', type: 'string', required: true, positional: true, help: 'Search query' },
11
12
  { name: 'limit', type: 'int', default: 10, help: 'Max number of results' },
12
13
  ],
13
- columns: ['title', 'score', 'answers', 'url'],
14
+ columns: ['rank', 'id', 'title', 'score', 'answers', 'views', 'is_answered', 'tags', 'author', 'creation_date', 'url'],
14
15
  pipeline: [
15
16
  { fetch: {
16
- url: 'https://api.stackexchange.com/2.3/search/advanced?order=desc&sort=relevance&q=${{ args.query }}&site=stackoverflow',
17
+ url: 'https://api.stackexchange.com/2.3/search/advanced?order=desc&sort=relevance&q=${{ args.query }}&site=stackoverflow&pagesize=${{ args.limit }}',
17
18
  } },
18
19
  { select: 'items' },
19
20
  { map: {
21
+ rank: '${{ index + 1 }}',
22
+ id: '${{ item.question_id }}',
20
23
  title: '${{ item.title }}',
21
24
  score: '${{ item.score }}',
22
25
  answers: '${{ item.answer_count }}',
26
+ views: '${{ item.view_count }}',
27
+ is_answered: '${{ item.is_answered }}',
28
+ tags: `\${{ item.tags | join(', ') }}`,
29
+ author: '${{ item.owner.display_name }}',
30
+ creation_date: '${{ item.creation_date }}',
23
31
  url: '${{ item.link }}',
24
32
  } },
25
33
  { limit: '${{ args.limit }}' },