@sovovs/bycli 1.8.0

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 (1781) hide show
  1. package/LICENSE +191 -0
  2. package/NOTICE +27 -0
  3. package/README.md +41 -0
  4. package/README.zh-CN.md +39 -0
  5. package/cli-manifest.json +32818 -0
  6. package/clis/12306/me.js +73 -0
  7. package/clis/12306/orders.js +96 -0
  8. package/clis/12306/passengers.js +90 -0
  9. package/clis/12306/price.js +166 -0
  10. package/clis/12306/stations.js +66 -0
  11. package/clis/12306/train.js +91 -0
  12. package/clis/12306/trains.js +119 -0
  13. package/clis/12306/utils.js +272 -0
  14. package/clis/12306/utils.test.js +331 -0
  15. package/clis/1688/assets.js +205 -0
  16. package/clis/1688/assets.test.js +39 -0
  17. package/clis/1688/download.js +77 -0
  18. package/clis/1688/download.test.js +31 -0
  19. package/clis/1688/item.js +188 -0
  20. package/clis/1688/item.test.js +67 -0
  21. package/clis/1688/search.js +310 -0
  22. package/clis/1688/search.test.js +75 -0
  23. package/clis/1688/shared.js +557 -0
  24. package/clis/1688/shared.test.js +57 -0
  25. package/clis/1688/store.js +227 -0
  26. package/clis/1688/store.test.js +62 -0
  27. package/clis/1point3acres/digest.js +35 -0
  28. package/clis/1point3acres/forum.js +51 -0
  29. package/clis/1point3acres/forums.js +44 -0
  30. package/clis/1point3acres/hot.js +35 -0
  31. package/clis/1point3acres/latest.js +35 -0
  32. package/clis/1point3acres/notifications.js +64 -0
  33. package/clis/1point3acres/search.js +71 -0
  34. package/clis/1point3acres/thread.js +117 -0
  35. package/clis/1point3acres/user.js +77 -0
  36. package/clis/1point3acres/utils.js +247 -0
  37. package/clis/36kr/article.js +66 -0
  38. package/clis/36kr/article.test.js +46 -0
  39. package/clis/36kr/hot.js +87 -0
  40. package/clis/36kr/hot.test.js +15 -0
  41. package/clis/36kr/news.js +52 -0
  42. package/clis/36kr/news.test.js +85 -0
  43. package/clis/36kr/search.js +79 -0
  44. package/clis/51job/company.js +126 -0
  45. package/clis/51job/detail.js +109 -0
  46. package/clis/51job/hot.js +56 -0
  47. package/clis/51job/search.js +80 -0
  48. package/clis/51job/utils.js +302 -0
  49. package/clis/51job/utils.test.js +69 -0
  50. package/clis/_atlassian/shared.js +577 -0
  51. package/clis/_atlassian/shared.test.js +170 -0
  52. package/clis/_shared/common.js +32 -0
  53. package/clis/_shared/desktop-commands.js +112 -0
  54. package/clis/_shared/search-adapter.js +70 -0
  55. package/clis/aibase/news.js +110 -0
  56. package/clis/aibase/news.test.js +59 -0
  57. package/clis/amazon/bestsellers.js +8 -0
  58. package/clis/amazon/bestsellers.test.js +29 -0
  59. package/clis/amazon/discussion.js +123 -0
  60. package/clis/amazon/discussion.test.js +124 -0
  61. package/clis/amazon/movers-shakers.js +8 -0
  62. package/clis/amazon/new-releases.js +8 -0
  63. package/clis/amazon/offer.js +141 -0
  64. package/clis/amazon/offer.test.js +29 -0
  65. package/clis/amazon/product.js +93 -0
  66. package/clis/amazon/product.test.js +24 -0
  67. package/clis/amazon/rankings.js +227 -0
  68. package/clis/amazon/rankings.test.js +41 -0
  69. package/clis/amazon/search.js +88 -0
  70. package/clis/amazon/search.test.js +22 -0
  71. package/clis/amazon/shared.js +365 -0
  72. package/clis/amazon/shared.test.js +44 -0
  73. package/clis/antigravity/SKILL.md +38 -0
  74. package/clis/antigravity/dump.js +29 -0
  75. package/clis/antigravity/extract-code.js +33 -0
  76. package/clis/antigravity/model.js +45 -0
  77. package/clis/antigravity/new.js +26 -0
  78. package/clis/antigravity/read.js +35 -0
  79. package/clis/antigravity/send.js +36 -0
  80. package/clis/antigravity/serve.js +558 -0
  81. package/clis/antigravity/status.js +19 -0
  82. package/clis/antigravity/watch.js +43 -0
  83. package/clis/apple-podcasts/commands.test.js +119 -0
  84. package/clis/apple-podcasts/episodes.js +29 -0
  85. package/clis/apple-podcasts/search.js +31 -0
  86. package/clis/apple-podcasts/top.js +45 -0
  87. package/clis/apple-podcasts/utils.js +30 -0
  88. package/clis/apple-podcasts/utils.test.js +57 -0
  89. package/clis/arxiv/arxiv.test.js +112 -0
  90. package/clis/arxiv/author.js +44 -0
  91. package/clis/arxiv/paper.js +22 -0
  92. package/clis/arxiv/recent.js +33 -0
  93. package/clis/arxiv/search.js +36 -0
  94. package/clis/arxiv/utils.js +112 -0
  95. package/clis/baidu-scholar/search.js +87 -0
  96. package/clis/baidu-scholar/search.test.js +23 -0
  97. package/clis/band/bands.js +73 -0
  98. package/clis/band/mentions.js +128 -0
  99. package/clis/band/post.js +176 -0
  100. package/clis/band/posts.js +95 -0
  101. package/clis/barchart/flow.js +116 -0
  102. package/clis/barchart/greeks.js +208 -0
  103. package/clis/barchart/greeks.test.js +138 -0
  104. package/clis/barchart/options.js +107 -0
  105. package/clis/barchart/quote.js +135 -0
  106. package/clis/bbc/news.js +42 -0
  107. package/clis/bbc/topic.js +57 -0
  108. package/clis/bbc/utils.js +79 -0
  109. package/clis/bilibili/comment.js +125 -0
  110. package/clis/bilibili/comment.test.js +153 -0
  111. package/clis/bilibili/comments.js +136 -0
  112. package/clis/bilibili/comments.test.js +142 -0
  113. package/clis/bilibili/download.js +82 -0
  114. package/clis/bilibili/dynamic.js +34 -0
  115. package/clis/bilibili/dynamic.test.js +68 -0
  116. package/clis/bilibili/favorite.js +47 -0
  117. package/clis/bilibili/feed.js +220 -0
  118. package/clis/bilibili/following.js +44 -0
  119. package/clis/bilibili/history.js +45 -0
  120. package/clis/bilibili/hot.js +40 -0
  121. package/clis/bilibili/hot.test.js +17 -0
  122. package/clis/bilibili/me.js +13 -0
  123. package/clis/bilibili/ranking.js +25 -0
  124. package/clis/bilibili/search.js +24 -0
  125. package/clis/bilibili/subtitle.js +138 -0
  126. package/clis/bilibili/subtitle.test.js +196 -0
  127. package/clis/bilibili/summary.js +167 -0
  128. package/clis/bilibili/summary.test.js +210 -0
  129. package/clis/bilibili/user-videos.js +39 -0
  130. package/clis/bilibili/utils.js +184 -0
  131. package/clis/bilibili/utils.test.js +61 -0
  132. package/clis/bilibili/video.js +69 -0
  133. package/clis/bilibili/video.test.js +132 -0
  134. package/clis/binance/asks.js +22 -0
  135. package/clis/binance/commands.test.js +70 -0
  136. package/clis/binance/depth.js +21 -0
  137. package/clis/binance/gainers.js +23 -0
  138. package/clis/binance/klines.js +22 -0
  139. package/clis/binance/losers.js +23 -0
  140. package/clis/binance/pairs.js +22 -0
  141. package/clis/binance/price.js +19 -0
  142. package/clis/binance/prices.js +20 -0
  143. package/clis/binance/ticker.js +22 -0
  144. package/clis/binance/top.js +22 -0
  145. package/clis/binance/trades.js +21 -0
  146. package/clis/bloomberg/businessweek.js +18 -0
  147. package/clis/bloomberg/economics.js +18 -0
  148. package/clis/bloomberg/feeds.js +16 -0
  149. package/clis/bloomberg/industries.js +18 -0
  150. package/clis/bloomberg/main.js +18 -0
  151. package/clis/bloomberg/markets.js +18 -0
  152. package/clis/bloomberg/news.js +106 -0
  153. package/clis/bloomberg/opinions.js +18 -0
  154. package/clis/bloomberg/politics.js +18 -0
  155. package/clis/bloomberg/tech.js +18 -0
  156. package/clis/bloomberg/utils.js +364 -0
  157. package/clis/bloomberg/utils.test.js +129 -0
  158. package/clis/bluesky/feeds.js +28 -0
  159. package/clis/bluesky/followers.js +28 -0
  160. package/clis/bluesky/following.js +28 -0
  161. package/clis/bluesky/profile.js +30 -0
  162. package/clis/bluesky/search.js +29 -0
  163. package/clis/bluesky/starter-packs.js +29 -0
  164. package/clis/bluesky/thread.js +31 -0
  165. package/clis/bluesky/trending.js +20 -0
  166. package/clis/bluesky/user.js +35 -0
  167. package/clis/booking/booking.test.js +356 -0
  168. package/clis/booking/search.js +351 -0
  169. package/clis/boss/batchgreet.js +61 -0
  170. package/clis/boss/chatlist.js +116 -0
  171. package/clis/boss/chatlist.test.js +211 -0
  172. package/clis/boss/chatmsg.js +117 -0
  173. package/clis/boss/chatmsg.test.js +230 -0
  174. package/clis/boss/detail.js +63 -0
  175. package/clis/boss/exchange.js +46 -0
  176. package/clis/boss/greet.js +49 -0
  177. package/clis/boss/invite.js +64 -0
  178. package/clis/boss/joblist.js +31 -0
  179. package/clis/boss/mark.js +70 -0
  180. package/clis/boss/recommend.js +45 -0
  181. package/clis/boss/resume.js +150 -0
  182. package/clis/boss/search.js +197 -0
  183. package/clis/boss/search.test.js +78 -0
  184. package/clis/boss/send.js +44 -0
  185. package/clis/boss/stats.js +66 -0
  186. package/clis/boss/utils.js +468 -0
  187. package/clis/boss/utils.test.js +34 -0
  188. package/clis/brave/search.js +80 -0
  189. package/clis/brave/search.test.js +76 -0
  190. package/clis/chaoxing/assignments.js +76 -0
  191. package/clis/chaoxing/exams.js +75 -0
  192. package/clis/chaoxing/utils.js +223 -0
  193. package/clis/chaoxing/utils.test.js +45 -0
  194. package/clis/chatgpt/ask.js +58 -0
  195. package/clis/chatgpt/commands.test.js +45 -0
  196. package/clis/chatgpt/detail.js +51 -0
  197. package/clis/chatgpt/envelope.test.js +108 -0
  198. package/clis/chatgpt/history.js +39 -0
  199. package/clis/chatgpt/image.js +162 -0
  200. package/clis/chatgpt/image.test.js +188 -0
  201. package/clis/chatgpt/new.js +25 -0
  202. package/clis/chatgpt/read.js +44 -0
  203. package/clis/chatgpt/send.js +47 -0
  204. package/clis/chatgpt/status.js +29 -0
  205. package/clis/chatgpt/utils.js +961 -0
  206. package/clis/chatgpt/utils.test.js +330 -0
  207. package/clis/chatgpt-app/ask.js +76 -0
  208. package/clis/chatgpt-app/ax.js +385 -0
  209. package/clis/chatgpt-app/ax.test.js +35 -0
  210. package/clis/chatgpt-app/model.js +25 -0
  211. package/clis/chatgpt-app/new.js +28 -0
  212. package/clis/chatgpt-app/read.js +32 -0
  213. package/clis/chatgpt-app/send.js +34 -0
  214. package/clis/chatgpt-app/status.js +26 -0
  215. package/clis/chatwise/ask.js +54 -0
  216. package/clis/chatwise/composer.test.js +186 -0
  217. package/clis/chatwise/export.js +47 -0
  218. package/clis/chatwise/history.js +61 -0
  219. package/clis/chatwise/model.js +85 -0
  220. package/clis/chatwise/new.js +2 -0
  221. package/clis/chatwise/read.js +40 -0
  222. package/clis/chatwise/screenshot.js +2 -0
  223. package/clis/chatwise/send.js +28 -0
  224. package/clis/chatwise/status.js +2 -0
  225. package/clis/chatwise/utils.js +143 -0
  226. package/clis/chess/analyze.js +35 -0
  227. package/clis/chess/analyze.test.js +79 -0
  228. package/clis/chess/game.js +114 -0
  229. package/clis/chess/game.test.js +178 -0
  230. package/clis/chess/games.js +67 -0
  231. package/clis/chess/games.test.js +164 -0
  232. package/clis/chess/stats.js +32 -0
  233. package/clis/chess/stats.test.js +79 -0
  234. package/clis/chess/utils.js +170 -0
  235. package/clis/chess/utils.test.js +230 -0
  236. package/clis/claude/ask.js +144 -0
  237. package/clis/claude/ask.test.js +338 -0
  238. package/clis/claude/commands.test.js +118 -0
  239. package/clis/claude/detail.js +38 -0
  240. package/clis/claude/history.js +33 -0
  241. package/clis/claude/new.js +29 -0
  242. package/clis/claude/read.js +27 -0
  243. package/clis/claude/send.js +48 -0
  244. package/clis/claude/status.js +26 -0
  245. package/clis/claude/utils.js +463 -0
  246. package/clis/claude/utils.test.js +148 -0
  247. package/clis/cnki/search.js +61 -0
  248. package/clis/cnki/search.test.js +18 -0
  249. package/clis/codex/ask.js +78 -0
  250. package/clis/codex/dump.js +2 -0
  251. package/clis/codex/export.js +38 -0
  252. package/clis/codex/extract-diff.js +45 -0
  253. package/clis/codex/history.js +27 -0
  254. package/clis/codex/model.js +56 -0
  255. package/clis/codex/new.js +2 -0
  256. package/clis/codex/projects.js +28 -0
  257. package/clis/codex/read.js +41 -0
  258. package/clis/codex/screenshot.js +2 -0
  259. package/clis/codex/send.js +51 -0
  260. package/clis/codex/sidebar.js +356 -0
  261. package/clis/codex/sidebar.test.js +329 -0
  262. package/clis/codex/status.js +2 -0
  263. package/clis/coingecko/categories.js +75 -0
  264. package/clis/coingecko/coin.js +107 -0
  265. package/clis/coingecko/coingecko.test.js +109 -0
  266. package/clis/coingecko/derivatives.js +84 -0
  267. package/clis/coingecko/exchanges.js +74 -0
  268. package/clis/coingecko/global.js +71 -0
  269. package/clis/coingecko/top.js +64 -0
  270. package/clis/coingecko/trending.js +55 -0
  271. package/clis/confluence/commands.test.js +195 -0
  272. package/clis/confluence/create.js +39 -0
  273. package/clis/confluence/page.js +23 -0
  274. package/clis/confluence/search.js +34 -0
  275. package/clis/confluence/shared.js +173 -0
  276. package/clis/confluence/update.js +38 -0
  277. package/clis/coupang/add-to-cart.js +149 -0
  278. package/clis/coupang/coupang.test.js +159 -0
  279. package/clis/coupang/product.js +257 -0
  280. package/clis/coupang/search.js +474 -0
  281. package/clis/coupang/utils.js +316 -0
  282. package/clis/coupang/utils.test.js +62 -0
  283. package/clis/crates/crate.js +62 -0
  284. package/clis/crates/search.js +44 -0
  285. package/clis/crates/utils.js +72 -0
  286. package/clis/ctrip/ctrip.test.js +719 -0
  287. package/clis/ctrip/flight.js +136 -0
  288. package/clis/ctrip/hotel-search.js +132 -0
  289. package/clis/ctrip/hotel-suggest.js +45 -0
  290. package/clis/ctrip/search.js +41 -0
  291. package/clis/ctrip/utils.js +473 -0
  292. package/clis/cursor/ask.js +74 -0
  293. package/clis/cursor/composer.js +43 -0
  294. package/clis/cursor/dump.js +2 -0
  295. package/clis/cursor/export.js +52 -0
  296. package/clis/cursor/extract-code.js +36 -0
  297. package/clis/cursor/history.js +44 -0
  298. package/clis/cursor/model.js +54 -0
  299. package/clis/cursor/new.js +2 -0
  300. package/clis/cursor/read.js +45 -0
  301. package/clis/cursor/screenshot.js +2 -0
  302. package/clis/cursor/send.js +41 -0
  303. package/clis/cursor/status.js +2 -0
  304. package/clis/dblp/author.js +133 -0
  305. package/clis/dblp/dblp.test.js +397 -0
  306. package/clis/dblp/paper.js +40 -0
  307. package/clis/dblp/search.js +45 -0
  308. package/clis/dblp/utils.js +290 -0
  309. package/clis/dblp/venue.js +64 -0
  310. package/clis/deepseek/ask.js +158 -0
  311. package/clis/deepseek/ask.test.js +312 -0
  312. package/clis/deepseek/detail.js +46 -0
  313. package/clis/deepseek/detail.test.js +81 -0
  314. package/clis/deepseek/history.js +27 -0
  315. package/clis/deepseek/new.js +33 -0
  316. package/clis/deepseek/read.js +25 -0
  317. package/clis/deepseek/send.js +140 -0
  318. package/clis/deepseek/send.test.js +107 -0
  319. package/clis/deepseek/status.js +26 -0
  320. package/clis/deepseek/utils.js +487 -0
  321. package/clis/deepseek/utils.test.js +370 -0
  322. package/clis/defillama/defillama.test.js +99 -0
  323. package/clis/defillama/protocol.js +84 -0
  324. package/clis/defillama/protocols.js +55 -0
  325. package/clis/defillama/utils.js +99 -0
  326. package/clis/devto/devto.test.js +236 -0
  327. package/clis/devto/latest.js +74 -0
  328. package/clis/devto/read.js +103 -0
  329. package/clis/devto/tag.js +36 -0
  330. package/clis/devto/top.js +30 -0
  331. package/clis/devto/user.js +35 -0
  332. package/clis/dianping/__fixtures__/search.html +168 -0
  333. package/clis/dianping/__fixtures__/shop.html +6 -0
  334. package/clis/dianping/cityResolver.js +185 -0
  335. package/clis/dianping/dianping.test.js +578 -0
  336. package/clis/dianping/search.js +157 -0
  337. package/clis/dianping/shop.js +173 -0
  338. package/clis/dianping/utils.js +157 -0
  339. package/clis/dictionary/examples.js +28 -0
  340. package/clis/dictionary/search.js +30 -0
  341. package/clis/dictionary/synonyms.js +28 -0
  342. package/clis/discord-app/channels.js +58 -0
  343. package/clis/discord-app/delete.js +115 -0
  344. package/clis/discord-app/members.js +39 -0
  345. package/clis/discord-app/read.js +46 -0
  346. package/clis/discord-app/search.js +57 -0
  347. package/clis/discord-app/send.js +28 -0
  348. package/clis/discord-app/servers.js +37 -0
  349. package/clis/discord-app/status.js +17 -0
  350. package/clis/dockerhub/image.js +52 -0
  351. package/clis/dockerhub/search.js +47 -0
  352. package/clis/dockerhub/utils.js +100 -0
  353. package/clis/douban/book-hot.js +15 -0
  354. package/clis/douban/download.js +68 -0
  355. package/clis/douban/download.test.js +170 -0
  356. package/clis/douban/marks.js +116 -0
  357. package/clis/douban/movie-hot.js +15 -0
  358. package/clis/douban/movie-hot.test.js +14 -0
  359. package/clis/douban/photos.js +35 -0
  360. package/clis/douban/reviews.js +107 -0
  361. package/clis/douban/search.js +18 -0
  362. package/clis/douban/search.test.js +11 -0
  363. package/clis/douban/subject.js +46 -0
  364. package/clis/douban/subject.test.js +11 -0
  365. package/clis/douban/top250.js +68 -0
  366. package/clis/douban/utils.js +656 -0
  367. package/clis/douban/utils.test.js +438 -0
  368. package/clis/doubao/ask.js +40 -0
  369. package/clis/doubao/detail.js +35 -0
  370. package/clis/doubao/detail.test.js +42 -0
  371. package/clis/doubao/history.js +30 -0
  372. package/clis/doubao/history.test.js +37 -0
  373. package/clis/doubao/meeting-summary.js +41 -0
  374. package/clis/doubao/meeting-transcript.js +38 -0
  375. package/clis/doubao/new.js +22 -0
  376. package/clis/doubao/read.js +21 -0
  377. package/clis/doubao/send.js +24 -0
  378. package/clis/doubao/status.js +26 -0
  379. package/clis/doubao/utils.js +1149 -0
  380. package/clis/doubao/utils.test.js +344 -0
  381. package/clis/doubao-app/ask.js +54 -0
  382. package/clis/doubao-app/dump.js +25 -0
  383. package/clis/doubao-app/new.js +21 -0
  384. package/clis/doubao-app/read.js +19 -0
  385. package/clis/doubao-app/screenshot.js +19 -0
  386. package/clis/doubao-app/send.js +28 -0
  387. package/clis/doubao-app/status.js +17 -0
  388. package/clis/doubao-app/utils.js +110 -0
  389. package/clis/douyin/_shared/browser-fetch.js +66 -0
  390. package/clis/douyin/_shared/browser-fetch.test.js +64 -0
  391. package/clis/douyin/_shared/creation-id.js +5 -0
  392. package/clis/douyin/_shared/creation-id.test.js +22 -0
  393. package/clis/douyin/_shared/evaluate-result.js +16 -0
  394. package/clis/douyin/_shared/imagex-upload.js +53 -0
  395. package/clis/douyin/_shared/imagex-upload.test.js +87 -0
  396. package/clis/douyin/_shared/public-api.js +29 -0
  397. package/clis/douyin/_shared/sts2.js +21 -0
  398. package/clis/douyin/_shared/sts2.test.js +27 -0
  399. package/clis/douyin/_shared/text-extra.js +15 -0
  400. package/clis/douyin/_shared/text-extra.test.js +37 -0
  401. package/clis/douyin/_shared/timing.js +22 -0
  402. package/clis/douyin/_shared/timing.test.js +28 -0
  403. package/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  404. package/clis/douyin/_shared/tos-upload.js +331 -0
  405. package/clis/douyin/_shared/tos-upload.test.js +229 -0
  406. package/clis/douyin/_shared/transcode.js +45 -0
  407. package/clis/douyin/_shared/transcode.test.js +91 -0
  408. package/clis/douyin/_shared/types.js +1 -0
  409. package/clis/douyin/_shared/vod-upload.js +212 -0
  410. package/clis/douyin/_shared/vod-upload.test.js +38 -0
  411. package/clis/douyin/activities.js +23 -0
  412. package/clis/douyin/activities.test.js +55 -0
  413. package/clis/douyin/collections.js +23 -0
  414. package/clis/douyin/collections.test.js +45 -0
  415. package/clis/douyin/delete.js +152 -0
  416. package/clis/douyin/delete.test.js +100 -0
  417. package/clis/douyin/draft.js +355 -0
  418. package/clis/douyin/draft.test.js +335 -0
  419. package/clis/douyin/drafts.js +24 -0
  420. package/clis/douyin/drafts.test.js +11 -0
  421. package/clis/douyin/hashtag.js +114 -0
  422. package/clis/douyin/hashtag.test.js +171 -0
  423. package/clis/douyin/location.js +25 -0
  424. package/clis/douyin/location.test.js +23 -0
  425. package/clis/douyin/profile.js +29 -0
  426. package/clis/douyin/profile.test.js +46 -0
  427. package/clis/douyin/publish-upload-id.test.js +170 -0
  428. package/clis/douyin/publish.js +335 -0
  429. package/clis/douyin/publish.test.js +38 -0
  430. package/clis/douyin/stats.js +28 -0
  431. package/clis/douyin/stats.test.js +22 -0
  432. package/clis/douyin/update.js +32 -0
  433. package/clis/douyin/update.test.js +11 -0
  434. package/clis/douyin/user-videos.js +82 -0
  435. package/clis/douyin/user-videos.test.js +151 -0
  436. package/clis/douyin/videos.js +52 -0
  437. package/clis/douyin/videos.test.js +54 -0
  438. package/clis/duckduckgo/search.js +131 -0
  439. package/clis/duckduckgo/search.test.js +128 -0
  440. package/clis/duckduckgo/suggest.js +45 -0
  441. package/clis/duckduckgo/suggest.test.js +66 -0
  442. package/clis/eastmoney/_secid.js +78 -0
  443. package/clis/eastmoney/announcement.js +53 -0
  444. package/clis/eastmoney/convertible.js +74 -0
  445. package/clis/eastmoney/etf.js +66 -0
  446. package/clis/eastmoney/holders.js +79 -0
  447. package/clis/eastmoney/hot-rank.js +51 -0
  448. package/clis/eastmoney/hot-rank.test.js +59 -0
  449. package/clis/eastmoney/index-board.js +97 -0
  450. package/clis/eastmoney/kline.js +88 -0
  451. package/clis/eastmoney/kuaixun.js +55 -0
  452. package/clis/eastmoney/longhu.js +68 -0
  453. package/clis/eastmoney/money-flow.js +79 -0
  454. package/clis/eastmoney/northbound.js +58 -0
  455. package/clis/eastmoney/quote.js +108 -0
  456. package/clis/eastmoney/rank.js +95 -0
  457. package/clis/eastmoney/sectors.js +77 -0
  458. package/clis/endoflife/endoflife.test.js +51 -0
  459. package/clis/endoflife/product.js +55 -0
  460. package/clis/endoflife/utils.js +89 -0
  461. package/clis/facebook/__fixtures__/notifications-page.html +13 -0
  462. package/clis/facebook/add-friend.js +44 -0
  463. package/clis/facebook/events.js +41 -0
  464. package/clis/facebook/feed.js +305 -0
  465. package/clis/facebook/feed.test.js +169 -0
  466. package/clis/facebook/friends.js +39 -0
  467. package/clis/facebook/groups.js +47 -0
  468. package/clis/facebook/join-group.js +45 -0
  469. package/clis/facebook/marketplace-inbox.js +84 -0
  470. package/clis/facebook/marketplace-listings.js +84 -0
  471. package/clis/facebook/marketplace.test.js +91 -0
  472. package/clis/facebook/memories.js +36 -0
  473. package/clis/facebook/notifications.js +333 -0
  474. package/clis/facebook/notifications.test.js +458 -0
  475. package/clis/facebook/profile.js +38 -0
  476. package/clis/facebook/search.js +39 -0
  477. package/clis/facebook/search.test.js +55 -0
  478. package/clis/flathub/app.js +71 -0
  479. package/clis/flathub/flathub.test.js +90 -0
  480. package/clis/flathub/search.js +80 -0
  481. package/clis/flathub/utils.js +114 -0
  482. package/clis/flomo/memos.js +228 -0
  483. package/clis/flomo/memos.test.js +144 -0
  484. package/clis/gemini/ask.js +51 -0
  485. package/clis/gemini/ask.test.js +100 -0
  486. package/clis/gemini/deep-research-result.js +96 -0
  487. package/clis/gemini/deep-research-result.test.js +155 -0
  488. package/clis/gemini/deep-research.js +103 -0
  489. package/clis/gemini/deep-research.test.js +181 -0
  490. package/clis/gemini/image.js +111 -0
  491. package/clis/gemini/new.js +22 -0
  492. package/clis/gemini/reply-state.test.js +671 -0
  493. package/clis/gemini/utils.js +1938 -0
  494. package/clis/gemini/utils.test.js +198 -0
  495. package/clis/geogebra/add-circle.js +46 -0
  496. package/clis/geogebra/add-line.js +35 -0
  497. package/clis/geogebra/add-point.js +27 -0
  498. package/clis/geogebra/add-polygon.js +25 -0
  499. package/clis/geogebra/eval.js +35 -0
  500. package/clis/geogebra/geogebra.test.js +175 -0
  501. package/clis/geogebra/hexagon.js +62 -0
  502. package/clis/geogebra/info.js +72 -0
  503. package/clis/geogebra/list.js +35 -0
  504. package/clis/geogebra/triangle.js +60 -0
  505. package/clis/geogebra/utils.js +271 -0
  506. package/clis/gitee/index.js +3 -0
  507. package/clis/gitee/search.js +137 -0
  508. package/clis/gitee/search.test.js +65 -0
  509. package/clis/gitee/trending.js +568 -0
  510. package/clis/gitee/user.js +200 -0
  511. package/clis/gitee/user.test.js +63 -0
  512. package/clis/google/news.js +59 -0
  513. package/clis/google/search.js +138 -0
  514. package/clis/google/suggest.js +35 -0
  515. package/clis/google/trends.js +39 -0
  516. package/clis/google/utils.js +23 -0
  517. package/clis/google/utils.test.js +75 -0
  518. package/clis/google-scholar/cite.js +74 -0
  519. package/clis/google-scholar/cite.test.js +47 -0
  520. package/clis/google-scholar/profile.js +92 -0
  521. package/clis/google-scholar/profile.test.js +49 -0
  522. package/clis/google-scholar/search.js +73 -0
  523. package/clis/google-scholar/search.test.js +71 -0
  524. package/clis/goproxy/goproxy.test.js +103 -0
  525. package/clis/goproxy/module.js +47 -0
  526. package/clis/goproxy/utils.js +165 -0
  527. package/clis/goproxy/versions.js +59 -0
  528. package/clis/gov-law/commands.test.js +39 -0
  529. package/clis/gov-law/recent.js +22 -0
  530. package/clis/gov-law/search.js +41 -0
  531. package/clis/gov-law/shared.js +51 -0
  532. package/clis/gov-policy/__fixtures__/recent.html +16 -0
  533. package/clis/gov-policy/__fixtures__/search.html +41 -0
  534. package/clis/gov-policy/gov-policy.test.js +224 -0
  535. package/clis/gov-policy/recent.js +90 -0
  536. package/clis/gov-policy/search.js +91 -0
  537. package/clis/gov-policy/utils.js +54 -0
  538. package/clis/grok/ask.js +77 -0
  539. package/clis/grok/ask.test.js +29 -0
  540. package/clis/grok/detail.js +60 -0
  541. package/clis/grok/history.js +48 -0
  542. package/clis/grok/image.js +343 -0
  543. package/clis/grok/image.test.ts +127 -0
  544. package/clis/grok/new.js +20 -0
  545. package/clis/grok/read.js +39 -0
  546. package/clis/grok/send.js +50 -0
  547. package/clis/grok/status.js +41 -0
  548. package/clis/grok/utils.js +326 -0
  549. package/clis/grok/utils.test.js +103 -0
  550. package/clis/hackernews/ask.js +31 -0
  551. package/clis/hackernews/best.js +31 -0
  552. package/clis/hackernews/hackernews.test.js +132 -0
  553. package/clis/hackernews/jobs.js +29 -0
  554. package/clis/hackernews/new.js +31 -0
  555. package/clis/hackernews/read.js +188 -0
  556. package/clis/hackernews/search.js +38 -0
  557. package/clis/hackernews/show.js +31 -0
  558. package/clis/hackernews/top.js +31 -0
  559. package/clis/hackernews/user.js +23 -0
  560. package/clis/hf/datasets.js +88 -0
  561. package/clis/hf/hf.test.js +16 -0
  562. package/clis/hf/models.js +91 -0
  563. package/clis/hf/paper.js +79 -0
  564. package/clis/hf/spaces.js +101 -0
  565. package/clis/hf/top.js +121 -0
  566. package/clis/homebrew/cask.js +39 -0
  567. package/clis/homebrew/formula.js +41 -0
  568. package/clis/homebrew/popular.js +54 -0
  569. package/clis/homebrew/utils.js +100 -0
  570. package/clis/hupu/__fixtures__/hot-home.html +64 -0
  571. package/clis/hupu/detail.js +72 -0
  572. package/clis/hupu/hot.js +163 -0
  573. package/clis/hupu/hot.test.js +224 -0
  574. package/clis/hupu/like.js +76 -0
  575. package/clis/hupu/mentions.js +160 -0
  576. package/clis/hupu/reply.js +72 -0
  577. package/clis/hupu/search.js +60 -0
  578. package/clis/hupu/unlike.js +76 -0
  579. package/clis/hupu/utils.js +319 -0
  580. package/clis/imdb/person.js +204 -0
  581. package/clis/imdb/reviews.js +89 -0
  582. package/clis/imdb/search.js +162 -0
  583. package/clis/imdb/title.js +94 -0
  584. package/clis/imdb/top.js +54 -0
  585. package/clis/imdb/trending.js +53 -0
  586. package/clis/imdb/utils.js +285 -0
  587. package/clis/imdb/utils.test.js +88 -0
  588. package/clis/indeed/indeed.test.js +375 -0
  589. package/clis/indeed/job.js +86 -0
  590. package/clis/indeed/search.js +110 -0
  591. package/clis/indeed/utils.js +152 -0
  592. package/clis/instagram/_shared/private-publish.js +1030 -0
  593. package/clis/instagram/_shared/private-publish.test.js +705 -0
  594. package/clis/instagram/_shared/protocol-capture.js +282 -0
  595. package/clis/instagram/_shared/protocol-capture.test.js +114 -0
  596. package/clis/instagram/_shared/runtime-info.js +81 -0
  597. package/clis/instagram/collection-create.js +58 -0
  598. package/clis/instagram/collection-delete.js +92 -0
  599. package/clis/instagram/comment.js +48 -0
  600. package/clis/instagram/download.js +226 -0
  601. package/clis/instagram/download.test.js +118 -0
  602. package/clis/instagram/explore.js +42 -0
  603. package/clis/instagram/follow.js +44 -0
  604. package/clis/instagram/followers.js +46 -0
  605. package/clis/instagram/following.js +46 -0
  606. package/clis/instagram/like.js +46 -0
  607. package/clis/instagram/note.js +223 -0
  608. package/clis/instagram/note.test.js +53 -0
  609. package/clis/instagram/post.js +1497 -0
  610. package/clis/instagram/post.test.js +1647 -0
  611. package/clis/instagram/profile.js +40 -0
  612. package/clis/instagram/reel.js +827 -0
  613. package/clis/instagram/reel.test.js +167 -0
  614. package/clis/instagram/save.js +46 -0
  615. package/clis/instagram/saved.js +53 -0
  616. package/clis/instagram/search.js +39 -0
  617. package/clis/instagram/story.js +116 -0
  618. package/clis/instagram/story.test.js +134 -0
  619. package/clis/instagram/unfollow.js +41 -0
  620. package/clis/instagram/unlike.js +46 -0
  621. package/clis/instagram/unsave.js +46 -0
  622. package/clis/instagram/user.js +49 -0
  623. package/clis/jd/add-cart.js +72 -0
  624. package/clis/jd/cart.js +80 -0
  625. package/clis/jd/commands.test.js +41 -0
  626. package/clis/jd/detail.js +63 -0
  627. package/clis/jd/item.js +730 -0
  628. package/clis/jd/item.test.js +354 -0
  629. package/clis/jd/item.test.ts +517 -0
  630. package/clis/jd/reviews.js +55 -0
  631. package/clis/jd/search.js +66 -0
  632. package/clis/jianyu/detail.js +21 -0
  633. package/clis/jianyu/search.js +634 -0
  634. package/clis/jianyu/search.test.js +153 -0
  635. package/clis/jianyu/shared/china-bid-search.js +165 -0
  636. package/clis/jianyu/shared/procurement-contract.js +324 -0
  637. package/clis/jianyu/shared/procurement-contract.test.js +72 -0
  638. package/clis/jianyu/shared/procurement-detail.js +107 -0
  639. package/clis/jianyu/shared/procurement-detail.test.js +84 -0
  640. package/clis/jike/comment.js +107 -0
  641. package/clis/jike/create.js +106 -0
  642. package/clis/jike/feed.js +68 -0
  643. package/clis/jike/like.js +61 -0
  644. package/clis/jike/notifications.js +169 -0
  645. package/clis/jike/post.js +72 -0
  646. package/clis/jike/read.test.js +86 -0
  647. package/clis/jike/repost.js +103 -0
  648. package/clis/jike/search.js +68 -0
  649. package/clis/jike/topic.js +65 -0
  650. package/clis/jike/user.js +65 -0
  651. package/clis/jike/utils.js +25 -0
  652. package/clis/jimeng/generate.js +84 -0
  653. package/clis/jimeng/history.js +48 -0
  654. package/clis/jimeng/new.js +44 -0
  655. package/clis/jimeng/workspaces.js +42 -0
  656. package/clis/jira/attachments.js +28 -0
  657. package/clis/jira/commands.test.js +287 -0
  658. package/clis/jira/comments.js +28 -0
  659. package/clis/jira/issue.js +28 -0
  660. package/clis/jira/links.js +28 -0
  661. package/clis/jira/search.js +47 -0
  662. package/clis/jira/shared.js +256 -0
  663. package/clis/juejin/search.js +123 -0
  664. package/clis/juejin/search.test.js +98 -0
  665. package/clis/ke/chengjiao.js +78 -0
  666. package/clis/ke/ershoufang.js +101 -0
  667. package/clis/ke/utils.js +104 -0
  668. package/clis/ke/xiaoqu.js +78 -0
  669. package/clis/ke/zufang.js +95 -0
  670. package/clis/lesswrong/_helpers.js +75 -0
  671. package/clis/lesswrong/comments.js +65 -0
  672. package/clis/lesswrong/curated.js +31 -0
  673. package/clis/lesswrong/frontpage.js +31 -0
  674. package/clis/lesswrong/frontpage.test.js +37 -0
  675. package/clis/lesswrong/new.js +31 -0
  676. package/clis/lesswrong/read.js +46 -0
  677. package/clis/lesswrong/sequences.js +28 -0
  678. package/clis/lesswrong/shortform.js +31 -0
  679. package/clis/lesswrong/tag.js +46 -0
  680. package/clis/lesswrong/tags.js +28 -0
  681. package/clis/lesswrong/top-month.js +31 -0
  682. package/clis/lesswrong/top-week.js +31 -0
  683. package/clis/lesswrong/top-year.js +31 -0
  684. package/clis/lesswrong/top.js +31 -0
  685. package/clis/lesswrong/user-posts.js +42 -0
  686. package/clis/lesswrong/user.js +45 -0
  687. package/clis/lichess/lichess.test.js +85 -0
  688. package/clis/lichess/top.js +46 -0
  689. package/clis/lichess/user.js +91 -0
  690. package/clis/lichess/utils.js +97 -0
  691. package/clis/linkedin/connect.js +401 -0
  692. package/clis/linkedin/connect.test.js +213 -0
  693. package/clis/linkedin/inbox.js +234 -0
  694. package/clis/linkedin/inbox.test.js +152 -0
  695. package/clis/linkedin/job-detail.js +167 -0
  696. package/clis/linkedin/job-detail.test.js +38 -0
  697. package/clis/linkedin/jobs-preferences.js +113 -0
  698. package/clis/linkedin/jobs-preferences.test.js +43 -0
  699. package/clis/linkedin/people-search.js +262 -0
  700. package/clis/linkedin/people-search.test.js +216 -0
  701. package/clis/linkedin/post-analytics.js +74 -0
  702. package/clis/linkedin/post-analytics.test.js +40 -0
  703. package/clis/linkedin/posts-core.js +241 -0
  704. package/clis/linkedin/posts.js +22 -0
  705. package/clis/linkedin/posts.test.js +40 -0
  706. package/clis/linkedin/profile-analytics.js +104 -0
  707. package/clis/linkedin/profile-analytics.test.js +67 -0
  708. package/clis/linkedin/profile-experience.js +671 -0
  709. package/clis/linkedin/profile-experience.test.js +152 -0
  710. package/clis/linkedin/profile-projects.js +311 -0
  711. package/clis/linkedin/profile-projects.test.js +111 -0
  712. package/clis/linkedin/profile-read.js +148 -0
  713. package/clis/linkedin/profile-read.test.js +77 -0
  714. package/clis/linkedin/safe-send.js +357 -0
  715. package/clis/linkedin/safe-send.test.js +204 -0
  716. package/clis/linkedin/salesnav-inbox.js +210 -0
  717. package/clis/linkedin/salesnav-inbox.test.js +113 -0
  718. package/clis/linkedin/salesnav-message.js +360 -0
  719. package/clis/linkedin/salesnav-message.test.js +172 -0
  720. package/clis/linkedin/salesnav-search.js +186 -0
  721. package/clis/linkedin/salesnav-search.test.js +76 -0
  722. package/clis/linkedin/salesnav-thread.js +212 -0
  723. package/clis/linkedin/salesnav-thread.test.js +79 -0
  724. package/clis/linkedin/search.js +462 -0
  725. package/clis/linkedin/search.test.js +222 -0
  726. package/clis/linkedin/sent-invitations.js +92 -0
  727. package/clis/linkedin/sent-invitations.test.js +62 -0
  728. package/clis/linkedin/services-read.js +213 -0
  729. package/clis/linkedin/services-read.test.js +105 -0
  730. package/clis/linkedin/shared.js +124 -0
  731. package/clis/linkedin/thread-snapshot.js +214 -0
  732. package/clis/linkedin/thread-snapshot.test.js +89 -0
  733. package/clis/linkedin/timeline.js +511 -0
  734. package/clis/linkedin/timeline.test.js +81 -0
  735. package/clis/linkedin-learning/course.js +138 -0
  736. package/clis/linkedin-learning/course.test.js +114 -0
  737. package/clis/linkedin-learning/search.js +155 -0
  738. package/clis/linkedin-learning/search.test.js +144 -0
  739. package/clis/linkedin-learning/trending.js +133 -0
  740. package/clis/linkedin-learning/trending.test.js +123 -0
  741. package/clis/linux-do/categories.js +50 -0
  742. package/clis/linux-do/feed.js +395 -0
  743. package/clis/linux-do/feed.test.js +153 -0
  744. package/clis/linux-do/search.js +29 -0
  745. package/clis/linux-do/tags.js +28 -0
  746. package/clis/linux-do/topic-content.js +155 -0
  747. package/clis/linux-do/topic-content.test.js +59 -0
  748. package/clis/linux-do/topic.js +53 -0
  749. package/clis/linux-do/user-posts.js +57 -0
  750. package/clis/linux-do/user-topics.js +37 -0
  751. package/clis/lobsters/active.js +29 -0
  752. package/clis/lobsters/domain.js +92 -0
  753. package/clis/lobsters/hot.js +29 -0
  754. package/clis/lobsters/lobsters.test.js +169 -0
  755. package/clis/lobsters/newest.js +29 -0
  756. package/clis/lobsters/read.js +196 -0
  757. package/clis/lobsters/tag.js +35 -0
  758. package/clis/maimai/search-talents.js +177 -0
  759. package/clis/maven/artifact.js +49 -0
  760. package/clis/maven/search.js +51 -0
  761. package/clis/maven/utils.js +110 -0
  762. package/clis/mdn/search.js +97 -0
  763. package/clis/medium/feed.js +16 -0
  764. package/clis/medium/search.js +16 -0
  765. package/clis/medium/tag.js +135 -0
  766. package/clis/medium/user.js +16 -0
  767. package/clis/medium/utils.js +79 -0
  768. package/clis/mubu/doc.js +41 -0
  769. package/clis/mubu/docs.js +44 -0
  770. package/clis/mubu/notes.js +245 -0
  771. package/clis/mubu/recent.js +28 -0
  772. package/clis/mubu/search.js +63 -0
  773. package/clis/mubu/utils.js +304 -0
  774. package/clis/notebooklm/add-source.js +269 -0
  775. package/clis/notebooklm/add-source.test.js +97 -0
  776. package/clis/notebooklm/compat.test.js +16 -0
  777. package/clis/notebooklm/create.js +76 -0
  778. package/clis/notebooklm/create.test.js +58 -0
  779. package/clis/notebooklm/current.js +28 -0
  780. package/clis/notebooklm/generate-audio.js +91 -0
  781. package/clis/notebooklm/generate-audio.test.js +63 -0
  782. package/clis/notebooklm/generate-slides.js +106 -0
  783. package/clis/notebooklm/generate-slides.test.js +75 -0
  784. package/clis/notebooklm/get.js +37 -0
  785. package/clis/notebooklm/history.js +25 -0
  786. package/clis/notebooklm/history.test.js +58 -0
  787. package/clis/notebooklm/list.js +36 -0
  788. package/clis/notebooklm/note-list.js +28 -0
  789. package/clis/notebooklm/note-list.test.js +56 -0
  790. package/clis/notebooklm/notes-get.js +47 -0
  791. package/clis/notebooklm/notes-get.test.js +72 -0
  792. package/clis/notebooklm/open.js +42 -0
  793. package/clis/notebooklm/open.test.js +63 -0
  794. package/clis/notebooklm/rpc.js +203 -0
  795. package/clis/notebooklm/rpc.test.js +131 -0
  796. package/clis/notebooklm/shared.js +3 -0
  797. package/clis/notebooklm/source-fulltext.js +44 -0
  798. package/clis/notebooklm/source-fulltext.test.js +106 -0
  799. package/clis/notebooklm/source-get.js +40 -0
  800. package/clis/notebooklm/source-get.test.js +84 -0
  801. package/clis/notebooklm/source-guide.js +44 -0
  802. package/clis/notebooklm/source-guide.test.js +104 -0
  803. package/clis/notebooklm/source-list.js +30 -0
  804. package/clis/notebooklm/status.js +31 -0
  805. package/clis/notebooklm/summary.js +30 -0
  806. package/clis/notebooklm/summary.test.js +78 -0
  807. package/clis/notebooklm/utils.js +814 -0
  808. package/clis/notebooklm/utils.test.js +449 -0
  809. package/clis/notebooklm/write-note.js +103 -0
  810. package/clis/notebooklm/write-note.test.js +70 -0
  811. package/clis/nowcoder/companies.js +24 -0
  812. package/clis/nowcoder/creators.js +28 -0
  813. package/clis/nowcoder/detail.js +62 -0
  814. package/clis/nowcoder/experience.js +37 -0
  815. package/clis/nowcoder/hot.js +25 -0
  816. package/clis/nowcoder/jobs.js +22 -0
  817. package/clis/nowcoder/notifications.js +30 -0
  818. package/clis/nowcoder/papers.js +41 -0
  819. package/clis/nowcoder/practice.js +38 -0
  820. package/clis/nowcoder/recommend.js +31 -0
  821. package/clis/nowcoder/referral.js +40 -0
  822. package/clis/nowcoder/salary.js +41 -0
  823. package/clis/nowcoder/search.js +50 -0
  824. package/clis/nowcoder/suggest.js +34 -0
  825. package/clis/nowcoder/topics.js +28 -0
  826. package/clis/nowcoder/trending.js +26 -0
  827. package/clis/npm/downloads.js +59 -0
  828. package/clis/npm/package.js +70 -0
  829. package/clis/npm/search.js +49 -0
  830. package/clis/npm/utils.js +76 -0
  831. package/clis/nuget/nuget.test.js +111 -0
  832. package/clis/nuget/package.js +101 -0
  833. package/clis/nuget/search.js +69 -0
  834. package/clis/nuget/utils.js +87 -0
  835. package/clis/nvd/cve.js +121 -0
  836. package/clis/oeis/oeis.test.js +88 -0
  837. package/clis/oeis/search.js +63 -0
  838. package/clis/oeis/sequence.js +71 -0
  839. package/clis/oeis/utils.js +88 -0
  840. package/clis/ones/common.js +144 -0
  841. package/clis/ones/enrich-tasks.js +37 -0
  842. package/clis/ones/login.js +81 -0
  843. package/clis/ones/logout.js +18 -0
  844. package/clis/ones/me.js +31 -0
  845. package/clis/ones/my-tasks.js +121 -0
  846. package/clis/ones/resolve-labels.js +64 -0
  847. package/clis/ones/task-helpers.js +212 -0
  848. package/clis/ones/task-helpers.test.js +12 -0
  849. package/clis/ones/task.js +67 -0
  850. package/clis/ones/tasks.js +80 -0
  851. package/clis/ones/token-info.js +43 -0
  852. package/clis/ones/worklog.js +268 -0
  853. package/clis/ones/worklog.test.js +20 -0
  854. package/clis/openalex/search.js +69 -0
  855. package/clis/openalex/utils.js +160 -0
  856. package/clis/openalex/work.js +65 -0
  857. package/clis/openfda/drug-label.js +74 -0
  858. package/clis/openfda/food-recall.js +65 -0
  859. package/clis/openfda/openfda.test.js +114 -0
  860. package/clis/openfda/utils.js +67 -0
  861. package/clis/openreview/author.js +58 -0
  862. package/clis/openreview/openreview.test.js +427 -0
  863. package/clis/openreview/paper.js +43 -0
  864. package/clis/openreview/reviews.js +131 -0
  865. package/clis/openreview/search.js +46 -0
  866. package/clis/openreview/utils.js +172 -0
  867. package/clis/openreview/venue.js +63 -0
  868. package/clis/osv/osv.test.js +97 -0
  869. package/clis/osv/query.js +72 -0
  870. package/clis/osv/utils.js +169 -0
  871. package/clis/osv/vulnerability.js +54 -0
  872. package/clis/packagist/package.js +49 -0
  873. package/clis/packagist/search.js +43 -0
  874. package/clis/packagist/utils.js +113 -0
  875. package/clis/paperreview/commands.test.js +243 -0
  876. package/clis/paperreview/feedback.js +53 -0
  877. package/clis/paperreview/review.js +38 -0
  878. package/clis/paperreview/submit.js +86 -0
  879. package/clis/paperreview/utils.js +197 -0
  880. package/clis/paperreview/utils.test.js +49 -0
  881. package/clis/pixiv/detail.js +59 -0
  882. package/clis/pixiv/download.js +79 -0
  883. package/clis/pixiv/download.test.js +87 -0
  884. package/clis/pixiv/illusts.js +66 -0
  885. package/clis/pixiv/illusts.test.js +99 -0
  886. package/clis/pixiv/ranking.js +60 -0
  887. package/clis/pixiv/search.js +44 -0
  888. package/clis/pixiv/search.test.js +83 -0
  889. package/clis/pixiv/user.js +54 -0
  890. package/clis/pixiv/utils.js +49 -0
  891. package/clis/powerchina/search.js +251 -0
  892. package/clis/powerchina/search.test.js +93 -0
  893. package/clis/producthunt/browse.js +100 -0
  894. package/clis/producthunt/hot.js +111 -0
  895. package/clis/producthunt/posts.js +29 -0
  896. package/clis/producthunt/today.js +36 -0
  897. package/clis/producthunt/utils.js +99 -0
  898. package/clis/producthunt/utils.test.js +64 -0
  899. package/clis/pubmed/article.js +50 -0
  900. package/clis/pubmed/author.js +64 -0
  901. package/clis/pubmed/citations.js +36 -0
  902. package/clis/pubmed/pubmed.test.js +276 -0
  903. package/clis/pubmed/related.js +45 -0
  904. package/clis/pubmed/search.js +75 -0
  905. package/clis/pubmed/utils.js +309 -0
  906. package/clis/pypi/downloads.js +66 -0
  907. package/clis/pypi/package.js +79 -0
  908. package/clis/pypi/utils.js +55 -0
  909. package/clis/qqmail/inbox.js +189 -0
  910. package/clis/quark/ls.js +64 -0
  911. package/clis/quark/mkdir.js +37 -0
  912. package/clis/quark/mv.js +54 -0
  913. package/clis/quark/rename.js +27 -0
  914. package/clis/quark/rm.js +25 -0
  915. package/clis/quark/save.js +81 -0
  916. package/clis/quark/share-tree.js +46 -0
  917. package/clis/quark/utils.js +146 -0
  918. package/clis/quark/utils.test.js +58 -0
  919. package/clis/qwen/ask.js +85 -0
  920. package/clis/qwen/detail.js +62 -0
  921. package/clis/qwen/history.js +61 -0
  922. package/clis/qwen/image.js +179 -0
  923. package/clis/qwen/new.js +23 -0
  924. package/clis/qwen/read.js +41 -0
  925. package/clis/qwen/send.js +55 -0
  926. package/clis/qwen/status.js +37 -0
  927. package/clis/qwen/utils.js +409 -0
  928. package/clis/qwen/utils.test.js +45 -0
  929. package/clis/reddit/comment.js +58 -0
  930. package/clis/reddit/extract-media.test.js +149 -0
  931. package/clis/reddit/frontpage.js +70 -0
  932. package/clis/reddit/frontpage.test.js +34 -0
  933. package/clis/reddit/home.js +146 -0
  934. package/clis/reddit/home.test.js +170 -0
  935. package/clis/reddit/hot.js +81 -0
  936. package/clis/reddit/hot.test.js +32 -0
  937. package/clis/reddit/popular.js +80 -0
  938. package/clis/reddit/popular.test.js +26 -0
  939. package/clis/reddit/read.js +532 -0
  940. package/clis/reddit/read.test.js +335 -0
  941. package/clis/reddit/reply.js +182 -0
  942. package/clis/reddit/reply.test.js +89 -0
  943. package/clis/reddit/save.js +52 -0
  944. package/clis/reddit/saved.js +50 -0
  945. package/clis/reddit/search.js +103 -0
  946. package/clis/reddit/search.test.js +26 -0
  947. package/clis/reddit/subreddit-info.js +116 -0
  948. package/clis/reddit/subreddit-info.test.js +163 -0
  949. package/clis/reddit/subreddit.js +98 -0
  950. package/clis/reddit/subreddit.test.js +31 -0
  951. package/clis/reddit/subscribe.js +51 -0
  952. package/clis/reddit/subscribed.js +165 -0
  953. package/clis/reddit/subscribed.test.js +168 -0
  954. package/clis/reddit/upvote.js +65 -0
  955. package/clis/reddit/upvoted.js +50 -0
  956. package/clis/reddit/user-comments.js +45 -0
  957. package/clis/reddit/user-posts.js +43 -0
  958. package/clis/reddit/user.js +38 -0
  959. package/clis/reddit/whoami.js +83 -0
  960. package/clis/reddit/whoami.test.js +105 -0
  961. package/clis/rednote/comments.js +76 -0
  962. package/clis/rednote/download.js +59 -0
  963. package/clis/rednote/feed.js +95 -0
  964. package/clis/rednote/navigation.test.js +26 -0
  965. package/clis/rednote/note.js +68 -0
  966. package/clis/rednote/notifications.js +139 -0
  967. package/clis/rednote/rednote.test.js +222 -0
  968. package/clis/rednote/search.js +107 -0
  969. package/clis/rednote/user.js +55 -0
  970. package/clis/rest-countries/country.js +65 -0
  971. package/clis/rest-countries/region.js +64 -0
  972. package/clis/rest-countries/rest-countries.test.js +83 -0
  973. package/clis/rest-countries/utils.js +126 -0
  974. package/clis/reuters/article-detail.js +53 -0
  975. package/clis/reuters/reuters.test.js +299 -0
  976. package/clis/reuters/search.js +63 -0
  977. package/clis/reuters/utils.js +159 -0
  978. package/clis/rfc/rfc.js +52 -0
  979. package/clis/rfc/rfc.test.js +74 -0
  980. package/clis/rfc/utils.js +72 -0
  981. package/clis/rubygems/gem.js +42 -0
  982. package/clis/rubygems/search.js +47 -0
  983. package/clis/rubygems/utils.js +86 -0
  984. package/clis/sinablog/article.js +15 -0
  985. package/clis/sinablog/hot.js +15 -0
  986. package/clis/sinablog/search.js +52 -0
  987. package/clis/sinablog/user.js +16 -0
  988. package/clis/sinablog/utils.js +186 -0
  989. package/clis/sinafinance/news.js +62 -0
  990. package/clis/sinafinance/rolling-news.js +41 -0
  991. package/clis/sinafinance/stock-rank.js +66 -0
  992. package/clis/sinafinance/stock.js +121 -0
  993. package/clis/sinafinance/stock.test.js +59 -0
  994. package/clis/smzdm/search.js +55 -0
  995. package/clis/spotify/spotify.js +327 -0
  996. package/clis/spotify/utils.js +66 -0
  997. package/clis/spotify/utils.test.js +67 -0
  998. package/clis/stackoverflow/bounties.js +35 -0
  999. package/clis/stackoverflow/hot.js +32 -0
  1000. package/clis/stackoverflow/read.js +314 -0
  1001. package/clis/stackoverflow/related.js +66 -0
  1002. package/clis/stackoverflow/search.js +35 -0
  1003. package/clis/stackoverflow/stackoverflow.test.js +404 -0
  1004. package/clis/stackoverflow/tag.js +60 -0
  1005. package/clis/stackoverflow/unanswered.js +33 -0
  1006. package/clis/stackoverflow/user.js +50 -0
  1007. package/clis/stackoverflow/utils.js +118 -0
  1008. package/clis/steam/app.js +67 -0
  1009. package/clis/steam/search.js +58 -0
  1010. package/clis/steam/steam.test.js +46 -0
  1011. package/clis/steam/top-sellers.js +26 -0
  1012. package/clis/steam/utils.js +107 -0
  1013. package/clis/substack/feed.js +16 -0
  1014. package/clis/substack/publication.js +16 -0
  1015. package/clis/substack/search.js +79 -0
  1016. package/clis/substack/utils.js +136 -0
  1017. package/clis/substack/utils.test.js +44 -0
  1018. package/clis/suno/commands.test.js +188 -0
  1019. package/clis/suno/download.js +140 -0
  1020. package/clis/suno/download.test.js +151 -0
  1021. package/clis/suno/generate.js +231 -0
  1022. package/clis/suno/generate.test.js +252 -0
  1023. package/clis/suno/list.js +79 -0
  1024. package/clis/suno/status.js +63 -0
  1025. package/clis/suno/utils.js +549 -0
  1026. package/clis/suno/utils.test.js +329 -0
  1027. package/clis/taobao/add-cart.js +150 -0
  1028. package/clis/taobao/cart.js +96 -0
  1029. package/clis/taobao/commands.test.js +41 -0
  1030. package/clis/taobao/detail.js +71 -0
  1031. package/clis/taobao/reviews.js +77 -0
  1032. package/clis/taobao/search.js +97 -0
  1033. package/clis/tdx/hot-rank.js +48 -0
  1034. package/clis/tdx/hot-rank.test.js +59 -0
  1035. package/clis/test-utils.js +61 -0
  1036. package/clis/ths/hot-rank.js +50 -0
  1037. package/clis/ths/hot-rank.test.js +64 -0
  1038. package/clis/tieba/commands.test.js +79 -0
  1039. package/clis/tieba/hot.js +48 -0
  1040. package/clis/tieba/posts.js +86 -0
  1041. package/clis/tieba/read.js +141 -0
  1042. package/clis/tieba/search.js +109 -0
  1043. package/clis/tieba/utils.js +240 -0
  1044. package/clis/tieba/utils.test.js +290 -0
  1045. package/clis/tiktok/comment.js +145 -0
  1046. package/clis/tiktok/creator-videos.js +270 -0
  1047. package/clis/tiktok/creator-videos.test.js +113 -0
  1048. package/clis/tiktok/explore.js +144 -0
  1049. package/clis/tiktok/follow.js +122 -0
  1050. package/clis/tiktok/following.js +164 -0
  1051. package/clis/tiktok/friends.js +146 -0
  1052. package/clis/tiktok/like.js +34 -0
  1053. package/clis/tiktok/live.js +144 -0
  1054. package/clis/tiktok/notifications.js +153 -0
  1055. package/clis/tiktok/profile.js +55 -0
  1056. package/clis/tiktok/refactor.test.js +389 -0
  1057. package/clis/tiktok/save.js +30 -0
  1058. package/clis/tiktok/search.js +40 -0
  1059. package/clis/tiktok/unfollow.js +131 -0
  1060. package/clis/tiktok/unlike.js +34 -0
  1061. package/clis/tiktok/unsave.js +32 -0
  1062. package/clis/tiktok/user.js +216 -0
  1063. package/clis/tiktok/utils.js +505 -0
  1064. package/clis/tiktok/write-refactor.test.js +370 -0
  1065. package/clis/toutiao/articles.js +55 -0
  1066. package/clis/toutiao/hot.js +63 -0
  1067. package/clis/toutiao/toutiao.test.js +378 -0
  1068. package/clis/toutiao/utils.js +161 -0
  1069. package/clis/tvmaze/search.js +61 -0
  1070. package/clis/tvmaze/show.js +60 -0
  1071. package/clis/tvmaze/tvmaze.test.js +93 -0
  1072. package/clis/tvmaze/utils.js +110 -0
  1073. package/clis/twitter/accept.js +204 -0
  1074. package/clis/twitter/article.js +167 -0
  1075. package/clis/twitter/block.js +90 -0
  1076. package/clis/twitter/bookmark-folder.js +190 -0
  1077. package/clis/twitter/bookmark-folder.test.js +395 -0
  1078. package/clis/twitter/bookmark-folders.js +114 -0
  1079. package/clis/twitter/bookmark-folders.test.js +152 -0
  1080. package/clis/twitter/bookmark.js +74 -0
  1081. package/clis/twitter/bookmark.test.js +74 -0
  1082. package/clis/twitter/bookmarks.js +181 -0
  1083. package/clis/twitter/bookmarks.test.js +205 -0
  1084. package/clis/twitter/delete.js +85 -0
  1085. package/clis/twitter/delete.test.js +85 -0
  1086. package/clis/twitter/device-follow.js +193 -0
  1087. package/clis/twitter/device-follow.test.js +287 -0
  1088. package/clis/twitter/download.js +481 -0
  1089. package/clis/twitter/download.test.js +457 -0
  1090. package/clis/twitter/follow.js +67 -0
  1091. package/clis/twitter/followers.js +193 -0
  1092. package/clis/twitter/followers.test.js +62 -0
  1093. package/clis/twitter/following.js +270 -0
  1094. package/clis/twitter/following.test.js +338 -0
  1095. package/clis/twitter/hide-reply.js +87 -0
  1096. package/clis/twitter/hide-reply.test.js +76 -0
  1097. package/clis/twitter/like.js +80 -0
  1098. package/clis/twitter/like.test.js +73 -0
  1099. package/clis/twitter/likes.js +241 -0
  1100. package/clis/twitter/likes.test.js +220 -0
  1101. package/clis/twitter/list-add.js +263 -0
  1102. package/clis/twitter/list-add.test.js +133 -0
  1103. package/clis/twitter/list-create.js +155 -0
  1104. package/clis/twitter/list-create.test.js +169 -0
  1105. package/clis/twitter/list-remove.js +306 -0
  1106. package/clis/twitter/list-remove.test.js +110 -0
  1107. package/clis/twitter/list-tweets.js +198 -0
  1108. package/clis/twitter/list-tweets.test.js +196 -0
  1109. package/clis/twitter/lists.js +177 -0
  1110. package/clis/twitter/lists.test.js +253 -0
  1111. package/clis/twitter/notifications.js +128 -0
  1112. package/clis/twitter/post.js +313 -0
  1113. package/clis/twitter/post.test.js +258 -0
  1114. package/clis/twitter/profile.js +115 -0
  1115. package/clis/twitter/profile.test.js +39 -0
  1116. package/clis/twitter/quote.js +172 -0
  1117. package/clis/twitter/quote.test.js +194 -0
  1118. package/clis/twitter/reply-dm.js +183 -0
  1119. package/clis/twitter/reply.js +248 -0
  1120. package/clis/twitter/reply.test.js +237 -0
  1121. package/clis/twitter/retweet.js +94 -0
  1122. package/clis/twitter/retweet.test.js +73 -0
  1123. package/clis/twitter/search.js +346 -0
  1124. package/clis/twitter/search.test.js +349 -0
  1125. package/clis/twitter/shared.js +455 -0
  1126. package/clis/twitter/shared.test.js +794 -0
  1127. package/clis/twitter/thread.js +161 -0
  1128. package/clis/twitter/thread.test.js +58 -0
  1129. package/clis/twitter/timeline.js +211 -0
  1130. package/clis/twitter/timeline.test.js +104 -0
  1131. package/clis/twitter/trending.js +58 -0
  1132. package/clis/twitter/trending.test.js +15 -0
  1133. package/clis/twitter/tweets.js +315 -0
  1134. package/clis/twitter/tweets.test.js +362 -0
  1135. package/clis/twitter/unblock.js +73 -0
  1136. package/clis/twitter/unbookmark.js +71 -0
  1137. package/clis/twitter/unbookmark.test.js +73 -0
  1138. package/clis/twitter/unfollow.js +73 -0
  1139. package/clis/twitter/unlike.js +80 -0
  1140. package/clis/twitter/unlike.test.js +75 -0
  1141. package/clis/twitter/unretweet.js +94 -0
  1142. package/clis/twitter/unretweet.test.js +73 -0
  1143. package/clis/twitter/utils.js +323 -0
  1144. package/clis/twitter/utils.test.js +169 -0
  1145. package/clis/uisdc/news.js +105 -0
  1146. package/clis/uisdc/news.test.js +66 -0
  1147. package/clis/uiverse/_shared.js +427 -0
  1148. package/clis/uiverse/_shared.test.js +62 -0
  1149. package/clis/uiverse/code.js +49 -0
  1150. package/clis/uiverse/navigation.test.js +12 -0
  1151. package/clis/uiverse/preview.js +73 -0
  1152. package/clis/upwork/detail.js +132 -0
  1153. package/clis/upwork/feed.js +109 -0
  1154. package/clis/upwork/search.js +115 -0
  1155. package/clis/upwork/upwork.test.js +566 -0
  1156. package/clis/upwork/utils.js +323 -0
  1157. package/clis/v2ex/daily.js +99 -0
  1158. package/clis/v2ex/hot.js +26 -0
  1159. package/clis/v2ex/latest.js +26 -0
  1160. package/clis/v2ex/me.js +100 -0
  1161. package/clis/v2ex/member.js +28 -0
  1162. package/clis/v2ex/node.js +39 -0
  1163. package/clis/v2ex/nodes.js +26 -0
  1164. package/clis/v2ex/notifications.js +72 -0
  1165. package/clis/v2ex/replies.js +27 -0
  1166. package/clis/v2ex/topic.js +31 -0
  1167. package/clis/v2ex/user.js +34 -0
  1168. package/clis/wanfang/search.js +66 -0
  1169. package/clis/wanfang/search.test.js +23 -0
  1170. package/clis/web/read.js +491 -0
  1171. package/clis/web/read.test.js +392 -0
  1172. package/clis/weibo/comments.js +53 -0
  1173. package/clis/weibo/delete.js +172 -0
  1174. package/clis/weibo/delete.test.js +94 -0
  1175. package/clis/weibo/envelope.test.js +85 -0
  1176. package/clis/weibo/favorites.js +170 -0
  1177. package/clis/weibo/favorites.test.js +114 -0
  1178. package/clis/weibo/feed.js +69 -0
  1179. package/clis/weibo/hot.js +39 -0
  1180. package/clis/weibo/me.js +75 -0
  1181. package/clis/weibo/post.js +75 -0
  1182. package/clis/weibo/publish.js +306 -0
  1183. package/clis/weibo/publish.test.js +192 -0
  1184. package/clis/weibo/search.js +80 -0
  1185. package/clis/weibo/user-posts.js +234 -0
  1186. package/clis/weibo/user-posts.test.js +92 -0
  1187. package/clis/weibo/user.js +63 -0
  1188. package/clis/weibo/utils.js +59 -0
  1189. package/clis/weibo/utils.test.js +36 -0
  1190. package/clis/weixin/create-draft.js +226 -0
  1191. package/clis/weixin/download.js +323 -0
  1192. package/clis/weixin/drafts.js +66 -0
  1193. package/clis/weixin/drafts.test.js +69 -0
  1194. package/clis/weixin/search.js +157 -0
  1195. package/clis/weixin/search.test.js +227 -0
  1196. package/clis/weread/ai-outline.js +171 -0
  1197. package/clis/weread/ai-outline.test.js +83 -0
  1198. package/clis/weread/book-search.js +438 -0
  1199. package/clis/weread/book-search.test.js +242 -0
  1200. package/clis/weread/book.js +233 -0
  1201. package/clis/weread/commands.test.js +398 -0
  1202. package/clis/weread/highlights.js +24 -0
  1203. package/clis/weread/notebooks.js +22 -0
  1204. package/clis/weread/notes.js +30 -0
  1205. package/clis/weread/private-api-regression.test.js +369 -0
  1206. package/clis/weread/ranking.js +29 -0
  1207. package/clis/weread/search-regression.test.js +494 -0
  1208. package/clis/weread/search.js +186 -0
  1209. package/clis/weread/shelf.js +66 -0
  1210. package/clis/weread/utils.js +321 -0
  1211. package/clis/weread/utils.test.js +128 -0
  1212. package/clis/weread-official/book.js +135 -0
  1213. package/clis/weread-official/commands.test.js +385 -0
  1214. package/clis/weread-official/discover.js +107 -0
  1215. package/clis/weread-official/list-apis.js +95 -0
  1216. package/clis/weread-official/notes.js +171 -0
  1217. package/clis/weread-official/readdata.js +158 -0
  1218. package/clis/weread-official/review.js +93 -0
  1219. package/clis/weread-official/search.js +106 -0
  1220. package/clis/weread-official/shelf.js +97 -0
  1221. package/clis/weread-official/utils.js +293 -0
  1222. package/clis/weread-official/utils.test.js +242 -0
  1223. package/clis/wikidata/entity.js +60 -0
  1224. package/clis/wikidata/search.js +50 -0
  1225. package/clis/wikidata/utils.js +117 -0
  1226. package/clis/wikidata/wikidata.test.js +83 -0
  1227. package/clis/wikipedia/page.js +95 -0
  1228. package/clis/wikipedia/random.js +20 -0
  1229. package/clis/wikipedia/search.js +31 -0
  1230. package/clis/wikipedia/summary.js +24 -0
  1231. package/clis/wikipedia/trending.js +40 -0
  1232. package/clis/wikipedia/trending.test.js +57 -0
  1233. package/clis/wikipedia/utils.js +31 -0
  1234. package/clis/wttr/current.js +63 -0
  1235. package/clis/wttr/forecast.js +71 -0
  1236. package/clis/wttr/utils.js +50 -0
  1237. package/clis/wttr/wttr.test.js +84 -0
  1238. package/clis/xianyu/chat.js +74 -0
  1239. package/clis/xianyu/chat.test.js +84 -0
  1240. package/clis/xianyu/im.js +322 -0
  1241. package/clis/xianyu/im.test.js +253 -0
  1242. package/clis/xianyu/inbox.js +96 -0
  1243. package/clis/xianyu/item.js +153 -0
  1244. package/clis/xianyu/item.test.js +56 -0
  1245. package/clis/xianyu/messages.js +91 -0
  1246. package/clis/xianyu/publish.js +485 -0
  1247. package/clis/xianyu/publish.test.js +220 -0
  1248. package/clis/xianyu/reply.js +82 -0
  1249. package/clis/xianyu/search.js +135 -0
  1250. package/clis/xianyu/search.test.js +17 -0
  1251. package/clis/xianyu/utils.js +8 -0
  1252. package/clis/xiaoe/catalog.js +227 -0
  1253. package/clis/xiaoe/content.js +175 -0
  1254. package/clis/xiaoe/courses.js +127 -0
  1255. package/clis/xiaoe/detail.js +36 -0
  1256. package/clis/xiaoe/play-url.js +121 -0
  1257. package/clis/xiaoe/xiaoe.test.js +486 -0
  1258. package/clis/xiaohongshu/comments.js +177 -0
  1259. package/clis/xiaohongshu/comments.test.js +258 -0
  1260. package/clis/xiaohongshu/creator-note-detail.js +482 -0
  1261. package/clis/xiaohongshu/creator-note-detail.test.js +451 -0
  1262. package/clis/xiaohongshu/creator-notes-summary.js +98 -0
  1263. package/clis/xiaohongshu/creator-notes-summary.test.js +94 -0
  1264. package/clis/xiaohongshu/creator-notes.js +479 -0
  1265. package/clis/xiaohongshu/creator-notes.test.js +281 -0
  1266. package/clis/xiaohongshu/creator-profile.js +55 -0
  1267. package/clis/xiaohongshu/creator-stats.js +76 -0
  1268. package/clis/xiaohongshu/creator-stats.test.js +24 -0
  1269. package/clis/xiaohongshu/delete-note.js +260 -0
  1270. package/clis/xiaohongshu/delete-note.test.js +172 -0
  1271. package/clis/xiaohongshu/download.js +246 -0
  1272. package/clis/xiaohongshu/download.test.js +316 -0
  1273. package/clis/xiaohongshu/feed.js +41 -0
  1274. package/clis/xiaohongshu/navigation.test.js +34 -0
  1275. package/clis/xiaohongshu/note-helpers.js +69 -0
  1276. package/clis/xiaohongshu/note.js +104 -0
  1277. package/clis/xiaohongshu/note.test.js +249 -0
  1278. package/clis/xiaohongshu/notifications.js +46 -0
  1279. package/clis/xiaohongshu/publish.js +707 -0
  1280. package/clis/xiaohongshu/publish.test.js +608 -0
  1281. package/clis/xiaohongshu/search.js +330 -0
  1282. package/clis/xiaohongshu/search.test.js +325 -0
  1283. package/clis/xiaohongshu/user-helpers.js +78 -0
  1284. package/clis/xiaohongshu/user-helpers.test.js +160 -0
  1285. package/clis/xiaohongshu/user.js +86 -0
  1286. package/clis/xiaoyuzhou/auth.js +303 -0
  1287. package/clis/xiaoyuzhou/auth.test.js +124 -0
  1288. package/clis/xiaoyuzhou/download.js +54 -0
  1289. package/clis/xiaoyuzhou/download.test.js +135 -0
  1290. package/clis/xiaoyuzhou/episode.js +34 -0
  1291. package/clis/xiaoyuzhou/podcast-episodes.js +41 -0
  1292. package/clis/xiaoyuzhou/podcast-episodes.test.js +78 -0
  1293. package/clis/xiaoyuzhou/podcast.js +33 -0
  1294. package/clis/xiaoyuzhou/transcript.js +77 -0
  1295. package/clis/xiaoyuzhou/transcript.test.js +195 -0
  1296. package/clis/xiaoyuzhou/utils.js +15 -0
  1297. package/clis/xiaoyuzhou/utils.test.js +39 -0
  1298. package/clis/xueqiu/comments.js +355 -0
  1299. package/clis/xueqiu/comments.test.js +696 -0
  1300. package/clis/xueqiu/danjuan-utils.js +126 -0
  1301. package/clis/xueqiu/danjuan-utils.test.js +41 -0
  1302. package/clis/xueqiu/earnings-date.js +45 -0
  1303. package/clis/xueqiu/feed.js +39 -0
  1304. package/clis/xueqiu/fund-holdings.js +29 -0
  1305. package/clis/xueqiu/fund-snapshot.js +26 -0
  1306. package/clis/xueqiu/groups.js +23 -0
  1307. package/clis/xueqiu/hot-stock.js +32 -0
  1308. package/clis/xueqiu/hot.js +37 -0
  1309. package/clis/xueqiu/kline.js +44 -0
  1310. package/clis/xueqiu/search.js +37 -0
  1311. package/clis/xueqiu/stock.js +65 -0
  1312. package/clis/xueqiu/utils.js +48 -0
  1313. package/clis/xueqiu/utils.test.js +26 -0
  1314. package/clis/xueqiu/watchlist.js +37 -0
  1315. package/clis/yahoo/search.js +92 -0
  1316. package/clis/yahoo/search.test.js +94 -0
  1317. package/clis/yahoo-finance/quote.js +74 -0
  1318. package/clis/yollomi/background.js +46 -0
  1319. package/clis/yollomi/edit.js +57 -0
  1320. package/clis/yollomi/face-swap.js +44 -0
  1321. package/clis/yollomi/generate.js +101 -0
  1322. package/clis/yollomi/models.js +34 -0
  1323. package/clis/yollomi/object-remover.js +43 -0
  1324. package/clis/yollomi/remove-bg.js +39 -0
  1325. package/clis/yollomi/restore.js +39 -0
  1326. package/clis/yollomi/try-on.js +47 -0
  1327. package/clis/yollomi/upload.js +72 -0
  1328. package/clis/yollomi/upscale.js +54 -0
  1329. package/clis/yollomi/utils.js +180 -0
  1330. package/clis/yollomi/video.js +57 -0
  1331. package/clis/youdao/note.js +258 -0
  1332. package/clis/youdao/note.test.js +99 -0
  1333. package/clis/youtube/channel.js +209 -0
  1334. package/clis/youtube/channel.test.js +59 -0
  1335. package/clis/youtube/comments.js +96 -0
  1336. package/clis/youtube/feed.js +121 -0
  1337. package/clis/youtube/feed.test.js +131 -0
  1338. package/clis/youtube/history.js +119 -0
  1339. package/clis/youtube/like.js +67 -0
  1340. package/clis/youtube/playlist.js +98 -0
  1341. package/clis/youtube/search.js +100 -0
  1342. package/clis/youtube/subscribe.js +76 -0
  1343. package/clis/youtube/subscriptions.js +58 -0
  1344. package/clis/youtube/transcript-group.js +226 -0
  1345. package/clis/youtube/transcript-group.test.js +99 -0
  1346. package/clis/youtube/transcript.js +656 -0
  1347. package/clis/youtube/transcript.test.js +296 -0
  1348. package/clis/youtube/unlike.js +67 -0
  1349. package/clis/youtube/unsubscribe.js +76 -0
  1350. package/clis/youtube/utils.js +220 -0
  1351. package/clis/youtube/utils.test.js +84 -0
  1352. package/clis/youtube/video.js +117 -0
  1353. package/clis/youtube/watch-later.js +77 -0
  1354. package/clis/yuanbao/ask.js +362 -0
  1355. package/clis/yuanbao/ask.test.js +124 -0
  1356. package/clis/yuanbao/detail.js +65 -0
  1357. package/clis/yuanbao/history.js +51 -0
  1358. package/clis/yuanbao/new.js +72 -0
  1359. package/clis/yuanbao/new.test.js +30 -0
  1360. package/clis/yuanbao/read.js +38 -0
  1361. package/clis/yuanbao/send.js +57 -0
  1362. package/clis/yuanbao/shared.js +341 -0
  1363. package/clis/yuanbao/shared.test.js +80 -0
  1364. package/clis/yuanbao/status.js +44 -0
  1365. package/clis/zhihu/answer-comments.js +280 -0
  1366. package/clis/zhihu/answer-comments.test.js +287 -0
  1367. package/clis/zhihu/answer-detail.js +216 -0
  1368. package/clis/zhihu/answer-detail.test.js +338 -0
  1369. package/clis/zhihu/answer.js +54 -0
  1370. package/clis/zhihu/answer.test.js +54 -0
  1371. package/clis/zhihu/collection.js +199 -0
  1372. package/clis/zhihu/collection.test.js +337 -0
  1373. package/clis/zhihu/collections.js +128 -0
  1374. package/clis/zhihu/collections.test.js +182 -0
  1375. package/clis/zhihu/comment.js +55 -0
  1376. package/clis/zhihu/comment.test.js +50 -0
  1377. package/clis/zhihu/download.js +80 -0
  1378. package/clis/zhihu/download.test.js +12 -0
  1379. package/clis/zhihu/favorite.js +87 -0
  1380. package/clis/zhihu/favorite.test.js +59 -0
  1381. package/clis/zhihu/follow.js +50 -0
  1382. package/clis/zhihu/follow.test.js +42 -0
  1383. package/clis/zhihu/hot.js +44 -0
  1384. package/clis/zhihu/like.js +47 -0
  1385. package/clis/zhihu/like.test.js +41 -0
  1386. package/clis/zhihu/question.js +113 -0
  1387. package/clis/zhihu/question.test.js +251 -0
  1388. package/clis/zhihu/recommend.js +103 -0
  1389. package/clis/zhihu/recommend.test.js +143 -0
  1390. package/clis/zhihu/search.js +206 -0
  1391. package/clis/zhihu/search.test.js +198 -0
  1392. package/clis/zhihu/target.js +91 -0
  1393. package/clis/zhihu/target.test.js +77 -0
  1394. package/clis/zhihu/text.js +29 -0
  1395. package/clis/zhihu/text.test.js +24 -0
  1396. package/clis/zhihu/write-shared.js +228 -0
  1397. package/clis/zhihu/write-shared.test.js +176 -0
  1398. package/clis/zlibrary/commands.test.js +65 -0
  1399. package/clis/zlibrary/info.js +48 -0
  1400. package/clis/zlibrary/search.js +47 -0
  1401. package/clis/zlibrary/utils.js +136 -0
  1402. package/clis/zsxq/dynamics.js +48 -0
  1403. package/clis/zsxq/groups.js +33 -0
  1404. package/clis/zsxq/search.js +44 -0
  1405. package/clis/zsxq/search.test.js +24 -0
  1406. package/clis/zsxq/topic.js +50 -0
  1407. package/clis/zsxq/topic.test.js +30 -0
  1408. package/clis/zsxq/topics.js +26 -0
  1409. package/clis/zsxq/topics.test.js +24 -0
  1410. package/clis/zsxq/utils.js +233 -0
  1411. package/clis/zsxq/utils.test.js +31 -0
  1412. package/dist/src/adapter-shadow.d.ts +11 -0
  1413. package/dist/src/adapter-shadow.js +72 -0
  1414. package/dist/src/adapter-shadow.test.d.ts +1 -0
  1415. package/dist/src/adapter-shadow.test.js +49 -0
  1416. package/dist/src/adapter-source.d.ts +11 -0
  1417. package/dist/src/adapter-source.js +24 -0
  1418. package/dist/src/adapter-source.test.d.ts +1 -0
  1419. package/dist/src/adapter-source.test.js +29 -0
  1420. package/dist/src/browser/analyze.d.ts +103 -0
  1421. package/dist/src/browser/analyze.js +230 -0
  1422. package/dist/src/browser/analyze.test.d.ts +1 -0
  1423. package/dist/src/browser/analyze.test.js +166 -0
  1424. package/dist/src/browser/article-extract.d.ts +57 -0
  1425. package/dist/src/browser/article-extract.e2e.test.d.ts +1 -0
  1426. package/dist/src/browser/article-extract.e2e.test.js +105 -0
  1427. package/dist/src/browser/article-extract.js +169 -0
  1428. package/dist/src/browser/article-extract.test.d.ts +1 -0
  1429. package/dist/src/browser/article-extract.test.js +94 -0
  1430. package/dist/src/browser/ax-snapshot.d.ts +37 -0
  1431. package/dist/src/browser/ax-snapshot.js +217 -0
  1432. package/dist/src/browser/ax-snapshot.test.d.ts +1 -0
  1433. package/dist/src/browser/ax-snapshot.test.js +91 -0
  1434. package/dist/src/browser/base-page.d.ts +145 -0
  1435. package/dist/src/browser/base-page.js +1054 -0
  1436. package/dist/src/browser/base-page.test.d.ts +1 -0
  1437. package/dist/src/browser/base-page.test.js +758 -0
  1438. package/dist/src/browser/bridge.d.ts +28 -0
  1439. package/dist/src/browser/bridge.js +155 -0
  1440. package/dist/src/browser/cdp-click-fixture.test.d.ts +1 -0
  1441. package/dist/src/browser/cdp-click-fixture.test.js +87 -0
  1442. package/dist/src/browser/cdp.d.ts +46 -0
  1443. package/dist/src/browser/cdp.js +536 -0
  1444. package/dist/src/browser/cdp.test.d.ts +1 -0
  1445. package/dist/src/browser/cdp.test.js +79 -0
  1446. package/dist/src/browser/compound.d.ts +59 -0
  1447. package/dist/src/browser/compound.js +112 -0
  1448. package/dist/src/browser/compound.test.d.ts +1 -0
  1449. package/dist/src/browser/compound.test.js +175 -0
  1450. package/dist/src/browser/daemon-client.d.ts +133 -0
  1451. package/dist/src/browser/daemon-client.js +154 -0
  1452. package/dist/src/browser/daemon-client.test.d.ts +1 -0
  1453. package/dist/src/browser/daemon-client.test.js +199 -0
  1454. package/dist/src/browser/daemon-lifecycle.d.ts +23 -0
  1455. package/dist/src/browser/daemon-lifecycle.js +67 -0
  1456. package/dist/src/browser/daemon-version.d.ts +4 -0
  1457. package/dist/src/browser/daemon-version.js +12 -0
  1458. package/dist/src/browser/dom-helpers.d.ts +41 -0
  1459. package/dist/src/browser/dom-helpers.js +239 -0
  1460. package/dist/src/browser/dom-helpers.test.d.ts +1 -0
  1461. package/dist/src/browser/dom-helpers.test.js +106 -0
  1462. package/dist/src/browser/dom-snapshot.d.ts +93 -0
  1463. package/dist/src/browser/dom-snapshot.js +894 -0
  1464. package/dist/src/browser/dom-snapshot.test.d.ts +11 -0
  1465. package/dist/src/browser/dom-snapshot.test.js +313 -0
  1466. package/dist/src/browser/errors.d.ts +38 -0
  1467. package/dist/src/browser/errors.js +78 -0
  1468. package/dist/src/browser/errors.test.d.ts +1 -0
  1469. package/dist/src/browser/errors.test.js +60 -0
  1470. package/dist/src/browser/extract.d.ts +69 -0
  1471. package/dist/src/browser/extract.js +132 -0
  1472. package/dist/src/browser/extract.test.d.ts +1 -0
  1473. package/dist/src/browser/extract.test.js +129 -0
  1474. package/dist/src/browser/find.d.ts +84 -0
  1475. package/dist/src/browser/find.js +398 -0
  1476. package/dist/src/browser/find.test.d.ts +1 -0
  1477. package/dist/src/browser/find.test.js +180 -0
  1478. package/dist/src/browser/html-tree.d.ts +75 -0
  1479. package/dist/src/browser/html-tree.js +112 -0
  1480. package/dist/src/browser/html-tree.test.d.ts +1 -0
  1481. package/dist/src/browser/html-tree.test.js +181 -0
  1482. package/dist/src/browser/index.d.ts +14 -0
  1483. package/dist/src/browser/index.js +12 -0
  1484. package/dist/src/browser/network-cache.d.ts +49 -0
  1485. package/dist/src/browser/network-cache.js +66 -0
  1486. package/dist/src/browser/network-cache.test.d.ts +1 -0
  1487. package/dist/src/browser/network-cache.test.js +58 -0
  1488. package/dist/src/browser/network-key.d.ts +22 -0
  1489. package/dist/src/browser/network-key.js +66 -0
  1490. package/dist/src/browser/network-key.test.d.ts +1 -0
  1491. package/dist/src/browser/network-key.test.js +49 -0
  1492. package/dist/src/browser/page.d.ts +83 -0
  1493. package/dist/src/browser/page.js +453 -0
  1494. package/dist/src/browser/page.test.d.ts +1 -0
  1495. package/dist/src/browser/page.test.js +378 -0
  1496. package/dist/src/browser/profile.d.ts +14 -0
  1497. package/dist/src/browser/profile.js +85 -0
  1498. package/dist/src/browser/shape-filter.d.ts +52 -0
  1499. package/dist/src/browser/shape-filter.js +101 -0
  1500. package/dist/src/browser/shape-filter.test.d.ts +1 -0
  1501. package/dist/src/browser/shape-filter.test.js +101 -0
  1502. package/dist/src/browser/shape.d.ts +23 -0
  1503. package/dist/src/browser/shape.js +95 -0
  1504. package/dist/src/browser/shape.test.d.ts +1 -0
  1505. package/dist/src/browser/shape.test.js +82 -0
  1506. package/dist/src/browser/stealth.d.ts +11 -0
  1507. package/dist/src/browser/stealth.js +359 -0
  1508. package/dist/src/browser/stealth.test.d.ts +1 -0
  1509. package/dist/src/browser/stealth.test.js +134 -0
  1510. package/dist/src/browser/tabs.d.ts +13 -0
  1511. package/dist/src/browser/tabs.js +70 -0
  1512. package/dist/src/browser/target-errors.d.ts +39 -0
  1513. package/dist/src/browser/target-errors.js +45 -0
  1514. package/dist/src/browser/target-errors.test.d.ts +1 -0
  1515. package/dist/src/browser/target-errors.test.js +94 -0
  1516. package/dist/src/browser/target-resolver.d.ts +158 -0
  1517. package/dist/src/browser/target-resolver.js +570 -0
  1518. package/dist/src/browser/target-resolver.test.d.ts +1 -0
  1519. package/dist/src/browser/target-resolver.test.js +118 -0
  1520. package/dist/src/browser/utils.d.ts +20 -0
  1521. package/dist/src/browser/utils.js +64 -0
  1522. package/dist/src/browser/utils.test.d.ts +1 -0
  1523. package/dist/src/browser/utils.test.js +29 -0
  1524. package/dist/src/browser/verify-fixture.d.ts +65 -0
  1525. package/dist/src/browser/verify-fixture.js +318 -0
  1526. package/dist/src/browser/verify-fixture.test.d.ts +1 -0
  1527. package/dist/src/browser/verify-fixture.test.js +219 -0
  1528. package/dist/src/browser/visual-refs.d.ts +11 -0
  1529. package/dist/src/browser/visual-refs.js +108 -0
  1530. package/dist/src/browser.test.d.ts +1 -0
  1531. package/dist/src/browser.test.js +232 -0
  1532. package/dist/src/build-manifest.d.ts +103 -0
  1533. package/dist/src/build-manifest.js +362 -0
  1534. package/dist/src/build-manifest.test.d.ts +1 -0
  1535. package/dist/src/build-manifest.test.js +341 -0
  1536. package/dist/src/capabilityRouting.d.ts +19 -0
  1537. package/dist/src/capabilityRouting.js +56 -0
  1538. package/dist/src/capabilityRouting.test.d.ts +1 -0
  1539. package/dist/src/capabilityRouting.test.js +63 -0
  1540. package/dist/src/cli-argv-preprocess.d.ts +63 -0
  1541. package/dist/src/cli-argv-preprocess.js +269 -0
  1542. package/dist/src/cli-argv-preprocess.test.d.ts +1 -0
  1543. package/dist/src/cli-argv-preprocess.test.js +209 -0
  1544. package/dist/src/cli.d.ts +61 -0
  1545. package/dist/src/cli.js +3173 -0
  1546. package/dist/src/cli.test.d.ts +1 -0
  1547. package/dist/src/cli.test.js +2803 -0
  1548. package/dist/src/commanderAdapter.d.ts +21 -0
  1549. package/dist/src/commanderAdapter.js +208 -0
  1550. package/dist/src/commanderAdapter.test.d.ts +1 -0
  1551. package/dist/src/commanderAdapter.test.js +352 -0
  1552. package/dist/src/commands/daemon.d.ts +9 -0
  1553. package/dist/src/commands/daemon.js +81 -0
  1554. package/dist/src/commands/daemon.test.d.ts +1 -0
  1555. package/dist/src/commands/daemon.test.js +219 -0
  1556. package/dist/src/completion-fast.d.ts +22 -0
  1557. package/dist/src/completion-fast.js +88 -0
  1558. package/dist/src/completion-shared.d.ts +13 -0
  1559. package/dist/src/completion-shared.js +59 -0
  1560. package/dist/src/completion.d.ts +21 -0
  1561. package/dist/src/completion.js +68 -0
  1562. package/dist/src/completion.test.d.ts +1 -0
  1563. package/dist/src/completion.test.js +24 -0
  1564. package/dist/src/constants.d.ts +15 -0
  1565. package/dist/src/constants.js +32 -0
  1566. package/dist/src/convention-audit.d.ts +50 -0
  1567. package/dist/src/convention-audit.js +553 -0
  1568. package/dist/src/convention-audit.test.d.ts +1 -0
  1569. package/dist/src/convention-audit.test.js +247 -0
  1570. package/dist/src/daemon-utils.d.ts +18 -0
  1571. package/dist/src/daemon-utils.js +37 -0
  1572. package/dist/src/daemon.d.ts +22 -0
  1573. package/dist/src/daemon.js +437 -0
  1574. package/dist/src/daemon.test.d.ts +1 -0
  1575. package/dist/src/daemon.test.js +60 -0
  1576. package/dist/src/discovery.d.ts +43 -0
  1577. package/dist/src/discovery.js +265 -0
  1578. package/dist/src/doctor.d.ts +39 -0
  1579. package/dist/src/doctor.js +230 -0
  1580. package/dist/src/doctor.test.d.ts +1 -0
  1581. package/dist/src/doctor.test.js +263 -0
  1582. package/dist/src/download/article-download.d.ts +72 -0
  1583. package/dist/src/download/article-download.js +305 -0
  1584. package/dist/src/download/article-download.test.d.ts +1 -0
  1585. package/dist/src/download/article-download.test.js +235 -0
  1586. package/dist/src/download/index.d.ts +71 -0
  1587. package/dist/src/download/index.js +345 -0
  1588. package/dist/src/download/index.test.d.ts +1 -0
  1589. package/dist/src/download/index.test.js +140 -0
  1590. package/dist/src/download/media-download.d.ts +49 -0
  1591. package/dist/src/download/media-download.js +127 -0
  1592. package/dist/src/download/media-download.test.d.ts +1 -0
  1593. package/dist/src/download/media-download.test.js +112 -0
  1594. package/dist/src/download/progress.d.ts +36 -0
  1595. package/dist/src/download/progress.js +120 -0
  1596. package/dist/src/download/progress.test.d.ts +1 -0
  1597. package/dist/src/download/progress.test.js +36 -0
  1598. package/dist/src/electron-apps.d.ts +31 -0
  1599. package/dist/src/electron-apps.js +70 -0
  1600. package/dist/src/electron-apps.test.d.ts +1 -0
  1601. package/dist/src/electron-apps.test.js +49 -0
  1602. package/dist/src/engine.test.d.ts +1 -0
  1603. package/dist/src/engine.test.js +312 -0
  1604. package/dist/src/errors.d.ts +114 -0
  1605. package/dist/src/errors.js +174 -0
  1606. package/dist/src/errors.test.d.ts +1 -0
  1607. package/dist/src/errors.test.js +109 -0
  1608. package/dist/src/execution.d.ts +24 -0
  1609. package/dist/src/execution.js +509 -0
  1610. package/dist/src/execution.test.d.ts +1 -0
  1611. package/dist/src/execution.test.js +623 -0
  1612. package/dist/src/extension-manifest-regression.test.d.ts +1 -0
  1613. package/dist/src/extension-manifest-regression.test.js +12 -0
  1614. package/dist/src/external-clis.yaml +109 -0
  1615. package/dist/src/external.d.ts +49 -0
  1616. package/dist/src/external.js +219 -0
  1617. package/dist/src/external.test.d.ts +1 -0
  1618. package/dist/src/external.test.js +146 -0
  1619. package/dist/src/help.d.ts +88 -0
  1620. package/dist/src/help.js +544 -0
  1621. package/dist/src/help.test.d.ts +1 -0
  1622. package/dist/src/help.test.js +58 -0
  1623. package/dist/src/hooks.d.ts +46 -0
  1624. package/dist/src/hooks.js +58 -0
  1625. package/dist/src/hooks.test.d.ts +4 -0
  1626. package/dist/src/hooks.test.js +92 -0
  1627. package/dist/src/interceptor.d.ts +44 -0
  1628. package/dist/src/interceptor.js +183 -0
  1629. package/dist/src/interceptor.test.d.ts +4 -0
  1630. package/dist/src/interceptor.test.js +81 -0
  1631. package/dist/src/launcher.d.ts +40 -0
  1632. package/dist/src/launcher.js +208 -0
  1633. package/dist/src/launcher.test.d.ts +1 -0
  1634. package/dist/src/launcher.test.js +138 -0
  1635. package/dist/src/logger.d.ts +26 -0
  1636. package/dist/src/logger.js +49 -0
  1637. package/dist/src/main.d.ts +5 -0
  1638. package/dist/src/main.js +171 -0
  1639. package/dist/src/manifest-types.d.ts +41 -0
  1640. package/dist/src/manifest-types.js +9 -0
  1641. package/dist/src/node-network.d.ts +10 -0
  1642. package/dist/src/node-network.js +174 -0
  1643. package/dist/src/node-network.test.d.ts +1 -0
  1644. package/dist/src/node-network.test.js +55 -0
  1645. package/dist/src/observation/artifact.d.ts +16 -0
  1646. package/dist/src/observation/artifact.js +260 -0
  1647. package/dist/src/observation/artifact.test.d.ts +1 -0
  1648. package/dist/src/observation/artifact.test.js +121 -0
  1649. package/dist/src/observation/events.d.ts +89 -0
  1650. package/dist/src/observation/events.js +1 -0
  1651. package/dist/src/observation/index.d.ts +7 -0
  1652. package/dist/src/observation/index.js +7 -0
  1653. package/dist/src/observation/manager.d.ts +9 -0
  1654. package/dist/src/observation/manager.js +27 -0
  1655. package/dist/src/observation/manager.test.d.ts +1 -0
  1656. package/dist/src/observation/manager.test.js +13 -0
  1657. package/dist/src/observation/redaction.d.ts +11 -0
  1658. package/dist/src/observation/redaction.js +81 -0
  1659. package/dist/src/observation/redaction.test.d.ts +1 -0
  1660. package/dist/src/observation/redaction.test.js +32 -0
  1661. package/dist/src/observation/retention.d.ts +32 -0
  1662. package/dist/src/observation/retention.js +160 -0
  1663. package/dist/src/observation/retention.test.d.ts +1 -0
  1664. package/dist/src/observation/retention.test.js +118 -0
  1665. package/dist/src/observation/ring-buffer.d.ts +22 -0
  1666. package/dist/src/observation/ring-buffer.js +45 -0
  1667. package/dist/src/observation/ring-buffer.test.d.ts +1 -0
  1668. package/dist/src/observation/ring-buffer.test.js +22 -0
  1669. package/dist/src/observation/session.d.ts +25 -0
  1670. package/dist/src/observation/session.js +50 -0
  1671. package/dist/src/output.d.ts +14 -0
  1672. package/dist/src/output.js +152 -0
  1673. package/dist/src/output.test.d.ts +1 -0
  1674. package/dist/src/output.test.js +53 -0
  1675. package/dist/src/package-exports.test.d.ts +1 -0
  1676. package/dist/src/package-exports.test.js +112 -0
  1677. package/dist/src/package-paths.d.ts +8 -0
  1678. package/dist/src/package-paths.js +41 -0
  1679. package/dist/src/pipeline/executor.d.ts +11 -0
  1680. package/dist/src/pipeline/executor.js +93 -0
  1681. package/dist/src/pipeline/executor.test.d.ts +4 -0
  1682. package/dist/src/pipeline/executor.test.js +182 -0
  1683. package/dist/src/pipeline/index.d.ts +5 -0
  1684. package/dist/src/pipeline/index.js +5 -0
  1685. package/dist/src/pipeline/registry.d.ts +28 -0
  1686. package/dist/src/pipeline/registry.js +55 -0
  1687. package/dist/src/pipeline/steps/browser.d.ts +13 -0
  1688. package/dist/src/pipeline/steps/browser.js +86 -0
  1689. package/dist/src/pipeline/steps/download.d.ts +18 -0
  1690. package/dist/src/pipeline/steps/download.js +252 -0
  1691. package/dist/src/pipeline/steps/download.test.d.ts +1 -0
  1692. package/dist/src/pipeline/steps/download.test.js +102 -0
  1693. package/dist/src/pipeline/steps/fetch.d.ts +5 -0
  1694. package/dist/src/pipeline/steps/fetch.js +122 -0
  1695. package/dist/src/pipeline/steps/fetch.test.d.ts +1 -0
  1696. package/dist/src/pipeline/steps/fetch.test.js +117 -0
  1697. package/dist/src/pipeline/steps/intercept.d.ts +5 -0
  1698. package/dist/src/pipeline/steps/intercept.js +50 -0
  1699. package/dist/src/pipeline/steps/tap.d.ts +12 -0
  1700. package/dist/src/pipeline/steps/tap.js +93 -0
  1701. package/dist/src/pipeline/steps/transform.d.ts +9 -0
  1702. package/dist/src/pipeline/steps/transform.js +70 -0
  1703. package/dist/src/pipeline/template.d.ts +17 -0
  1704. package/dist/src/pipeline/template.js +338 -0
  1705. package/dist/src/pipeline/template.test.d.ts +4 -0
  1706. package/dist/src/pipeline/template.test.js +175 -0
  1707. package/dist/src/pipeline/transform.test.d.ts +4 -0
  1708. package/dist/src/pipeline/transform.test.js +133 -0
  1709. package/dist/src/plugin-manifest.d.ts +70 -0
  1710. package/dist/src/plugin-manifest.js +160 -0
  1711. package/dist/src/plugin-manifest.test.d.ts +4 -0
  1712. package/dist/src/plugin-manifest.test.js +179 -0
  1713. package/dist/src/plugin-scaffold.d.ts +28 -0
  1714. package/dist/src/plugin-scaffold.js +143 -0
  1715. package/dist/src/plugin-scaffold.test.d.ts +4 -0
  1716. package/dist/src/plugin-scaffold.test.js +83 -0
  1717. package/dist/src/plugin.d.ts +145 -0
  1718. package/dist/src/plugin.js +1245 -0
  1719. package/dist/src/plugin.test.d.ts +4 -0
  1720. package/dist/src/plugin.test.js +1324 -0
  1721. package/dist/src/registry-api.d.ts +13 -0
  1722. package/dist/src/registry-api.js +10 -0
  1723. package/dist/src/registry.d.ts +143 -0
  1724. package/dist/src/registry.js +123 -0
  1725. package/dist/src/registry.test.d.ts +4 -0
  1726. package/dist/src/registry.test.js +209 -0
  1727. package/dist/src/runtime-detect.d.ts +31 -0
  1728. package/dist/src/runtime-detect.js +47 -0
  1729. package/dist/src/runtime-detect.test.d.ts +1 -0
  1730. package/dist/src/runtime-detect.test.js +39 -0
  1731. package/dist/src/runtime.d.ts +47 -0
  1732. package/dist/src/runtime.js +66 -0
  1733. package/dist/src/scripts/framework.d.ts +7 -0
  1734. package/dist/src/scripts/framework.js +25 -0
  1735. package/dist/src/scripts/interact.d.ts +4 -0
  1736. package/dist/src/scripts/interact.js +20 -0
  1737. package/dist/src/scripts/store.d.ts +13 -0
  1738. package/dist/src/scripts/store.js +48 -0
  1739. package/dist/src/serialization.d.ts +42 -0
  1740. package/dist/src/serialization.js +102 -0
  1741. package/dist/src/serialization.test.d.ts +1 -0
  1742. package/dist/src/serialization.test.js +78 -0
  1743. package/dist/src/snapshotFormatter.d.ts +11 -0
  1744. package/dist/src/snapshotFormatter.js +338 -0
  1745. package/dist/src/snapshotFormatter.test.d.ts +7 -0
  1746. package/dist/src/snapshotFormatter.test.js +521 -0
  1747. package/dist/src/tui.d.ts +28 -0
  1748. package/dist/src/tui.js +179 -0
  1749. package/dist/src/tui.test.d.ts +1 -0
  1750. package/dist/src/tui.test.js +19 -0
  1751. package/dist/src/types.d.ts +236 -0
  1752. package/dist/src/types.js +7 -0
  1753. package/dist/src/update-check.d.ts +66 -0
  1754. package/dist/src/update-check.js +202 -0
  1755. package/dist/src/update-check.test.d.ts +1 -0
  1756. package/dist/src/update-check.test.js +116 -0
  1757. package/dist/src/utils.d.ts +57 -0
  1758. package/dist/src/utils.js +152 -0
  1759. package/dist/src/utils.test.d.ts +1 -0
  1760. package/dist/src/utils.test.js +155 -0
  1761. package/dist/src/validate.d.ts +21 -0
  1762. package/dist/src/validate.js +129 -0
  1763. package/dist/src/validate.test.d.ts +9 -0
  1764. package/dist/src/validate.test.js +90 -0
  1765. package/dist/src/verify.d.ts +26 -0
  1766. package/dist/src/verify.js +67 -0
  1767. package/dist/src/version.d.ts +4 -0
  1768. package/dist/src/version.js +22 -0
  1769. package/dist/src/weixin-download.test.d.ts +1 -0
  1770. package/dist/src/weixin-download.test.js +45 -0
  1771. package/package.json +113 -0
  1772. package/scripts/check-doc-coverage.sh +81 -0
  1773. package/scripts/check-listing-id-pairing.mjs +193 -0
  1774. package/scripts/check-silent-column-drop.mjs +105 -0
  1775. package/scripts/check-typed-error-lint.mjs +118 -0
  1776. package/scripts/clean-dist.cjs +13 -0
  1777. package/scripts/copy-yaml.cjs +12 -0
  1778. package/scripts/fetch-adapters.js +293 -0
  1779. package/scripts/postinstall.js +174 -0
  1780. package/scripts/silent-column-drop-baseline.json +910 -0
  1781. package/scripts/typed-error-lint-baseline.json +1162 -0
