@jackwener/opencli 1.6.9 → 1.7.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 (531) hide show
  1. package/README.md +128 -59
  2. package/README.zh-CN.md +134 -78
  3. package/dist/clis/_shared/common.d.ts +3 -0
  4. package/dist/clis/_shared/common.js +22 -0
  5. package/dist/clis/bilibili/hot.js +35 -0
  6. package/dist/clis/bluesky/feeds.d.ts +1 -0
  7. package/dist/clis/bluesky/feeds.js +27 -0
  8. package/dist/clis/bluesky/followers.d.ts +1 -0
  9. package/dist/clis/bluesky/followers.js +27 -0
  10. package/dist/clis/bluesky/following.d.ts +1 -0
  11. package/dist/clis/bluesky/following.js +27 -0
  12. package/dist/clis/bluesky/profile.d.ts +1 -0
  13. package/dist/clis/bluesky/profile.js +29 -0
  14. package/dist/clis/bluesky/search.d.ts +1 -0
  15. package/dist/clis/bluesky/search.js +28 -0
  16. package/dist/clis/bluesky/starter-packs.d.ts +1 -0
  17. package/dist/clis/bluesky/starter-packs.js +28 -0
  18. package/dist/clis/bluesky/thread.d.ts +1 -0
  19. package/dist/clis/bluesky/thread.js +30 -0
  20. package/dist/clis/bluesky/trending.d.ts +1 -0
  21. package/dist/clis/bluesky/trending.js +19 -0
  22. package/dist/clis/bluesky/user.d.ts +1 -0
  23. package/dist/clis/bluesky/user.js +33 -0
  24. package/dist/clis/cnki/search.d.ts +1 -0
  25. package/dist/clis/cnki/search.js +60 -0
  26. package/dist/clis/cnki/search.test.d.ts +1 -0
  27. package/dist/clis/cnki/search.test.js +18 -0
  28. package/dist/clis/devto/tag.d.ts +1 -0
  29. package/dist/clis/devto/tag.js +32 -0
  30. package/dist/clis/devto/top.d.ts +1 -0
  31. package/dist/clis/devto/top.js +26 -0
  32. package/dist/clis/devto/user.d.ts +1 -0
  33. package/dist/clis/devto/user.js +31 -0
  34. package/dist/clis/dictionary/examples.d.ts +1 -0
  35. package/dist/clis/dictionary/examples.js +27 -0
  36. package/dist/clis/dictionary/search.d.ts +1 -0
  37. package/dist/clis/dictionary/search.js +29 -0
  38. package/dist/clis/dictionary/synonyms.d.ts +1 -0
  39. package/dist/clis/dictionary/synonyms.js +27 -0
  40. package/dist/clis/douban/subject.d.ts +1 -0
  41. package/dist/clis/douban/subject.js +118 -0
  42. package/dist/clis/douban/top250.d.ts +1 -0
  43. package/dist/clis/douban/top250.js +67 -0
  44. package/dist/clis/facebook/add-friend.d.ts +1 -0
  45. package/dist/clis/facebook/add-friend.js +43 -0
  46. package/dist/clis/facebook/events.d.ts +1 -0
  47. package/dist/clis/facebook/events.js +40 -0
  48. package/dist/clis/facebook/feed.d.ts +1 -0
  49. package/dist/clis/facebook/feed.js +59 -0
  50. package/dist/clis/facebook/friends.d.ts +1 -0
  51. package/dist/clis/facebook/friends.js +38 -0
  52. package/dist/clis/facebook/groups.d.ts +1 -0
  53. package/dist/clis/facebook/groups.js +46 -0
  54. package/dist/clis/facebook/join-group.d.ts +1 -0
  55. package/dist/clis/facebook/join-group.js +44 -0
  56. package/dist/clis/facebook/memories.d.ts +1 -0
  57. package/dist/clis/facebook/memories.js +35 -0
  58. package/dist/clis/facebook/notifications.d.ts +1 -0
  59. package/dist/clis/facebook/notifications.js +36 -0
  60. package/dist/clis/facebook/profile.d.ts +1 -0
  61. package/dist/clis/facebook/profile.js +37 -0
  62. package/dist/clis/facebook/search.d.ts +1 -0
  63. package/dist/clis/facebook/search.js +38 -0
  64. package/dist/clis/facebook/search.test.d.ts +1 -1
  65. package/dist/clis/facebook/search.test.js +6 -9
  66. package/dist/clis/gitee/index.d.ts +3 -0
  67. package/dist/clis/gitee/index.js +3 -0
  68. package/dist/clis/gitee/search.d.ts +1 -0
  69. package/dist/clis/gitee/search.js +136 -0
  70. package/dist/clis/gitee/trending.d.ts +1 -0
  71. package/dist/clis/gitee/trending.js +567 -0
  72. package/dist/clis/gitee/user.d.ts +1 -0
  73. package/dist/clis/gitee/user.js +199 -0
  74. package/dist/clis/gitee/user.test.d.ts +1 -0
  75. package/dist/clis/gitee/user.test.js +63 -0
  76. package/dist/clis/hackernews/ask.d.ts +1 -0
  77. package/dist/clis/hackernews/ask.js +29 -0
  78. package/dist/clis/hackernews/best.d.ts +1 -0
  79. package/dist/clis/hackernews/best.js +29 -0
  80. package/dist/clis/hackernews/jobs.d.ts +1 -0
  81. package/dist/clis/hackernews/jobs.js +27 -0
  82. package/dist/clis/hackernews/new.d.ts +1 -0
  83. package/dist/clis/hackernews/new.js +29 -0
  84. package/dist/clis/hackernews/search.d.ts +1 -0
  85. package/dist/clis/hackernews/search.js +36 -0
  86. package/dist/clis/hackernews/show.d.ts +1 -0
  87. package/dist/clis/hackernews/show.js +29 -0
  88. package/dist/clis/hackernews/top.d.ts +1 -0
  89. package/dist/clis/hackernews/top.js +29 -0
  90. package/dist/clis/hackernews/user.d.ts +1 -0
  91. package/dist/clis/hackernews/user.js +22 -0
  92. package/dist/clis/hupu/hot.d.ts +1 -0
  93. package/dist/clis/hupu/hot.js +40 -0
  94. package/dist/clis/instagram/comment.d.ts +1 -0
  95. package/dist/clis/instagram/comment.js +47 -0
  96. package/dist/clis/instagram/explore.d.ts +1 -0
  97. package/dist/clis/instagram/explore.js +41 -0
  98. package/dist/clis/instagram/follow.d.ts +1 -0
  99. package/dist/clis/instagram/follow.js +43 -0
  100. package/dist/clis/instagram/followers.d.ts +1 -0
  101. package/dist/clis/instagram/followers.js +45 -0
  102. package/dist/clis/instagram/following.d.ts +1 -0
  103. package/dist/clis/instagram/following.js +45 -0
  104. package/dist/clis/instagram/like.d.ts +1 -0
  105. package/dist/clis/instagram/like.js +45 -0
  106. package/dist/clis/instagram/profile.d.ts +1 -0
  107. package/dist/clis/instagram/profile.js +39 -0
  108. package/dist/clis/instagram/save.d.ts +1 -0
  109. package/dist/clis/instagram/save.js +45 -0
  110. package/dist/clis/instagram/saved.d.ts +1 -0
  111. package/dist/clis/instagram/saved.js +38 -0
  112. package/dist/clis/instagram/search.d.ts +1 -0
  113. package/dist/clis/instagram/search.js +38 -0
  114. package/dist/clis/instagram/unfollow.d.ts +1 -0
  115. package/dist/clis/instagram/unfollow.js +40 -0
  116. package/dist/clis/instagram/unlike.d.ts +1 -0
  117. package/dist/clis/instagram/unlike.js +45 -0
  118. package/dist/clis/instagram/unsave.d.ts +1 -0
  119. package/dist/clis/instagram/unsave.js +45 -0
  120. package/dist/clis/instagram/user.d.ts +1 -0
  121. package/dist/clis/instagram/user.js +48 -0
  122. package/dist/clis/jd/add-cart.d.ts +1 -0
  123. package/dist/clis/jd/add-cart.js +71 -0
  124. package/dist/clis/jd/cart.d.ts +1 -0
  125. package/dist/clis/jd/cart.js +79 -0
  126. package/dist/clis/jd/commands.test.d.ts +5 -0
  127. package/dist/clis/jd/commands.test.js +64 -0
  128. package/dist/clis/jd/detail.d.ts +1 -0
  129. package/dist/clis/jd/detail.js +62 -0
  130. package/dist/clis/jd/reviews.d.ts +1 -0
  131. package/dist/clis/jd/reviews.js +54 -0
  132. package/dist/clis/jd/search.d.ts +1 -0
  133. package/dist/clis/jd/search.js +65 -0
  134. package/dist/clis/jianyu/detail.d.ts +1 -0
  135. package/dist/clis/jianyu/detail.js +20 -0
  136. package/dist/clis/jianyu/search.d.ts +41 -4
  137. package/dist/clis/jianyu/search.js +458 -96
  138. package/dist/clis/jianyu/search.test.js +105 -0
  139. package/dist/clis/jianyu/shared/china-bid-search.d.ts +12 -0
  140. package/dist/clis/jianyu/shared/china-bid-search.js +165 -0
  141. package/dist/clis/jianyu/shared/procurement-contract.d.ts +68 -0
  142. package/dist/clis/jianyu/shared/procurement-contract.js +324 -0
  143. package/dist/clis/jianyu/shared/procurement-contract.test.d.ts +1 -0
  144. package/dist/clis/jianyu/shared/procurement-contract.test.js +72 -0
  145. package/dist/clis/jianyu/shared/procurement-detail.d.ts +6 -0
  146. package/dist/clis/jianyu/shared/procurement-detail.js +92 -0
  147. package/dist/clis/jianyu/shared/procurement-detail.test.d.ts +1 -0
  148. package/dist/clis/jianyu/shared/procurement-detail.test.js +72 -0
  149. package/dist/clis/jike/post.d.ts +1 -0
  150. package/dist/clis/jike/post.js +61 -0
  151. package/dist/clis/jike/topic.d.ts +1 -0
  152. package/dist/clis/jike/topic.js +51 -0
  153. package/dist/clis/jike/user.d.ts +1 -0
  154. package/dist/clis/jike/user.js +50 -0
  155. package/dist/clis/jimeng/generate.d.ts +1 -0
  156. package/dist/clis/jimeng/generate.js +83 -0
  157. package/dist/clis/jimeng/history.d.ts +1 -0
  158. package/dist/clis/jimeng/history.js +47 -0
  159. package/dist/clis/jimeng/new.d.ts +1 -0
  160. package/dist/clis/jimeng/new.js +43 -0
  161. package/dist/clis/jimeng/workspaces.d.ts +1 -0
  162. package/dist/clis/jimeng/workspaces.js +41 -0
  163. package/dist/clis/linux-do/categories.d.ts +1 -0
  164. package/dist/clis/linux-do/categories.js +65 -0
  165. package/dist/clis/linux-do/search.d.ts +1 -0
  166. package/dist/clis/linux-do/search.js +41 -0
  167. package/dist/clis/linux-do/tags.d.ts +1 -0
  168. package/dist/clis/linux-do/tags.js +39 -0
  169. package/dist/clis/linux-do/topic-content.test.js +5 -5
  170. package/dist/clis/linux-do/topic.d.ts +1 -0
  171. package/dist/clis/linux-do/topic.js +56 -0
  172. package/dist/clis/linux-do/user-posts.d.ts +1 -0
  173. package/dist/clis/linux-do/user-posts.js +61 -0
  174. package/dist/clis/linux-do/user-topics.d.ts +1 -0
  175. package/dist/clis/linux-do/user-topics.js +48 -0
  176. package/dist/clis/lobsters/active.d.ts +1 -0
  177. package/dist/clis/lobsters/active.js +26 -0
  178. package/dist/clis/lobsters/hot.d.ts +1 -0
  179. package/dist/clis/lobsters/hot.js +26 -0
  180. package/dist/clis/lobsters/newest.d.ts +1 -0
  181. package/dist/clis/lobsters/newest.js +26 -0
  182. package/dist/clis/lobsters/tag.d.ts +1 -0
  183. package/dist/clis/lobsters/tag.js +32 -0
  184. package/dist/clis/pixiv/detail.d.ts +1 -0
  185. package/dist/clis/pixiv/detail.js +58 -0
  186. package/dist/clis/pixiv/ranking.d.ts +1 -0
  187. package/dist/clis/pixiv/ranking.js +59 -0
  188. package/dist/clis/pixiv/user.d.ts +1 -0
  189. package/dist/clis/pixiv/user.js +52 -0
  190. package/dist/clis/reddit/frontpage.d.ts +1 -0
  191. package/dist/clis/reddit/frontpage.js +31 -0
  192. package/dist/clis/reddit/hot.d.ts +1 -0
  193. package/dist/clis/reddit/hot.js +45 -0
  194. package/dist/clis/reddit/popular.d.ts +1 -0
  195. package/dist/clis/reddit/popular.js +41 -0
  196. package/dist/clis/reddit/search.d.ts +1 -0
  197. package/dist/clis/reddit/search.js +65 -0
  198. package/dist/clis/reddit/subreddit.d.ts +1 -0
  199. package/dist/clis/reddit/subreddit.js +52 -0
  200. package/dist/clis/reddit/user-comments.d.ts +1 -0
  201. package/dist/clis/reddit/user-comments.js +44 -0
  202. package/dist/clis/reddit/user-posts.d.ts +1 -0
  203. package/dist/clis/reddit/user-posts.js +42 -0
  204. package/dist/clis/reddit/user.d.ts +1 -0
  205. package/dist/clis/reddit/user.js +37 -0
  206. package/dist/clis/stackoverflow/bounties.d.ts +1 -0
  207. package/dist/clis/stackoverflow/bounties.js +27 -0
  208. package/dist/clis/stackoverflow/hot.d.ts +1 -0
  209. package/dist/clis/stackoverflow/hot.js +24 -0
  210. package/dist/clis/stackoverflow/search.d.ts +1 -0
  211. package/dist/clis/stackoverflow/search.js +27 -0
  212. package/dist/clis/stackoverflow/unanswered.d.ts +1 -0
  213. package/dist/clis/stackoverflow/unanswered.js +26 -0
  214. package/dist/clis/steam/top-sellers.d.ts +1 -0
  215. package/dist/clis/steam/top-sellers.js +25 -0
  216. package/dist/clis/taobao/add-cart.d.ts +1 -0
  217. package/dist/clis/taobao/add-cart.js +149 -0
  218. package/dist/clis/taobao/cart.d.ts +1 -0
  219. package/dist/clis/taobao/cart.js +95 -0
  220. package/dist/clis/taobao/commands.test.d.ts +5 -0
  221. package/dist/clis/taobao/commands.test.js +64 -0
  222. package/dist/clis/taobao/detail.d.ts +1 -0
  223. package/dist/clis/taobao/detail.js +70 -0
  224. package/dist/clis/taobao/reviews.d.ts +1 -0
  225. package/dist/clis/taobao/reviews.js +76 -0
  226. package/dist/clis/taobao/search.d.ts +1 -0
  227. package/dist/clis/taobao/search.js +96 -0
  228. package/dist/clis/tiktok/comment.d.ts +1 -0
  229. package/dist/clis/tiktok/comment.js +57 -0
  230. package/dist/clis/tiktok/explore.d.ts +1 -0
  231. package/dist/clis/tiktok/explore.js +35 -0
  232. package/dist/clis/tiktok/follow.d.ts +1 -0
  233. package/dist/clis/tiktok/follow.js +39 -0
  234. package/dist/clis/tiktok/following.d.ts +1 -0
  235. package/dist/clis/tiktok/following.js +42 -0
  236. package/dist/clis/tiktok/friends.d.ts +1 -0
  237. package/dist/clis/tiktok/friends.js +43 -0
  238. package/dist/clis/tiktok/like.d.ts +1 -0
  239. package/dist/clis/tiktok/like.js +33 -0
  240. package/dist/clis/tiktok/live.d.ts +1 -0
  241. package/dist/clis/tiktok/live.js +47 -0
  242. package/dist/clis/tiktok/notifications.d.ts +1 -0
  243. package/dist/clis/tiktok/notifications.js +49 -0
  244. package/dist/clis/tiktok/profile.d.ts +1 -0
  245. package/dist/clis/tiktok/profile.js +54 -0
  246. package/dist/clis/tiktok/save.d.ts +1 -0
  247. package/dist/clis/tiktok/save.js +29 -0
  248. package/dist/clis/tiktok/search.d.ts +1 -0
  249. package/dist/clis/tiktok/search.js +39 -0
  250. package/dist/clis/tiktok/unfollow.d.ts +1 -0
  251. package/dist/clis/tiktok/unfollow.js +44 -0
  252. package/dist/clis/tiktok/unlike.d.ts +1 -0
  253. package/dist/clis/tiktok/unlike.js +33 -0
  254. package/dist/clis/tiktok/unsave.d.ts +1 -0
  255. package/dist/clis/tiktok/unsave.js +31 -0
  256. package/dist/clis/tiktok/user.d.ts +1 -0
  257. package/dist/clis/tiktok/user.js +41 -0
  258. package/dist/clis/v2ex/hot.d.ts +1 -0
  259. package/dist/clis/v2ex/hot.js +25 -0
  260. package/dist/clis/v2ex/latest.d.ts +1 -0
  261. package/dist/clis/v2ex/latest.js +25 -0
  262. package/dist/clis/v2ex/member.d.ts +1 -0
  263. package/dist/clis/v2ex/member.js +27 -0
  264. package/dist/clis/v2ex/node.d.ts +1 -0
  265. package/dist/clis/v2ex/node.js +38 -0
  266. package/dist/clis/v2ex/nodes.d.ts +1 -0
  267. package/dist/clis/v2ex/nodes.js +25 -0
  268. package/dist/clis/v2ex/replies.d.ts +1 -0
  269. package/dist/clis/v2ex/replies.js +26 -0
  270. package/dist/clis/v2ex/topic.d.ts +1 -0
  271. package/dist/clis/v2ex/topic.js +30 -0
  272. package/dist/clis/v2ex/user.d.ts +1 -0
  273. package/dist/clis/v2ex/user.js +33 -0
  274. package/dist/clis/xiaoe/catalog.d.ts +1 -0
  275. package/dist/clis/xiaoe/catalog.js +161 -0
  276. package/dist/clis/xiaoe/content.d.ts +1 -0
  277. package/dist/clis/xiaoe/content.js +39 -0
  278. package/dist/clis/xiaoe/courses.d.ts +1 -0
  279. package/dist/clis/xiaoe/courses.js +69 -0
  280. package/dist/clis/xiaoe/detail.d.ts +1 -0
  281. package/dist/clis/xiaoe/detail.js +35 -0
  282. package/dist/clis/xiaoe/play-url.d.ts +1 -0
  283. package/dist/clis/xiaoe/play-url.js +120 -0
  284. package/dist/clis/xiaohongshu/feed.d.ts +1 -0
  285. package/dist/clis/xiaohongshu/feed.js +32 -0
  286. package/dist/clis/xiaohongshu/notifications.d.ts +1 -0
  287. package/dist/clis/xiaohongshu/notifications.js +38 -0
  288. package/dist/clis/xueqiu/earnings-date.d.ts +1 -0
  289. package/dist/clis/xueqiu/earnings-date.js +61 -0
  290. package/dist/clis/xueqiu/feed.d.ts +1 -0
  291. package/dist/clis/xueqiu/feed.js +48 -0
  292. package/dist/clis/xueqiu/groups.d.ts +1 -0
  293. package/dist/clis/xueqiu/groups.js +25 -0
  294. package/dist/clis/xueqiu/hot-stock.d.ts +1 -0
  295. package/dist/clis/xueqiu/hot-stock.js +44 -0
  296. package/dist/clis/xueqiu/hot.d.ts +1 -0
  297. package/dist/clis/xueqiu/hot.js +44 -0
  298. package/dist/clis/xueqiu/kline.d.ts +1 -0
  299. package/dist/clis/xueqiu/kline.js +64 -0
  300. package/dist/clis/xueqiu/search.d.ts +1 -0
  301. package/dist/clis/xueqiu/search.js +49 -0
  302. package/dist/clis/xueqiu/stock.d.ts +1 -0
  303. package/dist/clis/xueqiu/stock.js +72 -0
  304. package/dist/clis/xueqiu/watchlist.d.ts +1 -0
  305. package/dist/clis/xueqiu/watchlist.js +45 -0
  306. package/dist/clis/zhihu/hot.d.ts +1 -0
  307. package/dist/clis/zhihu/hot.js +43 -0
  308. package/dist/clis/zhihu/search.d.ts +1 -0
  309. package/dist/clis/zhihu/search.js +52 -0
  310. package/dist/src/browser/bridge.js +1 -1
  311. package/dist/src/browser/daemon-client.d.ts +16 -4
  312. package/dist/src/browser/daemon-client.js +33 -15
  313. package/dist/src/browser/daemon-client.test.js +0 -3
  314. package/dist/src/browser/dom-helpers.test.js +3 -2
  315. package/dist/src/browser/errors.d.ts +26 -1
  316. package/dist/src/browser/errors.js +40 -7
  317. package/dist/src/browser/errors.test.d.ts +1 -0
  318. package/dist/src/browser/errors.test.js +51 -0
  319. package/dist/src/browser/page.d.ts +9 -8
  320. package/dist/src/browser/page.js +33 -31
  321. package/dist/src/browser.test.js +25 -6
  322. package/dist/src/build-manifest.d.ts +5 -11
  323. package/dist/src/build-manifest.js +6 -75
  324. package/dist/src/build-manifest.test.js +1 -39
  325. package/dist/src/cascade.js +3 -2
  326. package/dist/src/cli.d.ts +3 -3
  327. package/dist/src/cli.js +71 -71
  328. package/dist/src/cli.test.js +20 -15
  329. package/dist/src/clis/binance/asks.d.ts +1 -0
  330. package/dist/src/clis/binance/asks.js +20 -0
  331. package/dist/src/clis/binance/commands.test.d.ts +3 -1
  332. package/dist/src/clis/binance/commands.test.js +10 -6
  333. package/dist/src/clis/binance/depth.d.ts +1 -0
  334. package/dist/src/clis/binance/depth.js +20 -0
  335. package/dist/src/clis/binance/gainers.d.ts +1 -0
  336. package/dist/src/clis/binance/gainers.js +21 -0
  337. package/dist/src/clis/binance/klines.d.ts +1 -0
  338. package/dist/src/clis/binance/klines.js +20 -0
  339. package/dist/src/clis/binance/losers.d.ts +1 -0
  340. package/dist/src/clis/binance/losers.js +21 -0
  341. package/dist/src/clis/binance/pairs.d.ts +1 -0
  342. package/dist/src/clis/binance/pairs.js +20 -0
  343. package/dist/src/clis/binance/price.d.ts +1 -0
  344. package/dist/src/clis/binance/price.js +17 -0
  345. package/dist/src/clis/binance/prices.d.ts +1 -0
  346. package/dist/src/clis/binance/prices.js +18 -0
  347. package/dist/src/clis/binance/ticker.d.ts +1 -0
  348. package/dist/src/clis/binance/ticker.js +20 -0
  349. package/dist/src/clis/binance/top.d.ts +1 -0
  350. package/dist/src/clis/binance/top.js +20 -0
  351. package/dist/src/clis/binance/trades.d.ts +1 -0
  352. package/dist/src/clis/binance/trades.js +19 -0
  353. package/dist/src/commands/daemon.d.ts +2 -6
  354. package/dist/src/commands/daemon.js +2 -58
  355. package/dist/src/commands/daemon.test.js +24 -120
  356. package/dist/src/completion-fast.d.ts +25 -0
  357. package/dist/src/completion-fast.js +140 -0
  358. package/dist/src/completion.d.ts +1 -0
  359. package/dist/src/completion.js +1 -0
  360. package/dist/src/constants.d.ts +0 -2
  361. package/dist/src/constants.js +0 -2
  362. package/dist/src/daemon.d.ts +1 -1
  363. package/dist/src/daemon.js +2 -15
  364. package/dist/src/diagnostic.test.js +2 -2
  365. package/dist/src/discovery.d.ts +3 -3
  366. package/dist/src/discovery.js +34 -97
  367. package/dist/src/download/index.d.ts +1 -1
  368. package/dist/src/engine.test.js +4 -19
  369. package/dist/src/execution.js +5 -1
  370. package/dist/src/generate-verified.d.ts +105 -0
  371. package/dist/src/generate-verified.js +696 -0
  372. package/dist/src/generate-verified.test.d.ts +1 -0
  373. package/dist/src/generate-verified.test.js +925 -0
  374. package/dist/src/generate.d.ts +9 -1
  375. package/dist/src/generate.js +2 -2
  376. package/dist/src/main.js +65 -12
  377. package/dist/src/pipeline/steps/download.d.ts +1 -17
  378. package/dist/src/pipeline/steps/download.js +20 -31
  379. package/dist/src/pipeline/steps/intercept.d.ts +1 -1
  380. package/dist/src/pipeline/steps/intercept.js +1 -1
  381. package/dist/src/pipeline/steps/tap.d.ts +1 -1
  382. package/dist/src/pipeline/steps/tap.js +1 -1
  383. package/dist/src/plugin-scaffold.d.ts +2 -2
  384. package/dist/src/plugin-scaffold.js +24 -21
  385. package/dist/src/plugin-scaffold.test.js +1 -1
  386. package/dist/src/plugin.d.ts +1 -1
  387. package/dist/src/plugin.js +4 -6
  388. package/dist/src/plugin.test.js +31 -31
  389. package/dist/src/record.js +26 -25
  390. package/dist/src/runtime-detect.js +3 -7
  391. package/dist/src/scripts/framework.d.ts +3 -0
  392. package/dist/src/scripts/framework.js +8 -4
  393. package/dist/src/scripts/store.d.ts +5 -1
  394. package/dist/src/scripts/store.js +5 -1
  395. package/dist/src/skill-generate.d.ts +30 -0
  396. package/dist/src/skill-generate.js +75 -0
  397. package/dist/src/skill-generate.test.d.ts +1 -0
  398. package/dist/src/skill-generate.test.js +173 -0
  399. package/dist/src/synthesize.d.ts +1 -1
  400. package/dist/src/synthesize.js +7 -8
  401. package/dist/src/types.d.ts +3 -1
  402. package/package.json +4 -3
  403. package/dist/clis/bilibili/hot.yaml +0 -38
  404. package/dist/clis/bluesky/feeds.yaml +0 -29
  405. package/dist/clis/bluesky/followers.yaml +0 -33
  406. package/dist/clis/bluesky/following.yaml +0 -33
  407. package/dist/clis/bluesky/profile.yaml +0 -27
  408. package/dist/clis/bluesky/search.yaml +0 -34
  409. package/dist/clis/bluesky/starter-packs.yaml +0 -34
  410. package/dist/clis/bluesky/thread.yaml +0 -32
  411. package/dist/clis/bluesky/trending.yaml +0 -27
  412. package/dist/clis/bluesky/user.yaml +0 -34
  413. package/dist/clis/devto/tag.yaml +0 -34
  414. package/dist/clis/devto/top.yaml +0 -29
  415. package/dist/clis/devto/user.yaml +0 -33
  416. package/dist/clis/dictionary/examples.yaml +0 -25
  417. package/dist/clis/dictionary/search.yaml +0 -27
  418. package/dist/clis/dictionary/synonyms.yaml +0 -25
  419. package/dist/clis/douban/subject.yaml +0 -107
  420. package/dist/clis/douban/top250.yaml +0 -70
  421. package/dist/clis/facebook/add-friend.yaml +0 -43
  422. package/dist/clis/facebook/events.yaml +0 -44
  423. package/dist/clis/facebook/feed.yaml +0 -63
  424. package/dist/clis/facebook/friends.yaml +0 -42
  425. package/dist/clis/facebook/groups.yaml +0 -50
  426. package/dist/clis/facebook/join-group.yaml +0 -44
  427. package/dist/clis/facebook/memories.yaml +0 -39
  428. package/dist/clis/facebook/notifications.yaml +0 -40
  429. package/dist/clis/facebook/profile.yaml +0 -37
  430. package/dist/clis/facebook/search.yaml +0 -47
  431. package/dist/clis/hackernews/ask.yaml +0 -38
  432. package/dist/clis/hackernews/best.yaml +0 -38
  433. package/dist/clis/hackernews/jobs.yaml +0 -36
  434. package/dist/clis/hackernews/new.yaml +0 -38
  435. package/dist/clis/hackernews/search.yaml +0 -44
  436. package/dist/clis/hackernews/show.yaml +0 -38
  437. package/dist/clis/hackernews/top.yaml +0 -38
  438. package/dist/clis/hackernews/user.yaml +0 -25
  439. package/dist/clis/hupu/hot.yaml +0 -43
  440. package/dist/clis/instagram/comment.yaml +0 -52
  441. package/dist/clis/instagram/explore.yaml +0 -43
  442. package/dist/clis/instagram/follow.yaml +0 -41
  443. package/dist/clis/instagram/followers.yaml +0 -51
  444. package/dist/clis/instagram/following.yaml +0 -51
  445. package/dist/clis/instagram/like.yaml +0 -46
  446. package/dist/clis/instagram/profile.yaml +0 -42
  447. package/dist/clis/instagram/save.yaml +0 -46
  448. package/dist/clis/instagram/saved.yaml +0 -40
  449. package/dist/clis/instagram/search.yaml +0 -44
  450. package/dist/clis/instagram/unfollow.yaml +0 -38
  451. package/dist/clis/instagram/unlike.yaml +0 -46
  452. package/dist/clis/instagram/unsave.yaml +0 -46
  453. package/dist/clis/instagram/user.yaml +0 -54
  454. package/dist/clis/jike/post.yaml +0 -59
  455. package/dist/clis/jike/topic.yaml +0 -53
  456. package/dist/clis/jike/user.yaml +0 -52
  457. package/dist/clis/jimeng/generate.yaml +0 -85
  458. package/dist/clis/jimeng/history.yaml +0 -46
  459. package/dist/clis/linux-do/categories.yaml +0 -70
  460. package/dist/clis/linux-do/search.yaml +0 -48
  461. package/dist/clis/linux-do/tags.yaml +0 -41
  462. package/dist/clis/linux-do/topic.yaml +0 -62
  463. package/dist/clis/linux-do/user-posts.yaml +0 -67
  464. package/dist/clis/linux-do/user-topics.yaml +0 -54
  465. package/dist/clis/lobsters/active.yaml +0 -29
  466. package/dist/clis/lobsters/hot.yaml +0 -29
  467. package/dist/clis/lobsters/newest.yaml +0 -29
  468. package/dist/clis/lobsters/tag.yaml +0 -34
  469. package/dist/clis/pixiv/detail.yaml +0 -49
  470. package/dist/clis/pixiv/ranking.yaml +0 -53
  471. package/dist/clis/pixiv/user.yaml +0 -46
  472. package/dist/clis/reddit/frontpage.yaml +0 -30
  473. package/dist/clis/reddit/hot.yaml +0 -47
  474. package/dist/clis/reddit/popular.yaml +0 -40
  475. package/dist/clis/reddit/search.yaml +0 -61
  476. package/dist/clis/reddit/subreddit.yaml +0 -50
  477. package/dist/clis/reddit/user-comments.yaml +0 -46
  478. package/dist/clis/reddit/user-posts.yaml +0 -44
  479. package/dist/clis/reddit/user.yaml +0 -40
  480. package/dist/clis/stackoverflow/bounties.yaml +0 -29
  481. package/dist/clis/stackoverflow/hot.yaml +0 -28
  482. package/dist/clis/stackoverflow/search.yaml +0 -33
  483. package/dist/clis/stackoverflow/unanswered.yaml +0 -28
  484. package/dist/clis/steam/top-sellers.yaml +0 -29
  485. package/dist/clis/tiktok/comment.yaml +0 -66
  486. package/dist/clis/tiktok/explore.yaml +0 -39
  487. package/dist/clis/tiktok/follow.yaml +0 -39
  488. package/dist/clis/tiktok/following.yaml +0 -46
  489. package/dist/clis/tiktok/friends.yaml +0 -47
  490. package/dist/clis/tiktok/like.yaml +0 -38
  491. package/dist/clis/tiktok/live.yaml +0 -51
  492. package/dist/clis/tiktok/notifications.yaml +0 -52
  493. package/dist/clis/tiktok/profile.yaml +0 -45
  494. package/dist/clis/tiktok/save.yaml +0 -34
  495. package/dist/clis/tiktok/search.yaml +0 -47
  496. package/dist/clis/tiktok/unfollow.yaml +0 -44
  497. package/dist/clis/tiktok/unlike.yaml +0 -38
  498. package/dist/clis/tiktok/unsave.yaml +0 -36
  499. package/dist/clis/tiktok/user.yaml +0 -44
  500. package/dist/clis/v2ex/hot.yaml +0 -28
  501. package/dist/clis/v2ex/latest.yaml +0 -28
  502. package/dist/clis/v2ex/member.yaml +0 -29
  503. package/dist/clis/v2ex/node.yaml +0 -34
  504. package/dist/clis/v2ex/nodes.yaml +0 -31
  505. package/dist/clis/v2ex/replies.yaml +0 -32
  506. package/dist/clis/v2ex/topic.yaml +0 -33
  507. package/dist/clis/v2ex/user.yaml +0 -34
  508. package/dist/clis/xiaoe/catalog.yaml +0 -129
  509. package/dist/clis/xiaoe/content.yaml +0 -43
  510. package/dist/clis/xiaoe/courses.yaml +0 -73
  511. package/dist/clis/xiaoe/detail.yaml +0 -39
  512. package/dist/clis/xiaoe/play-url.yaml +0 -124
  513. package/dist/clis/xiaohongshu/feed.yaml +0 -31
  514. package/dist/clis/xiaohongshu/notifications.yaml +0 -37
  515. package/dist/clis/xueqiu/earnings-date.yaml +0 -69
  516. package/dist/clis/xueqiu/feed.yaml +0 -53
  517. package/dist/clis/xueqiu/groups.yaml +0 -23
  518. package/dist/clis/xueqiu/hot-stock.yaml +0 -49
  519. package/dist/clis/xueqiu/hot.yaml +0 -46
  520. package/dist/clis/xueqiu/kline.yaml +0 -65
  521. package/dist/clis/xueqiu/search.yaml +0 -55
  522. package/dist/clis/xueqiu/stock.yaml +0 -69
  523. package/dist/clis/xueqiu/watchlist.yaml +0 -46
  524. package/dist/clis/zhihu/hot.yaml +0 -46
  525. package/dist/clis/zhihu/search.yaml +0 -59
  526. package/dist/src/daemon.test.js +0 -65
  527. package/dist/src/idle-manager.d.ts +0 -19
  528. package/dist/src/idle-manager.js +0 -54
  529. package/dist/src/yaml-schema.d.ts +0 -29
  530. package/dist/src/yaml-schema.js +0 -22
  531. /package/dist/{src/daemon.test.d.ts → clis/bilibili/hot.d.ts} +0 -0
