@jackwener/opencli 1.7.10 → 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 (740) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +5 -4
  3. package/cli-manifest.json +1443 -24
  4. package/clis/1688/assets.js +1 -0
  5. package/clis/1688/download.js +1 -0
  6. package/clis/1688/item.js +1 -0
  7. package/clis/1688/search.js +2 -1
  8. package/clis/1688/store.js +1 -0
  9. package/clis/36kr/article.js +1 -0
  10. package/clis/36kr/hot.js +1 -0
  11. package/clis/36kr/news.js +1 -0
  12. package/clis/36kr/search.js +1 -0
  13. package/clis/51job/company.js +1 -0
  14. package/clis/51job/detail.js +1 -0
  15. package/clis/51job/hot.js +1 -0
  16. package/clis/51job/search.js +1 -0
  17. package/clis/amazon/bestsellers.js +1 -0
  18. package/clis/amazon/discussion.js +1 -0
  19. package/clis/amazon/movers-shakers.js +1 -0
  20. package/clis/amazon/new-releases.js +1 -0
  21. package/clis/amazon/offer.js +1 -0
  22. package/clis/amazon/product.js +1 -0
  23. package/clis/amazon/rankings.js +1 -0
  24. package/clis/amazon/search.js +1 -0
  25. package/clis/antigravity/dump.js +1 -0
  26. package/clis/antigravity/extract-code.js +1 -0
  27. package/clis/antigravity/model.js +1 -0
  28. package/clis/antigravity/new.js +1 -0
  29. package/clis/antigravity/read.js +1 -0
  30. package/clis/antigravity/send.js +1 -0
  31. package/clis/antigravity/status.js +1 -0
  32. package/clis/antigravity/watch.js +1 -0
  33. package/clis/apple-podcasts/episodes.js +1 -0
  34. package/clis/apple-podcasts/search.js +1 -0
  35. package/clis/apple-podcasts/top.js +1 -0
  36. package/clis/arxiv/arxiv.test.js +112 -0
  37. package/clis/arxiv/paper.js +4 -3
  38. package/clis/arxiv/recent.js +33 -0
  39. package/clis/arxiv/search.js +19 -7
  40. package/clis/arxiv/utils.js +68 -5
  41. package/clis/baidu-scholar/search.js +1 -0
  42. package/clis/band/bands.js +1 -0
  43. package/clis/band/mentions.js +1 -0
  44. package/clis/band/post.js +1 -0
  45. package/clis/band/posts.js +1 -0
  46. package/clis/barchart/flow.js +1 -0
  47. package/clis/barchart/greeks.js +1 -0
  48. package/clis/barchart/options.js +1 -0
  49. package/clis/barchart/quote.js +1 -0
  50. package/clis/bbc/news.js +1 -0
  51. package/clis/bilibili/comments.js +1 -0
  52. package/clis/bilibili/download.js +1 -0
  53. package/clis/bilibili/dynamic.js +1 -0
  54. package/clis/bilibili/favorite.js +1 -0
  55. package/clis/bilibili/feed.js +2 -0
  56. package/clis/bilibili/following.js +1 -0
  57. package/clis/bilibili/history.js +1 -0
  58. package/clis/bilibili/hot.js +6 -1
  59. package/clis/bilibili/hot.test.js +17 -0
  60. package/clis/bilibili/me.js +1 -1
  61. package/clis/bilibili/ranking.js +1 -0
  62. package/clis/bilibili/search.js +1 -1
  63. package/clis/bilibili/subtitle.js +1 -0
  64. package/clis/bilibili/user-videos.js +1 -0
  65. package/clis/bilibili/video.js +1 -0
  66. package/clis/binance/asks.js +1 -0
  67. package/clis/binance/depth.js +1 -0
  68. package/clis/binance/gainers.js +1 -0
  69. package/clis/binance/klines.js +1 -0
  70. package/clis/binance/losers.js +1 -0
  71. package/clis/binance/pairs.js +1 -0
  72. package/clis/binance/price.js +1 -0
  73. package/clis/binance/prices.js +1 -0
  74. package/clis/binance/ticker.js +1 -0
  75. package/clis/binance/top.js +1 -0
  76. package/clis/binance/trades.js +1 -0
  77. package/clis/bloomberg/businessweek.js +1 -0
  78. package/clis/bloomberg/economics.js +1 -0
  79. package/clis/bloomberg/feeds.js +1 -0
  80. package/clis/bloomberg/industries.js +1 -0
  81. package/clis/bloomberg/main.js +1 -0
  82. package/clis/bloomberg/markets.js +1 -0
  83. package/clis/bloomberg/news.js +1 -0
  84. package/clis/bloomberg/opinions.js +1 -0
  85. package/clis/bloomberg/politics.js +1 -0
  86. package/clis/bloomberg/tech.js +1 -0
  87. package/clis/bluesky/feeds.js +1 -0
  88. package/clis/bluesky/followers.js +1 -0
  89. package/clis/bluesky/following.js +1 -0
  90. package/clis/bluesky/profile.js +1 -0
  91. package/clis/bluesky/search.js +1 -0
  92. package/clis/bluesky/starter-packs.js +1 -0
  93. package/clis/bluesky/thread.js +1 -0
  94. package/clis/bluesky/trending.js +1 -0
  95. package/clis/bluesky/user.js +3 -1
  96. package/clis/boss/batchgreet.js +1 -0
  97. package/clis/boss/chatlist.js +1 -0
  98. package/clis/boss/chatmsg.js +1 -0
  99. package/clis/boss/detail.js +1 -0
  100. package/clis/boss/exchange.js +1 -0
  101. package/clis/boss/greet.js +1 -0
  102. package/clis/boss/invite.js +1 -0
  103. package/clis/boss/joblist.js +1 -0
  104. package/clis/boss/mark.js +1 -0
  105. package/clis/boss/recommend.js +1 -0
  106. package/clis/boss/resume.js +1 -0
  107. package/clis/boss/search.js +1 -0
  108. package/clis/boss/send.js +1 -0
  109. package/clis/boss/stats.js +1 -0
  110. package/clis/chaoxing/assignments.js +1 -0
  111. package/clis/chaoxing/exams.js +1 -0
  112. package/clis/chatgpt/image.js +1 -0
  113. package/clis/chatgpt-app/ask.js +1 -0
  114. package/clis/chatgpt-app/model.js +1 -0
  115. package/clis/chatgpt-app/new.js +1 -0
  116. package/clis/chatgpt-app/read.js +1 -0
  117. package/clis/chatgpt-app/send.js +1 -0
  118. package/clis/chatgpt-app/status.js +1 -0
  119. package/clis/chatwise/ask.js +1 -0
  120. package/clis/chatwise/export.js +1 -0
  121. package/clis/chatwise/history.js +1 -0
  122. package/clis/chatwise/model.js +1 -0
  123. package/clis/chatwise/read.js +1 -0
  124. package/clis/chatwise/send.js +1 -0
  125. package/clis/claude/ask.js +1 -0
  126. package/clis/claude/detail.js +1 -0
  127. package/clis/claude/history.js +1 -0
  128. package/clis/claude/new.js +1 -0
  129. package/clis/claude/read.js +1 -0
  130. package/clis/claude/send.js +1 -0
  131. package/clis/claude/status.js +1 -0
  132. package/clis/cnki/search.js +1 -0
  133. package/clis/codex/ask.js +1 -0
  134. package/clis/codex/export.js +1 -0
  135. package/clis/codex/extract-diff.js +1 -0
  136. package/clis/codex/history.js +1 -0
  137. package/clis/codex/model.js +1 -0
  138. package/clis/codex/read.js +1 -0
  139. package/clis/codex/send.js +1 -0
  140. package/clis/coupang/add-to-cart.js +1 -0
  141. package/clis/coupang/search.js +1 -0
  142. package/clis/ctrip/search.js +1 -0
  143. package/clis/cursor/ask.js +1 -0
  144. package/clis/cursor/composer.js +1 -0
  145. package/clis/cursor/export.js +1 -0
  146. package/clis/cursor/extract-code.js +1 -0
  147. package/clis/cursor/history.js +1 -0
  148. package/clis/cursor/model.js +1 -0
  149. package/clis/cursor/read.js +1 -0
  150. package/clis/cursor/send.js +1 -0
  151. package/clis/dblp/dblp.test.js +397 -0
  152. package/clis/dblp/paper.js +40 -0
  153. package/clis/dblp/search.js +45 -0
  154. package/clis/dblp/utils.js +290 -0
  155. package/clis/deepseek/ask.js +1 -0
  156. package/clis/deepseek/history.js +1 -0
  157. package/clis/deepseek/new.js +1 -0
  158. package/clis/deepseek/read.js +1 -0
  159. package/clis/deepseek/status.js +1 -0
  160. package/clis/devto/devto.test.js +236 -0
  161. package/clis/devto/read.js +103 -0
  162. package/clis/devto/tag.js +5 -1
  163. package/clis/devto/top.js +5 -1
  164. package/clis/devto/user.js +5 -1
  165. package/clis/dianping/__fixtures__/search.html +168 -0
  166. package/clis/dianping/__fixtures__/shop.html +6 -0
  167. package/clis/dianping/dianping.test.js +424 -0
  168. package/clis/dianping/search.js +154 -0
  169. package/clis/dianping/shop.js +173 -0
  170. package/clis/dianping/utils.js +157 -0
  171. package/clis/dictionary/examples.js +1 -0
  172. package/clis/dictionary/search.js +1 -0
  173. package/clis/dictionary/synonyms.js +1 -0
  174. package/clis/discord-app/channels.js +1 -0
  175. package/clis/discord-app/delete.js +1 -0
  176. package/clis/discord-app/members.js +1 -0
  177. package/clis/discord-app/read.js +1 -0
  178. package/clis/discord-app/search.js +1 -0
  179. package/clis/discord-app/send.js +1 -0
  180. package/clis/discord-app/servers.js +1 -0
  181. package/clis/discord-app/status.js +1 -0
  182. package/clis/douban/book-hot.js +1 -0
  183. package/clis/douban/download.js +1 -0
  184. package/clis/douban/marks.js +1 -0
  185. package/clis/douban/movie-hot.js +2 -1
  186. package/clis/douban/movie-hot.test.js +14 -0
  187. package/clis/douban/photos.js +2 -1
  188. package/clis/douban/reviews.js +1 -0
  189. package/clis/douban/search.js +1 -0
  190. package/clis/douban/subject.js +1 -0
  191. package/clis/douban/top250.js +1 -0
  192. package/clis/douban/utils.js +11 -13
  193. package/clis/douban/utils.test.js +79 -0
  194. package/clis/doubao/ask.js +1 -0
  195. package/clis/doubao/detail.js +1 -0
  196. package/clis/doubao/history.js +1 -0
  197. package/clis/doubao/meeting-summary.js +1 -0
  198. package/clis/doubao/meeting-transcript.js +1 -0
  199. package/clis/doubao/new.js +1 -0
  200. package/clis/doubao/read.js +1 -0
  201. package/clis/doubao/send.js +1 -0
  202. package/clis/doubao/status.js +1 -0
  203. package/clis/doubao-app/ask.js +1 -0
  204. package/clis/doubao-app/dump.js +1 -0
  205. package/clis/doubao-app/new.js +1 -0
  206. package/clis/doubao-app/read.js +1 -0
  207. package/clis/doubao-app/screenshot.js +1 -0
  208. package/clis/doubao-app/send.js +1 -0
  209. package/clis/doubao-app/status.js +1 -0
  210. package/clis/douyin/activities.js +1 -0
  211. package/clis/douyin/collections.js +1 -0
  212. package/clis/douyin/delete.js +1 -0
  213. package/clis/douyin/draft.js +1 -0
  214. package/clis/douyin/drafts.js +1 -0
  215. package/clis/douyin/hashtag.js +1 -0
  216. package/clis/douyin/location.js +1 -0
  217. package/clis/douyin/profile.js +1 -0
  218. package/clis/douyin/publish.js +1 -0
  219. package/clis/douyin/stats.js +1 -0
  220. package/clis/douyin/update.js +1 -0
  221. package/clis/douyin/user-videos.js +1 -0
  222. package/clis/douyin/videos.js +1 -0
  223. package/clis/eastmoney/announcement.js +1 -0
  224. package/clis/eastmoney/convertible.js +1 -0
  225. package/clis/eastmoney/etf.js +1 -0
  226. package/clis/eastmoney/holders.js +1 -0
  227. package/clis/eastmoney/hot-rank.js +1 -0
  228. package/clis/eastmoney/index-board.js +1 -0
  229. package/clis/eastmoney/kline.js +1 -0
  230. package/clis/eastmoney/kuaixun.js +1 -0
  231. package/clis/eastmoney/longhu.js +1 -0
  232. package/clis/eastmoney/money-flow.js +1 -0
  233. package/clis/eastmoney/northbound.js +1 -0
  234. package/clis/eastmoney/quote.js +1 -0
  235. package/clis/eastmoney/rank.js +1 -0
  236. package/clis/eastmoney/sectors.js +1 -0
  237. package/clis/facebook/add-friend.js +1 -0
  238. package/clis/facebook/events.js +1 -0
  239. package/clis/facebook/feed.js +1 -0
  240. package/clis/facebook/friends.js +1 -0
  241. package/clis/facebook/groups.js +1 -0
  242. package/clis/facebook/join-group.js +1 -0
  243. package/clis/facebook/marketplace-inbox.js +1 -0
  244. package/clis/facebook/marketplace-listings.js +1 -0
  245. package/clis/facebook/memories.js +1 -0
  246. package/clis/facebook/notifications.js +1 -0
  247. package/clis/facebook/profile.js +1 -0
  248. package/clis/facebook/search.js +1 -0
  249. package/clis/gemini/ask.js +1 -0
  250. package/clis/gemini/deep-research-result.js +1 -0
  251. package/clis/gemini/deep-research.js +1 -0
  252. package/clis/gemini/image.js +1 -0
  253. package/clis/gemini/new.js +1 -0
  254. package/clis/gitee/search.js +1 -0
  255. package/clis/gitee/trending.js +1 -0
  256. package/clis/gitee/user.js +1 -0
  257. package/clis/google/news.js +1 -0
  258. package/clis/google/search.js +1 -0
  259. package/clis/google/suggest.js +1 -0
  260. package/clis/google/trends.js +1 -0
  261. package/clis/google-scholar/cite.js +1 -0
  262. package/clis/google-scholar/profile.js +1 -0
  263. package/clis/google-scholar/search.js +1 -0
  264. package/clis/gov-law/recent.js +1 -0
  265. package/clis/gov-law/search.js +1 -0
  266. package/clis/gov-policy/recent.js +1 -0
  267. package/clis/gov-policy/search.js +1 -0
  268. package/clis/grok/ask.js +1 -0
  269. package/clis/grok/image.ts +1 -0
  270. package/clis/hackernews/ask.js +3 -1
  271. package/clis/hackernews/best.js +3 -1
  272. package/clis/hackernews/hackernews.test.js +132 -0
  273. package/clis/hackernews/jobs.js +3 -1
  274. package/clis/hackernews/new.js +3 -1
  275. package/clis/hackernews/read.js +188 -0
  276. package/clis/hackernews/search.js +3 -1
  277. package/clis/hackernews/show.js +3 -1
  278. package/clis/hackernews/top.js +3 -1
  279. package/clis/hackernews/user.js +1 -0
  280. package/clis/hf/top.js +1 -0
  281. package/clis/hupu/detail.js +1 -0
  282. package/clis/hupu/hot.js +3 -1
  283. package/clis/hupu/like.js +1 -0
  284. package/clis/hupu/mentions.js +2 -1
  285. package/clis/hupu/reply.js +1 -0
  286. package/clis/hupu/search.js +3 -1
  287. package/clis/hupu/unlike.js +1 -0
  288. package/clis/imdb/person.js +1 -0
  289. package/clis/imdb/reviews.js +1 -0
  290. package/clis/imdb/search.js +1 -0
  291. package/clis/imdb/title.js +1 -0
  292. package/clis/imdb/top.js +1 -0
  293. package/clis/imdb/trending.js +1 -0
  294. package/clis/indeed/indeed.test.js +375 -0
  295. package/clis/indeed/job.js +86 -0
  296. package/clis/indeed/search.js +110 -0
  297. package/clis/indeed/utils.js +152 -0
  298. package/clis/instagram/collection-create.js +1 -0
  299. package/clis/instagram/collection-delete.js +92 -0
  300. package/clis/instagram/comment.js +1 -0
  301. package/clis/instagram/download.js +1 -0
  302. package/clis/instagram/explore.js +1 -0
  303. package/clis/instagram/follow.js +1 -0
  304. package/clis/instagram/followers.js +1 -0
  305. package/clis/instagram/following.js +1 -0
  306. package/clis/instagram/like.js +1 -0
  307. package/clis/instagram/note.js +1 -0
  308. package/clis/instagram/post.js +1 -0
  309. package/clis/instagram/profile.js +1 -0
  310. package/clis/instagram/reel.js +1 -0
  311. package/clis/instagram/save.js +1 -0
  312. package/clis/instagram/saved.js +1 -0
  313. package/clis/instagram/search.js +1 -0
  314. package/clis/instagram/story.js +1 -0
  315. package/clis/instagram/unfollow.js +1 -0
  316. package/clis/instagram/unlike.js +1 -0
  317. package/clis/instagram/unsave.js +1 -0
  318. package/clis/instagram/user.js +1 -0
  319. package/clis/jd/add-cart.js +1 -0
  320. package/clis/jd/cart.js +1 -0
  321. package/clis/jd/detail.js +1 -0
  322. package/clis/jd/item.js +1 -0
  323. package/clis/jd/reviews.js +1 -0
  324. package/clis/jd/search.js +1 -0
  325. package/clis/jianyu/detail.js +1 -0
  326. package/clis/jianyu/search.js +1 -0
  327. package/clis/jike/comment.js +1 -0
  328. package/clis/jike/create.js +1 -0
  329. package/clis/jike/feed.js +3 -1
  330. package/clis/jike/like.js +1 -0
  331. package/clis/jike/notifications.js +1 -0
  332. package/clis/jike/post.js +1 -0
  333. package/clis/jike/repost.js +1 -0
  334. package/clis/jike/search.js +3 -1
  335. package/clis/jike/topic.js +1 -0
  336. package/clis/jike/user.js +3 -1
  337. package/clis/jimeng/generate.js +1 -0
  338. package/clis/jimeng/history.js +1 -0
  339. package/clis/jimeng/new.js +1 -0
  340. package/clis/jimeng/workspaces.js +1 -0
  341. package/clis/ke/chengjiao.js +1 -0
  342. package/clis/ke/ershoufang.js +1 -0
  343. package/clis/ke/xiaoqu.js +1 -0
  344. package/clis/ke/zufang.js +1 -0
  345. package/clis/lesswrong/comments.js +1 -0
  346. package/clis/lesswrong/curated.js +1 -0
  347. package/clis/lesswrong/frontpage.js +1 -0
  348. package/clis/lesswrong/new.js +1 -0
  349. package/clis/lesswrong/read.js +1 -0
  350. package/clis/lesswrong/sequences.js +1 -0
  351. package/clis/lesswrong/shortform.js +1 -0
  352. package/clis/lesswrong/tag.js +1 -0
  353. package/clis/lesswrong/tags.js +1 -0
  354. package/clis/lesswrong/top-month.js +1 -0
  355. package/clis/lesswrong/top-week.js +1 -0
  356. package/clis/lesswrong/top-year.js +1 -0
  357. package/clis/lesswrong/top.js +1 -0
  358. package/clis/lesswrong/user-posts.js +1 -0
  359. package/clis/lesswrong/user.js +1 -0
  360. package/clis/linkedin/search.js +1 -0
  361. package/clis/linkedin/timeline.js +1 -0
  362. package/clis/linux-do/categories.js +1 -0
  363. package/clis/linux-do/category.js +1 -0
  364. package/clis/linux-do/feed.js +1 -0
  365. package/clis/linux-do/hot.js +1 -0
  366. package/clis/linux-do/latest.js +1 -0
  367. package/clis/linux-do/search.js +1 -0
  368. package/clis/linux-do/tags.js +2 -1
  369. package/clis/linux-do/topic-content.js +1 -0
  370. package/clis/linux-do/topic.js +1 -0
  371. package/clis/linux-do/user-posts.js +1 -0
  372. package/clis/linux-do/user-topics.js +1 -0
  373. package/clis/lobsters/active.js +4 -1
  374. package/clis/lobsters/hot.js +4 -1
  375. package/clis/lobsters/lobsters.test.js +169 -0
  376. package/clis/lobsters/newest.js +4 -1
  377. package/clis/lobsters/read.js +196 -0
  378. package/clis/lobsters/tag.js +4 -1
  379. package/clis/maimai/search-talents.js +1 -0
  380. package/clis/medium/feed.js +1 -0
  381. package/clis/medium/search.js +1 -0
  382. package/clis/medium/user.js +1 -0
  383. package/clis/mubu/doc.js +1 -0
  384. package/clis/mubu/docs.js +1 -0
  385. package/clis/mubu/notes.js +1 -0
  386. package/clis/mubu/recent.js +1 -0
  387. package/clis/mubu/search.js +1 -0
  388. package/clis/notebooklm/current.js +1 -0
  389. package/clis/notebooklm/get.js +1 -0
  390. package/clis/notebooklm/history.js +1 -0
  391. package/clis/notebooklm/list.js +1 -0
  392. package/clis/notebooklm/note-list.js +1 -0
  393. package/clis/notebooklm/notes-get.js +1 -0
  394. package/clis/notebooklm/open.js +1 -0
  395. package/clis/notebooklm/source-fulltext.js +1 -0
  396. package/clis/notebooklm/source-get.js +1 -0
  397. package/clis/notebooklm/source-guide.js +1 -0
  398. package/clis/notebooklm/source-list.js +1 -0
  399. package/clis/notebooklm/status.js +1 -0
  400. package/clis/notebooklm/summary.js +1 -0
  401. package/clis/notion/export.js +1 -0
  402. package/clis/notion/favorites.js +1 -0
  403. package/clis/notion/new.js +1 -0
  404. package/clis/notion/read.js +1 -0
  405. package/clis/notion/search.js +1 -0
  406. package/clis/notion/sidebar.js +1 -0
  407. package/clis/notion/status.js +1 -0
  408. package/clis/notion/write.js +1 -0
  409. package/clis/nowcoder/companies.js +1 -0
  410. package/clis/nowcoder/creators.js +1 -0
  411. package/clis/nowcoder/detail.js +1 -0
  412. package/clis/nowcoder/experience.js +1 -0
  413. package/clis/nowcoder/hot.js +1 -0
  414. package/clis/nowcoder/jobs.js +1 -0
  415. package/clis/nowcoder/notifications.js +1 -0
  416. package/clis/nowcoder/papers.js +1 -0
  417. package/clis/nowcoder/practice.js +1 -0
  418. package/clis/nowcoder/recommend.js +1 -0
  419. package/clis/nowcoder/referral.js +1 -0
  420. package/clis/nowcoder/salary.js +1 -0
  421. package/clis/nowcoder/search.js +1 -0
  422. package/clis/nowcoder/suggest.js +1 -0
  423. package/clis/nowcoder/topics.js +1 -0
  424. package/clis/nowcoder/trending.js +1 -0
  425. package/clis/ones/login.js +1 -0
  426. package/clis/ones/logout.js +1 -0
  427. package/clis/ones/me.js +1 -0
  428. package/clis/ones/my-tasks.js +1 -0
  429. package/clis/ones/task.js +1 -0
  430. package/clis/ones/tasks.js +1 -0
  431. package/clis/ones/token-info.js +1 -0
  432. package/clis/ones/worklog.js +1 -0
  433. package/clis/openreview/openreview.test.js +345 -0
  434. package/clis/openreview/paper.js +43 -0
  435. package/clis/openreview/reviews.js +131 -0
  436. package/clis/openreview/search.js +46 -0
  437. package/clis/openreview/utils.js +158 -0
  438. package/clis/openreview/venue.js +63 -0
  439. package/clis/paperreview/feedback.js +1 -0
  440. package/clis/paperreview/review.js +1 -0
  441. package/clis/paperreview/submit.js +1 -0
  442. package/clis/pixiv/detail.js +1 -0
  443. package/clis/pixiv/download.js +1 -0
  444. package/clis/pixiv/illusts.js +2 -1
  445. package/clis/pixiv/ranking.js +2 -1
  446. package/clis/pixiv/search.js +2 -1
  447. package/clis/pixiv/user.js +2 -0
  448. package/clis/powerchina/search.js +1 -0
  449. package/clis/producthunt/browse.js +1 -0
  450. package/clis/producthunt/hot.js +1 -0
  451. package/clis/producthunt/posts.js +1 -0
  452. package/clis/producthunt/today.js +1 -0
  453. package/clis/quark/ls.js +1 -0
  454. package/clis/quark/mkdir.js +1 -0
  455. package/clis/quark/mv.js +1 -0
  456. package/clis/quark/rename.js +1 -0
  457. package/clis/quark/rm.js +1 -0
  458. package/clis/quark/save.js +1 -0
  459. package/clis/quark/share-tree.js +1 -0
  460. package/clis/reddit/comment.js +1 -0
  461. package/clis/reddit/frontpage.js +1 -0
  462. package/clis/reddit/hot.js +6 -1
  463. package/clis/reddit/hot.test.js +18 -0
  464. package/clis/reddit/popular.js +1 -0
  465. package/clis/reddit/read.js +1 -0
  466. package/clis/reddit/save.js +1 -0
  467. package/clis/reddit/saved.js +1 -0
  468. package/clis/reddit/search.js +1 -0
  469. package/clis/reddit/subreddit.js +1 -0
  470. package/clis/reddit/subscribe.js +1 -0
  471. package/clis/reddit/upvote.js +1 -0
  472. package/clis/reddit/upvoted.js +1 -0
  473. package/clis/reddit/user-comments.js +1 -0
  474. package/clis/reddit/user-posts.js +1 -0
  475. package/clis/reddit/user.js +1 -0
  476. package/clis/reuters/search.js +1 -0
  477. package/clis/sinablog/article.js +1 -0
  478. package/clis/sinablog/hot.js +1 -0
  479. package/clis/sinablog/search.js +1 -0
  480. package/clis/sinablog/user.js +1 -0
  481. package/clis/sinafinance/news.js +1 -0
  482. package/clis/sinafinance/rolling-news.js +1 -0
  483. package/clis/sinafinance/stock-rank.js +1 -0
  484. package/clis/sinafinance/stock.js +1 -0
  485. package/clis/smzdm/search.js +1 -0
  486. package/clis/spotify/spotify.js +11 -0
  487. package/clis/stackoverflow/bounties.js +11 -3
  488. package/clis/stackoverflow/hot.js +10 -2
  489. package/clis/stackoverflow/read.js +314 -0
  490. package/clis/stackoverflow/search.js +10 -2
  491. package/clis/stackoverflow/stackoverflow.test.js +346 -0
  492. package/clis/stackoverflow/unanswered.js +9 -2
  493. package/clis/steam/top-sellers.js +1 -0
  494. package/clis/substack/feed.js +1 -0
  495. package/clis/substack/publication.js +1 -0
  496. package/clis/substack/search.js +1 -0
  497. package/clis/taobao/add-cart.js +1 -0
  498. package/clis/taobao/cart.js +1 -0
  499. package/clis/taobao/detail.js +1 -0
  500. package/clis/taobao/reviews.js +1 -0
  501. package/clis/taobao/search.js +1 -0
  502. package/clis/tdx/hot-rank.js +1 -0
  503. package/clis/ths/hot-rank.js +1 -0
  504. package/clis/tieba/hot.js +2 -1
  505. package/clis/tieba/posts.js +1 -0
  506. package/clis/tieba/read.js +1 -0
  507. package/clis/tieba/search.js +2 -1
  508. package/clis/tiktok/comment.js +1 -0
  509. package/clis/tiktok/explore.js +1 -0
  510. package/clis/tiktok/follow.js +1 -0
  511. package/clis/tiktok/following.js +1 -0
  512. package/clis/tiktok/friends.js +1 -0
  513. package/clis/tiktok/like.js +1 -0
  514. package/clis/tiktok/live.js +1 -0
  515. package/clis/tiktok/notifications.js +1 -0
  516. package/clis/tiktok/profile.js +1 -0
  517. package/clis/tiktok/save.js +1 -0
  518. package/clis/tiktok/search.js +1 -0
  519. package/clis/tiktok/unfollow.js +1 -0
  520. package/clis/tiktok/unlike.js +1 -0
  521. package/clis/tiktok/unsave.js +1 -0
  522. package/clis/tiktok/user.js +1 -0
  523. package/clis/toutiao/articles.js +1 -0
  524. package/clis/twitter/accept.js +1 -0
  525. package/clis/twitter/article.js +1 -0
  526. package/clis/twitter/block.js +1 -0
  527. package/clis/twitter/bookmark.js +1 -0
  528. package/clis/twitter/bookmarks.js +2 -1
  529. package/clis/twitter/delete.js +1 -0
  530. package/clis/twitter/download.js +1 -0
  531. package/clis/twitter/follow.js +1 -0
  532. package/clis/twitter/followers.js +1 -0
  533. package/clis/twitter/following.js +1 -0
  534. package/clis/twitter/hide-reply.js +1 -0
  535. package/clis/twitter/like.js +1 -0
  536. package/clis/twitter/likes.js +2 -1
  537. package/clis/twitter/list-add.js +1 -0
  538. package/clis/twitter/list-remove.js +1 -0
  539. package/clis/twitter/list-tweets.js +1 -0
  540. package/clis/twitter/lists.js +1 -0
  541. package/clis/twitter/notifications.js +1 -0
  542. package/clis/twitter/post.js +1 -0
  543. package/clis/twitter/profile.js +1 -0
  544. package/clis/twitter/reply-dm.js +1 -0
  545. package/clis/twitter/reply.js +1 -0
  546. package/clis/twitter/search.js +1 -0
  547. package/clis/twitter/thread.js +1 -0
  548. package/clis/twitter/timeline.js +1 -0
  549. package/clis/twitter/trending.js +11 -12
  550. package/clis/twitter/trending.test.js +15 -0
  551. package/clis/twitter/tweets.js +2 -1
  552. package/clis/twitter/tweets.test.js +2 -2
  553. package/clis/twitter/unblock.js +1 -0
  554. package/clis/twitter/unbookmark.js +1 -0
  555. package/clis/twitter/unfollow.js +1 -0
  556. package/clis/uiverse/code.js +1 -0
  557. package/clis/uiverse/preview.js +1 -0
  558. package/clis/v2ex/daily.js +1 -0
  559. package/clis/v2ex/hot.js +1 -0
  560. package/clis/v2ex/latest.js +1 -0
  561. package/clis/v2ex/me.js +1 -0
  562. package/clis/v2ex/member.js +1 -0
  563. package/clis/v2ex/node.js +1 -0
  564. package/clis/v2ex/nodes.js +1 -0
  565. package/clis/v2ex/notifications.js +1 -0
  566. package/clis/v2ex/replies.js +1 -0
  567. package/clis/v2ex/topic.js +1 -0
  568. package/clis/v2ex/user.js +1 -0
  569. package/clis/wanfang/search.js +1 -0
  570. package/clis/web/read.js +1 -0
  571. package/clis/weibo/comments.js +1 -0
  572. package/clis/weibo/favorites.js +1 -0
  573. package/clis/weibo/feed.js +3 -1
  574. package/clis/weibo/hot.js +1 -0
  575. package/clis/weibo/me.js +1 -0
  576. package/clis/weibo/post.js +1 -0
  577. package/clis/weibo/publish.js +1 -0
  578. package/clis/weibo/search.js +8 -2
  579. package/clis/weibo/user.js +1 -0
  580. package/clis/weixin/create-draft.js +1 -0
  581. package/clis/weixin/download.js +1 -0
  582. package/clis/weixin/drafts.js +1 -0
  583. package/clis/weread/ai-outline.js +1 -0
  584. package/clis/weread/book.js +1 -0
  585. package/clis/weread/highlights.js +1 -0
  586. package/clis/weread/notebooks.js +1 -0
  587. package/clis/weread/notes.js +1 -0
  588. package/clis/weread/ranking.js +1 -0
  589. package/clis/weread/search.js +1 -0
  590. package/clis/weread/shelf.js +1 -0
  591. package/clis/wikipedia/random.js +1 -0
  592. package/clis/wikipedia/search.js +1 -0
  593. package/clis/wikipedia/summary.js +1 -0
  594. package/clis/wikipedia/trending.js +1 -0
  595. package/clis/xianyu/chat.js +1 -0
  596. package/clis/xianyu/item.js +1 -0
  597. package/clis/xianyu/search.js +1 -0
  598. package/clis/xiaoe/catalog.js +2 -1
  599. package/clis/xiaoe/content.js +1 -0
  600. package/clis/xiaoe/courses.js +1 -0
  601. package/clis/xiaoe/detail.js +1 -0
  602. package/clis/xiaoe/play-url.js +1 -0
  603. package/clis/xiaohongshu/comments.js +1 -0
  604. package/clis/xiaohongshu/creator-note-detail.js +1 -0
  605. package/clis/xiaohongshu/creator-notes-summary.js +1 -0
  606. package/clis/xiaohongshu/creator-notes.js +1 -0
  607. package/clis/xiaohongshu/creator-profile.js +1 -0
  608. package/clis/xiaohongshu/creator-stats.js +1 -0
  609. package/clis/xiaohongshu/download.js +1 -0
  610. package/clis/xiaohongshu/feed.js +2 -1
  611. package/clis/xiaohongshu/note.js +1 -0
  612. package/clis/xiaohongshu/notifications.js +1 -0
  613. package/clis/xiaohongshu/publish.js +1 -0
  614. package/clis/xiaohongshu/search.js +1 -0
  615. package/clis/xiaohongshu/user.js +1 -0
  616. package/clis/xiaoyuzhou/download.js +1 -0
  617. package/clis/xiaoyuzhou/episode.js +1 -0
  618. package/clis/xiaoyuzhou/podcast-episodes.js +1 -0
  619. package/clis/xiaoyuzhou/podcast.js +1 -0
  620. package/clis/xiaoyuzhou/transcript.js +1 -0
  621. package/clis/xueqiu/comments.js +1 -0
  622. package/clis/xueqiu/earnings-date.js +1 -0
  623. package/clis/xueqiu/feed.js +1 -0
  624. package/clis/xueqiu/fund-holdings.js +1 -0
  625. package/clis/xueqiu/fund-snapshot.js +1 -0
  626. package/clis/xueqiu/groups.js +1 -0
  627. package/clis/xueqiu/hot-stock.js +1 -0
  628. package/clis/xueqiu/hot.js +1 -0
  629. package/clis/xueqiu/kline.js +1 -0
  630. package/clis/xueqiu/search.js +1 -0
  631. package/clis/xueqiu/stock.js +1 -0
  632. package/clis/xueqiu/watchlist.js +1 -0
  633. package/clis/yahoo-finance/quote.js +1 -0
  634. package/clis/yollomi/background.js +1 -0
  635. package/clis/yollomi/edit.js +1 -0
  636. package/clis/yollomi/face-swap.js +1 -0
  637. package/clis/yollomi/generate.js +1 -0
  638. package/clis/yollomi/models.js +1 -0
  639. package/clis/yollomi/object-remover.js +1 -0
  640. package/clis/yollomi/remove-bg.js +1 -0
  641. package/clis/yollomi/restore.js +1 -0
  642. package/clis/yollomi/try-on.js +1 -0
  643. package/clis/yollomi/upload.js +1 -0
  644. package/clis/yollomi/upscale.js +1 -0
  645. package/clis/yollomi/video.js +1 -0
  646. package/clis/youtube/channel.js +1 -0
  647. package/clis/youtube/comments.js +1 -0
  648. package/clis/youtube/feed.js +8 -7
  649. package/clis/youtube/feed.test.js +131 -0
  650. package/clis/youtube/history.js +1 -0
  651. package/clis/youtube/like.js +1 -0
  652. package/clis/youtube/playlist.js +1 -0
  653. package/clis/youtube/search.js +1 -0
  654. package/clis/youtube/subscribe.js +1 -0
  655. package/clis/youtube/subscriptions.js +1 -0
  656. package/clis/youtube/transcript.js +1 -0
  657. package/clis/youtube/unlike.js +1 -0
  658. package/clis/youtube/unsubscribe.js +1 -0
  659. package/clis/youtube/video.js +1 -0
  660. package/clis/youtube/watch-later.js +1 -0
  661. package/clis/yuanbao/ask.js +1 -0
  662. package/clis/yuanbao/new.js +1 -0
  663. package/clis/zhihu/answer.js +1 -0
  664. package/clis/zhihu/collection.js +1 -0
  665. package/clis/zhihu/collections.js +1 -0
  666. package/clis/zhihu/comment.js +1 -0
  667. package/clis/zhihu/download.js +1 -0
  668. package/clis/zhihu/favorite.js +1 -0
  669. package/clis/zhihu/follow.js +1 -0
  670. package/clis/zhihu/hot.js +1 -0
  671. package/clis/zhihu/like.js +1 -0
  672. package/clis/zhihu/question.js +1 -0
  673. package/clis/zhihu/search.js +1 -0
  674. package/clis/zlibrary/info.js +1 -0
  675. package/clis/zlibrary/search.js +1 -0
  676. package/clis/zsxq/dynamics.js +1 -0
  677. package/clis/zsxq/groups.js +1 -0
  678. package/clis/zsxq/search.js +1 -0
  679. package/clis/zsxq/topic.js +1 -0
  680. package/clis/zsxq/topics.js +1 -0
  681. package/dist/src/adapter-shadow.d.ts +11 -0
  682. package/dist/src/adapter-shadow.js +72 -0
  683. package/dist/src/adapter-shadow.test.d.ts +1 -0
  684. package/dist/src/adapter-shadow.test.js +49 -0
  685. package/dist/src/adapter-source.test.js +1 -1
  686. package/dist/src/browser/analyze.test.js +2 -0
  687. package/dist/src/browser/base-page.d.ts +15 -2
  688. package/dist/src/browser/base-page.js +159 -8
  689. package/dist/src/browser/base-page.test.js +103 -1
  690. package/dist/src/browser/cdp.js +54 -0
  691. package/dist/src/browser/cdp.test.js +26 -0
  692. package/dist/src/browser/dom-helpers.d.ts +1 -1
  693. package/dist/src/browser/dom-helpers.js +15 -3
  694. package/dist/src/browser/page.d.ts +1 -0
  695. package/dist/src/browser/page.js +7 -1
  696. package/dist/src/browser/page.test.js +17 -0
  697. package/dist/src/browser/target-resolver.d.ts +17 -2
  698. package/dist/src/browser/target-resolver.js +85 -4
  699. package/dist/src/browser/verify-fixture.d.ts +7 -1
  700. package/dist/src/browser/verify-fixture.js +105 -0
  701. package/dist/src/browser/verify-fixture.test.js +59 -1
  702. package/dist/src/build-manifest.d.ts +68 -33
  703. package/dist/src/build-manifest.js +178 -29
  704. package/dist/src/build-manifest.test.js +85 -5
  705. package/dist/src/capabilityRouting.test.js +1 -1
  706. package/dist/src/cli.js +150 -11
  707. package/dist/src/cli.test.js +237 -0
  708. package/dist/src/commanderAdapter.d.ts +1 -1
  709. package/dist/src/commanderAdapter.js +17 -7
  710. package/dist/src/commanderAdapter.test.js +7 -6
  711. package/dist/src/convention-audit.d.ts +50 -0
  712. package/dist/src/convention-audit.js +546 -0
  713. package/dist/src/convention-audit.test.d.ts +1 -0
  714. package/dist/src/convention-audit.test.js +226 -0
  715. package/dist/src/discovery.js +2 -0
  716. package/dist/src/doctor.d.ts +2 -0
  717. package/dist/src/doctor.js +6 -0
  718. package/dist/src/doctor.test.js +36 -1
  719. package/dist/src/engine.test.js +10 -10
  720. package/dist/src/execution.js +1 -1
  721. package/dist/src/execution.test.js +9 -9
  722. package/dist/src/help.d.ts +15 -0
  723. package/dist/src/help.js +149 -0
  724. package/dist/src/main.js +13 -7
  725. package/dist/src/manifest-types.d.ts +41 -0
  726. package/dist/src/manifest-types.js +9 -0
  727. package/dist/src/plugin.test.js +26 -26
  728. package/dist/src/registry.d.ts +5 -0
  729. package/dist/src/registry.js +8 -0
  730. package/dist/src/registry.test.js +27 -20
  731. package/dist/src/serialization.d.ts +4 -0
  732. package/dist/src/serialization.js +27 -1
  733. package/dist/src/serialization.test.js +24 -2
  734. package/dist/src/types.d.ts +2 -0
  735. package/package.json +5 -2
  736. package/scripts/check-listing-id-pairing.mjs +193 -0
  737. package/scripts/check-silent-column-drop.mjs +105 -0
  738. package/scripts/check-typed-error-lint.mjs +118 -0
  739. package/scripts/silent-column-drop-baseline.json +962 -0
  740. package/scripts/typed-error-lint-baseline.json +1586 -0