@@ -0,0 +1,3173 @@
1
+ /**
2
+ * CLI entry point: registers built-in commands and wires up Commander.
3
+ *
4
+ * Built-in commands are registered inline here (list, validate, explore, etc.).
5
+ * Dynamic adapter commands are registered via commanderAdapter.ts.
6
+ */
7
+ import * as fs from 'node:fs';
8
+ import * as os from 'node:os';
9
+ import * as path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { Command, InvalidArgumentError, Option } from 'commander';
12
+ import { findPackageRoot, getBuiltEntryCandidates } from './package-paths.js';
13
+ import { fullName, getRegistry, strategyLabel } from './registry.js';
14
+ import { serializeCommand, formatArgSummary } from './serialization.js';
15
+ import { render as renderOutput } from './output.js';
16
+ import { PKG_VERSION } from './version.js';
17
+ import { printCompletionScript } from './completion.js';
18
+ import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled, formatExternalCliLabel } from './external.js';
19
+ import { registerAllCommands } from './commanderAdapter.js';
20
+ import { classifyAdapter, formatRootAdapterHelpText, installCommanderNamespaceStructuredHelp, installStructuredHelp, leadingPositionalFromUsage, rootHelpData } from './help.js';
21
+ import { EXIT_CODES, getErrorMessage, BrowserConnectError } from './errors.js';
22
+ import { TargetError } from './browser/target-errors.js';
23
+ import { resolveTargetJs, getTextResolvedJs, getValueResolvedJs, getAttributesResolvedJs, selectResolvedJs, isAutocompleteResolvedJs } from './browser/target-resolver.js';
24
+ import { buildFindJs, buildSemanticFindJs, isFindError } from './browser/find.js';
25
+ import { inferShape } from './browser/shape.js';
26
+ import { assignKeys } from './browser/network-key.js';
27
+ import { DEFAULT_TTL_MS, findEntry, loadNetworkCache, saveNetworkCache } from './browser/network-cache.js';
28
+ import { parseFilter, shapeMatchesFilter } from './browser/shape-filter.js';
29
+ import { buildHtmlTreeJs } from './browser/html-tree.js';
30
+ import { buildExtractHtmlJs, runExtractFromHtml } from './browser/extract.js';
31
+ import { analyzeSite } from './browser/analyze.js';
32
+ import { daemonRestart, daemonStatus, daemonStop } from './commands/daemon.js';
33
+ import { log } from './logger.js';
34
+ import { bindTab, BrowserCommandError, fetchDaemonStatus, sendCommand } from './browser/daemon-client.js';
35
+ import { aliasForContextId, loadProfileConfig, renameProfile, resolveProfileContextId, setDefaultProfile } from './browser/profile.js';
36
+ import { formatDaemonVersion, isDaemonStale } from './browser/daemon-version.js';
37
+ const CLI_FILE = fileURLToPath(import.meta.url);
38
+ const BROWSER_TAB_OPTION_DESCRIPTION = 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"';
39
+ const FOLLOW_POLL_MS = 1_000;
40
+ function parseDurationMs(raw, flagName) {
41
+ if (raw === undefined || raw === null || raw === '')
42
+ return null;
43
+ const str = String(raw).trim();
44
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/.exec(str);
45
+ if (!match)
46
+ return { error: `--${flagName} must be a duration like 500ms, 30s, 2m, got "${str}"` };
47
+ const value = Number.parseFloat(match[1]);
48
+ const unit = match[2] ?? 'ms';
49
+ const multiplier = unit === 'h' ? 3_600_000 : unit === 'm' ? 60_000 : unit === 's' ? 1_000 : 1;
50
+ return Math.round(value * multiplier);
51
+ }
52
+ function timestampFromRaw(value) {
53
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : Date.now();
54
+ }
55
+ function toIsoTimestamp(timestamp) {
56
+ if (typeof timestamp !== 'number' || !Number.isFinite(timestamp) || timestamp <= 0)
57
+ return undefined;
58
+ return new Date(timestamp).toISOString();
59
+ }
60
+ function filterByTimeWindow(items, opts, now = Date.now()) {
61
+ const sinceTs = opts.sinceMs != null ? now - opts.sinceMs : undefined;
62
+ const untilTs = opts.untilMs != null ? now - opts.untilMs : undefined;
63
+ return items.filter((item) => {
64
+ const ts = item.timestamp ?? now;
65
+ if (sinceTs !== undefined && ts < sinceTs)
66
+ return false;
67
+ if (untilTs !== undefined && ts > untilTs)
68
+ return false;
69
+ return true;
70
+ });
71
+ }
72
+ export function selectFreshByTimestamp(items, lastSeenTs) {
73
+ const fresh = items.filter((item) => Number(item.timestamp ?? 0) > lastSeenTs);
74
+ const nextSeenTs = fresh.length > 0
75
+ ? Math.max(lastSeenTs, ...fresh.map((item) => Number(item.timestamp ?? 0)).filter(Number.isFinite))
76
+ : lastSeenTs;
77
+ return { fresh, lastSeenTs: nextSeenTs };
78
+ }
79
+ /**
80
+ * Normalize raw capture entries (from daemon/CDP `readNetworkCapture` or
81
+ * the JS interceptor's `window.__bycli_net`) into a consistent shape.
82
+ * Response preview is parsed as JSON when possible, otherwise kept as string.
83
+ * `bodyFullSize` / `bodyTruncated` surface capture-layer truncation so the
84
+ * agent-facing envelope can warn when the body isn't whole.
85
+ */
86
+ async function captureNetworkItems(page) {
87
+ if (page.readNetworkCapture) {
88
+ const raw = await page.readNetworkCapture();
89
+ if (Array.isArray(raw) && raw.length > 0) {
90
+ return raw.map((e) => {
91
+ const preview = e.responsePreview ?? null;
92
+ let body = null;
93
+ if (preview) {
94
+ try {
95
+ body = JSON.parse(preview);
96
+ }
97
+ catch {
98
+ body = preview;
99
+ }
100
+ }
101
+ const fullSize = typeof e.responseBodyFullSize === 'number'
102
+ ? e.responseBodyFullSize
103
+ : (preview ? preview.length : 0);
104
+ const truncated = e.responseBodyTruncated === true;
105
+ return {
106
+ url: e.url || '',
107
+ method: e.method || 'GET',
108
+ status: e.responseStatus || 0,
109
+ size: fullSize,
110
+ ct: e.responseContentType || '',
111
+ body,
112
+ bodyFullSize: fullSize,
113
+ bodyTruncated: truncated,
114
+ timestamp: timestampFromRaw(e.timestamp),
115
+ };
116
+ });
117
+ }
118
+ }
119
+ const raw = await page.evaluate(`(function(){ var out = window.__bycli_net || []; window.__bycli_net = []; return JSON.stringify(out); })()`);
120
+ try {
121
+ const parsed = JSON.parse(raw);
122
+ return parsed.map((item) => ({ ...item, timestamp: timestampFromRaw(item.timestamp) }));
123
+ }
124
+ catch {
125
+ if (process.env.BYCLI_VERBOSE)
126
+ log.warn(`[network] Failed to parse interceptor buffer: ${typeof raw === 'string' ? raw.slice(0, 200) : String(raw)}`);
127
+ return [];
128
+ }
129
+ }
130
+ /** Drop static-resource / telemetry noise so agents see only API-shaped traffic. */
131
+ function filterNetworkItems(items) {
132
+ return items.filter((r) => {
133
+ const ct = r.ct?.toLowerCase() ?? '';
134
+ return ((ct.includes('json') || ct.includes('xml') || ct.includes('text/plain') || ct.includes('javascript')) &&
135
+ !/\.(js|css|png|jpg|gif|svg|woff|ico|map)(\?|$)/i.test(r.url) &&
136
+ !/analytics|tracking|telemetry|beacon|pixel|gtag|fbevents/i.test(r.url));
137
+ });
138
+ }
139
+ /** Exit codes by network error code — usage errors vs runtime failures. */
140
+ const NETWORK_ERROR_EXIT = {
141
+ invalid_args: EXIT_CODES.USAGE_ERROR,
142
+ invalid_filter: EXIT_CODES.USAGE_ERROR,
143
+ invalid_max_body: EXIT_CODES.USAGE_ERROR,
144
+ };
145
+ /** Emit a structured error JSON so agents can branch on `error.code` without regex. */
146
+ function emitNetworkError(code, message, extra = {}) {
147
+ console.log(JSON.stringify({ error: { code, message, ...extra } }, null, 2));
148
+ process.exitCode = NETWORK_ERROR_EXIT[code] ?? EXIT_CODES.GENERIC_ERROR;
149
+ }
150
+ export function checkSiteMemory(site) {
151
+ const siteDir = path.join(os.homedir(), '.bycli', 'sites', site);
152
+ const endpointsPath = path.join(siteDir, 'endpoints.json');
153
+ const notesPath = path.join(siteDir, 'notes.md');
154
+ let endpointsCount = 0;
155
+ let endpointsPresent = fs.existsSync(endpointsPath);
156
+ if (endpointsPresent) {
157
+ try {
158
+ const parsed = JSON.parse(fs.readFileSync(endpointsPath, 'utf-8'));
159
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
160
+ endpointsCount = Object.keys(parsed).length;
161
+ }
162
+ else if (Array.isArray(parsed)) {
163
+ endpointsCount = parsed.length;
164
+ }
165
+ }
166
+ catch {
167
+ endpointsPresent = false;
168
+ }
169
+ }
170
+ const notesPresent = fs.existsSync(notesPath);
171
+ return {
172
+ ok: endpointsPresent && endpointsCount > 0 && notesPresent,
173
+ siteDir,
174
+ endpoints: { present: endpointsPresent, count: endpointsCount, path: endpointsPath },
175
+ notes: { present: notesPresent, path: notesPath },
176
+ };
177
+ }
178
+ export function printSiteMemoryReport(report, strict) {
179
+ if (report.ok) {
180
+ console.log(` ✓ Memory: endpoints.json (${report.endpoints.count}), notes.md present at ${report.siteDir}`);
181
+ return;
182
+ }
183
+ const marker = strict ? '✗' : '⚠';
184
+ const missing = [];
185
+ if (!report.endpoints.present)
186
+ missing.push('endpoints.json');
187
+ else if (report.endpoints.count === 0)
188
+ missing.push('endpoints.json (empty)');
189
+ if (!report.notes.present)
190
+ missing.push('notes.md');
191
+ console.log(` ${marker} Memory: missing ${missing.join(', ')} under ${report.siteDir}`);
192
+ console.log(` Write the endpoint you just verified + a 1-line session note so the next agent starts from minute 0, not minute 95.`);
193
+ if (!strict) {
194
+ console.log(` (Re-run with --strict-memory to fail instead of warn.)`);
195
+ }
196
+ }
197
+ /** Coerce adapter JSON output into a row array. Accepts `[{...}]`, single `{}`, or `{items:[...]}`-style envelopes. */
198
+ export function normalizeVerifyRows(data) {
199
+ if (Array.isArray(data)) {
200
+ return data.map((r) => (r && typeof r === 'object' ? r : { value: r }));
201
+ }
202
+ if (data && typeof data === 'object') {
203
+ const obj = data;
204
+ for (const k of ['rows', 'items', 'data', 'results']) {
205
+ if (Array.isArray(obj[k])) {
206
+ return obj[k].map((r) => (r && typeof r === 'object' ? r : { value: r }));
207
+ }
208
+ }
209
+ return [obj];
210
+ }
211
+ return [];
212
+ }
213
+ /** Render up to 10 rows as a compact padded table for eyeball inspection during verify. */
214
+ export function renderVerifyPreview(rows, opts = {}) {
215
+ const maxRows = opts.maxRows ?? 10;
216
+ const maxCols = opts.maxCols ?? 6;
217
+ const cellMax = opts.cellMax ?? 40;
218
+ if (rows.length === 0)
219
+ return ' (no rows)';
220
+ const allCols = Array.from(new Set(rows.flatMap((r) => Object.keys(r))));
221
+ const cols = allCols.slice(0, maxCols);
222
+ const shown = rows.slice(0, maxRows);
223
+ const cellOf = (v) => {
224
+ if (v === null || v === undefined)
225
+ return '';
226
+ const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
227
+ return s.replace(/\s+/g, ' ').slice(0, cellMax);
228
+ };
229
+ const widths = cols.map((c) => Math.max(c.length, ...shown.map((r) => cellOf(r[c]).length)));
230
+ const fmtRow = (vals) => vals.map((v, i) => v.padEnd(widths[i])).join(' ');
231
+ const out = [];
232
+ out.push(` ${fmtRow(cols)}`);
233
+ out.push(` ${widths.map((w) => '-'.repeat(w)).join(' ')}`);
234
+ for (const r of shown)
235
+ out.push(` ${fmtRow(cols.map((c) => cellOf(r[c])))}`);
236
+ if (rows.length > maxRows)
237
+ out.push(` ... and ${rows.length - maxRows} more row(s)`);
238
+ if (allCols.length > maxCols)
239
+ out.push(` (${allCols.length - maxCols} more column(s) hidden)`);
240
+ return out.join('\n');
241
+ }
242
+ function getBrowserCacheDir() {
243
+ return process.env.BYCLI_CACHE_DIR || path.join(os.homedir(), '.bycli', 'cache');
244
+ }
245
+ function getBrowserTargetStatePath(scope) {
246
+ const safeSession = scope.replace(/[^a-zA-Z0-9_-]+/g, '_');
247
+ return path.join(getBrowserCacheDir(), 'browser-state', `${safeSession}.json`);
248
+ }
249
+ function loadBrowserTargetState(scope) {
250
+ try {
251
+ const raw = fs.readFileSync(getBrowserTargetStatePath(scope), 'utf-8');
252
+ const parsed = JSON.parse(raw);
253
+ return parsed && typeof parsed === 'object' ? parsed : null;
254
+ }
255
+ catch {
256
+ return null;
257
+ }
258
+ }
259
+ function saveBrowserTargetState(defaultPage, scope) {
260
+ const target = getBrowserTargetStatePath(scope);
261
+ if (!defaultPage) {
262
+ fs.rmSync(target, { force: true });
263
+ return;
264
+ }
265
+ fs.mkdirSync(path.dirname(target), { recursive: true });
266
+ fs.writeFileSync(target, JSON.stringify({ defaultPage, updatedAt: new Date().toISOString() }), 'utf-8');
267
+ }
268
+ function hasBrowserTabTarget(tabs, targetPage) {
269
+ return tabs.some((tab) => {
270
+ return typeof tab === 'object'
271
+ && tab !== null
272
+ && 'page' in tab
273
+ && typeof tab.page === 'string'
274
+ && tab.page === targetPage;
275
+ });
276
+ }
277
+ async function resolveBrowserTargetInSession(page, targetPage, opts) {
278
+ const candidate = targetPage.trim();
279
+ if (!candidate)
280
+ return undefined;
281
+ let tabs;
282
+ try {
283
+ tabs = await page.tabs();
284
+ }
285
+ catch (err) {
286
+ if (opts.source === 'saved') {
287
+ saveBrowserTargetState(undefined, opts.scope);
288
+ return undefined;
289
+ }
290
+ throw new Error(`Target tab ${candidate} could not be validated in the current browser session. ` +
291
+ 'The Browser Bridge session may have restarted; re-run "bycli browser tab list" and choose a current target.', { cause: err });
292
+ }
293
+ if (Array.isArray(tabs) && hasBrowserTabTarget(tabs, candidate)) {
294
+ return candidate;
295
+ }
296
+ if (opts.source === 'saved') {
297
+ saveBrowserTargetState(undefined, opts.scope);
298
+ return undefined;
299
+ }
300
+ throw new Error(`Target tab ${candidate} is not part of the current browser session. ` +
301
+ 'The Browser Bridge session may have restarted; re-run "bycli browser tab list" and choose a current target.');
302
+ }
303
+ function getBrowserScope(session, contextId) {
304
+ return contextId ? `${contextId}:${session}` : session;
305
+ }
306
+ async function resolveStoredBrowserTarget(page, scope) {
307
+ const defaultPage = loadBrowserTargetState(scope)?.defaultPage?.trim();
308
+ if (!defaultPage)
309
+ return undefined;
310
+ return resolveBrowserTargetInSession(page, defaultPage, { scope, source: 'saved' });
311
+ }
312
+ /** Create a browser page for browser commands. Uses a named browser session for continuity. */
313
+ async function getBrowserPage(session, targetPage, contextId, opts = {}) {
314
+ const { BrowserBridge } = await import('./browser/index.js');
315
+ const bridge = new BrowserBridge();
316
+ // Internal GC timeout for browser sessions. Not the per-command runtime timeout.
317
+ const envTimeout = process.env.BYCLI_BROWSER_IDLE_TIMEOUT;
318
+ const idleTimeout = envTimeout ? parseInt(envTimeout, 10) : undefined;
319
+ const page = await bridge.connect({
320
+ timeout: 30,
321
+ session,
322
+ surface: 'browser',
323
+ ...(contextId && { contextId }),
324
+ ...(idleTimeout && idleTimeout > 0 && { idleTimeout }),
325
+ windowMode: opts.windowMode ?? getBrowserWindowMode(undefined, 'foreground'),
326
+ });
327
+ const targetScope = getBrowserScope(session, contextId);
328
+ const resolvedTargetPage = targetPage
329
+ ? await resolveBrowserTargetInSession(page, targetPage, { scope: targetScope, source: 'explicit' })
330
+ : await resolveStoredBrowserTarget(page, targetScope);
331
+ if (resolvedTargetPage) {
332
+ if (!page.setActivePage) {
333
+ throw new Error('This browser session does not support explicit tab targeting');
334
+ }
335
+ page.setActivePage(resolvedTargetPage);
336
+ }
337
+ return page;
338
+ }
339
+ function getBrowserWindowMode(command, defaultMode) {
340
+ const optionRaw = getCommandOption(command, 'window');
341
+ if (optionRaw !== undefined && optionRaw !== '') {
342
+ if (optionRaw === 'foreground' || optionRaw === 'background')
343
+ return optionRaw;
344
+ throw new Error(`--window must be one of: foreground, background. Received: "${String(optionRaw)}"`);
345
+ }
346
+ const envRaw = process.env.BYCLI_WINDOW;
347
+ if (envRaw !== undefined && envRaw !== '') {
348
+ if (envRaw === 'foreground' || envRaw === 'background')
349
+ return envRaw;
350
+ throw new Error(`BYCLI_WINDOW must be one of: foreground, background. Received: "${envRaw}"`);
351
+ }
352
+ return defaultMode;
353
+ }
354
+ function addBrowserTabOption(command) {
355
+ return command.option('--tab <targetId>', BROWSER_TAB_OPTION_DESCRIPTION);
356
+ }
357
+ function getBrowserTargetId(command) {
358
+ if (!command)
359
+ return undefined;
360
+ const opts = command.optsWithGlobals ? command.optsWithGlobals() : command.opts();
361
+ return typeof opts.tab === 'string' && opts.tab.trim() ? opts.tab.trim() : undefined;
362
+ }
363
+ function getCommandOption(command, option) {
364
+ let current = command;
365
+ while (current) {
366
+ const opts = current.opts();
367
+ if (Object.prototype.hasOwnProperty.call(opts, option) && opts[option] !== undefined)
368
+ return opts[option];
369
+ current = current.parent;
370
+ }
371
+ return undefined;
372
+ }
373
+ function getBrowserSession(command) {
374
+ // The CLI surface is `bycli browser <session> <subcommand>`. main.ts rewrites
375
+ // argv to insert `--session <name>` before commander parses it; this helper
376
+ // reads back the rewritten flag.
377
+ const raw = getCommandOption(command, 'session');
378
+ if (typeof raw === 'string' && raw.trim())
379
+ return raw.trim();
380
+ throw new Error('<session> is a required positional argument: bycli browser <session> <command>');
381
+ }
382
+ function getBrowserContextId(command) {
383
+ const raw = getCommandOption(command, 'profile');
384
+ return resolveProfileContextId(typeof raw === 'string' && raw.trim() ? raw.trim() : undefined);
385
+ }
386
+ function getPageSession(page) {
387
+ const session = page.session;
388
+ if (typeof session === 'string' && session.trim())
389
+ return session.trim();
390
+ throw new Error('Browser page is missing a session');
391
+ }
392
+ function getPageScope(page) {
393
+ const contextId = page.contextId;
394
+ return getBrowserScope(getPageSession(page), typeof contextId === 'string' && contextId.trim() ? contextId.trim() : undefined);
395
+ }
396
+ function snapshotMetricText(snapshot) {
397
+ return typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2);
398
+ }
399
+ function snapshotMetrics(snapshot, elapsedMs) {
400
+ const text = snapshotMetricText(snapshot);
401
+ const interactiveMatch = text.match(/^interactive:\s*(\d+)\s*$/m);
402
+ return {
403
+ ok: true,
404
+ chars: text.length,
405
+ bytes: Buffer.byteLength(text, 'utf8'),
406
+ lines: text ? text.split(/\r?\n/).length : 0,
407
+ approx_tokens: Math.ceil(text.length / 4),
408
+ refs: (text.match(/(^|\n)\s*\[\d+\]/g) ?? []).length,
409
+ frame_sections: (text.match(/(^|\n)frame /g) ?? []).length,
410
+ ...(interactiveMatch ? { interactive: Number(interactiveMatch[1]) } : {}),
411
+ elapsed_ms: elapsedMs,
412
+ };
413
+ }
414
+ async function snapshotSourceMetrics(page, source) {
415
+ const started = Date.now();
416
+ try {
417
+ const snapshot = await page.snapshot({ viewportExpand: 2000, source });
418
+ return snapshotMetrics(snapshot, Date.now() - started);
419
+ }
420
+ catch (err) {
421
+ return {
422
+ ok: false,
423
+ elapsed_ms: Date.now() - started,
424
+ error: {
425
+ ...(err instanceof Error && 'code' in err ? { code: String(err.code) } : {}),
426
+ message: err instanceof Error ? err.message : String(err),
427
+ },
428
+ };
429
+ }
430
+ }
431
+ function resolveBrowserTabTarget(targetId, opts) {
432
+ if (typeof targetId === 'string' && targetId.trim())
433
+ return targetId.trim();
434
+ const tab = opts instanceof Command ? opts.opts().tab : opts?.tab;
435
+ if (typeof tab === 'string' && tab.trim())
436
+ return tab.trim();
437
+ return undefined;
438
+ }
439
+ function parsePositiveIntOption(val, label, fallback) {
440
+ if (val === undefined)
441
+ return fallback;
442
+ const parsed = parseInt(val, 10);
443
+ if (Number.isNaN(parsed) || parsed <= 0) {
444
+ console.error(`[cli] Invalid ${label}="${val}", using default ${fallback}`);
445
+ return fallback;
446
+ }
447
+ return parsed;
448
+ }
449
+ function parseScreenshotDim(val, label) {
450
+ if (!/^\d+$/.test(val)) {
451
+ throw new InvalidArgumentError(`--${label} must be a positive integer (got "${val}")`);
452
+ }
453
+ const parsed = parseInt(val, 10);
454
+ if (parsed <= 0) {
455
+ throw new InvalidArgumentError(`--${label} must be a positive integer (got "${val}")`);
456
+ }
457
+ return parsed;
458
+ }
459
+ function applyVerbose(opts) {
460
+ if (opts.verbose)
461
+ process.env.BYCLI_VERBOSE = '1';
462
+ }
463
+ function formatChildCommandSummary(command) {
464
+ return [...new Set(command.commands.map(child => child.name()))]
465
+ .sort((a, b) => a.localeCompare(b))
466
+ .join(', ');
467
+ }
468
+ function applyRootSubcommandSummaries(program) {
469
+ for (const command of program.commands) {
470
+ if (command.commands.length === 0)
471
+ continue;
472
+ const summary = formatChildCommandSummary(command);
473
+ if (summary)
474
+ command.description(summary);
475
+ }
476
+ }
477
+ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
478
+ const program = new Command();
479
+ // enablePositionalOptions: prevents parent from consuming flags meant for subcommands;
480
+ // prerequisite for passThroughOptions to forward --help/--version to external binaries
481
+ program
482
+ .name('bycli')
483
+ .description('Make any website your CLI. Zero setup. AI-powered.')
484
+ .version(PKG_VERSION)
485
+ .option('--profile <name>', 'Chrome profile/context alias for Browser Bridge commands')
486
+ .enablePositionalOptions();
487
+ // ── Built-in: list ────────────────────────────────────────────────────────
488
+ program
489
+ .command('list')
490
+ .description('List all available CLI commands')
491
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
492
+ .action((opts) => {
493
+ const registry = getRegistry();
494
+ const commands = [...new Set(registry.values())].sort((a, b) => fullName(a).localeCompare(fullName(b)));
495
+ const fmt = opts.format;
496
+ const isStructured = fmt === 'json' || fmt === 'yaml';
497
+ if (fmt !== 'table') {
498
+ const rows = isStructured
499
+ ? commands.map(serializeCommand)
500
+ : commands.map(c => ({
501
+ command: fullName(c),
502
+ site: c.site,
503
+ name: c.name,
504
+ aliases: c.aliases?.join(', ') ?? '',
505
+ description: c.description,
506
+ access: c.access,
507
+ strategy: strategyLabel(c),
508
+ browser: !!c.browser,
509
+ args: formatArgSummary(c.args),
510
+ }));
511
+ renderOutput(rows, {
512
+ fmt,
513
+ columns: ['command', 'site', 'name', 'aliases', 'description', 'access', 'strategy', 'browser', 'args',
514
+ ...(isStructured ? ['columns', 'domain'] : [])],
515
+ title: 'bycli/list',
516
+ source: 'bycli list',
517
+ });
518
+ return;
519
+ }
520
+ // Table (default) — grouped by site
521
+ const sites = new Map();
522
+ for (const cmd of commands) {
523
+ const g = sites.get(cmd.site) ?? [];
524
+ g.push(cmd);
525
+ sites.set(cmd.site, g);
526
+ }
527
+ console.log();
528
+ console.log(' bycli' + ' — available commands');
529
+ console.log();
530
+ for (const [site, cmds] of sites) {
531
+ console.log(` ${site}`);
532
+ for (const cmd of cmds) {
533
+ const label = strategyLabel(cmd);
534
+ const tag = label === 'public'
535
+ ? '[public]'
536
+ : `[${label}]`;
537
+ const aliases = cmd.aliases?.length ? ` (aliases: ${cmd.aliases.join(', ')})` : '';
538
+ console.log(` ${cmd.name} ${tag}${aliases}${cmd.description ? ` — ${cmd.description}` : ''}`);
539
+ }
540
+ console.log();
541
+ }
542
+ const externalClis = loadExternalClis();
543
+ if (externalClis.length > 0) {
544
+ console.log(' external CLIs');
545
+ for (const ext of externalClis) {
546
+ const isInstalled = isBinaryInstalled(ext.binary);
547
+ const tag = isInstalled ? '[installed]' : '[auto-install]';
548
+ console.log(` ${formatExternalCliLabel(ext)} ${tag}${ext.description ? ` — ${ext.description}` : ''}`);
549
+ }
550
+ console.log();
551
+ }
552
+ console.log(` ${commands.length} built-in commands across ${sites.size} sites, ${externalClis.length} external CLIs`);
553
+ console.log();
554
+ });
555
+ // ── Built-in: validate / verify ───────────────────────────────────────────
556
+ program
557
+ .command('validate')
558
+ .description('Validate CLI definitions')
559
+ .argument('[target]', 'site or site/name')
560
+ .action(async (target) => {
561
+ const { validateClisWithTarget, renderValidationReport } = await import('./validate.js');
562
+ console.log(renderValidationReport(validateClisWithTarget([BUILTIN_CLIS, USER_CLIS], target)));
563
+ });
564
+ program
565
+ .command('verify')
566
+ .description('Validate + smoke test')
567
+ .argument('[target]')
568
+ .option('--smoke', 'Run smoke tests', false)
569
+ .action(async (target, opts) => {
570
+ const { verifyClis, renderVerifyReport } = await import('./verify.js');
571
+ const r = await verifyClis({ builtinClis: BUILTIN_CLIS, userClis: USER_CLIS, target, smoke: opts.smoke });
572
+ console.log(renderVerifyReport(r));
573
+ process.exitCode = r.ok ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERIC_ERROR;
574
+ });
575
+ program
576
+ .command('convention-audit')
577
+ .description('Scan adapters for agent-native convention violations')
578
+ .argument('[target]', 'site or site/name')
579
+ .option('--site <site>', 'Limit audit to one site')
580
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml', 'table')
581
+ .option('--strict', 'Exit non-zero when violations are found', false)
582
+ .action(async (target, opts) => {
583
+ const { runConventionAudit, renderConventionAuditText } = await import('./convention-audit.js');
584
+ const report = runConventionAudit({
585
+ projectRoot: findPackageRoot(CLI_FILE),
586
+ target,
587
+ site: opts.site,
588
+ });
589
+ const fmt = String(opts.format ?? 'table').toLowerCase();
590
+ if (fmt === 'json' || fmt === 'yaml' || fmt === 'yml') {
591
+ renderOutput(report, { fmt });
592
+ }
593
+ else {
594
+ console.log(renderConventionAuditText(report));
595
+ }
596
+ if (opts.strict && !report.ok)
597
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
598
+ });
599
+ // ── Built-in: browser (browser control for Claude Code skill) ───────────────
600
+ //
601
+ // Make websites accessible for AI agents.
602
+ // All commands wrapped in browserAction() for consistent error handling.
603
+ const browser = program
604
+ .command('browser')
605
+ // --session is an internal hidden option used by the daemon protocol and direct
606
+ // program.parseAsync callers (tests). User-facing surface is the <session>
607
+ // positional; main.ts argv preprocessor rewrites positional -> --session.
608
+ .addOption(new Option('--session <name>', 'Internal — set automatically from the <session> positional').hideHelp())
609
+ .option('--window <mode>', 'Browser window mode: foreground or background')
610
+ .description('Browser control — navigate, click, type, extract, wait (no LLM needed)')
611
+ .usage('<session> <command> [options]')
612
+ .addHelpText('after', `
613
+ <session> is a required positional: pass the name of the browser session every subcommand should operate on. Reuse the same name across calls to keep the tab/state alive; pick a different name to isolate parallel browser work.
614
+
615
+ Examples:
616
+ $ bycli browser work open https://x.com
617
+ $ bycli browser work click 12
618
+ $ bycli browser work state
619
+ $ bycli browser work bind
620
+ $ bycli browser work unbind
621
+ `);
622
+ const originalBrowserDescription = browser.description();
623
+ /**
624
+ * Resolve a `<target>` (numeric ref or CSS selector) via the unified resolver.
625
+ * Returns the CSS match count so callers can propagate `matches_n` into the
626
+ * JSON envelope printed back to the agent.
627
+ */
628
+ async function resolveRef(page, ref, opts = {}) {
629
+ const resolution = await page.evaluate(resolveTargetJs(ref, opts));
630
+ if (!resolution.ok) {
631
+ throw new TargetError({
632
+ code: resolution.code,
633
+ message: resolution.message,
634
+ hint: resolution.hint,
635
+ candidates: resolution.candidates,
636
+ matches_n: resolution.matches_n,
637
+ });
638
+ }
639
+ return { matches_n: resolution.matches_n, match_level: resolution.match_level };
640
+ }
641
+ /**
642
+ * Parse `--nth <n>` flag, returning the parsed 0-based index or a usage error.
643
+ * The surface mirrors `--depth` etc. in `browser get html --as json`: the flag
644
+ * is optional, must be a non-negative integer when present, and on failure we
645
+ * emit the structured error envelope rather than throwing past the command.
646
+ */
647
+ function parseNthFlag(raw) {
648
+ if (raw === undefined || raw === null || raw === '')
649
+ return null;
650
+ const str = String(raw);
651
+ if (!/^\d+$/.test(str)) {
652
+ return { error: `--nth must be a non-negative integer, got "${str}"` };
653
+ }
654
+ return Number.parseInt(str, 10);
655
+ }
656
+ /** Emit the `{ error: { code, message, hint?, candidates?, matches_n? } }` envelope used by the selector-first commands. */
657
+ function emitTargetError(err) {
658
+ console.log(JSON.stringify({
659
+ error: {
660
+ code: err.code,
661
+ message: err.message,
662
+ hint: err.hint,
663
+ ...(err.candidates && { candidates: err.candidates }),
664
+ ...(err.matches_n !== undefined && { matches_n: err.matches_n }),
665
+ },
666
+ }, null, 2));
667
+ }
668
+ function isJavaScriptDialogMessage(message) {
669
+ const normalized = message.toLowerCase();
670
+ return normalized.includes('javascript dialog');
671
+ }
672
+ function emitJavaScriptDialogError(message) {
673
+ console.log(JSON.stringify({
674
+ error: {
675
+ code: 'javascript_dialog_open',
676
+ message,
677
+ hint: 'Handle the modal first: bycli browser dialog accept (or dismiss). Use --text for prompt dialogs.',
678
+ },
679
+ }, null, 2));
680
+ }
681
+ /** Wrap browser actions with error handling and optional --json output */
682
+ function browserAction(fn) {
683
+ return async (...args) => {
684
+ let page = null;
685
+ try {
686
+ const command = args.at(-1) instanceof Command ? args.at(-1) : undefined;
687
+ const targetPage = getBrowserTargetId(command);
688
+ const session = getBrowserSession(command);
689
+ const contextId = getBrowserContextId(command);
690
+ const windowMode = getBrowserWindowMode(command, 'foreground');
691
+ page = await getBrowserPage(session, targetPage, contextId, { windowMode });
692
+ await fn(page, ...args);
693
+ }
694
+ catch (err) {
695
+ if (err instanceof BrowserConnectError) {
696
+ log.error(err.message);
697
+ if (err.hint)
698
+ log.error(`Hint: ${err.hint}`);
699
+ }
700
+ else if (err instanceof BrowserCommandError) {
701
+ if (isJavaScriptDialogMessage(err.message)) {
702
+ emitJavaScriptDialogError(err.message);
703
+ }
704
+ else if (err.code) {
705
+ console.log(JSON.stringify({
706
+ error: {
707
+ code: err.code,
708
+ message: err.message,
709
+ ...(err.hint ? { hint: err.hint } : {}),
710
+ },
711
+ }, null, 2));
712
+ }
713
+ log.error(err.message);
714
+ if (err.hint)
715
+ log.error(`Hint: ${err.hint}`);
716
+ }
717
+ else if (err instanceof TargetError) {
718
+ // Agent-facing structured envelope on stdout + short human line on stderr.
719
+ emitTargetError(err);
720
+ log.error(`[${err.code}] ${err.message}`);
721
+ if (err.hint)
722
+ log.error(`Hint: ${err.hint}`);
723
+ }
724
+ else {
725
+ const msg = getErrorMessage(err);
726
+ if (isJavaScriptDialogMessage(msg)) {
727
+ emitJavaScriptDialogError(msg);
728
+ log.error(msg);
729
+ }
730
+ else if (msg.includes('attach failed') || msg.includes('chrome-extension://')) {
731
+ log.error(`Browser attach failed — another extension may be interfering. Try disabling 1Password.`);
732
+ }
733
+ else {
734
+ log.error(msg);
735
+ }
736
+ }
737
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
738
+ }
739
+ };
740
+ }
741
+ browser.command('bind')
742
+ .description('Bind the current Chrome tab/window to the browser session named by <session>')
743
+ .action(async (optsOrCommand, maybeCommand) => {
744
+ const command = optsOrCommand instanceof Command ? optsOrCommand : maybeCommand;
745
+ const session = getBrowserSession(command);
746
+ try {
747
+ const { BrowserBridge } = await import('./browser/index.js');
748
+ const bridge = new BrowserBridge();
749
+ const contextId = getBrowserContextId(command);
750
+ await bridge.connect({ timeout: 30, session, surface: 'browser', ...(contextId && { contextId }) });
751
+ const data = await bindTab(session, { ...(contextId && { contextId }) });
752
+ saveBrowserTargetState(undefined, getBrowserScope(session, contextId));
753
+ console.log(JSON.stringify({ session, ...((data && typeof data === 'object') ? data : { data }) }, null, 2));
754
+ }
755
+ catch (err) {
756
+ if (err instanceof BrowserCommandError && err.code) {
757
+ console.log(JSON.stringify({
758
+ error: {
759
+ code: err.code,
760
+ message: err.message,
761
+ ...(err.hint ? { hint: err.hint } : {}),
762
+ },
763
+ }, null, 2));
764
+ }
765
+ log.error(err instanceof Error ? err.message : String(err));
766
+ if (err instanceof BrowserCommandError && err.hint)
767
+ log.error(`Hint: ${err.hint}`);
768
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
769
+ }
770
+ });
771
+ browser.command('unbind')
772
+ .description('Detach the bound browser session named by <session> without closing the user tab/window')
773
+ .action(async (optsOrCommand, maybeCommand) => {
774
+ const command = optsOrCommand instanceof Command ? optsOrCommand : maybeCommand;
775
+ const session = getBrowserSession(command);
776
+ try {
777
+ const { BrowserBridge } = await import('./browser/index.js');
778
+ const bridge = new BrowserBridge();
779
+ const contextId = getBrowserContextId(command);
780
+ await bridge.connect({ timeout: 30, session, surface: 'browser', ...(contextId && { contextId }) });
781
+ await sendCommand('close-window', { session, surface: 'browser', ...(contextId && { contextId }) });
782
+ saveBrowserTargetState(undefined, getBrowserScope(session, contextId));
783
+ console.log(JSON.stringify({ unbound: true, session }, null, 2));
784
+ }
785
+ catch (err) {
786
+ if (err instanceof BrowserCommandError && err.code) {
787
+ console.log(JSON.stringify({
788
+ error: {
789
+ code: err.code,
790
+ message: err.message,
791
+ ...(err.hint ? { hint: err.hint } : {}),
792
+ },
793
+ }, null, 2));
794
+ }
795
+ log.error(err instanceof Error ? err.message : String(err));
796
+ if (err instanceof BrowserCommandError && err.hint)
797
+ log.error(`Hint: ${err.hint}`);
798
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
799
+ }
800
+ });
801
+ const browserTab = browser
802
+ .command('tab')
803
+ .description('Tab management — list, create, and close tabs in the browser session');
804
+ browserTab.command('list')
805
+ .description('List tabs in the browser session with target IDs')
806
+ .action(browserAction(async (page) => {
807
+ const tabs = await page.tabs();
808
+ console.log(JSON.stringify(tabs, null, 2));
809
+ }));
810
+ browserTab.command('new')
811
+ .argument('[url]', 'Optional URL to open in the new tab')
812
+ .description('Create a new tab and print its target ID')
813
+ .action(browserAction(async (page, url) => {
814
+ if (!page.newTab) {
815
+ throw new Error('This browser session does not support creating tabs');
816
+ }
817
+ const createdPage = await page.newTab(url);
818
+ console.log(JSON.stringify({
819
+ page: createdPage,
820
+ url: url ?? null,
821
+ }, null, 2));
822
+ }));
823
+ addBrowserTabOption(browserTab.command('select')
824
+ .argument('[targetId]', 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"')
825
+ .description('Select a tab by target ID and make it the default browser tab'))
826
+ .action(browserAction(async (page, targetId, opts) => {
827
+ const resolvedTarget = resolveBrowserTabTarget(targetId, opts);
828
+ if (!resolvedTarget) {
829
+ throw new Error('Target tab required. Pass it as an argument or --tab <targetId>.');
830
+ }
831
+ await page.selectTab(resolvedTarget);
832
+ saveBrowserTargetState(resolvedTarget, getPageScope(page));
833
+ console.log(JSON.stringify({ selected: resolvedTarget }, null, 2));
834
+ }));
835
+ addBrowserTabOption(browserTab.command('close')
836
+ .argument('[targetId]', 'Target tab/page identity returned by "browser open", "browser tab new", or "browser tab list"')
837
+ .description('Close a tab by target ID'))
838
+ .action(browserAction(async (page, targetId, opts) => {
839
+ const resolvedTarget = resolveBrowserTabTarget(targetId, opts);
840
+ if (!page.closeTab) {
841
+ throw new Error('This browser session does not support closing tabs');
842
+ }
843
+ if (!resolvedTarget) {
844
+ throw new Error('Target tab required. Pass it as an argument or --tab <targetId>.');
845
+ }
846
+ const validatedTarget = await resolveBrowserTargetInSession(page, resolvedTarget, {
847
+ scope: getPageScope(page),
848
+ source: 'explicit',
849
+ });
850
+ if (!validatedTarget) {
851
+ throw new Error(`Target tab ${resolvedTarget} is not part of the current browser session.`);
852
+ }
853
+ await page.closeTab(validatedTarget);
854
+ const scope = getPageScope(page);
855
+ if (loadBrowserTargetState(scope)?.defaultPage === validatedTarget) {
856
+ saveBrowserTargetState(undefined, scope);
857
+ }
858
+ console.log(JSON.stringify({ closed: validatedTarget }, null, 2));
859
+ }));
860
+ // ── Navigation ──
861
+ /**
862
+ * Network interceptor JS — injected on every open/navigate to capture
863
+ * fetch/XHR bodies when the session-level capture channel (CDP/extension)
864
+ * isn't available. Keeps parity with the CDP path's truncation contract:
865
+ * when a body exceeds the per-entry cap, we keep a string prefix and set
866
+ * `bodyTruncated: true` + `bodyFullSize: <original length>` so `browser
867
+ * network` can propagate a visible signal to the agent instead of
868
+ * silently dropping the body. Per-entry cap is 1 MiB and the ring is
869
+ * capped at 200 entries, bounding worst-case in-page memory.
870
+ */
871
+ const NETWORK_INTERCEPTOR_JS = `(function(){if(window.__bycli_net)return;window.__bycli_net=[];var M=200,B=1048576,F=window.fetch;function capture(url,method,status,text,ct){if(window.__bycli_net.length>=M)return;var full=text?text.length:0,trunc=full>B,stored=trunc?text.slice(0,B):text,body=null;if(stored){if(trunc){body=stored}else{try{body=JSON.parse(stored)}catch(e){body=stored}}}var e={url:url,method:method||'GET',status:status,size:full,ct:ct,body:body,timestamp:Date.now()};if(trunc){e.bodyTruncated=true;e.bodyFullSize=full}window.__bycli_net.push(e)}window.fetch=async function(){var r=await F.apply(this,arguments);try{var ct=r.headers.get('content-type')||'';if(ct.includes('json')||ct.includes('text')){var c=r.clone(),t=await c.text();capture(r.url||(arguments[0]&&arguments[0].url)||String(arguments[0]),(arguments[1]&&arguments[1].method)||'GET',r.status,t,ct)}}catch(e){}return r};var X=XMLHttpRequest.prototype,O=X.open,S=X.send;X.open=function(m,u){this._om=m;this._ou=u;return O.apply(this,arguments)};X.send=function(){var x=this;x.addEventListener('load',function(){try{var ct=x.getResponseHeader('content-type')||'';if(ct.includes('json')||ct.includes('text')){capture(x._ou,x._om||'GET',x.status,x.responseText||'',ct)}}catch(e){}});return S.apply(this,arguments)}})()`;
872
+ addBrowserTabOption(browser.command('open').argument('<url>').description('Open URL in the browser session'))
873
+ .action(browserAction(async (page, url, opts) => {
874
+ // Start session-level capture before navigation (catches initial requests)
875
+ const hasSessionCapture = await page.startNetworkCapture?.() ?? false;
876
+ await page.goto(url);
877
+ await page.wait(2);
878
+ // Fallback: inject JS interceptor when session capture is unavailable
879
+ if (!hasSessionCapture) {
880
+ try {
881
+ await page.evaluate(NETWORK_INTERCEPTOR_JS);
882
+ }
883
+ catch { /* non-fatal */ }
884
+ }
885
+ console.log(JSON.stringify({
886
+ url: await page.getCurrentUrl?.() ?? url,
887
+ ...(page.getActivePage?.() ? { page: page.getActivePage?.() } : {}),
888
+ }, null, 2));
889
+ }));
890
+ addBrowserTabOption(browser.command('back').description('Go back in browser history'))
891
+ .action(browserAction(async (page, opts) => {
892
+ await page.evaluate('history.back()');
893
+ await page.wait(2);
894
+ console.log('Navigated back');
895
+ }));
896
+ addBrowserTabOption(browser.command('scroll').argument('<direction>', 'up or down').option('--amount <pixels>', 'Pixels to scroll', '500'))
897
+ .description('Scroll page')
898
+ .action(browserAction(async (page, direction, opts) => {
899
+ if (direction !== 'up' && direction !== 'down') {
900
+ console.error(`Invalid direction "${direction}". Use "up" or "down".`);
901
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
902
+ return;
903
+ }
904
+ await page.scroll(direction, parseInt(opts.amount, 10));
905
+ console.log(`Scrolled ${direction}`);
906
+ }));
907
+ // ── Inspect ──
908
+ addBrowserTabOption(browser.command('state').description('Page state: URL, title, interactive elements with [N] indices')
909
+ .option('--source <source>', 'Snapshot backend: dom (default) or ax prototype', 'dom')
910
+ .option('--compare-sources', 'Print DOM vs AX snapshot metrics for observation promotion decisions', false))
911
+ .action(browserAction(async (page, opts) => {
912
+ if (opts.compareSources === true) {
913
+ const [dom, ax] = await Promise.all([
914
+ snapshotSourceMetrics(page, 'dom'),
915
+ snapshotSourceMetrics(page, 'ax'),
916
+ ]);
917
+ console.log(JSON.stringify({
918
+ url: await page.getCurrentUrl?.() ?? '',
919
+ sources: { dom, ax },
920
+ }, null, 2));
921
+ return;
922
+ }
923
+ const source = String(opts.source ?? 'dom').toLowerCase();
924
+ if (source !== 'dom' && source !== 'ax') {
925
+ console.log(JSON.stringify({
926
+ error: {
927
+ code: 'invalid_source',
928
+ message: `--source must be "dom" or "ax", got "${opts.source}"`,
929
+ },
930
+ }, null, 2));
931
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
932
+ return;
933
+ }
934
+ const snapshot = await page.snapshot({ viewportExpand: 2000, source: source });
935
+ const url = await page.getCurrentUrl?.() ?? '';
936
+ console.log(`URL: ${url}\n`);
937
+ console.log(typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2));
938
+ }));
939
+ addBrowserTabOption(browser.command('frames').description('List cross-origin iframe targets in snapshot order'))
940
+ .action(browserAction(async (page) => {
941
+ const frames = await page.frames?.() ?? [];
942
+ console.log(JSON.stringify(frames, null, 2));
943
+ }));
944
+ addBrowserTabOption(browser.command('screenshot').argument('[path]', 'Save to file (base64 if omitted)'))
945
+ .option('--full-page', 'Capture the full scrollable page, not just the viewport', false)
946
+ .option('--annotate', 'Overlay visible browser state ref labels on the screenshot', false)
947
+ .option('--width <n>', 'Override viewport width in CSS pixels for this screenshot only', (v) => parseScreenshotDim(v, 'width'))
948
+ .option('--height <n>', 'Override viewport height in CSS pixels for this screenshot only (ignored with --full-page)', (v) => parseScreenshotDim(v, 'height'))
949
+ .description('Take screenshot')
950
+ .action(browserAction(async (page, path, opts) => {
951
+ const shotOpts = {
952
+ fullPage: opts.fullPage === true,
953
+ annotate: opts.annotate === true,
954
+ width: opts.width,
955
+ height: opts.height,
956
+ };
957
+ const capture = opts.annotate === true
958
+ ? (page.annotatedScreenshot ?? page.screenshot).bind(page)
959
+ : page.screenshot.bind(page);
960
+ if (path) {
961
+ await capture({ ...shotOpts, path });
962
+ console.log(`Screenshot saved to: ${path}`);
963
+ }
964
+ else {
965
+ console.log(await capture({ ...shotOpts, format: 'png' }));
966
+ }
967
+ }));
968
+ addBrowserTabOption(browser.command('console'))
969
+ .option('--level <level>', 'Console level: all, error, warning, log, info, debug', 'all')
970
+ .option('--since <duration>', 'Only include messages from the last duration (for example: 30s, 2m)')
971
+ .option('--until <duration>', 'Only include messages older than the duration from now')
972
+ .option('--follow', 'Continuously print new console messages as JSON lines', false)
973
+ .description('Read recent browser console messages')
974
+ .action(browserAction(async (page, opts) => {
975
+ const sinceMs = parseDurationMs(opts.since, 'since');
976
+ const untilMs = parseDurationMs(opts.until, 'until');
977
+ if (sinceMs && typeof sinceMs === 'object') {
978
+ console.log(JSON.stringify({ error: { code: 'invalid_since', message: sinceMs.error } }, null, 2));
979
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
980
+ return;
981
+ }
982
+ if (untilMs && typeof untilMs === 'object') {
983
+ console.log(JSON.stringify({ error: { code: 'invalid_until', message: untilMs.error } }, null, 2));
984
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
985
+ return;
986
+ }
987
+ const normalize = (messages) => messages.map((message) => {
988
+ if (message && typeof message === 'object') {
989
+ const record = message;
990
+ return {
991
+ ...record,
992
+ timestamp: timestampFromRaw(record.timestamp),
993
+ };
994
+ }
995
+ return { type: 'log', text: String(message), timestamp: Date.now() };
996
+ });
997
+ const filter = (messages) => filterByTimeWindow(messages, { sinceMs, untilMs }).filter((message) => {
998
+ if (opts.level === 'all')
999
+ return true;
1000
+ const type = String(message.type ?? message.level ?? '').toLowerCase();
1001
+ return opts.level === 'error'
1002
+ ? type === 'error' || type === 'warning'
1003
+ : type === String(opts.level).toLowerCase();
1004
+ });
1005
+ if (opts.follow) {
1006
+ let lastSeenTs = 0;
1007
+ while (true) {
1008
+ const messages = filter(normalize(await page.consoleMessages('all')));
1009
+ const next = selectFreshByTimestamp(messages, lastSeenTs);
1010
+ for (const message of next.fresh) {
1011
+ console.log(JSON.stringify({
1012
+ ...message,
1013
+ timestamp: toIsoTimestamp(message.timestamp),
1014
+ }));
1015
+ }
1016
+ lastSeenTs = next.lastSeenTs;
1017
+ await new Promise((resolve) => setTimeout(resolve, FOLLOW_POLL_MS));
1018
+ }
1019
+ }
1020
+ const messages = filter(normalize(await page.consoleMessages(opts.level)));
1021
+ console.log(JSON.stringify({
1022
+ session: getPageSession(page),
1023
+ captured_at: new Date().toISOString(),
1024
+ count: messages.length,
1025
+ messages: messages.map((message) => ({
1026
+ ...message,
1027
+ timestamp: toIsoTimestamp(message.timestamp),
1028
+ })),
1029
+ }, null, 2));
1030
+ }));
1031
+ // ── Analyze (site recon, agent-native) ──
1032
+ //
1033
+ // Mechanizes the `site-recon.md` decision tree into one CLI call. The agent
1034
+ // calls `browser analyze <url>` and gets back:
1035
+ //
1036
+ // - pattern: A/B/C/D (mapped from network + SSR-globals signals)
1037
+ // - anti_bot: vendor + evidence + the one-liner for "what to do next"
1038
+ // - initial_state: which window globals are populated
1039
+ // - nearest_adapter: existing commands for the same site, if any
1040
+ // - recommended_next_step: a single imperative sentence
1041
+ //
1042
+ // Intent: replace the "open → eyeball network → curl → WAF → try again"
1043
+ // feedback loop with a single deterministic verdict. Without this, agents
1044
+ // burn ~20min per WAF-protected site re-discovering anti-bot posture.
1045
+ addBrowserTabOption(browser.command('analyze').argument('<url>'))
1046
+ .description('Classify site: anti-bot vendor, pattern (A/B/C/D), nearest adapter, recommended next step')
1047
+ .action(browserAction(async (page, url) => {
1048
+ const hasSessionCapture = await page.startNetworkCapture?.() ?? false;
1049
+ await page.goto(url);
1050
+ await page.wait(2);
1051
+ if (!hasSessionCapture) {
1052
+ try {
1053
+ await page.evaluate(NETWORK_INTERCEPTOR_JS);
1054
+ }
1055
+ catch { /* non-fatal */ }
1056
+ }
1057
+ await captureNetworkItems(page);
1058
+ // Best-effort: give the page another beat so XHR after DOMContentLoaded lands.
1059
+ await page.wait(1);
1060
+ const rawItems = await captureNetworkItems(page);
1061
+ const networkEntries = rawItems.map((e) => ({
1062
+ url: e.url,
1063
+ status: e.status,
1064
+ contentType: e.ct,
1065
+ bodyPreview: typeof e.body === 'string'
1066
+ ? e.body.slice(0, 2000)
1067
+ : (e.body ? JSON.stringify(e.body).slice(0, 2000) : null),
1068
+ }));
1069
+ const probeJs = `(function(){
1070
+ return {
1071
+ cookieNames: (document.cookie || '').split(';').map(function(c){ return c.trim().split('=')[0]; }).filter(Boolean),
1072
+ initialState: {
1073
+ __INITIAL_STATE__: typeof window.__INITIAL_STATE__ !== 'undefined',
1074
+ __NUXT__: typeof window.__NUXT__ !== 'undefined',
1075
+ __NEXT_DATA__: typeof window.__NEXT_DATA__ !== 'undefined',
1076
+ __APOLLO_STATE__: typeof window.__APOLLO_STATE__ !== 'undefined',
1077
+ },
1078
+ title: document.title || '',
1079
+ finalUrl: location.href,
1080
+ };
1081
+ })()`;
1082
+ const probe = await page.evaluate(probeJs);
1083
+ const browserCookieNames = (await page.getCookies({ url: probe.finalUrl || url }).catch(() => []))
1084
+ .map((c) => c.name)
1085
+ .filter(Boolean);
1086
+ const cookieNames = [...new Set([...probe.cookieNames, ...browserCookieNames])];
1087
+ const signals = {
1088
+ requestedUrl: url,
1089
+ finalUrl: probe.finalUrl,
1090
+ cookieNames,
1091
+ networkEntries,
1092
+ initialState: probe.initialState,
1093
+ title: probe.title,
1094
+ };
1095
+ const report = analyzeSite(signals, getRegistry());
1096
+ console.log(JSON.stringify(report, null, 2));
1097
+ }));
1098
+ // ── Find (structured CSS query, agent-native) ──
1099
+ //
1100
+ // `browser find --css <sel>` lets agents jump straight from a semantic
1101
+ // selector to a JSON list of matching elements, without having to parse
1102
+ // the free-text state snapshot to recover indices.
1103
+ const addSemanticLocatorOptions = (cmd) => cmd
1104
+ .option('--role <role>', 'Semantic role (button, link, textbox, option, etc.)')
1105
+ .option('--name <text>', 'Accessible name contains text (aria-label, label, title, placeholder, or visible text)')
1106
+ .option('--label <text>', 'Associated label contains text')
1107
+ .option('--text <text>', 'Visible text contains text')
1108
+ .option('--testid <id>', 'data-testid / data-test / test-id contains id');
1109
+ const addPrefixedSemanticLocatorOptions = (cmd, prefix) => cmd
1110
+ .option(`--${prefix}-role <role>`, `${prefix} semantic role`)
1111
+ .option(`--${prefix}-name <text>`, `${prefix} accessible name contains text`)
1112
+ .option(`--${prefix}-label <text>`, `${prefix} associated label contains text`)
1113
+ .option(`--${prefix}-text <text>`, `${prefix} visible text contains text`)
1114
+ .option(`--${prefix}-testid <id>`, `${prefix} data-testid / data-test / test-id contains id`);
1115
+ const semanticLocatorFromOptions = (opts) => {
1116
+ const locator = {};
1117
+ for (const key of ['role', 'name', 'label', 'text', 'testid']) {
1118
+ const value = opts[key];
1119
+ if (typeof value === 'string' && value.trim())
1120
+ locator[key] = value.trim();
1121
+ }
1122
+ return Object.keys(locator).length > 0 ? locator : null;
1123
+ };
1124
+ const prefixedSemanticLocatorFromOptions = (opts, prefix) => {
1125
+ const locator = {};
1126
+ const map = {
1127
+ role: `${prefix}Role`,
1128
+ name: `${prefix}Name`,
1129
+ label: `${prefix}Label`,
1130
+ text: `${prefix}Text`,
1131
+ testid: `${prefix}Testid`,
1132
+ };
1133
+ for (const key of ['role', 'name', 'label', 'text', 'testid']) {
1134
+ const value = opts[map[key]];
1135
+ if (typeof value === 'string' && value.trim())
1136
+ locator[key] = value.trim();
1137
+ }
1138
+ return Object.keys(locator).length > 0 ? locator : null;
1139
+ };
1140
+ const semanticTargetFromLocator = async (page, locator, mode) => {
1141
+ const result = await page.evaluate(buildSemanticFindJs({ ...locator, limit: 6 }));
1142
+ if (isFindError(result))
1143
+ return result;
1144
+ if (mode === 'write' && result.matches_n !== 1) {
1145
+ return {
1146
+ error: {
1147
+ code: 'semantic_ambiguous',
1148
+ message: `Semantic locator matched ${result.matches_n} elements; write actions require a unique target.`,
1149
+ hint: 'Add --name/--label/--text/--testid or use browser find with a narrower locator.',
1150
+ matches_n: result.matches_n,
1151
+ entries: result.entries,
1152
+ },
1153
+ };
1154
+ }
1155
+ const first = result.entries[0];
1156
+ if (!first) {
1157
+ return {
1158
+ error: {
1159
+ code: 'semantic_not_found',
1160
+ message: 'Semantic locator matched 0 elements',
1161
+ hint: 'Try browser state, --source ax, or relax the semantic locator.',
1162
+ },
1163
+ };
1164
+ }
1165
+ const target = String(first.ref);
1166
+ if (mode === 'read') {
1167
+ return {
1168
+ target,
1169
+ ...(result.matches_n > 1 ? { total_matches: result.matches_n } : {}),
1170
+ };
1171
+ }
1172
+ return target;
1173
+ };
1174
+ const semanticTargetFromOptions = async (page, opts, mode) => {
1175
+ const locator = semanticLocatorFromOptions(opts);
1176
+ if (!locator)
1177
+ return null;
1178
+ return semanticTargetFromLocator(page, locator, mode);
1179
+ };
1180
+ const resolveExplicitOrSemanticTarget = async (page, target, opts, mode) => {
1181
+ const explicit = typeof target === 'string' && target.trim() ? target.trim() : '';
1182
+ const hasSemantic = !!semanticLocatorFromOptions(opts);
1183
+ if (explicit && hasSemantic) {
1184
+ return {
1185
+ error: {
1186
+ code: 'usage_error',
1187
+ message: 'Pass either <target> or semantic locator flags, not both.',
1188
+ },
1189
+ };
1190
+ }
1191
+ if (explicit)
1192
+ return explicit;
1193
+ const semantic = await semanticTargetFromOptions(page, opts, mode);
1194
+ if (semantic)
1195
+ return semantic;
1196
+ return {
1197
+ error: {
1198
+ code: 'usage_error',
1199
+ message: 'Missing target. Pass a numeric ref/CSS selector, or semantic flags like --role button --name Submit.',
1200
+ },
1201
+ };
1202
+ };
1203
+ const printTargetResolutionError = (resolved) => {
1204
+ console.log(JSON.stringify(resolved, null, 2));
1205
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1206
+ };
1207
+ const resolveWriteTargetOrPrint = async (page, target, opts) => {
1208
+ const resolvedTarget = await resolveExplicitOrSemanticTarget(page, target, opts, 'write');
1209
+ if (typeof resolvedTarget === 'string')
1210
+ return resolvedTarget;
1211
+ if ('error' in resolvedTarget)
1212
+ printTargetResolutionError(resolvedTarget);
1213
+ return null;
1214
+ };
1215
+ const resolveWriteTargetAndValueOrPrint = async (page, targetOrValue, value, opts, valueLabel) => {
1216
+ const hasSemantic = !!semanticLocatorFromOptions(opts);
1217
+ if (hasSemantic && value !== undefined) {
1218
+ printTargetResolutionError({
1219
+ error: {
1220
+ code: 'usage_error',
1221
+ message: `When using semantic locator flags, pass only <${valueLabel}> as the positional argument.`,
1222
+ },
1223
+ });
1224
+ return null;
1225
+ }
1226
+ const resolvedValue = hasSemantic ? targetOrValue : value;
1227
+ if (resolvedValue === undefined) {
1228
+ printTargetResolutionError({
1229
+ error: {
1230
+ code: 'usage_error',
1231
+ message: `Missing ${valueLabel}.`,
1232
+ hint: hasSemantic
1233
+ ? `With semantic locator flags, pass the ${valueLabel} as the only positional argument.`
1234
+ : `Pass both a target and ${valueLabel}.`,
1235
+ },
1236
+ });
1237
+ return null;
1238
+ }
1239
+ const resolvedTarget = await resolveWriteTargetOrPrint(page, hasSemantic ? undefined : targetOrValue, opts);
1240
+ if (!resolvedTarget)
1241
+ return null;
1242
+ return { target: resolvedTarget, value: String(resolvedValue) };
1243
+ };
1244
+ const resolvePrefixedWriteTargetOrPrint = async (page, target, opts, prefix, label) => {
1245
+ const explicit = typeof target === 'string' && target.trim() ? target.trim() : '';
1246
+ const locator = prefixedSemanticLocatorFromOptions(opts, prefix);
1247
+ if (explicit && locator) {
1248
+ printTargetResolutionError({
1249
+ error: {
1250
+ code: 'usage_error',
1251
+ message: `Pass either <${label}> or --${prefix}-* semantic locator flags, not both.`,
1252
+ },
1253
+ });
1254
+ return null;
1255
+ }
1256
+ if (explicit)
1257
+ return explicit;
1258
+ if (locator) {
1259
+ const resolved = await semanticTargetFromLocator(page, locator, 'write');
1260
+ if (typeof resolved === 'string')
1261
+ return resolved;
1262
+ if ('error' in resolved)
1263
+ printTargetResolutionError(resolved);
1264
+ return null;
1265
+ }
1266
+ printTargetResolutionError({
1267
+ error: {
1268
+ code: 'usage_error',
1269
+ message: `Missing ${label}. Pass a numeric ref/CSS selector, or --${prefix}-role/--${prefix}-name semantic flags.`,
1270
+ },
1271
+ });
1272
+ return null;
1273
+ };
1274
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('find'))
1275
+ .option('--css <selector>', 'CSS selector (required)')
1276
+ .option('--limit <n>', 'Max entries returned', '50')
1277
+ .option('--text-max <n>', 'Max chars of trimmed text per entry', '120')
1278
+ .description('Find DOM elements by CSS or semantic locator — returns JSON {matches_n, entries[]}'))
1279
+ .action(browserAction(async (page, opts) => {
1280
+ const locator = semanticLocatorFromOptions(opts);
1281
+ if ((!opts.css || typeof opts.css !== 'string') && !locator) {
1282
+ console.log(JSON.stringify({
1283
+ error: {
1284
+ code: 'usage_error',
1285
+ message: '--css <selector> or a semantic locator flag is required',
1286
+ hint: 'Examples: bycli browser find --css ".btn.primary"; bycli browser find --role button --name Save',
1287
+ },
1288
+ }, null, 2));
1289
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1290
+ return;
1291
+ }
1292
+ if (opts.css && locator) {
1293
+ console.log(JSON.stringify({
1294
+ error: {
1295
+ code: 'usage_error',
1296
+ message: 'Pass either --css or semantic locator flags, not both.',
1297
+ },
1298
+ }, null, 2));
1299
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1300
+ return;
1301
+ }
1302
+ const limit = parseNthFlag(opts.limit);
1303
+ if (limit && typeof limit === 'object' && 'error' in limit) {
1304
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: limit.error.replace('--nth', '--limit') } }, null, 2));
1305
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1306
+ return;
1307
+ }
1308
+ const textMax = parseNthFlag(opts.textMax);
1309
+ if (textMax && typeof textMax === 'object' && 'error' in textMax) {
1310
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: textMax.error.replace('--nth', '--text-max') } }, null, 2));
1311
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1312
+ return;
1313
+ }
1314
+ const result = await page.evaluate(locator
1315
+ ? buildSemanticFindJs({
1316
+ ...locator,
1317
+ limit: limit ?? undefined,
1318
+ textMax: textMax ?? undefined,
1319
+ })
1320
+ : buildFindJs(opts.css, {
1321
+ limit: limit ?? undefined,
1322
+ textMax: textMax ?? undefined,
1323
+ }));
1324
+ if (isFindError(result)) {
1325
+ console.log(JSON.stringify(result, null, 2));
1326
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
1327
+ return;
1328
+ }
1329
+ console.log(JSON.stringify(result, null, 2));
1330
+ }));
1331
+ // ── Get commands (structured data extraction) ──
1332
+ const get = browser.command('get').description('Get page properties');
1333
+ addBrowserTabOption(get.command('title').description('Page title'))
1334
+ .action(browserAction(async (page) => {
1335
+ console.log(await page.evaluate('document.title'));
1336
+ }));
1337
+ addBrowserTabOption(get.command('url').description('Current page URL'))
1338
+ .action(browserAction(async (page) => {
1339
+ console.log(await page.getCurrentUrl?.() ?? await page.evaluate('location.href'));
1340
+ }));
1341
+ // Read commands (`get text/value/attributes`) always emit a JSON envelope:
1342
+ //
1343
+ // { value, matches_n } — success
1344
+ // { error: { code, message, hint, matches_n? } } — structured failure
1345
+ //
1346
+ // `<target>` accepts either a numeric ref (from `browser state`/`browser find`)
1347
+ // or a CSS selector. On multi-match CSS, the first element wins and the real
1348
+ // match count is exposed via `matches_n`; `--nth <n>` picks a specific one.
1349
+ const runGetCommand = async (page, target, opts, evalJs, field) => {
1350
+ const resolvedTarget = await resolveExplicitOrSemanticTarget(page, target, opts, 'read');
1351
+ if (typeof resolvedTarget !== 'string' && 'error' in resolvedTarget) {
1352
+ console.log(JSON.stringify(resolvedTarget, null, 2));
1353
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1354
+ return;
1355
+ }
1356
+ const targetRef = typeof resolvedTarget === 'string' ? resolvedTarget : resolvedTarget.target;
1357
+ const totalMatches = typeof resolvedTarget === 'string' ? undefined : resolvedTarget.total_matches;
1358
+ const nth = parseNthFlag(opts.nth);
1359
+ if (nth && typeof nth === 'object' && 'error' in nth) {
1360
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: nth.error } }, null, 2));
1361
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1362
+ return;
1363
+ }
1364
+ const { matches_n, match_level } = await resolveRef(page, targetRef, {
1365
+ firstOnMulti: nth === null,
1366
+ ...(typeof nth === 'number' ? { nth } : {}),
1367
+ });
1368
+ const raw = await page.evaluate(evalJs);
1369
+ let value;
1370
+ if (field === 'attributes') {
1371
+ // getAttributesResolvedJs stringifies the attribute record — parse it back so
1372
+ // the JSON envelope contains a real object rather than a nested JSON string.
1373
+ try {
1374
+ value = raw == null ? {} : JSON.parse(String(raw));
1375
+ }
1376
+ catch {
1377
+ value = raw;
1378
+ }
1379
+ }
1380
+ else {
1381
+ value = raw ?? null;
1382
+ }
1383
+ console.log(JSON.stringify({
1384
+ value,
1385
+ matches_n,
1386
+ match_level,
1387
+ ...(totalMatches && totalMatches > 1 ? { total_matches: totalMatches } : {}),
1388
+ }, null, 2));
1389
+ };
1390
+ addBrowserTabOption(addSemanticLocatorOptions(get.command('text'))
1391
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1392
+ .option('--nth <n>', 'Pick the nth match (0-based) when <target> is a multi-match CSS selector')
1393
+ .description('Element text content — JSON envelope {value, matches_n}'))
1394
+ .action(browserAction(async (page, target, opts) => runGetCommand(page, target, opts ?? {}, getTextResolvedJs(), 'text')));
1395
+ addBrowserTabOption(addSemanticLocatorOptions(get.command('value'))
1396
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1397
+ .option('--nth <n>', 'Pick the nth match (0-based) when <target> is a multi-match CSS selector')
1398
+ .description('Input/textarea value — JSON envelope {value, matches_n}'))
1399
+ .action(browserAction(async (page, target, opts) => runGetCommand(page, target, opts ?? {}, getValueResolvedJs(), 'value')));
1400
+ addBrowserTabOption(get.command('html')
1401
+ .option('--selector <css>', 'CSS selector scope (first match)')
1402
+ .option('--as <format>', 'Output format: "html" (default) or "json" for structured tree', 'html')
1403
+ .option('--max <n>', 'Max characters of raw HTML to return (0 = unlimited)', '0')
1404
+ .option('--depth <n>', '(--as json) Max tree depth below root (0 = root only, 0 disables = unlimited via empty)', '')
1405
+ .option('--children-max <n>', '(--as json) Max element children kept per node (empty = unlimited)', '')
1406
+ .option('--text-max <n>', '(--as json) Max chars of direct text kept per node (empty = unlimited)', '')
1407
+ .description('Page HTML (or scoped); use --as json for a {tag, attrs, text, children} tree'))
1408
+ .action(browserAction(async (page, opts) => {
1409
+ const format = String(opts.as || 'html').toLowerCase();
1410
+ if (format !== 'html' && format !== 'json') {
1411
+ console.log(JSON.stringify({ error: { code: 'invalid_format', message: `--as must be "html" or "json", got "${opts.as}"` } }, null, 2));
1412
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1413
+ return;
1414
+ }
1415
+ // `--max` is validated up-front (before touching the page) so a bad value
1416
+ // gets the same structured error regardless of selector/format path.
1417
+ const rawMax = String(opts.max ?? '0');
1418
+ if (!/^\d+$/.test(rawMax)) {
1419
+ console.log(JSON.stringify({ error: { code: 'invalid_max', message: `--max must be a non-negative integer, got "${opts.max}"` } }, null, 2));
1420
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1421
+ return;
1422
+ }
1423
+ const max = Number.parseInt(rawMax, 10);
1424
+ if (format === 'json') {
1425
+ const parseBudget = (flag, value) => {
1426
+ const raw = value === undefined || value === null ? '' : String(value);
1427
+ if (raw === '')
1428
+ return null;
1429
+ if (!/^\d+$/.test(raw))
1430
+ return { error: `${flag} must be a non-negative integer, got "${raw}"` };
1431
+ return Number.parseInt(raw, 10);
1432
+ };
1433
+ const depth = parseBudget('--depth', opts.depth);
1434
+ const childrenMax = parseBudget('--children-max', opts.childrenMax);
1435
+ const textMax = parseBudget('--text-max', opts.textMax);
1436
+ for (const budget of [depth, childrenMax, textMax]) {
1437
+ if (budget && typeof budget === 'object' && 'error' in budget) {
1438
+ console.log(JSON.stringify({ error: { code: 'invalid_budget', message: budget.error } }, null, 2));
1439
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1440
+ return;
1441
+ }
1442
+ }
1443
+ const js = buildHtmlTreeJs({
1444
+ selector: opts.selector ?? null,
1445
+ depth: depth,
1446
+ childrenMax: childrenMax,
1447
+ textMax: textMax,
1448
+ });
1449
+ const result = await page.evaluate(js);
1450
+ if (result && typeof result === 'object' && 'invalidSelector' in result && result.invalidSelector) {
1451
+ console.log(JSON.stringify({
1452
+ error: { code: 'invalid_selector', message: `Selector "${opts.selector}" is not a valid CSS selector: ${result.reason}` },
1453
+ }, null, 2));
1454
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1455
+ return;
1456
+ }
1457
+ const ok = result;
1458
+ if (!ok || ok.matched === 0) {
1459
+ console.log(JSON.stringify({
1460
+ error: {
1461
+ code: 'selector_not_found',
1462
+ message: opts.selector
1463
+ ? `Selector "${opts.selector}" matched 0 elements.`
1464
+ : 'Page has no documentElement.',
1465
+ },
1466
+ }, null, 2));
1467
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1468
+ return;
1469
+ }
1470
+ console.log(JSON.stringify(ok, null, 2));
1471
+ return;
1472
+ }
1473
+ // Raw HTML path — unbounded by default; --max optionally caps with a visible marker.
1474
+ // Selector lookup is wrapped in try/catch inside page context so an invalid
1475
+ // selector returns a structured signal instead of throwing through page.evaluate.
1476
+ const sel = opts.selector ? JSON.stringify(opts.selector) : 'null';
1477
+ const rawResult = await page.evaluate(`(() => {
1478
+ const s = ${sel};
1479
+ if (s) {
1480
+ try {
1481
+ const el = document.querySelector(s);
1482
+ return { kind: 'ok', html: el ? el.outerHTML : null };
1483
+ } catch (e) {
1484
+ return { kind: 'invalid_selector', reason: (e && e.message) || String(e) };
1485
+ }
1486
+ }
1487
+ return { kind: 'ok', html: document.documentElement ? document.documentElement.outerHTML : null };
1488
+ })()`);
1489
+ if (rawResult.kind === 'invalid_selector') {
1490
+ console.log(JSON.stringify({
1491
+ error: { code: 'invalid_selector', message: `Selector "${opts.selector}" is not a valid CSS selector: ${rawResult.reason}` },
1492
+ }, null, 2));
1493
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1494
+ return;
1495
+ }
1496
+ const html = rawResult.html;
1497
+ if (html === null) {
1498
+ if (opts.selector) {
1499
+ console.log(JSON.stringify({
1500
+ error: { code: 'selector_not_found', message: `Selector "${opts.selector}" matched 0 elements.` },
1501
+ }, null, 2));
1502
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1503
+ return;
1504
+ }
1505
+ console.log('(empty)');
1506
+ return;
1507
+ }
1508
+ if (max > 0 && html.length > max) {
1509
+ console.log(`<!-- bycli: truncated ${max} of ${html.length} chars; re-run without --max (or --max 0) for full -->\n${html.slice(0, max)}`);
1510
+ return;
1511
+ }
1512
+ console.log(html);
1513
+ }));
1514
+ addBrowserTabOption(addSemanticLocatorOptions(get.command('attributes'))
1515
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1516
+ .option('--nth <n>', 'Pick the nth match (0-based) when <target> is a multi-match CSS selector')
1517
+ .description('Element attributes — JSON envelope {value, matches_n}'))
1518
+ .action(browserAction(async (page, target, opts) => runGetCommand(page, target, opts ?? {}, getAttributesResolvedJs(), 'attributes')));
1519
+ // ── Interact ──
1520
+ //
1521
+ // Write commands (`click/type/select`) share the same `<target>` contract
1522
+ // as the read commands but *reject* multi-match CSS as `selector_ambiguous`
1523
+ // unless the caller passes `--nth <n>`. That asymmetry is intentional:
1524
+ // clicking "one of three buttons" at random is almost never what the agent
1525
+ // meant. Every branch emits a JSON envelope on stdout; error envelopes go
1526
+ // through the unified TargetError handler in browserAction.
1527
+ /**
1528
+ * Parse the `--nth` flag and convert it to `ResolveOptions`.
1529
+ * Returns `{ error }` when the flag was malformed (so the command can
1530
+ * print the structured usage error and exit) or `{ opts }` to feed
1531
+ * into resolveRef / page.click / page.typeText.
1532
+ */
1533
+ function nthToResolveOpts(raw) {
1534
+ const parsed = parseNthFlag(raw);
1535
+ if (parsed && typeof parsed === 'object' && 'error' in parsed)
1536
+ return parsed;
1537
+ if (typeof parsed === 'number')
1538
+ return { opts: { nth: parsed } };
1539
+ return { opts: {} };
1540
+ }
1541
+ function resolveUploadFilePaths(rawFiles) {
1542
+ const inputs = Array.isArray(rawFiles) ? rawFiles : [];
1543
+ if (inputs.length === 0) {
1544
+ return {
1545
+ error: {
1546
+ code: 'usage_error',
1547
+ message: 'At least one file path is required.',
1548
+ hint: 'Example: bycli browser upload "input[type=file]" ./receipt.pdf',
1549
+ },
1550
+ };
1551
+ }
1552
+ const files = [];
1553
+ for (const input of inputs) {
1554
+ const raw = String(input);
1555
+ const expanded = raw === '~' || raw.startsWith(`~${path.sep}`)
1556
+ ? path.join(os.homedir(), raw.slice(2))
1557
+ : raw;
1558
+ const resolved = path.resolve(expanded);
1559
+ if (!fs.existsSync(resolved)) {
1560
+ return { error: { code: 'file_not_found', message: `File not found: ${resolved}` } };
1561
+ }
1562
+ const stat = fs.statSync(resolved);
1563
+ if (!stat.isFile()) {
1564
+ return { error: { code: 'not_a_file', message: `Not a regular file: ${resolved}` } };
1565
+ }
1566
+ files.push(resolved);
1567
+ }
1568
+ return { files };
1569
+ }
1570
+ function parseResolveFlag(raw, flag) {
1571
+ const parsed = parseNthFlag(raw);
1572
+ if (parsed && typeof parsed === 'object' && 'error' in parsed) {
1573
+ return { error: parsed.error.replace('--nth', flag) };
1574
+ }
1575
+ if (typeof parsed === 'number')
1576
+ return { opts: { nth: parsed } };
1577
+ return { opts: {} };
1578
+ }
1579
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('click'))
1580
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1581
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1582
+ .description('Click element — JSON envelope {clicked, target, matches_n}'))
1583
+ .action(browserAction(async (page, target, opts) => {
1584
+ const resolvedTarget = await resolveExplicitOrSemanticTarget(page, target, opts ?? {}, 'write');
1585
+ if (typeof resolvedTarget !== 'string') {
1586
+ console.log(JSON.stringify(resolvedTarget, null, 2));
1587
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1588
+ return;
1589
+ }
1590
+ const parsed = nthToResolveOpts(opts?.nth);
1591
+ if ('error' in parsed) {
1592
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1593
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1594
+ return;
1595
+ }
1596
+ const { matches_n, match_level } = await page.click(resolvedTarget, parsed.opts);
1597
+ console.log(JSON.stringify({ clicked: true, target: resolvedTarget, matches_n, match_level }, null, 2));
1598
+ }));
1599
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('type'))
1600
+ .argument('[targetOrText]', 'Numeric ref/CSS target, or text when using --role/--name/etc.')
1601
+ .argument('[text]', 'Text to type')
1602
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1603
+ .description('Click element, then type text — JSON envelope {typed, text, target, matches_n, autocomplete}'))
1604
+ .action(browserAction(async (page, targetOrText, text, opts) => {
1605
+ const resolved = await resolveWriteTargetAndValueOrPrint(page, targetOrText, text, opts ?? {}, 'text');
1606
+ if (!resolved)
1607
+ return;
1608
+ const parsed = nthToResolveOpts(opts?.nth);
1609
+ if ('error' in parsed) {
1610
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1611
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1612
+ return;
1613
+ }
1614
+ // Click first (focuses the field), wait briefly, then type.
1615
+ await page.click(resolved.target, parsed.opts);
1616
+ await page.wait(0.3);
1617
+ const { matches_n, match_level } = await page.typeText(resolved.target, resolved.value, parsed.opts);
1618
+ // __resolved is already set by the resolver call inside page.typeText
1619
+ const isAutocomplete = await page.evaluate(isAutocompleteResolvedJs());
1620
+ if (isAutocomplete)
1621
+ await page.wait(0.4);
1622
+ console.log(JSON.stringify({
1623
+ typed: true,
1624
+ text: resolved.value,
1625
+ target: resolved.target,
1626
+ matches_n,
1627
+ match_level,
1628
+ autocomplete: !!isAutocomplete,
1629
+ }, null, 2));
1630
+ }));
1631
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('hover'))
1632
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1633
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1634
+ .description('Move the mouse over an element — JSON envelope {hovered, target, matches_n}'))
1635
+ .action(browserAction(async (page, target, opts) => {
1636
+ if (typeof page.hover !== 'function')
1637
+ throw new Error('browser hover is not supported by this browser backend');
1638
+ const resolvedTarget = await resolveWriteTargetOrPrint(page, target, opts ?? {});
1639
+ if (!resolvedTarget)
1640
+ return;
1641
+ const parsed = nthToResolveOpts(opts?.nth);
1642
+ if ('error' in parsed) {
1643
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1644
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1645
+ return;
1646
+ }
1647
+ const { matches_n, match_level } = await page.hover(resolvedTarget, parsed.opts);
1648
+ console.log(JSON.stringify({ hovered: true, target: resolvedTarget, matches_n, match_level }, null, 2));
1649
+ }));
1650
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('focus'))
1651
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1652
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1653
+ .description('Focus an element — JSON envelope {focused, target, matches_n}'))
1654
+ .action(browserAction(async (page, target, opts) => {
1655
+ if (typeof page.focus !== 'function')
1656
+ throw new Error('browser focus is not supported by this browser backend');
1657
+ const resolvedTarget = await resolveWriteTargetOrPrint(page, target, opts ?? {});
1658
+ if (!resolvedTarget)
1659
+ return;
1660
+ const parsed = nthToResolveOpts(opts?.nth);
1661
+ if ('error' in parsed) {
1662
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1663
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1664
+ return;
1665
+ }
1666
+ const { focused, matches_n, match_level } = await page.focus(resolvedTarget, parsed.opts);
1667
+ console.log(JSON.stringify({ focused, target: resolvedTarget, matches_n, match_level }, null, 2));
1668
+ }));
1669
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('dblclick'))
1670
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1671
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1672
+ .description('Double-click element — JSON envelope {dblclicked, target, matches_n}'))
1673
+ .action(browserAction(async (page, target, opts) => {
1674
+ if (typeof page.dblClick !== 'function')
1675
+ throw new Error('browser dblclick is not supported by this browser backend');
1676
+ const resolvedTarget = await resolveWriteTargetOrPrint(page, target, opts ?? {});
1677
+ if (!resolvedTarget)
1678
+ return;
1679
+ const parsed = nthToResolveOpts(opts?.nth);
1680
+ if ('error' in parsed) {
1681
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1682
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1683
+ return;
1684
+ }
1685
+ const { matches_n, match_level } = await page.dblClick(resolvedTarget, parsed.opts);
1686
+ console.log(JSON.stringify({ dblclicked: true, target: resolvedTarget, matches_n, match_level }, null, 2));
1687
+ }));
1688
+ const runCheckCommand = async (page, target, opts, checked) => {
1689
+ if (typeof page.setChecked !== 'function')
1690
+ throw new Error(`browser ${checked ? 'check' : 'uncheck'} is not supported by this browser backend`);
1691
+ const resolvedTarget = await resolveWriteTargetOrPrint(page, target, opts);
1692
+ if (!resolvedTarget)
1693
+ return;
1694
+ const parsed = nthToResolveOpts(opts?.nth);
1695
+ if ('error' in parsed) {
1696
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1697
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1698
+ return;
1699
+ }
1700
+ const result = await page.setChecked(resolvedTarget, checked, parsed.opts);
1701
+ console.log(JSON.stringify({
1702
+ checked: result.checked,
1703
+ changed: result.changed,
1704
+ target: resolvedTarget,
1705
+ matches_n: result.matches_n,
1706
+ match_level: result.match_level,
1707
+ ...(result.kind ? { kind: result.kind } : {}),
1708
+ }, null, 2));
1709
+ };
1710
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('check'))
1711
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1712
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1713
+ .description('Ensure a checkbox/radio/aria-checked control is checked — JSON envelope {checked, changed, target, matches_n}'))
1714
+ .action(browserAction(async (page, target, opts) => {
1715
+ await runCheckCommand(page, target, opts ?? {}, true);
1716
+ }));
1717
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('uncheck'))
1718
+ .argument('[target]', 'Numeric ref (from browser state / find), CSS selector, or omit when using --role/--name/etc.')
1719
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1720
+ .description('Ensure a checkbox/aria-checked control is unchecked — JSON envelope {checked, changed, target, matches_n}'))
1721
+ .action(browserAction(async (page, target, opts) => {
1722
+ await runCheckCommand(page, target, opts ?? {}, false);
1723
+ }));
1724
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('upload'))
1725
+ .argument('[targetOrFile]', 'Numeric ref/CSS target, or first file when using --role/--name/etc.')
1726
+ .argument('[files...]', 'Local file path(s) to attach')
1727
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1728
+ .description('Attach local files to a file input — JSON envelope {uploaded, files, file_names, target, matches_n}'))
1729
+ .action(browserAction(async (page, targetOrFile, files, opts) => {
1730
+ if (typeof page.uploadFiles !== 'function')
1731
+ throw new Error('browser upload is not supported by this browser backend');
1732
+ const hasSemantic = !!semanticLocatorFromOptions(opts ?? {});
1733
+ const target = hasSemantic ? undefined : targetOrFile;
1734
+ const resolvedTarget = await resolveWriteTargetOrPrint(page, target, opts ?? {});
1735
+ if (!resolvedTarget)
1736
+ return;
1737
+ const parsed = nthToResolveOpts(opts?.nth);
1738
+ if ('error' in parsed) {
1739
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1740
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1741
+ return;
1742
+ }
1743
+ const rawFiles = hasSemantic
1744
+ ? [targetOrFile, ...(Array.isArray(files) ? files : [])].filter((value) => value !== undefined)
1745
+ : files;
1746
+ const resolvedFiles = resolveUploadFilePaths(rawFiles);
1747
+ if ('error' in resolvedFiles) {
1748
+ console.log(JSON.stringify({ error: resolvedFiles.error }, null, 2));
1749
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1750
+ return;
1751
+ }
1752
+ const result = await page.uploadFiles(resolvedTarget, resolvedFiles.files, parsed.opts);
1753
+ console.log(JSON.stringify(result, null, 2));
1754
+ }));
1755
+ addBrowserTabOption(addPrefixedSemanticLocatorOptions(addPrefixedSemanticLocatorOptions(browser.command('drag'), 'from'), 'to')
1756
+ .argument('[source]', 'Numeric ref/CSS selector to drag from, or omit with --from-role/--from-name/etc.')
1757
+ .argument('[target]', 'Numeric ref/CSS selector to drop onto, or omit with --to-role/--to-name/etc.')
1758
+ .option('--from-nth <n>', 'When <source> is a multi-match CSS selector, pick the nth match (0-based)')
1759
+ .option('--to-nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1760
+ .description('Drag one element to another — JSON envelope {dragged, source, target, source_matches_n, target_matches_n}'))
1761
+ .action(browserAction(async (page, source, target, opts) => {
1762
+ if (typeof page.drag !== 'function')
1763
+ throw new Error('browser drag is not supported by this browser backend');
1764
+ const resolvedSource = await resolvePrefixedWriteTargetOrPrint(page, source, opts ?? {}, 'from', 'source');
1765
+ if (!resolvedSource)
1766
+ return;
1767
+ const resolvedTarget = await resolvePrefixedWriteTargetOrPrint(page, target, opts ?? {}, 'to', 'target');
1768
+ if (!resolvedTarget)
1769
+ return;
1770
+ const from = parseResolveFlag(opts?.fromNth, '--from-nth');
1771
+ if ('error' in from) {
1772
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: from.error } }, null, 2));
1773
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1774
+ return;
1775
+ }
1776
+ const to = parseResolveFlag(opts?.toNth, '--to-nth');
1777
+ if ('error' in to) {
1778
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: to.error } }, null, 2));
1779
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1780
+ return;
1781
+ }
1782
+ const result = await page.drag(resolvedSource, resolvedTarget, { from: from.opts, to: to.opts });
1783
+ console.log(JSON.stringify(result, null, 2));
1784
+ }));
1785
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('fill'))
1786
+ .argument('[targetOrText]', 'Numeric ref/CSS target, or text when using --role/--name/etc.')
1787
+ .argument('[text]', 'Text to set exactly')
1788
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1789
+ .description('Set input/textarea/contenteditable text exactly and verify the value — JSON envelope {filled, verified, text, actual}'))
1790
+ .action(browserAction(async (page, targetOrText, text, opts) => {
1791
+ const resolved = await resolveWriteTargetAndValueOrPrint(page, targetOrText, text, opts ?? {}, 'text');
1792
+ if (!resolved)
1793
+ return;
1794
+ const parsed = nthToResolveOpts(opts?.nth);
1795
+ if ('error' in parsed) {
1796
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1797
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1798
+ return;
1799
+ }
1800
+ const result = await page.fillText(resolved.target, resolved.value, parsed.opts);
1801
+ if (!result.verified)
1802
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
1803
+ console.log(JSON.stringify({
1804
+ filled: result.filled,
1805
+ verified: result.verified,
1806
+ target: resolved.target,
1807
+ text: resolved.value,
1808
+ actual: result.actual,
1809
+ length: result.length,
1810
+ matches_n: result.matches_n,
1811
+ match_level: result.match_level,
1812
+ ...(result.mode ? { mode: result.mode } : {}),
1813
+ }, null, 2));
1814
+ }));
1815
+ addBrowserTabOption(addSemanticLocatorOptions(browser.command('select'))
1816
+ .argument('[targetOrOption]', 'Numeric ref/CSS target, or option text when using --role/--name/etc.')
1817
+ .argument('[option]', 'Option text (or value) to select')
1818
+ .option('--nth <n>', 'When <target> is a multi-match CSS selector, pick the nth match (0-based)')
1819
+ .description('Select dropdown option — JSON envelope {selected, target, matches_n}'))
1820
+ .action(browserAction(async (page, targetOrOption, option, opts) => {
1821
+ const resolved = await resolveWriteTargetAndValueOrPrint(page, targetOrOption, option, opts ?? {}, 'option');
1822
+ if (!resolved)
1823
+ return;
1824
+ const parsed = nthToResolveOpts(opts?.nth);
1825
+ if ('error' in parsed) {
1826
+ console.log(JSON.stringify({ error: { code: 'usage_error', message: parsed.error } }, null, 2));
1827
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1828
+ return;
1829
+ }
1830
+ const { matches_n, match_level } = await resolveRef(page, resolved.target, parsed.opts);
1831
+ const result = await page.evaluate(selectResolvedJs(resolved.value));
1832
+ if (result?.error) {
1833
+ // The select-specific "Not a <select>" / "Option not found" errors
1834
+ // are domain-level failures — emit a structured envelope so agents
1835
+ // can branch on code rather than scrape a log line.
1836
+ console.log(JSON.stringify({
1837
+ error: {
1838
+ code: result.error === 'Not a <select>' ? 'not_a_select' : 'option_not_found',
1839
+ message: result.error,
1840
+ ...(result.available && { available: result.available }),
1841
+ matches_n,
1842
+ },
1843
+ }, null, 2));
1844
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
1845
+ return;
1846
+ }
1847
+ console.log(JSON.stringify({
1848
+ selected: result?.selected ?? resolved.value,
1849
+ target: resolved.target,
1850
+ matches_n,
1851
+ match_level,
1852
+ }, null, 2));
1853
+ }));
1854
+ addBrowserTabOption(browser.command('keys').argument('<key>', 'Key to press (Enter, Escape, Tab, Control+a)'))
1855
+ .description('Press keyboard key')
1856
+ .action(browserAction(async (page, key) => {
1857
+ await page.pressKey(key);
1858
+ console.log(`Pressed: ${key}`);
1859
+ }));
1860
+ const browserDialog = browser
1861
+ .command('dialog')
1862
+ .description('Handle a blocking JavaScript alert/confirm/prompt dialog');
1863
+ addBrowserTabOption(browserDialog.command('accept')
1864
+ .option('--text <text>', 'Prompt text to submit for prompt() dialogs')
1865
+ .description('Accept the currently open JavaScript dialog'))
1866
+ .action(browserAction(async (page, opts) => {
1867
+ if (!page.handleJavaScriptDialog) {
1868
+ throw new Error('This browser session does not support JavaScript dialog handling');
1869
+ }
1870
+ try {
1871
+ await page.handleJavaScriptDialog(true, opts?.text);
1872
+ }
1873
+ catch (err) {
1874
+ const message = getErrorMessage(err);
1875
+ if (message.toLowerCase().includes('no dialog')) {
1876
+ console.log(JSON.stringify({
1877
+ error: {
1878
+ code: 'no_javascript_dialog',
1879
+ message: 'No JavaScript dialog is currently open.',
1880
+ },
1881
+ }, null, 2));
1882
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1883
+ return;
1884
+ }
1885
+ throw err;
1886
+ }
1887
+ console.log(JSON.stringify({ handled: true, action: 'accept', ...(opts?.text !== undefined && { text: opts.text }) }, null, 2));
1888
+ }));
1889
+ addBrowserTabOption(browserDialog.command('dismiss')
1890
+ .description('Dismiss the currently open JavaScript dialog'))
1891
+ .action(browserAction(async (page) => {
1892
+ if (!page.handleJavaScriptDialog) {
1893
+ throw new Error('This browser session does not support JavaScript dialog handling');
1894
+ }
1895
+ try {
1896
+ await page.handleJavaScriptDialog(false);
1897
+ }
1898
+ catch (err) {
1899
+ const message = getErrorMessage(err);
1900
+ if (message.toLowerCase().includes('no dialog')) {
1901
+ console.log(JSON.stringify({
1902
+ error: {
1903
+ code: 'no_javascript_dialog',
1904
+ message: 'No JavaScript dialog is currently open.',
1905
+ },
1906
+ }, null, 2));
1907
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1908
+ return;
1909
+ }
1910
+ throw err;
1911
+ }
1912
+ console.log(JSON.stringify({ handled: true, action: 'dismiss' }, null, 2));
1913
+ }));
1914
+ // ── Wait commands ──
1915
+ addBrowserTabOption(browser.command('wait'))
1916
+ .argument('<type>', 'selector, text, time, xhr, or download')
1917
+ .argument('[value]', 'CSS selector, text string, seconds, XHR URL regex, or download filename/URL pattern')
1918
+ .option('--timeout <ms>', 'Timeout in milliseconds', '10000')
1919
+ .description('Wait for selector, text, time, matching XHR, or browser download (e.g. wait selector ".loaded", wait text "Success", wait time 3, wait xhr "/api/search", wait download receipt.pdf)')
1920
+ .action(browserAction(async (page, type, value, opts) => {
1921
+ const timeout = parseInt(opts.timeout, 10);
1922
+ if (type === 'time') {
1923
+ const seconds = parseFloat(value ?? '2');
1924
+ await page.wait(seconds);
1925
+ console.log(`Waited ${seconds}s`);
1926
+ }
1927
+ else if (type === 'selector') {
1928
+ if (!value) {
1929
+ console.error('Missing CSS selector');
1930
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1931
+ return;
1932
+ }
1933
+ await page.wait({ selector: value, timeout: timeout / 1000 });
1934
+ console.log(`Element "${value}" appeared`);
1935
+ }
1936
+ else if (type === 'text') {
1937
+ if (!value) {
1938
+ console.error('Missing text');
1939
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1940
+ return;
1941
+ }
1942
+ await page.wait({ text: value, timeout: timeout / 1000 });
1943
+ console.log(`Text "${value}" appeared`);
1944
+ }
1945
+ else if (type === 'xhr') {
1946
+ // Poll the capture ring until an entry matches the URL regex — turns
1947
+ // the common "open page, wait N seconds, hope the data landed" idiom
1948
+ // into a deterministic barrier keyed on the API the agent actually
1949
+ // cares about. Prevents silent "empty DOM" failures on slow SPAs.
1950
+ if (!value) {
1951
+ console.error('Missing XHR URL regex');
1952
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1953
+ return;
1954
+ }
1955
+ let re;
1956
+ try {
1957
+ re = new RegExp(value);
1958
+ }
1959
+ catch (err) {
1960
+ console.error(`Invalid regex "${value}": ${err instanceof Error ? err.message : String(err)}`);
1961
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1962
+ return;
1963
+ }
1964
+ const hasSessionCapture = await page.startNetworkCapture?.() ?? false;
1965
+ if (!hasSessionCapture) {
1966
+ try {
1967
+ await page.evaluate(NETWORK_INTERCEPTOR_JS);
1968
+ }
1969
+ catch { /* non-fatal */ }
1970
+ }
1971
+ await captureNetworkItems(page);
1972
+ const deadline = Date.now() + timeout;
1973
+ const pollMs = 400;
1974
+ let matched = null;
1975
+ while (Date.now() < deadline && !matched) {
1976
+ const items = await captureNetworkItems(page);
1977
+ matched = items.find((e) => re.test(e.url)) ?? null;
1978
+ if (!matched)
1979
+ await new Promise((r) => setTimeout(r, pollMs));
1980
+ }
1981
+ if (!matched) {
1982
+ console.log(JSON.stringify({
1983
+ error: {
1984
+ code: 'xhr_not_seen',
1985
+ message: `No captured XHR matched /${value}/ within ${timeout}ms`,
1986
+ hint: 'Check the pattern against `browser network` output; the endpoint may not have fired yet, or capture is disabled.',
1987
+ },
1988
+ }, null, 2));
1989
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
1990
+ return;
1991
+ }
1992
+ console.log(JSON.stringify({
1993
+ matched: { url: matched.url, status: matched.status, contentType: matched.ct },
1994
+ }, null, 2));
1995
+ }
1996
+ else if (type === 'download') {
1997
+ if (typeof page.waitForDownload !== 'function') {
1998
+ console.log(JSON.stringify({
1999
+ error: {
2000
+ code: 'download_wait_unavailable',
2001
+ message: 'The active browser backend does not support download lifecycle waits.',
2002
+ hint: 'Use the Browser Bridge extension version 1.0.8 or newer, then retry the command.',
2003
+ },
2004
+ }, null, 2));
2005
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2006
+ return;
2007
+ }
2008
+ const result = await page.waitForDownload(String(value ?? ''), timeout);
2009
+ if (!result.downloaded) {
2010
+ const code = result.state === 'interrupted' && result.id !== undefined ? 'download_failed' : 'download_not_seen';
2011
+ console.log(JSON.stringify({
2012
+ error: {
2013
+ code,
2014
+ message: result.error ?? `No download matched "${value ?? '*'}" within ${timeout}ms`,
2015
+ hint: 'Check the pattern against the expected filename or URL; use a longer --timeout if the download starts slowly.',
2016
+ },
2017
+ download: result,
2018
+ }, null, 2));
2019
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2020
+ return;
2021
+ }
2022
+ console.log(JSON.stringify(result, null, 2));
2023
+ }
2024
+ else {
2025
+ console.error(`Unknown wait type "${type}". Use: selector, text, time, xhr, or download`);
2026
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2027
+ }
2028
+ }));
2029
+ // ── Extract ──
2030
+ addBrowserTabOption(browser.command('eval')
2031
+ .argument('<js>', 'JavaScript code')
2032
+ .option('--frame <index>', 'Cross-origin iframe index from "browser frames"')
2033
+ .description('Execute JS in page context, return result'))
2034
+ .action(browserAction(async (page, js, opts) => {
2035
+ let result;
2036
+ if (opts.frame !== undefined) {
2037
+ const frameIndex = Number.parseInt(opts.frame, 10);
2038
+ if (!Number.isInteger(frameIndex) || frameIndex < 0) {
2039
+ console.error(`Invalid frame index "${opts.frame}". Use a 0-based index from "browser frames".`);
2040
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2041
+ return;
2042
+ }
2043
+ if (!page.evaluateInFrame) {
2044
+ throw new Error('This browser session does not support frame-targeted evaluation');
2045
+ }
2046
+ result = await page.evaluateInFrame(js, frameIndex);
2047
+ }
2048
+ else {
2049
+ result = await page.evaluate(js);
2050
+ }
2051
+ if (typeof result === 'string')
2052
+ console.log(result);
2053
+ else
2054
+ console.log(JSON.stringify(result, null, 2));
2055
+ }));
2056
+ // ── Extract (content reading) ──
2057
+ //
2058
+ // `extract` answers the "read this page" question that `get html` / `get text`
2059
+ // can't: denoise → markdown → paragraph-aware chunking. Agents walk long pages
2060
+ // by passing back the `next_start_char` cursor instead of juggling selectors.
2061
+ addBrowserTabOption(browser.command('extract')
2062
+ .option('--selector <css>', 'CSS selector scope; defaults to <main>/<article>/<body>')
2063
+ .option('--chunk-size <chars>', 'Target chunk size in chars', '20000')
2064
+ .option('--start <char>', 'Start offset (use next_start_char from a previous extract)', '0')
2065
+ .description('Extract page content as markdown, paragraph-aware chunks for long pages'))
2066
+ .action(browserAction(async (page, opts) => {
2067
+ const rawChunk = String(opts.chunkSize ?? '20000');
2068
+ if (!/^\d+$/.test(rawChunk) || Number.parseInt(rawChunk, 10) <= 0) {
2069
+ console.log(JSON.stringify({ error: { code: 'invalid_chunk_size', message: `--chunk-size must be a positive integer, got "${opts.chunkSize}"` } }, null, 2));
2070
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2071
+ return;
2072
+ }
2073
+ const rawStart = String(opts.start ?? '0');
2074
+ if (!/^\d+$/.test(rawStart)) {
2075
+ console.log(JSON.stringify({ error: { code: 'invalid_start', message: `--start must be a non-negative integer, got "${opts.start}"` } }, null, 2));
2076
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2077
+ return;
2078
+ }
2079
+ const chunkSize = Number.parseInt(rawChunk, 10);
2080
+ const start = Number.parseInt(rawStart, 10);
2081
+ const selector = typeof opts.selector === 'string' && opts.selector.length > 0 ? opts.selector : null;
2082
+ const js = buildExtractHtmlJs(selector);
2083
+ const res = await page.evaluate(js);
2084
+ if (!res) {
2085
+ console.log(JSON.stringify({ error: { code: 'extract_failed', message: 'Page returned no root element.' } }, null, 2));
2086
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2087
+ return;
2088
+ }
2089
+ if ('invalidSelector' in res) {
2090
+ console.log(JSON.stringify({ error: { code: 'invalid_selector', message: `Selector "${selector}" is not a valid CSS selector: ${res.reason}` } }, null, 2));
2091
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2092
+ return;
2093
+ }
2094
+ if ('notFound' in res) {
2095
+ console.log(JSON.stringify({ error: { code: 'selector_not_found', message: selector ? `Selector "${selector}" matched 0 elements.` : 'Page has no body/main/article element.' } }, null, 2));
2096
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2097
+ return;
2098
+ }
2099
+ const envelope = runExtractFromHtml({
2100
+ html: res.html,
2101
+ url: res.url,
2102
+ title: res.title,
2103
+ selector,
2104
+ start,
2105
+ chunkSize,
2106
+ });
2107
+ console.log(JSON.stringify(envelope, null, 2));
2108
+ }));
2109
+ // ── Network (API discovery) ──
2110
+ //
2111
+ // Default output is JSON (agent-native). Each entry carries a stable `key`
2112
+ // (GraphQL operationName or `METHOD host+pathname`) so agents can fetch
2113
+ // full bodies with `--detail <key>` even after subsequent commands.
2114
+ // Captures are persisted per browser session under ~/.bycli/cache/browser-network/.
2115
+ addBrowserTabOption(browser.command('network'))
2116
+ .option('--detail <key>', 'Emit full body for the entry with this key')
2117
+ .option('--all', 'Include static resources (js/css/images/telemetry)')
2118
+ .option('--raw', 'Emit full bodies for every entry (skip shape preview)')
2119
+ .option('--filter <fields>', 'Comma-separated field names; keep only entries whose body shape has ALL names as path segments')
2120
+ .option('--since <duration>', 'Only include entries from the last duration (for example: 30s, 2m)')
2121
+ .option('--until <duration>', 'Only include entries older than the duration from now')
2122
+ .option('--follow', 'Continuously print new matching entries as JSON lines', false)
2123
+ .option('--failed', 'Only include failed HTTP requests (status 0 or >= 400)', false)
2124
+ .option('--max-body <chars>', 'With --detail: cap the emitted body at N chars (0 = unlimited, default)', '0')
2125
+ .option('--ttl <ms>', 'Cache TTL in ms for --detail lookups', String(DEFAULT_TTL_MS))
2126
+ .description('Capture network requests as shape previews; retrieve full bodies by key')
2127
+ .action(browserAction(async (page, opts) => {
2128
+ const ttlMs = parsePositiveIntOption(opts.ttl, 'ttl', DEFAULT_TTL_MS);
2129
+ const session = getPageSession(page);
2130
+ const hasDetail = typeof opts.detail === 'string' && opts.detail.length > 0;
2131
+ const hasFilter = typeof opts.filter === 'string';
2132
+ const sinceMs = parseDurationMs(opts.since, 'since');
2133
+ const untilMs = parseDurationMs(opts.until, 'until');
2134
+ if (sinceMs && typeof sinceMs === 'object') {
2135
+ emitNetworkError('invalid_since', sinceMs.error);
2136
+ return;
2137
+ }
2138
+ if (untilMs && typeof untilMs === 'object') {
2139
+ emitNetworkError('invalid_until', untilMs.error);
2140
+ return;
2141
+ }
2142
+ // --detail and --filter do different things (one request by key vs. narrow
2143
+ // the list by shape), don't compose, and combining them has no sensible
2144
+ // semantic. Reject up front with a structured error instead of silently
2145
+ // dropping one.
2146
+ if (hasDetail && hasFilter) {
2147
+ emitNetworkError('invalid_args', '--filter and --detail cannot be used together (one narrows a list, the other fetches a specific entry).');
2148
+ return;
2149
+ }
2150
+ let filterFields = null;
2151
+ if (hasFilter) {
2152
+ const parsed = parseFilter(opts.filter);
2153
+ if ('reason' in parsed) {
2154
+ emitNetworkError('invalid_filter', parsed.reason);
2155
+ return;
2156
+ }
2157
+ filterFields = parsed.fields;
2158
+ }
2159
+ if (hasDetail && opts.follow) {
2160
+ emitNetworkError('invalid_args', '--follow cannot be used with --detail.');
2161
+ return;
2162
+ }
2163
+ // --detail short-circuits: read from cache only, no live capture needed.
2164
+ if (hasDetail) {
2165
+ const res = loadNetworkCache(session, { ttlMs });
2166
+ if (res.status === 'missing') {
2167
+ emitNetworkError('cache_missing', `No cached capture. Run "browser network" first (in session "${session}").`);
2168
+ return;
2169
+ }
2170
+ if (res.status === 'expired') {
2171
+ emitNetworkError('cache_expired', `Cache is stale (age ${res.ageMs}ms > ttl ${ttlMs}ms). Re-run "browser network" to refresh.`);
2172
+ return;
2173
+ }
2174
+ if (res.status === 'corrupt' || !res.file) {
2175
+ emitNetworkError('cache_corrupt', 'Cache file is malformed; re-run "browser network" to regenerate.');
2176
+ return;
2177
+ }
2178
+ const entry = findEntry(res.file, opts.detail);
2179
+ if (!entry) {
2180
+ emitNetworkError('key_not_found', `Key "${opts.detail}" not in cache.`, {
2181
+ available_keys: res.file.entries.map((e) => e.key),
2182
+ });
2183
+ return;
2184
+ }
2185
+ const rawMaxBody = String(opts.maxBody ?? '0');
2186
+ if (!/^\d+$/.test(rawMaxBody)) {
2187
+ emitNetworkError('invalid_max_body', `--max-body must be a non-negative integer, got "${opts.maxBody}"`);
2188
+ return;
2189
+ }
2190
+ const maxBody = Number.parseInt(rawMaxBody, 10);
2191
+ // Body shape/source:
2192
+ // - If capture already truncated it (entry.body_truncated), the body is a string.
2193
+ // - If the adapter stored a JSON value, it parsed cleanly at capture time; leave it.
2194
+ // - --max-body applies a transport-level cap when the caller wants to keep output small.
2195
+ let outputBody = entry.body;
2196
+ let transportTruncated = false;
2197
+ if (maxBody > 0 && typeof entry.body === 'string' && entry.body.length > maxBody) {
2198
+ outputBody = entry.body.slice(0, maxBody);
2199
+ transportTruncated = true;
2200
+ }
2201
+ const captureTruncated = entry.body_truncated === true;
2202
+ const detailEnvelope = {
2203
+ key: entry.key,
2204
+ url: entry.url,
2205
+ method: entry.method,
2206
+ status: entry.status,
2207
+ ct: entry.ct,
2208
+ size: entry.size,
2209
+ ...(typeof entry.timestamp === 'number' ? { timestamp: toIsoTimestamp(entry.timestamp) } : {}),
2210
+ shape: inferShape(entry.body),
2211
+ body: outputBody,
2212
+ };
2213
+ if (captureTruncated || transportTruncated) {
2214
+ detailEnvelope.body_truncated = true;
2215
+ detailEnvelope.body_full_size = entry.body_full_size ?? entry.size;
2216
+ detailEnvelope.body_truncation_reason = captureTruncated
2217
+ ? 'capture-limit'
2218
+ : 'max-body';
2219
+ }
2220
+ console.log(JSON.stringify(detailEnvelope, null, 2));
2221
+ return;
2222
+ }
2223
+ if (opts.follow) {
2224
+ if (!await page.startNetworkCapture?.()) {
2225
+ try {
2226
+ await page.evaluate(NETWORK_INTERCEPTOR_JS);
2227
+ }
2228
+ catch { /* non-fatal */ }
2229
+ }
2230
+ while (true) {
2231
+ const rawItems = await captureNetworkItems(page).catch((err) => {
2232
+ emitNetworkError('capture_failed', `Could not read network capture: ${err.message}`);
2233
+ return [];
2234
+ });
2235
+ let items = opts.all ? rawItems : filterNetworkItems(rawItems);
2236
+ items = filterByTimeWindow(items, { sinceMs, untilMs });
2237
+ if (opts.failed)
2238
+ items = items.filter((item) => item.status === 0 || item.status >= 400);
2239
+ const keyed = assignKeys(items);
2240
+ for (const item of keyed) {
2241
+ console.log(JSON.stringify({
2242
+ key: item.key,
2243
+ timestamp: toIsoTimestamp(item.timestamp),
2244
+ method: item.method,
2245
+ status: item.status,
2246
+ url: item.url,
2247
+ ct: item.ct,
2248
+ size: item.size,
2249
+ ...(item.bodyTruncated ? { body_truncated: true } : {}),
2250
+ }));
2251
+ }
2252
+ await new Promise((resolve) => setTimeout(resolve, FOLLOW_POLL_MS));
2253
+ }
2254
+ }
2255
+ // Fresh capture path.
2256
+ let rawItems;
2257
+ try {
2258
+ rawItems = await captureNetworkItems(page);
2259
+ }
2260
+ catch (err) {
2261
+ emitNetworkError('capture_failed', `Could not read network capture: ${err.message}`);
2262
+ return;
2263
+ }
2264
+ let items = opts.all ? rawItems : filterNetworkItems(rawItems);
2265
+ items = filterByTimeWindow(items, { sinceMs, untilMs });
2266
+ if (opts.failed)
2267
+ items = items.filter((item) => item.status === 0 || item.status >= 400);
2268
+ const filteredOut = rawItems.length - items.length;
2269
+ const keyed = assignKeys(items);
2270
+ const cacheEntries = keyed.map((it) => ({
2271
+ key: it.key,
2272
+ url: it.url,
2273
+ method: it.method,
2274
+ status: it.status,
2275
+ size: it.size,
2276
+ ct: it.ct,
2277
+ body: it.body,
2278
+ ...(typeof it.timestamp === 'number' ? { timestamp: it.timestamp } : {}),
2279
+ ...(it.bodyTruncated ? { body_truncated: true } : {}),
2280
+ ...(it.bodyTruncated && typeof it.bodyFullSize === 'number'
2281
+ ? { body_full_size: it.bodyFullSize }
2282
+ : {}),
2283
+ }));
2284
+ // Soft failure: the caller already has the data, so surface a warning
2285
+ // via the output envelope rather than erroring out the whole command.
2286
+ let cacheWarning = null;
2287
+ try {
2288
+ saveNetworkCache(session, cacheEntries);
2289
+ }
2290
+ catch (err) {
2291
+ cacheWarning = `Could not persist capture cache: ${err.message}. --detail lookups may miss this capture.`;
2292
+ }
2293
+ // Pair each cache entry with its shape up front so --filter can read
2294
+ // segments without recomputing, and the --raw view can keep the full
2295
+ // body. Cache persistence above stored the unfiltered set on purpose:
2296
+ // later `--detail <key>` lookups must still see requests that the
2297
+ // current --filter narrowed out.
2298
+ const shaped = cacheEntries.map((e) => ({ entry: e, shape: inferShape(e.body) }));
2299
+ const visible = filterFields
2300
+ ? shaped.filter((s) => shapeMatchesFilter(s.shape, filterFields))
2301
+ : shaped;
2302
+ const filterDropped = filterFields ? shaped.length - visible.length : 0;
2303
+ const envelope = {
2304
+ session,
2305
+ captured_at: new Date().toISOString(),
2306
+ count: visible.length,
2307
+ filtered_out: filteredOut,
2308
+ };
2309
+ if (filterFields) {
2310
+ envelope.filter = filterFields;
2311
+ envelope.filter_dropped = filterDropped;
2312
+ }
2313
+ if (cacheWarning)
2314
+ envelope.cache_warning = cacheWarning;
2315
+ const truncatedCount = visible.filter((s) => s.entry.body_truncated).length;
2316
+ if (truncatedCount > 0) {
2317
+ envelope.body_truncated_count = truncatedCount;
2318
+ envelope.body_truncated_hint = 'Some bodies exceeded the capture limit; their `shape` reflects only the captured prefix.';
2319
+ }
2320
+ if (opts.raw) {
2321
+ envelope.entries = visible.map((s) => ({
2322
+ ...s.entry,
2323
+ ...(typeof s.entry.timestamp === 'number' ? { timestamp: toIsoTimestamp(s.entry.timestamp) } : {}),
2324
+ }));
2325
+ }
2326
+ else {
2327
+ envelope.entries = visible.map((s) => ({
2328
+ key: s.entry.key,
2329
+ method: s.entry.method,
2330
+ ...(typeof s.entry.timestamp === 'number' ? { timestamp: toIsoTimestamp(s.entry.timestamp) } : {}),
2331
+ status: s.entry.status,
2332
+ url: s.entry.url,
2333
+ ct: s.entry.ct,
2334
+ size: s.entry.size,
2335
+ shape: s.shape,
2336
+ ...(s.entry.body_truncated ? { body_truncated: true } : {}),
2337
+ }));
2338
+ envelope.detail_hint = 'Run "browser network --detail <key>" for full body.';
2339
+ }
2340
+ console.log(JSON.stringify(envelope, null, 2));
2341
+ }));
2342
+ // ── Init (adapter scaffolding) ──
2343
+ browser.command('init')
2344
+ .argument('<name>', 'Adapter name in site/command format (e.g. hn/top)')
2345
+ .description('Generate adapter scaffold in ~/.bycli/clis/')
2346
+ .action(async (name) => {
2347
+ try {
2348
+ const parts = name.split('/');
2349
+ if (parts.length !== 2 || !parts[0] || !parts[1]) {
2350
+ console.error('Name must be site/command format (e.g. hn/top)');
2351
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2352
+ return;
2353
+ }
2354
+ const [site, command] = parts;
2355
+ if (!/^[a-zA-Z0-9_-]+$/.test(site) || !/^[a-zA-Z0-9_-]+$/.test(command)) {
2356
+ console.error('Name parts must be alphanumeric/dash/underscore only');
2357
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2358
+ return;
2359
+ }
2360
+ const os = await import('node:os');
2361
+ const fs = await import('node:fs');
2362
+ const path = await import('node:path');
2363
+ const dir = path.join(os.homedir(), '.bycli', 'clis', site);
2364
+ const filePath = path.join(dir, `${command}.js`);
2365
+ if (fs.existsSync(filePath)) {
2366
+ console.log(`Adapter already exists: ${filePath}`);
2367
+ return;
2368
+ }
2369
+ let domain = site;
2370
+ const template = `import { cli, Strategy } from '@sovovs/bycli/registry';
2371
+
2372
+ cli({
2373
+ site: '${site}',
2374
+ name: '${command}',
2375
+ description: '', // TODO: describe what this command does
2376
+ access: 'read', // TODO: 'read' for queries, 'write' for remote/account state changes
2377
+ example: 'bycli ${site} ${command} -f yaml',
2378
+ domain: '${domain}',
2379
+ strategy: Strategy.PUBLIC, // TODO: PUBLIC (no auth), COOKIE (needs login), UI (DOM interaction)
2380
+ browser: false, // TODO: set true if needs browser
2381
+ args: [
2382
+ { name: 'limit', type: 'int', default: 10, help: 'Number of items' },
2383
+ ],
2384
+ columns: [], // TODO: field names for table output (e.g. ['title', 'score', 'url'])
2385
+ func: async (kwargs) => {
2386
+ // TODO: implement data fetching
2387
+ // Prefer API calls (fetch) over browser automation
2388
+ // If you set browser: true, change this to: async (page, kwargs) => { ... }
2389
+ return [];
2390
+ },
2391
+ });
2392
+ `;
2393
+ fs.mkdirSync(dir, { recursive: true });
2394
+ fs.writeFileSync(filePath, template, 'utf-8');
2395
+ console.log(`Created: ${filePath}`);
2396
+ console.log('First time on this site? Run: bycli browser analyze <url>');
2397
+ console.log(`Edit the file to implement your adapter, then run: bycli browser verify ${name}`);
2398
+ }
2399
+ catch (err) {
2400
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
2401
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2402
+ }
2403
+ });
2404
+ // ── Verify (test adapter) ──
2405
+ browser.command('verify')
2406
+ .argument('<name>', 'Adapter name in site/command format (e.g. hn/top)')
2407
+ .option('--write-fixture', 'Write a starter fixture to ~/.bycli/sites/<site>/verify/<command>.json if none exists')
2408
+ .option('--update-fixture', 'Overwrite an existing fixture with one derived from current output')
2409
+ .option('--no-fixture', 'Ignore any fixture file for this run (no value-level validation)')
2410
+ .option('--strict-memory', 'Fail (not just warn) when ~/.bycli/sites/<site>/endpoints.json or notes.md is missing')
2411
+ .option('--seed-args <value>', 'Seed args when no fixture exists; use JSON array/object for multiple args or flags')
2412
+ .option('--trace <mode>', 'Trace capture for the adapter subprocess: off, on, retain-on-failure', 'off')
2413
+ .description('Execute an adapter and validate output; uses fixture at ~/.bycli/sites/<site>/verify/<cmd>.json when present')
2414
+ .action(async (name, opts = {}) => {
2415
+ try {
2416
+ const parts = name.split('/');
2417
+ if (parts.length !== 2) {
2418
+ console.error('Name must be site/command format');
2419
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2420
+ return;
2421
+ }
2422
+ const [site, command] = parts;
2423
+ if (!/^[a-zA-Z0-9_-]+$/.test(site) || !/^[a-zA-Z0-9_-]+$/.test(command)) {
2424
+ console.error('Name parts must be alphanumeric/dash/underscore only');
2425
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2426
+ return;
2427
+ }
2428
+ const { execFileSync } = await import('node:child_process');
2429
+ const { loadFixture, writeFixture, deriveFixture, validateRows, validateRowShape, fixturePath, expandFixtureArgs, parseSeedArgs } = await import('./browser/verify-fixture.js');
2430
+ const filePath = path.join(os.homedir(), '.bycli', 'clis', site, `${command}.js`);
2431
+ if (!fs.existsSync(filePath)) {
2432
+ console.error(`Adapter not found: ${filePath}`);
2433
+ console.error(`Run "bycli browser init ${name}" to create it.`);
2434
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2435
+ return;
2436
+ }
2437
+ console.log(`🔍 Verifying ${name}...\n`);
2438
+ console.log(` Loading: ${filePath}`);
2439
+ const useFixture = opts.fixture !== false;
2440
+ let fixture = useFixture ? loadFixture(site, command) : null;
2441
+ // Build adapter args: fixture.args override the legacy --limit 3 heuristic.
2442
+ // - object form { "limit": 3 } → `--limit 3`
2443
+ // - array form ["123", "--limit", "3"] → verbatim (for positional subjects)
2444
+ const adapterSrc = fs.readFileSync(filePath, 'utf-8');
2445
+ const hasLimitArg = /['"]limit['"]/.test(adapterSrc);
2446
+ const seedArgs = parseSeedArgs(opts.seedArgs);
2447
+ const explicitArgs = fixture?.args ?? seedArgs;
2448
+ const cliArgs = expandFixtureArgs(explicitArgs);
2449
+ if (explicitArgs === undefined && cliArgs.length === 0 && hasLimitArg)
2450
+ cliArgs.push('--limit', '3');
2451
+ const traceArgs = opts.trace && opts.trace !== 'off' ? ['--trace', opts.trace] : [];
2452
+ const argDisplay = [...cliArgs, ...traceArgs].join(' ');
2453
+ const invocation = resolveBrowserVerifyInvocation();
2454
+ // Always request JSON so we can validate structurally.
2455
+ const execArgs = [...invocation.args, site, command, ...cliArgs, ...traceArgs, '--format', 'json'];
2456
+ let rawJson;
2457
+ try {
2458
+ rawJson = execFileSync(invocation.binary, execArgs, {
2459
+ cwd: invocation.cwd,
2460
+ timeout: 30000,
2461
+ encoding: 'utf-8',
2462
+ env: process.env,
2463
+ stdio: ['pipe', 'pipe', 'pipe'],
2464
+ ...(invocation.shell ? { shell: true } : {}),
2465
+ });
2466
+ }
2467
+ catch (err) {
2468
+ console.log(` Executing: bycli ${site} ${command} ${argDisplay}\n`);
2469
+ const execErr = err;
2470
+ if (execErr.stdout)
2471
+ console.log(String(execErr.stdout));
2472
+ if (execErr.stderr)
2473
+ console.error(String(execErr.stderr).slice(0, 500));
2474
+ console.log(`\n ✗ Adapter failed. Fix the code and try again.`);
2475
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2476
+ return;
2477
+ }
2478
+ console.log(` Executing: bycli ${site} ${command} ${argDisplay}\n`);
2479
+ let rows;
2480
+ try {
2481
+ rows = normalizeVerifyRows(JSON.parse(rawJson));
2482
+ }
2483
+ catch {
2484
+ console.log(rawJson);
2485
+ console.log('\n ✗ Could not parse adapter output as JSON. Is `--format json` broken?');
2486
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2487
+ return;
2488
+ }
2489
+ console.log(renderVerifyPreview(rows));
2490
+ console.log(`\n → ${rows.length} row${rows.length === 1 ? '' : 's'}`);
2491
+ const shapeFailures = validateRowShape(rows);
2492
+ if (shapeFailures.length > 0) {
2493
+ console.log(`\n ✗ Adapter output violates row shape conventions:`);
2494
+ for (const f of shapeFailures.slice(0, 20)) {
2495
+ const where = f.rowIndex !== undefined ? `row[${f.rowIndex}] ` : '';
2496
+ console.log(` - [${f.rule}] ${where}${f.detail}`);
2497
+ }
2498
+ if (shapeFailures.length > 20) {
2499
+ console.log(` ... and ${shapeFailures.length - 20} more failure(s)`);
2500
+ }
2501
+ console.log(`\n Keep rows agent-native: <=12 top-level keys, nesting depth <=1, and id-shaped fields at top level.`);
2502
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2503
+ return;
2504
+ }
2505
+ // ── Fixture handling ───────────────────────────────────────────
2506
+ if (opts.writeFixture || opts.updateFixture) {
2507
+ if (fixture && !opts.updateFixture) {
2508
+ console.log(`\n Fixture already exists at ${fixturePath(site, command)}.`);
2509
+ console.log(` Use --update-fixture to overwrite.`);
2510
+ }
2511
+ else {
2512
+ const fixtureArgs = explicitArgs !== undefined
2513
+ ? explicitArgs
2514
+ : (hasLimitArg ? { limit: 3 } : undefined);
2515
+ const derived = deriveFixture(rows, fixtureArgs);
2516
+ const p = writeFixture(site, command, derived);
2517
+ console.log(`\n ${fixture ? '↻ Updated' : '✎ Wrote'} fixture: ${p}`);
2518
+ console.log(` Review and hand-tune the derived expectations (add patterns / notEmpty, tighten rowCount).`);
2519
+ fixture = derived;
2520
+ }
2521
+ }
2522
+ if (!fixture) {
2523
+ console.log(`\n ✓ Adapter runs. (No fixture at ${fixturePath(site, command)} — consider --write-fixture to seed one.)`);
2524
+ const memoryReport = checkSiteMemory(site);
2525
+ printSiteMemoryReport(memoryReport, opts.strictMemory);
2526
+ if (!memoryReport.ok && opts.strictMemory) {
2527
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2528
+ }
2529
+ return;
2530
+ }
2531
+ const failures = validateRows(rows, fixture);
2532
+ if (failures.length === 0) {
2533
+ console.log(`\n ✓ Adapter matches fixture (${fixturePath(site, command)}).`);
2534
+ const memoryReport = checkSiteMemory(site);
2535
+ printSiteMemoryReport(memoryReport, opts.strictMemory);
2536
+ if (!memoryReport.ok && opts.strictMemory) {
2537
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2538
+ }
2539
+ return;
2540
+ }
2541
+ console.log(`\n ✗ Adapter output does not match fixture:`);
2542
+ for (const f of failures.slice(0, 20)) {
2543
+ const where = f.rowIndex !== undefined ? `row[${f.rowIndex}] ` : '';
2544
+ console.log(` - [${f.rule}] ${where}${f.detail}`);
2545
+ }
2546
+ if (failures.length > 20) {
2547
+ console.log(` ... and ${failures.length - 20} more failure(s)`);
2548
+ }
2549
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2550
+ }
2551
+ catch (err) {
2552
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
2553
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2554
+ }
2555
+ });
2556
+ // ── Session ──
2557
+ browser.command('close').description('Release the current browser session tab lease')
2558
+ .action(browserAction(async (page) => {
2559
+ await page.closeWindow?.();
2560
+ console.log('Browser session tab lease released');
2561
+ }));
2562
+ // ── Built-in: doctor / completion ──────────────────────────────────────────
2563
+ program
2564
+ .command('doctor')
2565
+ .description('Diagnose bycli browser bridge connectivity')
2566
+ .option('-v, --verbose', 'Debug output')
2567
+ .action(async (opts) => {
2568
+ applyVerbose(opts);
2569
+ const { runBrowserDoctor, renderBrowserDoctorReport } = await import('./doctor.js');
2570
+ const report = await runBrowserDoctor({ cliVersion: PKG_VERSION });
2571
+ console.log(renderBrowserDoctorReport(report));
2572
+ });
2573
+ program
2574
+ .command('completion')
2575
+ .description('Output shell completion script')
2576
+ .argument('<shell>', 'Shell type: bash, zsh, or fish')
2577
+ .action((shell) => {
2578
+ printCompletionScript(shell);
2579
+ });
2580
+ // ── Plugin management ──────────────────────────────────────────────────────
2581
+ const pluginCmd = program.command('plugin').description('Manage bycli plugins');
2582
+ // Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
2583
+ const originalPluginDescription = pluginCmd.description();
2584
+ pluginCmd
2585
+ .command('install')
2586
+ .description('Install a plugin from a git repository')
2587
+ .argument('<source>', 'Plugin source (e.g. github:user/repo)')
2588
+ .action(async (source) => {
2589
+ const { installPlugin } = await import('./plugin.js');
2590
+ const { discoverPlugins } = await import('./discovery.js');
2591
+ try {
2592
+ const result = installPlugin(source);
2593
+ await discoverPlugins();
2594
+ if (Array.isArray(result)) {
2595
+ if (result.length === 0) {
2596
+ console.log('No plugins were installed (all skipped or incompatible).');
2597
+ }
2598
+ else {
2599
+ console.log(`\u2705 Installed ${result.length} plugin(s) from monorepo: ${result.join(', ')}`);
2600
+ }
2601
+ }
2602
+ else {
2603
+ console.log(`\u2705 Plugin "${result}" installed successfully. Commands are ready to use.`);
2604
+ }
2605
+ }
2606
+ catch (err) {
2607
+ console.error(`Error: ${getErrorMessage(err)}`);
2608
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2609
+ }
2610
+ });
2611
+ pluginCmd
2612
+ .command('uninstall')
2613
+ .description('Uninstall a plugin')
2614
+ .argument('<name>', 'Plugin name')
2615
+ .action(async (name) => {
2616
+ const { uninstallPlugin } = await import('./plugin.js');
2617
+ try {
2618
+ uninstallPlugin(name);
2619
+ console.log(`✅ Plugin "${name}" uninstalled.`);
2620
+ }
2621
+ catch (err) {
2622
+ console.error(`Error: ${getErrorMessage(err)}`);
2623
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2624
+ }
2625
+ });
2626
+ pluginCmd
2627
+ .command('update')
2628
+ .description('Update a plugin (or all plugins) to the latest version')
2629
+ .argument('[name]', 'Plugin name (required unless --all is passed)')
2630
+ .option('--all', 'Update all installed plugins')
2631
+ .action(async (name, opts) => {
2632
+ if (!name && !opts.all) {
2633
+ console.error('Error: Please specify a plugin name or use the --all flag.');
2634
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2635
+ return;
2636
+ }
2637
+ if (name && opts.all) {
2638
+ console.error('Error: Cannot specify both a plugin name and --all.');
2639
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2640
+ return;
2641
+ }
2642
+ const { updatePlugin, updateAllPlugins } = await import('./plugin.js');
2643
+ const { discoverPlugins } = await import('./discovery.js');
2644
+ if (opts.all) {
2645
+ const results = updateAllPlugins();
2646
+ if (results.length > 0) {
2647
+ await discoverPlugins();
2648
+ }
2649
+ let hasErrors = false;
2650
+ console.log(' Update Results:');
2651
+ for (const result of results) {
2652
+ if (result.success) {
2653
+ console.log(` ✓ ${result.name}`);
2654
+ continue;
2655
+ }
2656
+ hasErrors = true;
2657
+ console.log(` ✗ ${result.name} — ${String(result.error)}`);
2658
+ }
2659
+ if (results.length === 0) {
2660
+ console.log(' No plugins installed.');
2661
+ return;
2662
+ }
2663
+ console.log();
2664
+ if (hasErrors) {
2665
+ console.error('Completed with some errors.');
2666
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2667
+ }
2668
+ else {
2669
+ console.log('✅ All plugins updated successfully.');
2670
+ }
2671
+ return;
2672
+ }
2673
+ try {
2674
+ updatePlugin(name);
2675
+ await discoverPlugins();
2676
+ console.log(`✅ Plugin "${name}" updated successfully.`);
2677
+ }
2678
+ catch (err) {
2679
+ console.error(`Error: ${getErrorMessage(err)}`);
2680
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2681
+ }
2682
+ });
2683
+ pluginCmd
2684
+ .command('list')
2685
+ .description('List installed plugins')
2686
+ .option('-f, --format <fmt>', 'Output format: table, json', 'table')
2687
+ .action(async (opts) => {
2688
+ const { listPlugins } = await import('./plugin.js');
2689
+ const plugins = listPlugins();
2690
+ if (plugins.length === 0) {
2691
+ console.log(' No plugins installed.');
2692
+ console.log(' Install one with: bycli plugin install github:user/repo');
2693
+ return;
2694
+ }
2695
+ if (opts.format === 'json') {
2696
+ renderOutput(plugins, {
2697
+ fmt: 'json',
2698
+ columns: ['name', 'commands', 'source'],
2699
+ title: 'bycli/plugins',
2700
+ source: 'bycli plugin list',
2701
+ });
2702
+ return;
2703
+ }
2704
+ console.log();
2705
+ console.log(' Installed plugins');
2706
+ console.log();
2707
+ // Group by monorepo
2708
+ const standalone = plugins.filter((p) => !p.monorepoName);
2709
+ const monoGroups = new Map();
2710
+ for (const p of plugins) {
2711
+ if (!p.monorepoName)
2712
+ continue;
2713
+ const g = monoGroups.get(p.monorepoName) ?? [];
2714
+ g.push(p);
2715
+ monoGroups.set(p.monorepoName, g);
2716
+ }
2717
+ for (const p of standalone) {
2718
+ const version = p.version ? ` @${p.version}` : '';
2719
+ const desc = p.description ? ` — ${p.description}` : '';
2720
+ const cmds = p.commands.length > 0 ? ` (${p.commands.join(', ')})` : '';
2721
+ const src = p.source ? ` ← ${p.source}` : '';
2722
+ console.log(` ${p.name}${version}${desc}${cmds}${src}`);
2723
+ }
2724
+ for (const [mono, group] of monoGroups) {
2725
+ console.log();
2726
+ console.log(` 📦 ${mono}` + ' (monorepo)');
2727
+ for (const p of group) {
2728
+ const version = p.version ? ` @${p.version}` : '';
2729
+ const desc = p.description ? ` — ${p.description}` : '';
2730
+ const cmds = p.commands.length > 0 ? ` (${p.commands.join(', ')})` : '';
2731
+ console.log(` ${p.name}${version}${desc}${cmds}`);
2732
+ }
2733
+ }
2734
+ console.log();
2735
+ console.log(` ${plugins.length} plugin(s) installed`);
2736
+ console.log();
2737
+ });
2738
+ pluginCmd
2739
+ .command('create')
2740
+ .description('Create a new plugin scaffold')
2741
+ .argument('<name>', 'Plugin name (lowercase, hyphens allowed)')
2742
+ .option('-d, --dir <path>', 'Output directory (default: ./<name>)')
2743
+ .option('--description <text>', 'Plugin description')
2744
+ .action(async (name, opts) => {
2745
+ const { createPluginScaffold } = await import('./plugin-scaffold.js');
2746
+ try {
2747
+ const result = createPluginScaffold(name, {
2748
+ dir: opts.dir,
2749
+ description: opts.description,
2750
+ });
2751
+ console.log(`✅ Plugin scaffold created at ${result.dir}`);
2752
+ console.log();
2753
+ console.log(' Files created:');
2754
+ for (const f of result.files) {
2755
+ console.log(` ${f}`);
2756
+ }
2757
+ console.log();
2758
+ console.log(' Next steps:');
2759
+ console.log(` cd ${result.dir}`);
2760
+ console.log(` bycli plugin install file://${result.dir}`);
2761
+ console.log(` bycli ${name} hello`);
2762
+ }
2763
+ catch (err) {
2764
+ console.error(`Error: ${getErrorMessage(err)}`);
2765
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
2766
+ }
2767
+ });
2768
+ // ── Built-in: adapter management ─────────────────────────────────────────
2769
+ const adapterCmd = program.command('adapter').description('Manage CLI adapters');
2770
+ // Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
2771
+ const originalAdapterDescription = adapterCmd.description();
2772
+ adapterCmd
2773
+ .command('status')
2774
+ .description('Show which sites have local overrides vs using official baseline')
2775
+ .action(async () => {
2776
+ const os = await import('node:os');
2777
+ const userClisDir = path.join(os.homedir(), '.bycli', 'clis');
2778
+ const builtinClisDir = BUILTIN_CLIS;
2779
+ try {
2780
+ const userEntries = await fs.promises.readdir(userClisDir, { withFileTypes: true });
2781
+ const userSites = userEntries.filter(e => e.isDirectory()).map(e => e.name).sort();
2782
+ let builtinSites = [];
2783
+ try {
2784
+ const builtinEntries = await fs.promises.readdir(builtinClisDir, { withFileTypes: true });
2785
+ builtinSites = builtinEntries.filter(e => e.isDirectory()).map(e => e.name).sort();
2786
+ }
2787
+ catch { /* no builtin dir */ }
2788
+ if (userSites.length === 0) {
2789
+ console.log('No local adapter overrides. All sites use the official baseline.');
2790
+ return;
2791
+ }
2792
+ console.log(`Local overrides in ~/.bycli/clis/ (${userSites.length} sites):\n`);
2793
+ for (const site of userSites) {
2794
+ const isOfficial = builtinSites.includes(site);
2795
+ const label = isOfficial ? 'override' : 'custom';
2796
+ console.log(` ${site} [${label}]`);
2797
+ }
2798
+ console.log(`\nOfficial baseline: ${builtinSites.length} sites in package`);
2799
+ }
2800
+ catch {
2801
+ console.log('No local adapter overrides. All sites use the official baseline.');
2802
+ }
2803
+ });
2804
+ adapterCmd
2805
+ .command('eject')
2806
+ .description('Copy an official adapter to ~/.bycli/clis/ for local editing')
2807
+ .argument('<site>', 'Site name (e.g. twitter, bilibili)')
2808
+ .action(async (site) => {
2809
+ const os = await import('node:os');
2810
+ const userClisDir = path.join(os.homedir(), '.bycli', 'clis');
2811
+ const builtinSiteDir = path.join(BUILTIN_CLIS, site);
2812
+ const userSiteDir = path.join(userClisDir, site);
2813
+ try {
2814
+ await fs.promises.access(builtinSiteDir);
2815
+ }
2816
+ catch {
2817
+ console.error(`Error: Site "${site}" not found in official adapters.`);
2818
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2819
+ return;
2820
+ }
2821
+ try {
2822
+ await fs.promises.access(userSiteDir);
2823
+ console.error(`Site "${site}" already exists in ~/.bycli/clis/. Use "bycli adapter reset ${site}" first to restore official version.`);
2824
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2825
+ return;
2826
+ }
2827
+ catch { /* good, doesn't exist yet */ }
2828
+ fs.cpSync(builtinSiteDir, userSiteDir, { recursive: true });
2829
+ console.log(`✅ Ejected "${site}" to ~/.bycli/clis/${site}/`);
2830
+ console.log('You can now edit the adapter files. Changes take effect immediately.');
2831
+ console.log('Note: Official updates to this adapter will overwrite your changes.');
2832
+ });
2833
+ adapterCmd
2834
+ .command('reset')
2835
+ .description('Remove local override and restore official adapter version')
2836
+ .argument('[site]', 'Site name (e.g. twitter, bilibili)')
2837
+ .option('--all', 'Reset all local overrides')
2838
+ .action(async (site, opts) => {
2839
+ const os = await import('node:os');
2840
+ const userClisDir = path.join(os.homedir(), '.bycli', 'clis');
2841
+ if (opts.all) {
2842
+ try {
2843
+ const userEntries = await fs.promises.readdir(userClisDir, { withFileTypes: true });
2844
+ const dirs = userEntries.filter(e => e.isDirectory());
2845
+ if (dirs.length === 0) {
2846
+ console.log('No local sites to reset.');
2847
+ return;
2848
+ }
2849
+ for (const dir of dirs) {
2850
+ fs.rmSync(path.join(userClisDir, dir.name), { recursive: true, force: true });
2851
+ }
2852
+ console.log(`✅ Reset ${dirs.length} site(s). All adapters now use official baseline.`);
2853
+ }
2854
+ catch {
2855
+ console.log('No local sites to reset.');
2856
+ }
2857
+ return;
2858
+ }
2859
+ if (!site) {
2860
+ console.error('Error: Please specify a site name or use --all.');
2861
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2862
+ return;
2863
+ }
2864
+ const userSiteDir = path.join(userClisDir, site);
2865
+ try {
2866
+ await fs.promises.access(userSiteDir);
2867
+ }
2868
+ catch {
2869
+ console.error(`Site "${site}" has no local override.`);
2870
+ return;
2871
+ }
2872
+ const isOfficial = fs.existsSync(path.join(BUILTIN_CLIS, site));
2873
+ fs.rmSync(userSiteDir, { recursive: true, force: true });
2874
+ console.log(isOfficial
2875
+ ? `✅ Reset "${site}". Now using official baseline.`
2876
+ : `✅ Removed custom site "${site}".`);
2877
+ });
2878
+ // ── Built-in: browser profile selection ──────────────────────────────────
2879
+ const profileCmd = program.command('profile').description('Manage Browser Bridge Chrome profiles');
2880
+ // Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
2881
+ const originalProfileDescription = profileCmd.description();
2882
+ profileCmd
2883
+ .command('list')
2884
+ .description('List Chrome profiles connected through the Browser Bridge extension')
2885
+ .action(async () => {
2886
+ const status = await fetchDaemonStatus();
2887
+ const config = loadProfileConfig();
2888
+ const profiles = status?.profiles ?? [];
2889
+ if (!status) {
2890
+ console.log('Daemon is not running. Run bycli doctor after opening Chrome.');
2891
+ return;
2892
+ }
2893
+ if (isDaemonStale(status, PKG_VERSION) || !Array.isArray(status.profiles)) {
2894
+ console.log(`Daemon ${formatDaemonVersion(status)} is stale for CLI v${PKG_VERSION}.`);
2895
+ console.log('Run: bycli daemon restart');
2896
+ return;
2897
+ }
2898
+ if (profiles.length === 0) {
2899
+ console.log('No Browser Bridge profiles connected.');
2900
+ console.log('Open a Chrome profile with the byCLI extension installed, then run bycli profile list again.');
2901
+ return;
2902
+ }
2903
+ const knownContextIds = new Set(profiles.map((profile) => profile.contextId));
2904
+ console.log('Connected Browser Bridge profiles');
2905
+ console.log();
2906
+ for (const profile of profiles) {
2907
+ const alias = aliasForContextId(config, profile.contextId);
2908
+ const defaultMark = config.defaultContextId === profile.contextId ? ' default' : '';
2909
+ const aliasText = alias ? ` ${alias}` : '';
2910
+ const version = profile.extensionVersion ? ` v${profile.extensionVersion}` : ' version unknown';
2911
+ console.log(` ${profile.contextId}${aliasText}${defaultMark} — connected${version}`);
2912
+ }
2913
+ const disconnectedAliases = Object.entries(config.aliases)
2914
+ .filter(([, contextId]) => !knownContextIds.has(contextId));
2915
+ if (disconnectedAliases.length > 0 || (config.defaultContextId && !knownContextIds.has(config.defaultContextId))) {
2916
+ console.log();
2917
+ console.log('Disconnected saved profiles:');
2918
+ const shown = new Set();
2919
+ for (const [alias, contextId] of disconnectedAliases) {
2920
+ shown.add(contextId);
2921
+ console.log(` ${contextId} ${alias} — not connected`);
2922
+ }
2923
+ if (config.defaultContextId && !shown.has(config.defaultContextId) && !knownContextIds.has(config.defaultContextId)) {
2924
+ console.log(` ${config.defaultContextId} — default, not connected`);
2925
+ }
2926
+ }
2927
+ });
2928
+ profileCmd
2929
+ .command('rename')
2930
+ .description('Assign a local alias to a connected Browser Bridge profile')
2931
+ .argument('<contextId>', 'Profile contextId from bycli profile list')
2932
+ .argument('<alias>', 'Local alias, e.g. work or personal')
2933
+ .action((contextId, alias) => {
2934
+ try {
2935
+ renameProfile(contextId, alias);
2936
+ console.log(`Profile ${contextId} is now aliased as ${alias}.`);
2937
+ }
2938
+ catch (err) {
2939
+ console.error(`Error: ${getErrorMessage(err)}`);
2940
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2941
+ }
2942
+ });
2943
+ profileCmd
2944
+ .command('use')
2945
+ .description('Set the default Browser Bridge profile for future commands')
2946
+ .argument('<profile>', 'Profile alias or contextId')
2947
+ .action((profile) => {
2948
+ try {
2949
+ const config = setDefaultProfile(profile);
2950
+ console.log(`Default Browser Bridge profile: ${config.defaultContextId ?? profile}`);
2951
+ }
2952
+ catch (err) {
2953
+ console.error(`Error: ${getErrorMessage(err)}`);
2954
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2955
+ }
2956
+ });
2957
+ // ── Built-in: daemon ──────────────────────────────────────────────────────
2958
+ const daemonCmd = program.command('daemon').description('Manage the bycli daemon');
2959
+ // Snapshot before applyRootSubcommandSummaries() rewrites .description() to a child-name listing.
2960
+ const originalDaemonDescription = daemonCmd.description();
2961
+ daemonCmd
2962
+ .command('status')
2963
+ .description('Show daemon status')
2964
+ .action(async () => { await daemonStatus(); });
2965
+ daemonCmd
2966
+ .command('stop')
2967
+ .description('Stop the daemon')
2968
+ .action(async () => { await daemonStop(); });
2969
+ daemonCmd
2970
+ .command('restart')
2971
+ .description('Restart the daemon')
2972
+ .action(async () => { await daemonRestart(); });
2973
+ // ── External CLIs ─────────────────────────────────────────────────────────
2974
+ const externalClis = loadExternalClis();
2975
+ const externalCmd = program
2976
+ .command('external')
2977
+ .description('Manage external CLI passthrough commands');
2978
+ externalCmd
2979
+ .command('install')
2980
+ .description('Install an external CLI')
2981
+ .argument('<name>', 'Name of the external CLI')
2982
+ .action((name) => {
2983
+ const ext = externalClis.find(e => e.name === name);
2984
+ if (!ext) {
2985
+ console.error(`External CLI '${name}' not found in registry.`);
2986
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
2987
+ return;
2988
+ }
2989
+ installExternalCli(ext);
2990
+ });
2991
+ externalCmd
2992
+ .command('register')
2993
+ .description('Register an external CLI')
2994
+ .argument('<name>', 'Name of the CLI')
2995
+ .option('--binary <bin>', 'Binary name if different from name')
2996
+ .option('--install <cmd>', 'Auto-install command')
2997
+ .option('--desc <text>', 'Description')
2998
+ .action((name, opts) => {
2999
+ registerExternalCli(name, { binary: opts.binary, install: opts.install, description: opts.desc });
3000
+ });
3001
+ externalCmd
3002
+ .command('list')
3003
+ .description('List registered external CLIs')
3004
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml, md, csv', 'table')
3005
+ .action((opts) => {
3006
+ const rows = loadExternalClis().map((ext) => ({
3007
+ name: ext.name,
3008
+ package: ext.package ?? '',
3009
+ binary: ext.binary,
3010
+ installed: isBinaryInstalled(ext.binary),
3011
+ description: ext.description ?? '',
3012
+ homepage: ext.homepage ?? '',
3013
+ tags: ext.tags?.join(', ') ?? '',
3014
+ }));
3015
+ renderOutput(rows, {
3016
+ fmt: opts.format,
3017
+ columns: ['name', 'package', 'binary', 'installed', 'description', 'homepage', 'tags'],
3018
+ title: 'bycli/external/list',
3019
+ source: 'bycli external list',
3020
+ });
3021
+ });
3022
+ function passthroughExternal(name, parsedArgs) {
3023
+ const args = parsedArgs ?? (() => {
3024
+ const idx = process.argv.indexOf(name);
3025
+ return process.argv.slice(idx + 1);
3026
+ })();
3027
+ try {
3028
+ executeExternalCli(name, args, externalClis);
3029
+ }
3030
+ catch (err) {
3031
+ console.error(`Error: ${getErrorMessage(err)}`);
3032
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
3033
+ }
3034
+ }
3035
+ for (const ext of externalClis) {
3036
+ if (program.commands.some(c => c.name() === ext.name))
3037
+ continue;
3038
+ program
3039
+ .command(ext.name)
3040
+ .description(`(External) ${ext.description || ext.name}`)
3041
+ .argument('[args...]')
3042
+ .allowUnknownOption()
3043
+ .passThroughOptions()
3044
+ .helpOption(false)
3045
+ .action((args) => passthroughExternal(ext.name, args));
3046
+ }
3047
+ // ── Antigravity serve (long-running, special case) ────────────────────────
3048
+ const antigravityCmd = program.command('antigravity').description('antigravity commands');
3049
+ antigravityCmd
3050
+ .command('serve')
3051
+ .description('Start Anthropic-compatible API proxy for Antigravity')
3052
+ .option('--port <port>', 'Server port (default: 8082)', '8082')
3053
+ .option('--timeout <seconds>', 'Maximum time to wait for a reply (default: 120s)')
3054
+ .action(async (opts) => {
3055
+ // @ts-expect-error JS adapter — no type declarations
3056
+ const { startServe } = await import('../clis/antigravity/serve.js');
3057
+ await startServe({
3058
+ port: parseInt(opts.port, 10),
3059
+ timeout: opts.timeout ? parsePositiveIntOption(opts.timeout, '--timeout', 120) : undefined,
3060
+ });
3061
+ });
3062
+ // ── Dynamic adapter commands ──────────────────────────────────────────────
3063
+ const siteGroups = new Map();
3064
+ siteGroups.set('antigravity', antigravityCmd);
3065
+ const siteNames = registerAllCommands(program, siteGroups);
3066
+ applyRootSubcommandSummaries(program);
3067
+ // ── Help-text grouping: External CLIs / App adapters / Site adapters ──
3068
+ // Classification derives from each adapter's `domain` field — see classifyAdapter.
3069
+ // External CLIs are taken from the externalClis registry (passthrough binaries).
3070
+ const externalNames = externalClis.map(ext => ext.name);
3071
+ const externalHelpEntries = externalClis.map(ext => ({
3072
+ name: ext.name,
3073
+ label: formatExternalCliLabel(ext),
3074
+ }));
3075
+ const siteDomains = new Map();
3076
+ for (const [, cmd] of getRegistry()) {
3077
+ if (!siteDomains.has(cmd.site))
3078
+ siteDomains.set(cmd.site, cmd.domain);
3079
+ }
3080
+ const apps = [];
3081
+ const sites = [];
3082
+ for (const site of siteNames) {
3083
+ if (classifyAdapter(siteDomains.get(site)) === 'app')
3084
+ apps.push(site);
3085
+ else
3086
+ sites.push(site);
3087
+ }
3088
+ const adapterGroups = { external: externalHelpEntries, apps, sites };
3089
+ const adapterNameSet = new Set([...externalNames, ...siteNames]);
3090
+ installCommanderNamespaceStructuredHelp(browser, { globalCommand: program, description: originalBrowserDescription });
3091
+ installCommanderNamespaceStructuredHelp(daemonCmd, { globalCommand: program, description: originalDaemonDescription });
3092
+ installCommanderNamespaceStructuredHelp(pluginCmd, { globalCommand: program, description: originalPluginDescription });
3093
+ installCommanderNamespaceStructuredHelp(adapterCmd, { globalCommand: program, description: originalAdapterDescription });
3094
+ installCommanderNamespaceStructuredHelp(profileCmd, { globalCommand: program, description: originalProfileDescription });
3095
+ program.configureHelp({
3096
+ visibleCommands: (command) => command.commands.filter(child => command !== program || !adapterNameSet.has(child.name())),
3097
+ });
3098
+ // When an ancestor command declares a leading positional via `.usage(...)`
3099
+ // (e.g. `browser` -> `<session> <command> [options]`), inject the positional
3100
+ // between that ancestor's name and the next path segment so the help Usage
3101
+ // line is accurate: `Usage: bycli browser <session> click [target] [options]`
3102
+ // instead of `bycli browser click [target] [options]`. Commander does NOT
3103
+ // inherit configureHelp into subcommands, so we walk the descendant tree and
3104
+ // apply the override on each.
3105
+ const ancestorAwareCommandUsage = (cmd) => {
3106
+ const ancestors = [];
3107
+ let ancestor = cmd.parent;
3108
+ while (ancestor) {
3109
+ const positional = leadingPositionalFromUsage(ancestor);
3110
+ ancestors.unshift(positional ? `${ancestor.name()} ${positional}` : ancestor.name());
3111
+ ancestor = ancestor.parent;
3112
+ }
3113
+ return [...ancestors, cmd.name(), cmd.usage()].filter(Boolean).join(' ').trim();
3114
+ };
3115
+ function applyAncestorAwareUsage(cmd) {
3116
+ cmd.configureHelp({ commandUsage: ancestorAwareCommandUsage });
3117
+ for (const sub of cmd.commands)
3118
+ applyAncestorAwareUsage(sub);
3119
+ }
3120
+ applyAncestorAwareUsage(browser);
3121
+ installStructuredHelp(program, () => rootHelpData(program, adapterGroups), () => formatRootAdapterHelpText(adapterGroups));
3122
+ // ── Unknown command fallback ──────────────────────────────────────────────
3123
+ // Security: do NOT auto-discover and register arbitrary system binaries.
3124
+ // Only explicitly registered external CLIs are allowed.
3125
+ program.on('command:*', (operands) => {
3126
+ const binary = operands[0];
3127
+ console.error(`error: unknown command '${binary}'`);
3128
+ if (isBinaryInstalled(binary)) {
3129
+ console.error(` Tip: '${binary}' exists on your PATH. Use 'bycli external register ${binary}' to add it as an external CLI.`);
3130
+ }
3131
+ program.outputHelp();
3132
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
3133
+ });
3134
+ return program;
3135
+ }
3136
+ export function runCli(BUILTIN_CLIS, USER_CLIS) {
3137
+ createProgram(BUILTIN_CLIS, USER_CLIS).parse();
3138
+ }
3139
+ export { findPackageRoot };
3140
+ export function resolveBrowserVerifyInvocation(opts = {}) {
3141
+ const platform = opts.platform ?? process.platform;
3142
+ const fileExists = opts.fileExists ?? fs.existsSync;
3143
+ const readFile = opts.readFile ?? ((filePath) => fs.readFileSync(filePath, 'utf-8'));
3144
+ const projectRoot = opts.projectRoot ?? findPackageRoot(CLI_FILE, fileExists);
3145
+ for (const builtEntry of getBuiltEntryCandidates(projectRoot, readFile)) {
3146
+ if (fileExists(builtEntry)) {
3147
+ return {
3148
+ binary: process.execPath,
3149
+ args: [builtEntry],
3150
+ cwd: projectRoot,
3151
+ };
3152
+ }
3153
+ }
3154
+ const sourceEntry = path.join(projectRoot, 'src', 'main.ts');
3155
+ if (!fileExists(sourceEntry)) {
3156
+ throw new Error(`Could not find bycli entrypoint under ${projectRoot}. Expected built entry from package.json or src/main.ts.`);
3157
+ }
3158
+ const localTsxBin = path.join(projectRoot, 'node_modules', '.bin', platform === 'win32' ? 'tsx.cmd' : 'tsx');
3159
+ if (fileExists(localTsxBin)) {
3160
+ return {
3161
+ binary: localTsxBin,
3162
+ args: [sourceEntry],
3163
+ cwd: projectRoot,
3164
+ ...(platform === 'win32' ? { shell: true } : {}),
3165
+ };
3166
+ }
3167
+ return {
3168
+ binary: platform === 'win32' ? 'npx.cmd' : 'npx',
3169
+ args: ['tsx', sourceEntry],
3170
+ cwd: projectRoot,
3171
+ ...(platform === 'win32' ? { shell: true } : {}),
3172
+ };
3173
+ }