@@ -45,7 +45,7 @@ export class BrowserBridge {
45
45
  if (this._state === 'closed')
46
46
  return;
47
47
  this._state = 'closing';
48
- // We don't kill the daemon — it auto-exits on idle.
48
+ // We don't kill the daemon — it's persistent.
49
49
  // Just clean up our reference.
50
50
  this._page = null;
51
51
  this._state = 'closed';
@@ -7,6 +7,9 @@ import type { BrowserSessionInfo } from '../types.js';
7
7
  export interface DaemonCommand {
8
8
  id: string;
9
9
  action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'insert-text' | 'bind-current' | 'network-capture-start' | 'network-capture-read' | 'cdp';
10
+ /** Target page identity (targetId). Cross-layer contract — preferred over tabId. */
11
+ page?: string;
12
+ /** @deprecated Legacy tab ID — use `page` (targetId) instead. */
10
13
  tabId?: number;
11
14
  code?: string;
12
15
  workspace?: string;
@@ -29,12 +32,16 @@ export interface DaemonCommand {
29
32
  pattern?: string;
30
33
  cdpMethod?: string;
31
34
  cdpParams?: Record<string, unknown>;
35
+ /** When true, automation windows are created in the foreground */
36
+ windowFocused?: boolean;
32
37
  }
33
38
  export interface DaemonResult {
34
39
  id: string;
35
40
  ok: boolean;
36
41
  data?: unknown;
37
42
  error?: string;
43
+ /** Page identity (targetId) — present on page-scoped command responses */
44
+ page?: string;
38
45
  }
39
46
  export interface DaemonStatus {
40
47
  ok: boolean;
@@ -43,7 +50,6 @@ export interface DaemonStatus {
43
50
  extensionConnected: boolean;
44
51
  extensionVersion?: string;
45
52
  pending: number;
46
- lastCliRequestTime: number;
47
53
  memoryMB: number;
48
54
  port: number;
49
55
  }
@@ -71,11 +77,17 @@ export declare function requestDaemonShutdown(opts?: {
71
77
  timeout?: number;
72
78
  }): Promise<boolean>;
73
79
  /**
74
- * Send a command to the daemon and wait for a result.
75
- * Retries up to 4 times: network errors retry at 500ms,
76
- * transient extension errors retry at 1500ms.
80
+ * Send a command to the daemon and return the result data.
77
81
  */
78
82
  export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
83
+ /**
84
+ * Like sendCommand, but returns both data and page identity (targetId).
85
+ * Use this for page-scoped commands where the caller needs the page identity.
86
+ */
87
+ export declare function sendCommandFull(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<{
88
+ data: unknown;
89
+ page?: string;
90
+ }>;
79
91
  export declare function listSessions(): Promise<BrowserSessionInfo[]>;
80
92
  export declare function bindCurrentTab(workspace: string, opts?: {
81
93
  matchDomain?: string;
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { DEFAULT_DAEMON_PORT } from '../constants.js';
7
7
  import { sleep } from '../utils.js';
8
- import { isTransientBrowserError } from './errors.js';
8
+ import { classifyBrowserError } from './errors.js';
9
9
  const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
10
10
  const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
11
11
  const OPENCLI_HEADERS = { 'X-OpenCLI': '1' };
@@ -61,16 +61,21 @@ export async function requestDaemonShutdown(opts) {
61
61
  }
62
62
  }
63
63
  /**
64
- * Send a command to the daemon and wait for a result.
65
- * Retries up to 4 times: network errors retry at 500ms,
66
- * transient extension errors retry at 1500ms.
64
+ * Internal: send a command to the daemon with retry logic.
65
+ * Returns the raw DaemonResult. All retry policy lives here — callers
66
+ * (sendCommand, sendCommandFull) only shape the return value.
67
+ *
68
+ * Retries up to 4 times:
69
+ * - Network errors (TypeError, AbortError): retry at 500ms
70
+ * - Transient browser errors: retry at the delay suggested by classifyBrowserError()
67
71
  */
68
- export async function sendCommand(action, params = {}) {
72
+ async function sendCommandRaw(action, params) {
69
73
  const maxRetries = 4;
70
74
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
71
- // Generate a fresh ID per attempt to avoid daemon-side duplicate detection
72
75
  const id = generateId();
73
- const command = { id, action, ...params };
76
+ const wf = process.env.OPENCLI_WINDOW_FOCUSED;
77
+ const windowFocused = (wf === '1' || wf === 'true') ? true : undefined;
78
+ const command = { id, action, ...params, ...(windowFocused && { windowFocused }) };
74
79
  try {
75
80
  const res = await requestDaemon('/command', {
76
81
  method: 'POST',
@@ -80,29 +85,42 @@ export async function sendCommand(action, params = {}) {
80
85
  });
81
86
  const result = (await res.json());
82
87
  if (!result.ok) {
83
- // Check if error is a transient extension issue worth retrying
84
- if (isTransientBrowserError(new Error(result.error ?? '')) && attempt < maxRetries) {
85
- // Longer delay for extension recovery (service worker restart)
86
- await sleep(1500);
88
+ const advice = classifyBrowserError(new Error(result.error ?? ''));
89
+ if (advice.retryable && attempt < maxRetries) {
90
+ await sleep(advice.delayMs);
87
91
  continue;
88
92
  }
89
93
  throw new Error(result.error ?? 'Daemon command failed');
90
94
  }
91
- return result.data;
95
+ return result;
92
96
  }
93
97
  catch (err) {
94
- const isRetryable = err instanceof TypeError // fetch network error
98
+ const isNetworkError = err instanceof TypeError
95
99
  || (err instanceof Error && err.name === 'AbortError');
96
- if (isRetryable && attempt < maxRetries) {
100
+ if (isNetworkError && attempt < maxRetries) {
97
101
  await sleep(500);
98
102
  continue;
99
103
  }
100
104
  throw err;
101
105
  }
102
106
  }
103
- // Unreachable — the loop always returns or throws
104
107
  throw new Error('sendCommand: max retries exhausted');
105
108
  }
109
+ /**
110
+ * Send a command to the daemon and return the result data.
111
+ */
112
+ export async function sendCommand(action, params = {}) {
113
+ const result = await sendCommandRaw(action, params);
114
+ return result.data;
115
+ }
116
+ /**
117
+ * Like sendCommand, but returns both data and page identity (targetId).
118
+ * Use this for page-scoped commands where the caller needs the page identity.
119
+ */
120
+ export async function sendCommandFull(action, params = {}) {
121
+ const result = await sendCommandRaw(action, params);
122
+ return { data: result.data, page: result.page };
123
+ }
106
124
  export async function listSessions() {
107
125
  const result = await sendCommand('sessions');
108
126
  return Array.isArray(result) ? result : [];
@@ -15,7 +15,6 @@ describe('daemon-client', () => {
15
15
  extensionConnected: true,
16
16
  extensionVersion: '1.2.3',
17
17
  pending: 0,
18
- lastCliRequestTime: Date.now(),
19
18
  memoryMB: 32,
20
19
  port: 19825,
21
20
  };
@@ -53,7 +52,6 @@ describe('daemon-client', () => {
53
52
  uptime: 10,
54
53
  extensionConnected: false,
55
54
  pending: 0,
56
- lastCliRequestTime: Date.now(),
57
55
  memoryMB: 16,
58
56
  port: 19825,
59
57
  };
@@ -71,7 +69,6 @@ describe('daemon-client', () => {
71
69
  extensionConnected: true,
72
70
  extensionVersion: '1.2.3',
73
71
  pending: 0,
74
- lastCliRequestTime: Date.now(),
75
72
  memoryMB: 32,
76
73
  port: 19825,
77
74
  };
@@ -24,11 +24,12 @@ describe('waitForCaptureJs', () => {
24
24
  });
25
25
  it('resolves "captured" when __opencli_xhr is populated before deadline', async () => {
26
26
  const g = globalThis;
27
- g.__opencli_xhr = [];
27
+ const captured = [];
28
+ g.__opencli_xhr = captured;
28
29
  g.window = g; // stub window for Node eval
29
30
  const code = waitForCaptureJs(1000);
30
31
  const promise = eval(code);
31
- g.__opencli_xhr.push({ data: 'test' });
32
+ captured.push({ data: 'test' });
32
33
  await expect(promise).resolves.toBe('captured');
33
34
  delete g.__opencli_xhr;
34
35
  delete g.window;
@@ -6,7 +6,32 @@
6
6
  */
7
7
  import { BrowserConnectError, type BrowserConnectKind } from '../errors.js';
8
8
  /**
9
- * Check if an error message indicates a transient browser error worth retrying.
9
+ * Unified browser error classification.
10
+ *
11
+ * All transient error detection lives here — daemon-client, pipeline executor,
12
+ * and page retry logic all use this single system instead of maintaining
13
+ * separate pattern lists.
14
+ */
15
+ /** Error category — determines which layer should retry. */
16
+ export type BrowserErrorKind = 'extension-transient' | 'target-navigation' | 'non-retryable';
17
+ /** How the caller should handle the error. */
18
+ export interface RetryAdvice {
19
+ /** Error category — callers use this to decide whether *they* should retry. */
20
+ kind: BrowserErrorKind;
21
+ /** Whether the error is transient and worth retrying. */
22
+ retryable: boolean;
23
+ /** Suggested delay before retry (ms). */
24
+ delayMs: number;
25
+ }
26
+ /**
27
+ * Classify a browser error and return retry advice.
28
+ *
29
+ * Single source of truth for "is this error transient?" across all layers.
30
+ */
31
+ export declare function classifyBrowserError(err: unknown): RetryAdvice;
32
+ /**
33
+ * Check if an error is a transient browser error worth retrying.
34
+ * Convenience wrapper around classifyBrowserError().
10
35
  */
11
36
  export declare function isTransientBrowserError(err: unknown): boolean;
12
37
  export type ConnectFailureKind = BrowserConnectKind;
@@ -7,24 +7,57 @@
7
7
  import { BrowserConnectError } from '../errors.js';
8
8
  import { DEFAULT_DAEMON_PORT } from '../constants.js';
9
9
  /**
10
- * Transient browser error patterns — shared across daemon-client, pipeline executor,
11
- * and page retry logic. These errors indicate temporary conditions (extension restart,
12
- * service worker cycle, tab navigation) that are worth retrying.
10
+ * Extension/daemon transient patterns — service worker restarts, attach races,
11
+ * tab closure, daemon hiccups. These warrant a longer retry delay (~1500ms)
12
+ * because the extension needs time to recover.
13
13
  */
14
- const TRANSIENT_ERROR_PATTERNS = [
14
+ const EXTENSION_TRANSIENT_PATTERNS = [
15
15
  'Extension disconnected',
16
16
  'Extension not connected',
17
17
  'attach failed',
18
18
  'no longer exists',
19
19
  'CDP connection',
20
20
  'Daemon command failed',
21
+ 'No window with id',
21
22
  ];
22
23
  /**
23
- * Check if an error message indicates a transient browser error worth retrying.
24
+ * CDP target navigation patterns SPA client-side redirects can invalidate the
25
+ * CDP target after chrome.tabs reports 'complete'. These warrant a shorter retry
26
+ * delay (~200ms) because the new document is usually available quickly.
27
+ */
28
+ const TARGET_NAVIGATION_PATTERNS = [
29
+ 'Inspected target navigated or closed',
30
+ ];
31
+ function errorMessage(err) {
32
+ return err instanceof Error ? err.message : String(err);
33
+ }
34
+ /**
35
+ * Classify a browser error and return retry advice.
36
+ *
37
+ * Single source of truth for "is this error transient?" across all layers.
38
+ */
39
+ export function classifyBrowserError(err) {
40
+ const msg = errorMessage(err);
41
+ // Extension/daemon transient errors — longer recovery time
42
+ if (EXTENSION_TRANSIENT_PATTERNS.some(p => msg.includes(p))) {
43
+ return { kind: 'extension-transient', retryable: true, delayMs: 1500 };
44
+ }
45
+ // CDP target navigation errors — shorter recovery time
46
+ if (TARGET_NAVIGATION_PATTERNS.some(p => msg.includes(p))) {
47
+ return { kind: 'target-navigation', retryable: true, delayMs: 200 };
48
+ }
49
+ // CDP protocol error with target context (e.g., -32000 "target closed")
50
+ if (msg.includes('-32000') && msg.toLowerCase().includes('target')) {
51
+ return { kind: 'target-navigation', retryable: true, delayMs: 200 };
52
+ }
53
+ return { kind: 'non-retryable', retryable: false, delayMs: 0 };
54
+ }
55
+ /**
56
+ * Check if an error is a transient browser error worth retrying.
57
+ * Convenience wrapper around classifyBrowserError().
24
58
  */
25
59
  export function isTransientBrowserError(err) {
26
- const msg = err instanceof Error ? err.message : String(err);
27
- return TRANSIENT_ERROR_PATTERNS.some(pattern => msg.includes(pattern));
60
+ return classifyBrowserError(err).retryable;
28
61
  }
29
62
  export function formatBrowserConnectError(kind, detail) {
30
63
  switch (kind) {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { classifyBrowserError, isTransientBrowserError } from './errors.js';
3
+ describe('classifyBrowserError', () => {
4
+ it('classifies extension transient errors with 1500ms delay', () => {
5
+ for (const msg of [
6
+ 'Extension disconnected',
7
+ 'Extension not connected',
8
+ 'attach failed',
9
+ 'no longer exists',
10
+ 'CDP connection reset',
11
+ 'Daemon command failed',
12
+ 'No window with id: 123',
13
+ ]) {
14
+ const advice = classifyBrowserError(new Error(msg));
15
+ expect(advice.kind, `expected "${msg}" → extension-transient`).toBe('extension-transient');
16
+ expect(advice.retryable).toBe(true);
17
+ expect(advice.delayMs).toBe(1500);
18
+ }
19
+ });
20
+ it('classifies CDP target navigation errors with 200ms delay', () => {
21
+ const advice = classifyBrowserError(new Error('Inspected target navigated or closed'));
22
+ expect(advice.kind).toBe('target-navigation');
23
+ expect(advice.retryable).toBe(true);
24
+ expect(advice.delayMs).toBe(200);
25
+ });
26
+ it('classifies CDP -32000 target errors with 200ms delay', () => {
27
+ const advice = classifyBrowserError(new Error('{"code":-32000,"message":"Target closed"}'));
28
+ expect(advice.kind).toBe('target-navigation');
29
+ expect(advice.retryable).toBe(true);
30
+ expect(advice.delayMs).toBe(200);
31
+ });
32
+ it('returns non-retryable for unrelated errors', () => {
33
+ for (const msg of ['Permission denied', 'malformed exec payload', 'SyntaxError']) {
34
+ const advice = classifyBrowserError(new Error(msg));
35
+ expect(advice.kind).toBe('non-retryable');
36
+ expect(advice.retryable).toBe(false);
37
+ }
38
+ });
39
+ it('handles non-Error values', () => {
40
+ expect(classifyBrowserError('Extension disconnected').kind).toBe('extension-transient');
41
+ expect(classifyBrowserError(42).kind).toBe('non-retryable');
42
+ });
43
+ });
44
+ describe('isTransientBrowserError (convenience wrapper)', () => {
45
+ it('returns true for transient errors', () => {
46
+ expect(isTransientBrowserError(new Error('No window with id: 123'))).toBe(true);
47
+ });
48
+ it('returns false for non-transient errors', () => {
49
+ expect(isTransientBrowserError(new Error('Permission denied'))).toBe(false);
50
+ });
51
+ });
@@ -4,30 +4,31 @@
4
4
  * All browser operations are ultimately 'exec' (JS evaluation via CDP)
5
5
  * plus a few native Chrome Extension APIs (tabs, cookies, navigate).
6
6
  *
7
- * IMPORTANT: After goto(), we remember the tabId returned by the navigate
8
- * action and pass it to all subsequent commands. This avoids the issue
9
- * where resolveTabId() in the extension picks a chrome:// or
10
- * chrome-extension:// tab that can't be debugged.
7
+ * IMPORTANT: After goto(), we remember the page identity (targetId) returned
8
+ * by the navigate action and pass it to all subsequent commands. This ensures
9
+ * page-scoped operations target the correct page without guessing.
11
10
  */
12
11
  import type { BrowserCookie, ScreenshotOptions } from '../types.js';
13
12
  import { BasePage } from './base-page.js';
14
- export declare function isRetryableSettleError(err: unknown): boolean;
15
13
  /**
16
14
  * Page — implements IPage by talking to the daemon via HTTP.
17
15
  */
18
16
  export declare class Page extends BasePage {
19
17
  private readonly workspace;
20
18
  constructor(workspace?: string);
21
- /** Active tab ID, set after navigate and used in all subsequent commands */
22
- private _tabId;
19
+ /** Active page identity (targetId), set after navigate and used in all subsequent commands */
20
+ private _page;
23
21
  /** Helper: spread workspace into command params */
24
22
  private _wsOpt;
25
- /** Helper: spread workspace + tabId into command params */
23
+ /** Helper: spread workspace + page identity into command params */
26
24
  private _cmdOpts;
27
25
  goto(url: string, options?: {
28
26
  waitUntil?: 'load' | 'none';
29
27
  settleMs?: number;
30
28
  }): Promise<void>;
29
+ /** Get the active page identity (targetId) */
30
+ getActivePage(): string | undefined;
31
+ /** @deprecated Use getActivePage() instead */
31
32
  getActiveTabId(): number | undefined;
32
33
  evaluate(js: string): Promise<unknown>;
33
34
  getCookies(opts?: {
@@ -4,22 +4,17 @@
4
4
  * All browser operations are ultimately 'exec' (JS evaluation via CDP)
5
5
  * plus a few native Chrome Extension APIs (tabs, cookies, navigate).
6
6
  *
7
- * IMPORTANT: After goto(), we remember the tabId returned by the navigate
8
- * action and pass it to all subsequent commands. This avoids the issue
9
- * where resolveTabId() in the extension picks a chrome:// or
10
- * chrome-extension:// tab that can't be debugged.
7
+ * IMPORTANT: After goto(), we remember the page identity (targetId) returned
8
+ * by the navigate action and pass it to all subsequent commands. This ensures
9
+ * page-scoped operations target the correct page without guessing.
11
10
  */
12
- import { sendCommand } from './daemon-client.js';
11
+ import { sendCommand, sendCommandFull } from './daemon-client.js';
13
12
  import { wrapForEval } from './utils.js';
14
13
  import { saveBase64ToFile } from '../utils.js';
15
14
  import { generateStealthJs } from './stealth.js';
16
15
  import { waitForDomStableJs } from './dom-helpers.js';
17
16
  import { BasePage } from './base-page.js';
18
- export function isRetryableSettleError(err) {
19
- const message = err instanceof Error ? err.message : String(err);
20
- return message.includes('Inspected target navigated or closed')
21
- || (message.includes('-32000') && message.toLowerCase().includes('target'));
22
- }
17
+ import { classifyBrowserError } from './errors.js';
23
18
  /**
24
19
  * Page — implements IPage by talking to the daemon via HTTP.
25
20
  */
@@ -29,27 +24,27 @@ export class Page extends BasePage {
29
24
  super();
30
25
  this.workspace = workspace;
31
26
  }
32
- /** Active tab ID, set after navigate and used in all subsequent commands */
33
- _tabId;
27
+ /** Active page identity (targetId), set after navigate and used in all subsequent commands */
28
+ _page;
34
29
  /** Helper: spread workspace into command params */
35
30
  _wsOpt() {
36
31
  return { workspace: this.workspace };
37
32
  }
38
- /** Helper: spread workspace + tabId into command params */
33
+ /** Helper: spread workspace + page identity into command params */
39
34
  _cmdOpts() {
40
35
  return {
41
36
  workspace: this.workspace,
42
- ...(this._tabId !== undefined && { tabId: this._tabId }),
37
+ ...(this._page !== undefined && { page: this._page }),
43
38
  };
44
39
  }
45
40
  async goto(url, options) {
46
- const result = await sendCommand('navigate', {
41
+ const result = await sendCommandFull('navigate', {
47
42
  url,
48
43
  ...this._cmdOpts(),
49
44
  });
50
- // Remember the tabId and URL for subsequent calls
51
- if (result?.tabId) {
52
- this._tabId = result.tabId;
45
+ // Remember the page identity (targetId) for subsequent calls
46
+ if (result.page) {
47
+ this._page = result.page;
53
48
  }
54
49
  this._lastUrl = url;
55
50
  // Inject stealth + settle in a single round-trip instead of two sequential exec calls.
@@ -65,17 +60,18 @@ export class Page extends BasePage {
65
60
  await sendCommand('exec', combinedOpts);
66
61
  }
67
62
  catch (err) {
68
- if (!isRetryableSettleError(err))
63
+ const advice = classifyBrowserError(err);
64
+ // Only settle-retry on target navigation (SPA client-side redirects).
65
+ // Extension/daemon errors are already retried by sendCommandRaw —
66
+ // retrying them here would silently swallow real failures.
67
+ if (advice.kind !== 'target-navigation')
69
68
  throw err;
70
- // SPA client-side redirects can invalidate the CDP target after
71
- // chrome.tabs reports 'complete'. Wait briefly for the new document
72
- // to load, then retry the settle probe once.
73
69
  try {
74
- await new Promise((r) => setTimeout(r, 200));
70
+ await new Promise((r) => setTimeout(r, advice.delayMs));
75
71
  await sendCommand('exec', combinedOpts);
76
72
  }
77
73
  catch (retryErr) {
78
- if (!isRetryableSettleError(retryErr))
74
+ if (classifyBrowserError(retryErr).kind !== 'target-navigation')
79
75
  throw retryErr;
80
76
  }
81
77
  }
@@ -93,8 +89,13 @@ export class Page extends BasePage {
93
89
  }
94
90
  }
95
91
  }
92
+ /** Get the active page identity (targetId) */
93
+ getActivePage() {
94
+ return this._page;
95
+ }
96
+ /** @deprecated Use getActivePage() instead */
96
97
  getActiveTabId() {
97
- return this._tabId;
98
+ return undefined;
98
99
  }
99
100
  async evaluate(js) {
100
101
  const code = wrapForEval(js);
@@ -102,9 +103,10 @@ export class Page extends BasePage {
102
103
  return await sendCommand('exec', { code, ...this._cmdOpts() });
103
104
  }
104
105
  catch (err) {
105
- if (!isRetryableSettleError(err))
106
+ const advice = classifyBrowserError(err);
107
+ if (advice.kind !== 'target-navigation')
106
108
  throw err;
107
- await new Promise((resolve) => setTimeout(resolve, 200));
109
+ await new Promise((resolve) => setTimeout(resolve, advice.delayMs));
108
110
  return sendCommand('exec', { code, ...this._cmdOpts() });
109
111
  }
110
112
  }
@@ -121,7 +123,7 @@ export class Page extends BasePage {
121
123
  // Window may already be closed or daemon may be down
122
124
  }
123
125
  finally {
124
- this._tabId = undefined;
126
+ this._page = undefined;
125
127
  this._lastUrl = null;
126
128
  }
127
129
  }
@@ -130,9 +132,9 @@ export class Page extends BasePage {
130
132
  return Array.isArray(result) ? result : [];
131
133
  }
132
134
  async selectTab(index) {
133
- const result = await sendCommand('tabs', { op: 'select', index, ...this._wsOpt() });
134
- if (result?.selected)
135
- this._tabId = result.selected;
135
+ const result = await sendCommandFull('tabs', { op: 'select', index, ...this._wsOpt() });
136
+ if (result.page)
137
+ this._page = result.page;
136
138
  }
137
139
  /**
138
140
  * Capture a screenshot via CDP Page.captureScreenshot.
@@ -3,7 +3,7 @@ import { BrowserBridge, generateStealthJs } from './browser/index.js';
3
3
  import { extractTabEntries, diffTabIndexes, appendLimited } from './browser/tabs.js';
4
4
  import { withTimeoutMs } from './runtime.js';
5
5
  import { __test__ as cdpTest } from './browser/cdp.js';
6
- import { isRetryableSettleError } from './browser/page.js';
6
+ import { classifyBrowserError } from './browser/errors.js';
7
7
  import * as daemonClient from './browser/daemon-client.js';
8
8
  describe('browser helpers', () => {
9
9
  it('extracts tab entries from string snapshots', () => {
@@ -35,10 +35,18 @@ describe('browser helpers', () => {
35
35
  it('times out slow promises', async () => {
36
36
  await expect(withTimeoutMs(new Promise(() => { }), 10, 'timeout')).rejects.toThrow('timeout');
37
37
  });
38
- it('retries settle only for target-invalidated errors', () => {
39
- expect(isRetryableSettleError(new Error('{"code":-32000,"message":"Inspected target navigated or closed"}'))).toBe(true);
40
- expect(isRetryableSettleError(new Error('attach failed: target no longer exists'))).toBe(false);
41
- expect(isRetryableSettleError(new Error('malformed exec payload'))).toBe(false);
38
+ it('classifies browser errors with correct kind and retry advice', () => {
39
+ // CDP target navigation page-level settle retry
40
+ const nav = classifyBrowserError(new Error('{"code":-32000,"message":"Inspected target navigated or closed"}'));
41
+ expect(nav.kind).toBe('target-navigation');
42
+ expect(nav.delayMs).toBe(200);
43
+ // Extension transient — daemon-client retry only, NOT page-level
44
+ const ext = classifyBrowserError(new Error('Extension disconnected'));
45
+ expect(ext.kind).toBe('extension-transient');
46
+ expect(ext.delayMs).toBe(1500);
47
+ // Non-transient errors — not retryable
48
+ expect(classifyBrowserError(new Error('malformed exec payload')).kind).toBe('non-retryable');
49
+ expect(classifyBrowserError(new Error('Permission denied')).kind).toBe('non-retryable');
42
50
  });
43
51
  it('prefers the real Electron app target over DevTools and blank pages', () => {
44
52
  const target = cdpTest.selectCDPTarget([
@@ -105,7 +113,18 @@ describe('BrowserBridge state', () => {
105
113
  await expect(bridge.connect()).rejects.toThrow('Session is closing');
106
114
  });
107
115
  it('fails fast when daemon is running but extension is disconnected', async () => {
108
- vi.spyOn(daemonClient, 'getDaemonHealth').mockResolvedValue({ state: 'no-extension', status: { extensionConnected: false } });
116
+ vi.spyOn(daemonClient, 'getDaemonHealth').mockResolvedValue({
117
+ state: 'no-extension',
118
+ status: {
119
+ ok: true,
120
+ pid: 1,
121
+ uptime: 0,
122
+ extensionConnected: false,
123
+ pending: 0,
124
+ memoryMB: 0,
125
+ port: 0,
126
+ },
127
+ });
109
128
  const bridge = new BrowserBridge();
110
129
  await expect(bridge.connect({ timeout: 0.1 })).rejects.toThrow('Browser Bridge extension not connected');
111
130
  });
@@ -2,8 +2,8 @@
2
2
  /**
3
3
  * Build-time CLI manifest compiler.
4
4
  *
5
- * Scans all YAML/TS CLI definitions and pre-compiles them into a single
6
- * manifest.json for instant cold-start registration (no runtime YAML parsing).
5
+ * Scans all TS CLI definitions and pre-compiles them into a single
6
+ * manifest.json for instant cold-start registration.
7
7
  *
8
8
  * Usage: npx tsx src/build-manifest.ts
9
9
  * Output: cli-manifest.json at the package root
@@ -31,19 +31,13 @@ export interface ManifestEntry {
31
31
  timeout?: number;
32
32
  deprecated?: boolean | string;
33
33
  replacedBy?: string;
34
- /** 'yaml' or 'ts' — determines how executeCommand loads the handler */
35
- type: 'yaml' | 'ts';
36
- /** Relative path from clis/ dir, e.g. 'bilibili/hot.yaml' or 'bilibili/search.js' */
34
+ type: 'ts';
35
+ /** Relative path from clis/ dir, e.g. 'bilibili/search.js' */
37
36
  modulePath?: string;
38
- /** Relative path to the original source file from clis/ dir (for YAML: 'site/cmd.yaml') */
37
+ /** Relative path to the original source file from clis/ dir (e.g. 'site/cmd.ts') */
39
38
  sourceFile?: string;
40
39
  /** Pre-navigation control — see CliCommand.navigateBefore */
41
40
  navigateBefore?: boolean | string;
42
41
  }
43
42
  export declare function loadTsManifestEntries(filePath: string, site: string, importer?: (moduleHref: string) => Promise<unknown>): Promise<ManifestEntry[]>;
44
- /**
45
- * When both YAML and TS adapters exist for the same site/name,
46
- * prefer the TS version (it self-registers and typically has richer logic).
47
- */
48
- export declare function shouldReplaceManifestEntry(current: ManifestEntry, next: ManifestEntry): boolean;
49
43
  export declare function buildManifest(): Promise<ManifestEntry[]>;