@@ -0,0 +1,49 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import * as fs from 'node:fs';
3
+ import * as os from 'node:os';
4
+ import * as path from 'node:path';
5
+ import { findShadowedUserAdapters, formatAdapterShadowIssue } from './adapter-shadow.js';
6
+ describe('adapter shadow detection', () => {
7
+ it('reports user adapters that shadow packaged manifest commands', () => {
8
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-adapter-shadow-'));
9
+ try {
10
+ const userClisDir = path.join(root, 'user-clis');
11
+ const builtinRoot = path.join(root, 'pkg');
12
+ const builtinClisDir = path.join(builtinRoot, 'clis');
13
+ fs.mkdirSync(path.join(userClisDir, 'instagram'), { recursive: true });
14
+ fs.mkdirSync(path.join(userClisDir, 'twitter'), { recursive: true });
15
+ fs.mkdirSync(path.join(builtinClisDir, 'instagram'), { recursive: true });
16
+ fs.mkdirSync(path.join(builtinClisDir, 'twitter'), { recursive: true });
17
+ fs.writeFileSync(path.join(userClisDir, 'instagram', 'saved.js'), '', 'utf-8');
18
+ fs.writeFileSync(path.join(userClisDir, 'instagram', 'utils.js'), '', 'utf-8');
19
+ fs.writeFileSync(path.join(userClisDir, 'twitter', 'search.js'), '', 'utf-8');
20
+ fs.writeFileSync(path.join(builtinClisDir, 'instagram', 'saved.js'), '', 'utf-8');
21
+ fs.writeFileSync(path.join(builtinClisDir, 'instagram', 'utils.js'), '', 'utf-8');
22
+ fs.writeFileSync(path.join(builtinClisDir, 'twitter', 'search.js'), '', 'utf-8');
23
+ fs.writeFileSync(path.join(builtinRoot, 'cli-manifest.json'), `${JSON.stringify([
24
+ { site: 'instagram', name: 'saved', sourceFile: 'instagram/saved.js' },
25
+ ])}\n`, 'utf-8');
26
+ expect(findShadowedUserAdapters({ userClisDir, builtinClisDir })).toEqual([
27
+ {
28
+ name: 'instagram/saved',
29
+ userPath: path.join(userClisDir, 'instagram', 'saved.js'),
30
+ builtinPath: path.join(builtinClisDir, 'instagram', 'saved.js'),
31
+ },
32
+ ]);
33
+ }
34
+ finally {
35
+ fs.rmSync(root, { recursive: true, force: true });
36
+ }
37
+ });
38
+ it('formats a concise doctor issue', () => {
39
+ const issue = formatAdapterShadowIssue([
40
+ {
41
+ name: 'instagram/saved',
42
+ userPath: '/home/me/.opencli/clis/instagram/saved.js',
43
+ builtinPath: '/pkg/clis/instagram/saved.js',
44
+ },
45
+ ]);
46
+ expect(issue).toContain('instagram/saved');
47
+ expect(issue).toContain('opencli adapter reset <site>');
48
+ });
49
+ });
@@ -3,7 +3,7 @@ import { resolveAdapterSourcePath } from './adapter-source.js';
3
3
  function makeCmd(overrides = {}) {
4
4
  return {
5
5
  site: 'test-site',
6
- name: 'test-cmd',
6
+ name: 'test-cmd', access: 'read',
7
7
  description: 'test',
8
8
  args: [],
9
9
  ...overrides,
@@ -20,8 +20,10 @@ function mkCmd(site, name, domain) {
20
20
  return {
21
21
  site,
22
22
  name,
23
+ access: 'read',
23
24
  description: '',
24
25
  domain,
26
+ browser: false,
25
27
  args: [],
26
28
  };
27
29
  }
@@ -23,6 +23,7 @@ export declare abstract class BasePage implements IPage {
23
23
  protected _lastUrl: string | null;
24
24
  /** Cached previous snapshot hashes for incremental diff marking */
25
25
  private _prevSnapshotHashes;
26
+ private _cdpTargetMarkerSeq;
26
27
  abstract goto(url: string, options?: {
27
28
  waitUntil?: 'load' | 'none';
28
29
  settleMs?: number;
@@ -47,8 +48,20 @@ export declare abstract class BasePage implements IPage {
47
48
  abstract tabs(): Promise<unknown[]>;
48
49
  abstract selectTab(target: number | string): Promise<void>;
49
50
  click(ref: string, opts?: ResolveOptions): Promise<ResolveSuccess>;
50
- /** Override in subclasses with CDP native click support */
51
- protected tryNativeClick(_x: number, _y: number): Promise<boolean>;
51
+ /** Uses native CDP click support when the concrete page exposes it. */
52
+ protected tryNativeClick(x: number, y: number): Promise<boolean>;
53
+ /** Uses native CDP text insertion when the concrete page exposes it. */
54
+ protected tryNativeType(text: string): Promise<boolean>;
55
+ /** Uses native CDP key events when the concrete page exposes them. */
56
+ protected tryNativeKeyPress(key: string, modifiers: string[]): Promise<boolean>;
57
+ /**
58
+ * Run a DOM-domain CDP command against `window.__resolved`.
59
+ *
60
+ * CDP DOM.focus / DOM.scrollIntoViewIfNeeded need a nodeId, while our
61
+ * resolver stores the live Element in page JS. Bridge the two worlds with a
62
+ * short-lived marker attribute, then query it through CDP.
63
+ */
64
+ protected tryCdpOnResolvedElement(method: 'DOM.focus' | 'DOM.scrollIntoViewIfNeeded'): Promise<boolean>;
52
65
  typeText(ref: string, text: string, opts?: ResolveOptions): Promise<ResolveSuccess>;
53
66
  pressKey(key: string): Promise<void>;
54
67
  scrollTo(ref: string, opts?: ResolveOptions): Promise<unknown>;
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { generateSnapshotJs, getFormStateJs } from './dom-snapshot.js';
12
12
  import { pressKeyJs, waitForTextJs, waitForCaptureJs, waitForSelectorJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, } from './dom-helpers.js';
13
- import { resolveTargetJs, clickResolvedJs, typeResolvedJs, scrollResolvedJs, } from './target-resolver.js';
13
+ import { resolveTargetJs, clickResolvedJs, typeResolvedJs, prepareNativeTypeResolvedJs, scrollResolvedJs, } from './target-resolver.js';
14
14
  import { TargetError } from './target-errors.js';
15
15
  import { CliError } from '../errors.js';
16
16
  import { formatSnapshot } from '../snapshotFormatter.js';
@@ -36,10 +36,32 @@ function previewText(text) {
36
36
  const preview = (text ?? '').replace(/\s+/g, ' ').trim().slice(0, 300);
37
37
  return preview ? `Response preview: ${preview}` : undefined;
38
38
  }
39
+ function parseKeyChord(rawKey) {
40
+ const parts = rawKey.split('+').map(part => part.trim()).filter(Boolean);
41
+ if (parts.length <= 1)
42
+ return { key: rawKey, modifiers: [] };
43
+ const modifiers = [];
44
+ for (const token of parts.slice(0, -1)) {
45
+ const normalized = token.toLowerCase();
46
+ if (normalized === 'ctrl' || normalized === 'control')
47
+ modifiers.push('Ctrl');
48
+ else if (normalized === 'cmd' || normalized === 'command' || normalized === 'meta')
49
+ modifiers.push('Meta');
50
+ else if (normalized === 'option' || normalized === 'alt')
51
+ modifiers.push('Alt');
52
+ else if (normalized === 'shift')
53
+ modifiers.push('Shift');
54
+ else
55
+ return { key: rawKey, modifiers: [] };
56
+ }
57
+ const key = parts.at(-1);
58
+ return key ? { key, modifiers } : { key: rawKey, modifiers: [] };
59
+ }
39
60
  export class BasePage {
40
61
  _lastUrl = null;
41
62
  /** Cached previous snapshot hashes for incremental diff marking */
42
63
  _prevSnapshotHashes = null;
64
+ _cdpTargetMarkerSeq = 0;
43
65
  /**
44
66
  * Safely evaluate JS with pre-serialized arguments.
45
67
  * Each key in `args` becomes a `const` declaration with JSON-serialized value,
@@ -133,8 +155,9 @@ export class BasePage {
133
155
  async click(ref, opts = {}) {
134
156
  // Phase 1: Resolve target with fingerprint verification
135
157
  const resolved = await runResolve(this, ref, opts);
158
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
136
159
  // Phase 2: Execute click on resolved element
137
- const result = await this.evaluate(clickResolvedJs());
160
+ const result = await this.evaluate(clickResolvedJs({ skipScroll: nativeScrolled }));
138
161
  if (typeof result === 'string' || result == null)
139
162
  return resolved;
140
163
  if (result.status === 'clicked')
@@ -147,21 +170,149 @@ export class BasePage {
147
170
  }
148
171
  throw new Error(`Click failed: ${result.error ?? 'JS click and CDP fallback both failed'}`);
149
172
  }
150
- /** Override in subclasses with CDP native click support */
151
- async tryNativeClick(_x, _y) {
152
- return false;
173
+ /** Uses native CDP click support when the concrete page exposes it. */
174
+ async tryNativeClick(x, y) {
175
+ const nativeClick = this.nativeClick;
176
+ if (typeof nativeClick !== 'function')
177
+ return false;
178
+ try {
179
+ await nativeClick.call(this, x, y);
180
+ return true;
181
+ }
182
+ catch {
183
+ return false;
184
+ }
185
+ }
186
+ /** Uses native CDP text insertion when the concrete page exposes it. */
187
+ async tryNativeType(text) {
188
+ const nativeType = this.nativeType;
189
+ if (typeof nativeType === 'function') {
190
+ try {
191
+ await nativeType.call(this, text);
192
+ return true;
193
+ }
194
+ catch {
195
+ // Fall through to the older dedicated insertText primitive if present.
196
+ }
197
+ }
198
+ const insertText = this.insertText;
199
+ if (typeof insertText !== 'function')
200
+ return false;
201
+ try {
202
+ await insertText.call(this, text);
203
+ return true;
204
+ }
205
+ catch {
206
+ return false;
207
+ }
208
+ }
209
+ /** Uses native CDP key events when the concrete page exposes them. */
210
+ async tryNativeKeyPress(key, modifiers) {
211
+ const nativeKeyPress = this.nativeKeyPress;
212
+ if (typeof nativeKeyPress !== 'function')
213
+ return false;
214
+ try {
215
+ await nativeKeyPress.call(this, key, modifiers);
216
+ return true;
217
+ }
218
+ catch {
219
+ return false;
220
+ }
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
+ }
153
280
  }
154
281
  async typeText(ref, text, opts = {}) {
155
282
  const resolved = await runResolve(this, ref, opts);
156
- await this.evaluate(typeResolvedJs(text));
283
+ let typed = false;
284
+ let nativeScrolled = false;
285
+ let nativeFocused = false;
286
+ if (typeof this.nativeType === 'function' || typeof this.insertText === 'function') {
287
+ try {
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
+ }));
294
+ typed = preparation?.ok === true && await this.tryNativeType(text);
295
+ }
296
+ catch {
297
+ // Native input is a reliability upgrade, not the only path. Preserve
298
+ // the existing DOM setter fallback if preparation fails.
299
+ }
300
+ }
301
+ if (!typed) {
302
+ await this.evaluate(typeResolvedJs(text));
303
+ }
157
304
  return resolved;
158
305
  }
159
306
  async pressKey(key) {
160
- await this.evaluate(pressKeyJs(key));
307
+ const parsed = parseKeyChord(key);
308
+ if (!await this.tryNativeKeyPress(parsed.key, parsed.modifiers)) {
309
+ await this.evaluate(pressKeyJs(parsed.key, parsed.modifiers));
310
+ }
161
311
  }
162
312
  async scrollTo(ref, opts = {}) {
163
313
  const resolved = await runResolve(this, ref, opts);
164
- const result = (await this.evaluate(scrollResolvedJs()));
314
+ const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
315
+ const result = (await this.evaluate(scrollResolvedJs({ skipScroll: nativeScrolled })));
165
316
  // Fold match_level into the scroll payload so the user-facing envelope
166
317
  // carries it the same way click / type do.
167
318
  if (result && typeof result === 'object') {
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { describe, expect, it, vi } from 'vitest';
2
2
  import { CliError } from '../errors.js';
3
3
  import { BasePage } from './base-page.js';
4
4
  class TestPage extends BasePage {
@@ -15,6 +15,31 @@ class TestPage extends BasePage {
15
15
  async tabs() { return []; }
16
16
  async selectTab() { }
17
17
  }
18
+ class ActionPage extends BasePage {
19
+ results = [];
20
+ withArgsResults = [];
21
+ scripts = [];
22
+ withArgs = [];
23
+ nativeType;
24
+ insertText;
25
+ nativeKeyPress;
26
+ cdp;
27
+ async goto() { }
28
+ async evaluate(js) {
29
+ this.scripts.push(js);
30
+ return this.results.shift() ?? null;
31
+ }
32
+ async evaluateWithArgs(js, args) {
33
+ this.scripts.push(js);
34
+ this.withArgs.push(args);
35
+ return this.withArgsResults.shift() ?? null;
36
+ }
37
+ async getCookies() { return []; }
38
+ async screenshot() { return ''; }
39
+ async tabs() { return []; }
40
+ async selectTab() { }
41
+ }
42
+ const resolveOk = { ok: true, matches_n: 1, match_level: 'exact' };
18
43
  describe('BasePage.fetchJson', () => {
19
44
  it('passes a narrow browser-context JSON request and parses the response in Node', async () => {
20
45
  const page = new TestPage();
@@ -72,3 +97,80 @@ describe('BasePage.fetchJson', () => {
72
97
  });
73
98
  });
74
99
  });
100
+ describe('BasePage native input routing', () => {
101
+ it('types rich-editor text via native Input.insertText when available', async () => {
102
+ const page = new ActionPage();
103
+ page.nativeType = vi.fn().mockResolvedValue(undefined);
104
+ page.results = [resolveOk, { ok: true, mode: 'contenteditable' }];
105
+ await expect(page.typeText('#editor', 'hello')).resolves.toEqual({ matches_n: 1, match_level: 'exact' });
106
+ expect(page.nativeType).toHaveBeenCalledWith('hello');
107
+ expect(page.scripts).toHaveLength(2);
108
+ expect(page.scripts[1]).toContain('nearestContentEditableHost');
109
+ expect(page.scripts.join('\n')).not.toContain("return 'typed'");
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
+ });
132
+ it('keeps the DOM setter fallback when native text insertion is unavailable', async () => {
133
+ const page = new ActionPage();
134
+ page.results = [resolveOk, 'typed'];
135
+ await page.typeText('#q', 'hello');
136
+ expect(page.scripts).toHaveLength(2);
137
+ expect(page.scripts[1]).toContain('document.execCommand');
138
+ expect(page.scripts[1]).toContain("return 'typed'");
139
+ });
140
+ it('falls back to DOM typing if native text insertion fails', async () => {
141
+ const page = new ActionPage();
142
+ page.nativeType = vi.fn().mockRejectedValue(new Error('native failed'));
143
+ page.results = [resolveOk, { ok: true, mode: 'input' }, 'typed'];
144
+ await page.typeText('#q', 'hello');
145
+ expect(page.nativeType).toHaveBeenCalledWith('hello');
146
+ expect(page.scripts).toHaveLength(3);
147
+ expect(page.scripts[2]).toContain("return 'typed'");
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
+ });
162
+ it('presses key chords through native CDP key events when available', async () => {
163
+ const page = new ActionPage();
164
+ page.nativeKeyPress = vi.fn().mockResolvedValue(undefined);
165
+ await page.pressKey('Control+a');
166
+ expect(page.nativeKeyPress).toHaveBeenCalledWith('a', ['Ctrl']);
167
+ expect(page.scripts).toHaveLength(0);
168
+ });
169
+ it('falls back to synthetic keyboard events with parsed modifiers', async () => {
170
+ const page = new ActionPage();
171
+ await page.pressKey('Meta+N');
172
+ expect(page.scripts).toHaveLength(1);
173
+ expect(page.scripts[0]).toContain('key: "N"');
174
+ expect(page.scripts[0]).toContain('metaKey: true');
175
+ });
176
+ });
@@ -317,6 +317,60 @@ class CDPPage extends BasePage {
317
317
  async selectTab(_target) {
318
318
  // Not supported in direct CDP mode
319
319
  }
320
+ async cdp(method, params = {}) {
321
+ return this.bridge.send(method, params);
322
+ }
323
+ async handleJavaScriptDialog(accept, promptText) {
324
+ await this.cdp('Page.handleJavaScriptDialog', {
325
+ accept,
326
+ ...(promptText !== undefined && { promptText }),
327
+ });
328
+ }
329
+ async nativeClick(x, y) {
330
+ await this.cdp('Input.dispatchMouseEvent', {
331
+ type: 'mousePressed',
332
+ x,
333
+ y,
334
+ button: 'left',
335
+ clickCount: 1,
336
+ });
337
+ await this.cdp('Input.dispatchMouseEvent', {
338
+ type: 'mouseReleased',
339
+ x,
340
+ y,
341
+ button: 'left',
342
+ clickCount: 1,
343
+ });
344
+ }
345
+ async nativeType(text) {
346
+ await this.cdp('Input.insertText', { text });
347
+ }
348
+ async insertText(text) {
349
+ await this.nativeType(text);
350
+ }
351
+ async nativeKeyPress(key, modifiers = []) {
352
+ let modifierFlags = 0;
353
+ for (const mod of modifiers) {
354
+ if (mod === 'Alt')
355
+ modifierFlags |= 1;
356
+ if (mod === 'Ctrl' || mod === 'Control')
357
+ modifierFlags |= 2;
358
+ if (mod === 'Meta')
359
+ modifierFlags |= 4;
360
+ if (mod === 'Shift')
361
+ modifierFlags |= 8;
362
+ }
363
+ await this.cdp('Input.dispatchKeyEvent', {
364
+ type: 'keyDown',
365
+ key,
366
+ modifiers: modifierFlags,
367
+ });
368
+ await this.cdp('Input.dispatchKeyEvent', {
369
+ type: 'keyUp',
370
+ key,
371
+ modifiers: modifierFlags,
372
+ });
373
+ }
320
374
  }
321
375
  function isCookie(value) {
322
376
  return isRecord(value)
@@ -49,4 +49,30 @@ describe('CDPBridge cookies', () => {
49
49
  { name: 'exact', value: '2', domain: 'example.com' },
50
50
  ]);
51
51
  });
52
+ it('exposes native input helpers on direct CDP pages', async () => {
53
+ vi.stubEnv('OPENCLI_CDP_ENDPOINT', 'ws://127.0.0.1:9222/devtools/page/1');
54
+ const bridge = new CDPBridge();
55
+ const send = vi.spyOn(bridge, 'send').mockResolvedValue({});
56
+ const page = await bridge.connect();
57
+ send.mockClear();
58
+ expect(page.nativeType).toBeTypeOf('function');
59
+ expect(page.nativeKeyPress).toBeTypeOf('function');
60
+ expect(page.nativeClick).toBeTypeOf('function');
61
+ expect(page.handleJavaScriptDialog).toBeTypeOf('function');
62
+ expect(page.cdp).toBeTypeOf('function');
63
+ await page.nativeType('hello');
64
+ await page.nativeKeyPress('a', ['Ctrl']);
65
+ await page.nativeClick(10, 20);
66
+ await page.handleJavaScriptDialog(true, 'ok');
67
+ await page.cdp('Page.getLayoutMetrics', {});
68
+ expect(send.mock.calls).toEqual([
69
+ ['Input.insertText', { text: 'hello' }],
70
+ ['Input.dispatchKeyEvent', { type: 'keyDown', key: 'a', modifiers: 2 }],
71
+ ['Input.dispatchKeyEvent', { type: 'keyUp', key: 'a', modifiers: 2 }],
72
+ ['Input.dispatchMouseEvent', { type: 'mousePressed', x: 10, y: 20, button: 'left', clickCount: 1 }],
73
+ ['Input.dispatchMouseEvent', { type: 'mouseReleased', x: 10, y: 20, button: 'left', clickCount: 1 }],
74
+ ['Page.handleJavaScriptDialog', { accept: true, promptText: 'ok' }],
75
+ ['Page.getLayoutMetrics', {}],
76
+ ]);
77
+ });
52
78
  });
@@ -11,7 +11,7 @@ export declare function clickJs(ref: string): string;
11
11
  * Uses native setter for React compat + execCommand for contenteditable. */
12
12
  export declare function typeTextJs(ref: string, text: string): string;
13
13
  /** Generate JS to press a keyboard key */
14
- export declare function pressKeyJs(key: string): string;
14
+ export declare function pressKeyJs(key: string, modifiers?: string[]): string;
15
15
  /** Generate JS to wait for text to appear in the page */
16
16
  export declare function waitForTextJs(text: string, timeoutMs: number): string;
17
17
  /** Generate JS for scroll */
@@ -80,12 +80,24 @@ export function typeTextJs(ref, text) {
80
80
  `;
81
81
  }
82
82
  /** Generate JS to press a keyboard key */
83
- export function pressKeyJs(key) {
83
+ export function pressKeyJs(key, modifiers = []) {
84
+ const hasCtrl = modifiers.includes('Ctrl') || modifiers.includes('Control');
85
+ const hasAlt = modifiers.includes('Alt');
86
+ const hasMeta = modifiers.includes('Meta');
87
+ const hasShift = modifiers.includes('Shift');
84
88
  return `
85
89
  (() => {
86
90
  const el = document.activeElement || document.body;
87
- el.dispatchEvent(new KeyboardEvent('keydown', { key: ${JSON.stringify(key)}, bubbles: true }));
88
- el.dispatchEvent(new KeyboardEvent('keyup', { key: ${JSON.stringify(key)}, bubbles: true }));
91
+ const init = {
92
+ key: ${JSON.stringify(key)},
93
+ bubbles: true,
94
+ ctrlKey: ${hasCtrl},
95
+ altKey: ${hasAlt},
96
+ metaKey: ${hasMeta},
97
+ shiftKey: ${hasShift},
98
+ };
99
+ el.dispatchEvent(new KeyboardEvent('keydown', init));
100
+ el.dispatchEvent(new KeyboardEvent('keyup', init));
89
101
  return 'pressed';
90
102
  })()
91
103
  `;
@@ -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 {
@@ -374,7 +380,7 @@ export class Page extends BasePage {
374
380
  for (const mod of modifiers) {
375
381
  if (mod === 'Alt')
376
382
  modifierFlags |= 1;
377
- if (mod === 'Ctrl')
383
+ if (mod === 'Ctrl' || mod === 'Control')
378
384
  modifierFlags |= 2;
379
385
  if (mod === 'Meta')
380
386
  modifierFlags |= 4;
@@ -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,16 +72,31 @@ 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
  */
79
81
  export declare function typeResolvedJs(text: string): string;
82
+ /**
83
+ * Prepare the resolved element for native CDP Input.insertText.
84
+ *
85
+ * This preserves `browser type`'s existing "replace current text" semantics:
86
+ * focus the editable target, select its current contents, then let CDP insert
87
+ * real browser text input so rich editors can update their internal state.
88
+ */
89
+ export declare function prepareNativeTypeResolvedJs(opts?: {
90
+ skipScroll?: boolean;
91
+ skipFocus?: boolean;
92
+ }): string;
80
93
  /**
81
94
  * Generate JS for scrollTo that uses the unified resolver.
82
95
  * Assumes resolveTargetJs has been called and __resolved is set.
83
96
  */
84
- export declare function scrollResolvedJs(): string;
97
+ export declare function scrollResolvedJs(opts?: {
98
+ skipScroll?: boolean;
99
+ }): string;
85
100
  /**
86
101
  * Generate JS to get text content of resolved element.
87
102
  */