@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
@@ -61,6 +61,7 @@ export class BasePage {
61
61
  _lastUrl = null;
62
62
  /** Cached previous snapshot hashes for incremental diff marking */
63
63
  _prevSnapshotHashes = null;
64
+ _cdpTargetMarkerSeq = 0;
64
65
  /**
65
66
  * Safely evaluate JS with pre-serialized arguments.
66
67
  * Each key in `args` becomes a `const` declaration with JSON-serialized value,
@@ -154,8 +155,9 @@ export class BasePage {
154
155
  async click(ref, opts = {}) {
155
156
  // Phase 1: Resolve target with fingerprint verification
156
157
  const resolved = await runResolve(this, ref, opts);
158
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
157
159
  // Phase 2: Execute click on resolved element
158
- const result = await this.evaluate(clickResolvedJs());
160
+ const result = await this.evaluate(clickResolvedJs({ skipScroll: nativeScrolled }));
159
161
  if (typeof result === 'string' || result == null)
160
162
  return resolved;
161
163
  if (result.status === 'clicked')
@@ -217,12 +219,78 @@ export class BasePage {
217
219
  return false;
218
220
  }
219
221
  }
222
+ /**
223
+ * Run a DOM-domain CDP command against `window.__resolved`.
224
+ *
225
+ * CDP DOM.focus / DOM.scrollIntoViewIfNeeded need a nodeId, while our
226
+ * resolver stores the live Element in page JS. Bridge the two worlds with a
227
+ * short-lived marker attribute, then query it through CDP.
228
+ */
229
+ async tryCdpOnResolvedElement(method) {
230
+ const cdp = this.cdp;
231
+ if (typeof cdp !== 'function')
232
+ return false;
233
+ const markerAttr = 'data-opencli-cdp-target';
234
+ const markerValue = `${Date.now().toString(36)}-${++this._cdpTargetMarkerSeq}`;
235
+ const selector = `[${markerAttr}="${markerValue}"]`;
236
+ let marked = false;
237
+ try {
238
+ const marker = await this.evaluateWithArgs(`
239
+ (() => {
240
+ const el = window.__resolved;
241
+ if (!el || el.nodeType !== 1 || typeof el.setAttribute !== 'function') {
242
+ return { ok: false };
243
+ }
244
+ el.setAttribute(markerAttr, markerValue);
245
+ return { ok: true };
246
+ })()
247
+ `, { markerAttr, markerValue });
248
+ marked = marker?.ok === true;
249
+ if (!marked)
250
+ return false;
251
+ await cdp.call(this, 'DOM.enable', {}).catch(() => undefined);
252
+ const doc = await cdp.call(this, 'DOM.getDocument', {});
253
+ const rootNodeId = doc?.root?.nodeId;
254
+ if (typeof rootNodeId !== 'number')
255
+ return false;
256
+ const query = await cdp.call(this, 'DOM.querySelector', {
257
+ nodeId: rootNodeId,
258
+ selector,
259
+ });
260
+ const nodeId = query?.nodeId;
261
+ if (typeof nodeId !== 'number' || nodeId <= 0)
262
+ return false;
263
+ await cdp.call(this, method, { nodeId });
264
+ return true;
265
+ }
266
+ catch {
267
+ return false;
268
+ }
269
+ finally {
270
+ if (marked) {
271
+ await this.evaluateWithArgs(`
272
+ (() => {
273
+ for (const el of document.querySelectorAll(selector)) {
274
+ el.removeAttribute(markerAttr);
275
+ }
276
+ })()
277
+ `, { selector, markerAttr }).catch(() => undefined);
278
+ }
279
+ }
280
+ }
220
281
  async typeText(ref, text, opts = {}) {
221
282
  const resolved = await runResolve(this, ref, opts);
222
283
  let typed = false;
284
+ let nativeScrolled = false;
285
+ let nativeFocused = false;
223
286
  if (typeof this.nativeType === 'function' || typeof this.insertText === 'function') {
224
287
  try {
225
- const preparation = await this.evaluate(prepareNativeTypeResolvedJs());
288
+ nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
289
+ nativeFocused = await this.tryCdpOnResolvedElement('DOM.focus');
290
+ const preparation = await this.evaluate(prepareNativeTypeResolvedJs({
291
+ skipScroll: nativeScrolled,
292
+ skipFocus: nativeFocused,
293
+ }));
226
294
  typed = preparation?.ok === true && await this.tryNativeType(text);
227
295
  }
228
296
  catch {
@@ -243,7 +311,8 @@ export class BasePage {
243
311
  }
244
312
  async scrollTo(ref, opts = {}) {
245
313
  const resolved = await runResolve(this, ref, opts);
246
- const result = (await this.evaluate(scrollResolvedJs()));
314
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
315
+ const result = (await this.evaluate(scrollResolvedJs({ skipScroll: nativeScrolled })));
247
316
  // Fold match_level into the scroll payload so the user-facing envelope
248
317
  // carries it the same way click / type do.
249
318
  if (result && typeof result === 'object') {
@@ -17,15 +17,23 @@ class TestPage extends BasePage {
17
17
  }
18
18
  class ActionPage extends BasePage {
19
19
  results = [];
20
+ withArgsResults = [];
20
21
  scripts = [];
22
+ withArgs = [];
21
23
  nativeType;
22
24
  insertText;
23
25
  nativeKeyPress;
26
+ cdp;
24
27
  async goto() { }
25
28
  async evaluate(js) {
26
29
  this.scripts.push(js);
27
30
  return this.results.shift() ?? null;
28
31
  }
32
+ async evaluateWithArgs(js, args) {
33
+ this.scripts.push(js);
34
+ this.withArgs.push(args);
35
+ return this.withArgsResults.shift() ?? null;
36
+ }
29
37
  async getCookies() { return []; }
30
38
  async screenshot() { return ''; }
31
39
  async tabs() { return []; }
@@ -100,6 +108,27 @@ describe('BasePage native input routing', () => {
100
108
  expect(page.scripts[1]).toContain('nearestContentEditableHost');
101
109
  expect(page.scripts.join('\n')).not.toContain("return 'typed'");
102
110
  });
111
+ it('uses CDP DOM focus and scroll before native text insertion when available', async () => {
112
+ const page = new ActionPage();
113
+ page.nativeType = vi.fn().mockResolvedValue(undefined);
114
+ page.cdp = vi.fn()
115
+ .mockResolvedValueOnce({})
116
+ .mockResolvedValueOnce({ root: { nodeId: 1 } })
117
+ .mockResolvedValueOnce({ nodeId: 7 })
118
+ .mockResolvedValueOnce({})
119
+ .mockResolvedValueOnce({})
120
+ .mockResolvedValueOnce({ root: { nodeId: 1 } })
121
+ .mockResolvedValueOnce({ nodeId: 7 })
122
+ .mockResolvedValueOnce({});
123
+ page.results = [resolveOk, { ok: true, mode: 'input' }];
124
+ page.withArgsResults = [{ ok: true }, undefined, { ok: true }, undefined];
125
+ await page.typeText('#q', 'hello');
126
+ expect(page.cdp).toHaveBeenCalledWith('DOM.scrollIntoViewIfNeeded', { nodeId: 7 });
127
+ expect(page.cdp).toHaveBeenCalledWith('DOM.focus', { nodeId: 7 });
128
+ expect(page.nativeType).toHaveBeenCalledWith('hello');
129
+ expect(page.scripts.at(-1)).toContain('if (false) el.scrollIntoView');
130
+ expect(page.scripts.at(-1)).toContain('if (false) {');
131
+ });
103
132
  it('keeps the DOM setter fallback when native text insertion is unavailable', async () => {
104
133
  const page = new ActionPage();
105
134
  page.results = [resolveOk, 'typed'];
@@ -117,6 +146,19 @@ describe('BasePage native input routing', () => {
117
146
  expect(page.scripts).toHaveLength(3);
118
147
  expect(page.scripts[2]).toContain("return 'typed'");
119
148
  });
149
+ it('uses CDP DOM scrollIntoViewIfNeeded before JS click when available', async () => {
150
+ const page = new ActionPage();
151
+ page.cdp = vi.fn()
152
+ .mockResolvedValueOnce({})
153
+ .mockResolvedValueOnce({ root: { nodeId: 1 } })
154
+ .mockResolvedValueOnce({ nodeId: 9 })
155
+ .mockResolvedValueOnce({});
156
+ page.results = [resolveOk, { status: 'clicked', x: 1, y: 2 }];
157
+ page.withArgsResults = [{ ok: true }, undefined];
158
+ await page.click('#save');
159
+ expect(page.cdp).toHaveBeenCalledWith('DOM.scrollIntoViewIfNeeded', { nodeId: 9 });
160
+ expect(page.scripts.at(-1)).toContain('if (false) el.scrollIntoView');
161
+ });
120
162
  it('presses key chords through native CDP key events when available', async () => {
121
163
  const page = new ActionPage();
122
164
  page.nativeKeyPress = vi.fn().mockResolvedValue(undefined);
@@ -320,6 +320,12 @@ class CDPPage extends BasePage {
320
320
  async cdp(method, params = {}) {
321
321
  return this.bridge.send(method, params);
322
322
  }
323
+ async handleJavaScriptDialog(accept, promptText) {
324
+ await this.cdp('Page.handleJavaScriptDialog', {
325
+ accept,
326
+ ...(promptText !== undefined && { promptText }),
327
+ });
328
+ }
323
329
  async nativeClick(x, y) {
324
330
  await this.cdp('Input.dispatchMouseEvent', {
325
331
  type: 'mousePressed',
@@ -58,10 +58,12 @@ describe('CDPBridge cookies', () => {
58
58
  expect(page.nativeType).toBeTypeOf('function');
59
59
  expect(page.nativeKeyPress).toBeTypeOf('function');
60
60
  expect(page.nativeClick).toBeTypeOf('function');
61
+ expect(page.handleJavaScriptDialog).toBeTypeOf('function');
61
62
  expect(page.cdp).toBeTypeOf('function');
62
63
  await page.nativeType('hello');
63
64
  await page.nativeKeyPress('a', ['Ctrl']);
64
65
  await page.nativeClick(10, 20);
66
+ await page.handleJavaScriptDialog(true, 'ok');
65
67
  await page.cdp('Page.getLayoutMetrics', {});
66
68
  expect(send.mock.calls).toEqual([
67
69
  ['Input.insertText', { text: 'hello' }],
@@ -69,6 +71,7 @@ describe('CDPBridge cookies', () => {
69
71
  ['Input.dispatchKeyEvent', { type: 'keyUp', key: 'a', modifiers: 2 }],
70
72
  ['Input.dispatchMouseEvent', { type: 'mousePressed', x: 10, y: 20, button: 'left', clickCount: 1 }],
71
73
  ['Input.dispatchMouseEvent', { type: 'mouseReleased', x: 10, y: 20, button: 'left', clickCount: 1 }],
74
+ ['Page.handleJavaScriptDialog', { accept: true, promptText: 'ok' }],
72
75
  ['Page.getLayoutMetrics', {}],
73
76
  ]);
74
77
  });
@@ -68,6 +68,7 @@ export declare class Page extends BasePage {
68
68
  }>>;
69
69
  evaluateInFrame(js: string, frameIndex: number): Promise<unknown>;
70
70
  cdp(method: string, params?: Record<string, unknown>): Promise<unknown>;
71
+ handleJavaScriptDialog(accept: boolean, promptText?: string): Promise<void>;
71
72
  /** CDP native click fallback — called when JS el.click() fails */
72
73
  protected tryNativeClick(x: number, y: number): Promise<boolean>;
73
74
  /** Precise click using DOM.getContentQuads/getBoxModel for inline elements */
@@ -283,6 +283,12 @@ export class Page extends BasePage {
283
283
  ...this._cmdOpts(),
284
284
  });
285
285
  }
286
+ async handleJavaScriptDialog(accept, promptText) {
287
+ await this.cdp('Page.handleJavaScriptDialog', {
288
+ accept,
289
+ ...(promptText !== undefined && { promptText }),
290
+ });
291
+ }
286
292
  /** CDP native click fallback — called when JS el.click() fails */
287
293
  async tryNativeClick(x, y) {
288
294
  try {
@@ -102,6 +102,23 @@ describe('Page network capture compatibility', () => {
102
102
  expect(warnMock).toHaveBeenCalledTimes(1);
103
103
  });
104
104
  });
105
+ describe('Page CDP helpers', () => {
106
+ beforeEach(() => {
107
+ sendCommandMock.mockReset();
108
+ sendCommandFullMock.mockReset();
109
+ warnMock.mockReset();
110
+ });
111
+ it('handles JavaScript dialogs through the CDP passthrough', async () => {
112
+ sendCommandMock.mockResolvedValueOnce({});
113
+ const page = new Page('browser:default');
114
+ await page.handleJavaScriptDialog(true, 'confirm');
115
+ expect(sendCommandMock).toHaveBeenCalledWith('cdp', expect.objectContaining({
116
+ workspace: 'browser:default',
117
+ cdpMethod: 'Page.handleJavaScriptDialog',
118
+ cdpParams: { accept: true, promptText: 'confirm' },
119
+ }));
120
+ });
121
+ });
105
122
  describe('Page active target tracking', () => {
106
123
  beforeEach(() => {
107
124
  sendCommandMock.mockReset();
@@ -72,7 +72,9 @@ export declare function resolveTargetJs(ref: string, opts?: ResolveOptions): str
72
72
  * Generate JS for click that uses the unified resolver.
73
73
  * Assumes resolveTargetJs has been called and __resolved is set.
74
74
  */
75
- export declare function clickResolvedJs(): string;
75
+ export declare function clickResolvedJs(opts?: {
76
+ skipScroll?: boolean;
77
+ }): string;
76
78
  /**
77
79
  * Generate JS for type that uses the unified resolver.
78
80
  */
@@ -84,12 +86,17 @@ export declare function typeResolvedJs(text: string): string;
84
86
  * focus the editable target, select its current contents, then let CDP insert
85
87
  * real browser text input so rich editors can update their internal state.
86
88
  */
87
- export declare function prepareNativeTypeResolvedJs(): string;
89
+ export declare function prepareNativeTypeResolvedJs(opts?: {
90
+ skipScroll?: boolean;
91
+ skipFocus?: boolean;
92
+ }): string;
88
93
  /**
89
94
  * Generate JS for scrollTo that uses the unified resolver.
90
95
  * Assumes resolveTargetJs has been called and __resolved is set.
91
96
  */
92
- export declare function scrollResolvedJs(): string;
97
+ export declare function scrollResolvedJs(opts?: {
98
+ skipScroll?: boolean;
99
+ }): string;
93
100
  /**
94
101
  * Generate JS to get text content of resolved element.
95
102
  */
@@ -277,12 +277,13 @@ export function resolveTargetJs(ref, opts = {}) {
277
277
  * Generate JS for click that uses the unified resolver.
278
278
  * Assumes resolveTargetJs has been called and __resolved is set.
279
279
  */
280
- export function clickResolvedJs() {
280
+ export function clickResolvedJs(opts = {}) {
281
+ const shouldScroll = opts.skipScroll ? 'false' : 'true';
281
282
  return `
282
283
  (() => {
283
284
  const el = window.__resolved;
284
285
  if (!el) throw new Error('No resolved element');
285
- el.scrollIntoView({ behavior: 'instant', block: 'center' });
286
+ if (${shouldScroll}) el.scrollIntoView({ behavior: 'instant', block: 'center' });
286
287
  const rect = el.getBoundingClientRect();
287
288
  const x = Math.round(rect.left + rect.width / 2);
288
289
  const y = Math.round(rect.top + rect.height / 2);
@@ -338,7 +339,9 @@ export function typeResolvedJs(text) {
338
339
  * focus the editable target, select its current contents, then let CDP insert
339
340
  * real browser text input so rich editors can update their internal state.
340
341
  */
341
- export function prepareNativeTypeResolvedJs() {
342
+ export function prepareNativeTypeResolvedJs(opts = {}) {
343
+ const shouldScroll = opts.skipScroll ? 'false' : 'true';
344
+ const shouldFocus = opts.skipFocus ? 'false' : 'true';
342
345
  return `
343
346
  (() => {
344
347
  const original = window.__resolved;
@@ -369,11 +372,13 @@ export function prepareNativeTypeResolvedJs() {
369
372
  }
370
373
 
371
374
  window.__resolved = el;
372
- el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
373
- try {
374
- el.focus({ preventScroll: true });
375
- } catch (_) {
376
- el.focus();
375
+ if (${shouldScroll}) el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
376
+ if (${shouldFocus}) {
377
+ try {
378
+ el.focus({ preventScroll: true });
379
+ } catch (_) {
380
+ el.focus();
381
+ }
377
382
  }
378
383
 
379
384
  if (editableHost) {
@@ -410,12 +415,13 @@ export function prepareNativeTypeResolvedJs() {
410
415
  * Generate JS for scrollTo that uses the unified resolver.
411
416
  * Assumes resolveTargetJs has been called and __resolved is set.
412
417
  */
413
- export function scrollResolvedJs() {
418
+ export function scrollResolvedJs(opts = {}) {
419
+ const shouldScroll = opts.skipScroll ? 'false' : 'true';
414
420
  return `
415
421
  (() => {
416
422
  const el = window.__resolved;
417
423
  if (!el) throw new Error('No resolved element');
418
- el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
424
+ if (${shouldScroll}) el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
419
425
  return { scrolled: true, tag: el.tagName.toLowerCase(), text: (el.textContent || '').trim().slice(0, 80) };
420
426
  })()
421
427
  `;
@@ -31,11 +31,15 @@ export type Fixture = {
31
31
  expect?: FixtureExpect;
32
32
  };
33
33
  export type ValidationFailure = {
34
- rule: 'rowCount' | 'column' | 'type' | 'pattern' | 'notEmpty' | 'mustNotContain' | 'mustBeTruthy';
34
+ rule: 'rowCount' | 'column' | 'type' | 'pattern' | 'notEmpty' | 'mustNotContain' | 'mustBeTruthy' | 'shapeKeyCount' | 'shapeDepth' | 'shapeNestedId';
35
35
  detail: string;
36
36
  rowIndex?: number;
37
37
  };
38
38
  export type Row = Record<string, unknown>;
39
+ export type RowShapeOptions = {
40
+ maxTopLevelKeys?: number;
41
+ maxNestedDepth?: number;
42
+ };
39
43
  export declare function fixturePath(site: string, command: string): string;
40
44
  export declare function loadFixture(site: string, command: string): Fixture | null;
41
45
  export declare function writeFixture(site: string, command: string, fixture: Fixture): string;
@@ -51,6 +55,7 @@ export declare function writeFixture(site: string, command: string, fixture: Fix
51
55
  */
52
56
  export declare function deriveFixture(rows: Row[], args?: FixtureArgs): Fixture;
53
57
  export declare function validateRows(rows: Row[], fixture: Fixture): ValidationFailure[];
58
+ export declare function validateRowShape(rows: Row[], opts?: RowShapeOptions): ValidationFailure[];
54
59
  /**
55
60
  * Convert fixture args into argv tokens appended after the command name.
56
61
  * - Array form is passed through verbatim (stringified), supporting positional subjects.
@@ -30,6 +30,26 @@
30
30
  import * as fs from 'node:fs';
31
31
  import * as os from 'node:os';
32
32
  import * as path from 'node:path';
33
+ const DEFAULT_MAX_TOP_LEVEL_KEYS = 12;
34
+ const DEFAULT_MAX_NESTED_DEPTH = 1;
35
+ const ID_SHAPED_KEY_PATTERNS = [
36
+ /^id$/i,
37
+ /_id$/i,
38
+ /Id$/,
39
+ /^short_id$/i,
40
+ /^bvid$/i,
41
+ /^aid$/i,
42
+ /^tid$/i,
43
+ /^asin$/i,
44
+ /^sku$/i,
45
+ /^isbn$/i,
46
+ /^doi$/i,
47
+ /^slug$/i,
48
+ /^hn_id$/i,
49
+ /^username$/i,
50
+ /^handle$/i,
51
+ /^uri$/i,
52
+ ];
33
53
  export function fixturePath(site, command) {
34
54
  return path.join(os.homedir(), '.opencli', 'sites', site, 'verify', `${command}.json`);
35
55
  }
@@ -180,6 +200,39 @@ export function validateRows(rows, fixture) {
180
200
  });
181
201
  return failures;
182
202
  }
203
+ export function validateRowShape(rows, opts = {}) {
204
+ const failures = [];
205
+ const maxTopLevelKeys = opts.maxTopLevelKeys ?? DEFAULT_MAX_TOP_LEVEL_KEYS;
206
+ const maxNestedDepth = opts.maxNestedDepth ?? DEFAULT_MAX_NESTED_DEPTH;
207
+ rows.forEach((row, i) => {
208
+ const keys = Object.keys(row);
209
+ if (keys.length > maxTopLevelKeys) {
210
+ failures.push({
211
+ rule: 'shapeKeyCount',
212
+ detail: `row has ${keys.length} top-level keys, expected at most ${maxTopLevelKeys}`,
213
+ rowIndex: i,
214
+ });
215
+ }
216
+ for (const [key, value] of Object.entries(row)) {
217
+ const depth = nestedDepth(value);
218
+ if (depth > maxNestedDepth) {
219
+ failures.push({
220
+ rule: 'shapeDepth',
221
+ detail: `"${key}" nesting depth is ${depth}, expected at most ${maxNestedDepth}`,
222
+ rowIndex: i,
223
+ });
224
+ }
225
+ for (const path of nestedIdPaths(value, key)) {
226
+ failures.push({
227
+ rule: 'shapeNestedId',
228
+ detail: `id-shaped field "${path}" must be a top-level row key`,
229
+ rowIndex: i,
230
+ });
231
+ }
232
+ }
233
+ });
234
+ return failures;
235
+ }
183
236
  /**
184
237
  * Convert fixture args into argv tokens appended after the command name.
185
238
  * - Array form is passed through verbatim (stringified), supporting positional subjects.
@@ -221,6 +274,40 @@ function jsType(v) {
221
274
  return 'array';
222
275
  return typeof v;
223
276
  }
277
+ function nestedDepth(value) {
278
+ if (value === null || value === undefined || typeof value !== 'object')
279
+ return 0;
280
+ if (Array.isArray(value)) {
281
+ if (value.length === 0)
282
+ return 1;
283
+ return 1 + Math.max(...value.map(nestedDepth));
284
+ }
285
+ const values = Object.values(value);
286
+ if (values.length === 0)
287
+ return 1;
288
+ return 1 + Math.max(...values.map(nestedDepth));
289
+ }
290
+ function nestedIdPaths(value, prefix) {
291
+ if (value === null || value === undefined || typeof value !== 'object')
292
+ return [];
293
+ const paths = [];
294
+ if (Array.isArray(value)) {
295
+ value.forEach((item, index) => {
296
+ paths.push(...nestedIdPaths(item, `${prefix}[${index}]`));
297
+ });
298
+ return paths;
299
+ }
300
+ for (const [key, nested] of Object.entries(value)) {
301
+ const childPath = `${prefix}.${key}`;
302
+ if (isIdShapedKey(key))
303
+ paths.push(childPath);
304
+ paths.push(...nestedIdPaths(nested, childPath));
305
+ }
306
+ return paths;
307
+ }
308
+ function isIdShapedKey(key) {
309
+ return ID_SHAPED_KEY_PATTERNS.some((pattern) => pattern.test(key));
310
+ }
224
311
  function typeMatches(actual, declared) {
225
312
  const allowed = declared.split('|').map((s) => s.trim()).filter(Boolean);
226
313
  if (allowed.length === 0)
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { deriveFixture, expandFixtureArgs, parseSeedArgs, validateRows } from './verify-fixture.js';
2
+ import { deriveFixture, expandFixtureArgs, parseSeedArgs, validateRows, validateRowShape } from './verify-fixture.js';
3
3
  describe('validateRows', () => {
4
4
  it('passes when rows meet all expectations', () => {
5
5
  const fixture = {
@@ -98,6 +98,49 @@ describe('validateRows', () => {
98
98
  expect(failures.map((f) => f.rowIndex)).toEqual([1, 2, 3, 4]);
99
99
  });
100
100
  });
101
+ describe('validateRowShape', () => {
102
+ it('passes flat rows with a compact key set', () => {
103
+ const failures = validateRowShape([
104
+ { id: '1', title: 'A', author: { name: 'Ada' }, tags: ['ai', 'web'] },
105
+ ]);
106
+ expect(failures).toEqual([]);
107
+ });
108
+ it('reports rows with too many top-level keys', () => {
109
+ const row = Object.fromEntries(Array.from({ length: 13 }, (_, i) => [`k${i}`, i]));
110
+ const failures = validateRowShape([row]);
111
+ expect(failures).toEqual([
112
+ {
113
+ rule: 'shapeKeyCount',
114
+ detail: 'row has 13 top-level keys, expected at most 12',
115
+ rowIndex: 0,
116
+ },
117
+ ]);
118
+ });
119
+ it('reports nesting deeper than one level', () => {
120
+ const failures = validateRowShape([
121
+ { title: 'A', stats: { author: { name: 'Ada' } } },
122
+ ]);
123
+ expect(failures).toEqual([
124
+ {
125
+ rule: 'shapeDepth',
126
+ detail: '"stats" nesting depth is 2, expected at most 1',
127
+ rowIndex: 0,
128
+ },
129
+ ]);
130
+ });
131
+ it('reports nested id-shaped fields even when one-level nesting is otherwise allowed', () => {
132
+ const failures = validateRowShape([
133
+ { title: 'A', author: { user_id: 'u1', name: 'Ada' } },
134
+ ]);
135
+ expect(failures).toEqual([
136
+ {
137
+ rule: 'shapeNestedId',
138
+ detail: 'id-shaped field "author.user_id" must be a top-level row key',
139
+ rowIndex: 0,
140
+ },
141
+ ]);
142
+ });
143
+ });
101
144
  describe('deriveFixture', () => {
102
145
  it('returns rowCount.min=0 when rows are empty', () => {
103
146
  expect(deriveFixture([])).toEqual({ expect: { rowCount: { min: 0 } } });
@@ -75,6 +75,7 @@ function isCliCommandValue(value, site) {
75
75
  && typeof value.site === 'string'
76
76
  && value.site === site
77
77
  && typeof value.name === 'string'
78
+ && (value.access === 'read' || value.access === 'write')
78
79
  && Array.isArray(value.args);
79
80
  }
80
81
  function toManifestEntry(cmd, modulePath, sourceFile) {
@@ -83,6 +84,8 @@ function toManifestEntry(cmd, modulePath, sourceFile) {
83
84
  name: cmd.name,
84
85
  aliases: cmd.aliases,
85
86
  description: cmd.description ?? '',
87
+ access: cmd.access,
88
+ example: cmd.example,
86
89
  domain: cmd.domain,
87
90
  strategy: (cmd.strategy ?? 'public').toString().toLowerCase(),
88
91
  browser: cmd.browser ?? true,