@jackwener/opencli 1.6.8 → 1.6.10

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 (572) hide show
  1. package/README.md +117 -59
  2. package/README.zh-CN.md +123 -79
  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.d.ts +1 -0
  6. package/dist/clis/bilibili/hot.js +35 -0
  7. package/dist/clis/bluesky/feeds.d.ts +1 -0
  8. package/dist/clis/bluesky/feeds.js +27 -0
  9. package/dist/clis/bluesky/followers.d.ts +1 -0
  10. package/dist/clis/bluesky/followers.js +27 -0
  11. package/dist/clis/bluesky/following.d.ts +1 -0
  12. package/dist/clis/bluesky/following.js +27 -0
  13. package/dist/clis/bluesky/profile.d.ts +1 -0
  14. package/dist/clis/bluesky/profile.js +29 -0
  15. package/dist/clis/bluesky/search.d.ts +1 -0
  16. package/dist/clis/bluesky/search.js +28 -0
  17. package/dist/clis/bluesky/starter-packs.d.ts +1 -0
  18. package/dist/clis/bluesky/starter-packs.js +28 -0
  19. package/dist/clis/bluesky/thread.d.ts +1 -0
  20. package/dist/clis/bluesky/thread.js +30 -0
  21. package/dist/clis/bluesky/trending.d.ts +1 -0
  22. package/dist/clis/bluesky/trending.js +19 -0
  23. package/dist/clis/bluesky/user.d.ts +1 -0
  24. package/dist/clis/bluesky/user.js +33 -0
  25. package/dist/clis/cnki/search.d.ts +1 -0
  26. package/dist/clis/cnki/search.js +60 -0
  27. package/dist/clis/cnki/search.test.d.ts +1 -0
  28. package/dist/clis/cnki/search.test.js +18 -0
  29. package/dist/clis/devto/tag.d.ts +1 -0
  30. package/dist/clis/devto/tag.js +32 -0
  31. package/dist/clis/devto/top.d.ts +1 -0
  32. package/dist/clis/devto/top.js +26 -0
  33. package/dist/clis/devto/user.d.ts +1 -0
  34. package/dist/clis/devto/user.js +31 -0
  35. package/dist/clis/dictionary/examples.d.ts +1 -0
  36. package/dist/clis/dictionary/examples.js +27 -0
  37. package/dist/clis/dictionary/search.d.ts +1 -0
  38. package/dist/clis/dictionary/search.js +29 -0
  39. package/dist/clis/dictionary/synonyms.d.ts +1 -0
  40. package/dist/clis/dictionary/synonyms.js +27 -0
  41. package/dist/clis/douban/subject.d.ts +1 -0
  42. package/dist/clis/douban/subject.js +118 -0
  43. package/dist/clis/douban/top250.d.ts +1 -0
  44. package/dist/clis/douban/top250.js +67 -0
  45. package/dist/clis/facebook/add-friend.d.ts +1 -0
  46. package/dist/clis/facebook/add-friend.js +43 -0
  47. package/dist/clis/facebook/events.d.ts +1 -0
  48. package/dist/clis/facebook/events.js +40 -0
  49. package/dist/clis/facebook/feed.d.ts +1 -0
  50. package/dist/clis/facebook/feed.js +59 -0
  51. package/dist/clis/facebook/friends.d.ts +1 -0
  52. package/dist/clis/facebook/friends.js +38 -0
  53. package/dist/clis/facebook/groups.d.ts +1 -0
  54. package/dist/clis/facebook/groups.js +46 -0
  55. package/dist/clis/facebook/join-group.d.ts +1 -0
  56. package/dist/clis/facebook/join-group.js +44 -0
  57. package/dist/clis/facebook/memories.d.ts +1 -0
  58. package/dist/clis/facebook/memories.js +35 -0
  59. package/dist/clis/facebook/notifications.d.ts +1 -0
  60. package/dist/clis/facebook/notifications.js +36 -0
  61. package/dist/clis/facebook/profile.d.ts +1 -0
  62. package/dist/clis/facebook/profile.js +37 -0
  63. package/dist/clis/facebook/search.d.ts +1 -0
  64. package/dist/clis/facebook/search.js +38 -0
  65. package/dist/clis/facebook/search.test.d.ts +1 -1
  66. package/dist/clis/facebook/search.test.js +6 -9
  67. package/dist/clis/gitee/index.d.ts +3 -0
  68. package/dist/clis/gitee/index.js +3 -0
  69. package/dist/clis/gitee/search.d.ts +1 -0
  70. package/dist/clis/gitee/search.js +136 -0
  71. package/dist/clis/gitee/trending.d.ts +1 -0
  72. package/dist/clis/gitee/trending.js +567 -0
  73. package/dist/clis/gitee/user.d.ts +1 -0
  74. package/dist/clis/gitee/user.js +199 -0
  75. package/dist/clis/gitee/user.test.d.ts +1 -0
  76. package/dist/clis/gitee/user.test.js +63 -0
  77. package/dist/clis/hackernews/ask.d.ts +1 -0
  78. package/dist/clis/hackernews/ask.js +29 -0
  79. package/dist/clis/hackernews/best.d.ts +1 -0
  80. package/dist/clis/hackernews/best.js +29 -0
  81. package/dist/clis/hackernews/jobs.d.ts +1 -0
  82. package/dist/clis/hackernews/jobs.js +27 -0
  83. package/dist/clis/hackernews/new.d.ts +1 -0
  84. package/dist/clis/hackernews/new.js +29 -0
  85. package/dist/clis/hackernews/search.d.ts +1 -0
  86. package/dist/clis/hackernews/search.js +36 -0
  87. package/dist/clis/hackernews/show.d.ts +1 -0
  88. package/dist/clis/hackernews/show.js +29 -0
  89. package/dist/clis/hackernews/top.d.ts +1 -0
  90. package/dist/clis/hackernews/top.js +29 -0
  91. package/dist/clis/hackernews/user.d.ts +1 -0
  92. package/dist/clis/hackernews/user.js +22 -0
  93. package/dist/clis/hupu/hot.d.ts +1 -0
  94. package/dist/clis/hupu/hot.js +40 -0
  95. package/dist/clis/instagram/comment.d.ts +1 -0
  96. package/dist/clis/instagram/comment.js +47 -0
  97. package/dist/clis/instagram/explore.d.ts +1 -0
  98. package/dist/clis/instagram/explore.js +41 -0
  99. package/dist/clis/instagram/follow.d.ts +1 -0
  100. package/dist/clis/instagram/follow.js +43 -0
  101. package/dist/clis/instagram/followers.d.ts +1 -0
  102. package/dist/clis/instagram/followers.js +45 -0
  103. package/dist/clis/instagram/following.d.ts +1 -0
  104. package/dist/clis/instagram/following.js +45 -0
  105. package/dist/clis/instagram/like.d.ts +1 -0
  106. package/dist/clis/instagram/like.js +45 -0
  107. package/dist/clis/instagram/profile.d.ts +1 -0
  108. package/dist/clis/instagram/profile.js +39 -0
  109. package/dist/clis/instagram/save.d.ts +1 -0
  110. package/dist/clis/instagram/save.js +45 -0
  111. package/dist/clis/instagram/saved.d.ts +1 -0
  112. package/dist/clis/instagram/saved.js +38 -0
  113. package/dist/clis/instagram/search.d.ts +1 -0
  114. package/dist/clis/instagram/search.js +38 -0
  115. package/dist/clis/instagram/unfollow.d.ts +1 -0
  116. package/dist/clis/instagram/unfollow.js +40 -0
  117. package/dist/clis/instagram/unlike.d.ts +1 -0
  118. package/dist/clis/instagram/unlike.js +45 -0
  119. package/dist/clis/instagram/unsave.d.ts +1 -0
  120. package/dist/clis/instagram/unsave.js +45 -0
  121. package/dist/clis/instagram/user.d.ts +1 -0
  122. package/dist/clis/instagram/user.js +48 -0
  123. package/dist/clis/jd/add-cart.d.ts +1 -0
  124. package/dist/clis/jd/add-cart.js +71 -0
  125. package/dist/clis/jd/cart.d.ts +1 -0
  126. package/dist/clis/jd/cart.js +79 -0
  127. package/dist/clis/jd/commands.test.d.ts +5 -0
  128. package/dist/clis/jd/commands.test.js +64 -0
  129. package/dist/clis/jd/detail.d.ts +1 -0
  130. package/dist/clis/jd/detail.js +62 -0
  131. package/dist/clis/jd/reviews.d.ts +1 -0
  132. package/dist/clis/jd/reviews.js +54 -0
  133. package/dist/clis/jd/search.d.ts +1 -0
  134. package/dist/clis/jd/search.js +65 -0
  135. package/dist/clis/jianyu/search.d.ts +14 -0
  136. package/dist/clis/jianyu/search.js +135 -0
  137. package/dist/clis/jianyu/search.test.d.ts +1 -0
  138. package/dist/clis/jianyu/search.test.js +23 -0
  139. package/dist/clis/jike/post.d.ts +1 -0
  140. package/dist/clis/jike/post.js +61 -0
  141. package/dist/clis/jike/topic.d.ts +1 -0
  142. package/dist/clis/jike/topic.js +51 -0
  143. package/dist/clis/jike/user.d.ts +1 -0
  144. package/dist/clis/jike/user.js +50 -0
  145. package/dist/clis/jimeng/generate.d.ts +1 -0
  146. package/dist/clis/jimeng/generate.js +83 -0
  147. package/dist/clis/jimeng/history.d.ts +1 -0
  148. package/dist/clis/jimeng/history.js +47 -0
  149. package/dist/clis/jimeng/new.d.ts +1 -0
  150. package/dist/clis/jimeng/new.js +43 -0
  151. package/dist/clis/jimeng/workspaces.d.ts +1 -0
  152. package/dist/clis/jimeng/workspaces.js +41 -0
  153. package/dist/clis/linux-do/categories.d.ts +1 -0
  154. package/dist/clis/linux-do/categories.js +65 -0
  155. package/dist/clis/linux-do/search.d.ts +1 -0
  156. package/dist/clis/linux-do/search.js +41 -0
  157. package/dist/clis/linux-do/tags.d.ts +1 -0
  158. package/dist/clis/linux-do/tags.js +39 -0
  159. package/dist/clis/linux-do/topic-content.test.js +5 -5
  160. package/dist/clis/linux-do/topic.d.ts +1 -0
  161. package/dist/clis/linux-do/topic.js +56 -0
  162. package/dist/clis/linux-do/user-posts.d.ts +1 -0
  163. package/dist/clis/linux-do/user-posts.js +61 -0
  164. package/dist/clis/linux-do/user-topics.d.ts +1 -0
  165. package/dist/clis/linux-do/user-topics.js +48 -0
  166. package/dist/clis/lobsters/active.d.ts +1 -0
  167. package/dist/clis/lobsters/active.js +26 -0
  168. package/dist/clis/lobsters/hot.d.ts +1 -0
  169. package/dist/clis/lobsters/hot.js +26 -0
  170. package/dist/clis/lobsters/newest.d.ts +1 -0
  171. package/dist/clis/lobsters/newest.js +26 -0
  172. package/dist/clis/lobsters/tag.d.ts +1 -0
  173. package/dist/clis/lobsters/tag.js +32 -0
  174. package/dist/clis/pixiv/detail.d.ts +1 -0
  175. package/dist/clis/pixiv/detail.js +58 -0
  176. package/dist/clis/pixiv/ranking.d.ts +1 -0
  177. package/dist/clis/pixiv/ranking.js +59 -0
  178. package/dist/clis/pixiv/user.d.ts +1 -0
  179. package/dist/clis/pixiv/user.js +52 -0
  180. package/dist/clis/quark/ls.d.ts +1 -0
  181. package/dist/clis/quark/ls.js +63 -0
  182. package/dist/clis/quark/mkdir.d.ts +1 -0
  183. package/dist/clis/quark/mkdir.js +36 -0
  184. package/dist/clis/quark/mv.d.ts +1 -0
  185. package/dist/clis/quark/mv.js +53 -0
  186. package/dist/clis/quark/rename.d.ts +1 -0
  187. package/dist/clis/quark/rename.js +26 -0
  188. package/dist/clis/quark/rm.d.ts +1 -0
  189. package/dist/clis/quark/rm.js +24 -0
  190. package/dist/clis/quark/save.d.ts +1 -0
  191. package/dist/clis/quark/save.js +80 -0
  192. package/dist/clis/quark/share-tree.d.ts +1 -0
  193. package/dist/clis/quark/share-tree.js +45 -0
  194. package/dist/clis/quark/utils.d.ts +50 -0
  195. package/dist/clis/quark/utils.js +146 -0
  196. package/dist/clis/quark/utils.test.d.ts +1 -0
  197. package/dist/clis/quark/utils.test.js +58 -0
  198. package/dist/clis/reddit/frontpage.d.ts +1 -0
  199. package/dist/clis/reddit/frontpage.js +31 -0
  200. package/dist/clis/reddit/hot.d.ts +1 -0
  201. package/dist/clis/reddit/hot.js +45 -0
  202. package/dist/clis/reddit/popular.d.ts +1 -0
  203. package/dist/clis/reddit/popular.js +41 -0
  204. package/dist/clis/reddit/search.d.ts +1 -0
  205. package/dist/clis/reddit/search.js +65 -0
  206. package/dist/clis/reddit/subreddit.d.ts +1 -0
  207. package/dist/clis/reddit/subreddit.js +52 -0
  208. package/dist/clis/reddit/user-comments.d.ts +1 -0
  209. package/dist/clis/reddit/user-comments.js +44 -0
  210. package/dist/clis/reddit/user-posts.d.ts +1 -0
  211. package/dist/clis/reddit/user-posts.js +42 -0
  212. package/dist/clis/reddit/user.d.ts +1 -0
  213. package/dist/clis/reddit/user.js +37 -0
  214. package/dist/clis/stackoverflow/bounties.d.ts +1 -0
  215. package/dist/clis/stackoverflow/bounties.js +27 -0
  216. package/dist/clis/stackoverflow/hot.d.ts +1 -0
  217. package/dist/clis/stackoverflow/hot.js +24 -0
  218. package/dist/clis/stackoverflow/search.d.ts +1 -0
  219. package/dist/clis/stackoverflow/search.js +27 -0
  220. package/dist/clis/stackoverflow/unanswered.d.ts +1 -0
  221. package/dist/clis/stackoverflow/unanswered.js +26 -0
  222. package/dist/clis/steam/top-sellers.d.ts +1 -0
  223. package/dist/clis/steam/top-sellers.js +25 -0
  224. package/dist/clis/taobao/add-cart.d.ts +1 -0
  225. package/dist/clis/taobao/add-cart.js +149 -0
  226. package/dist/clis/taobao/cart.d.ts +1 -0
  227. package/dist/clis/taobao/cart.js +95 -0
  228. package/dist/clis/taobao/commands.test.d.ts +5 -0
  229. package/dist/clis/taobao/commands.test.js +64 -0
  230. package/dist/clis/taobao/detail.d.ts +1 -0
  231. package/dist/clis/taobao/detail.js +70 -0
  232. package/dist/clis/taobao/reviews.d.ts +1 -0
  233. package/dist/clis/taobao/reviews.js +76 -0
  234. package/dist/clis/taobao/search.d.ts +1 -0
  235. package/dist/clis/taobao/search.js +96 -0
  236. package/dist/clis/tiktok/comment.d.ts +1 -0
  237. package/dist/clis/tiktok/comment.js +57 -0
  238. package/dist/clis/tiktok/explore.d.ts +1 -0
  239. package/dist/clis/tiktok/explore.js +35 -0
  240. package/dist/clis/tiktok/follow.d.ts +1 -0
  241. package/dist/clis/tiktok/follow.js +39 -0
  242. package/dist/clis/tiktok/following.d.ts +1 -0
  243. package/dist/clis/tiktok/following.js +42 -0
  244. package/dist/clis/tiktok/friends.d.ts +1 -0
  245. package/dist/clis/tiktok/friends.js +43 -0
  246. package/dist/clis/tiktok/like.d.ts +1 -0
  247. package/dist/clis/tiktok/like.js +33 -0
  248. package/dist/clis/tiktok/live.d.ts +1 -0
  249. package/dist/clis/tiktok/live.js +47 -0
  250. package/dist/clis/tiktok/notifications.d.ts +1 -0
  251. package/dist/clis/tiktok/notifications.js +49 -0
  252. package/dist/clis/tiktok/profile.d.ts +1 -0
  253. package/dist/clis/tiktok/profile.js +54 -0
  254. package/dist/clis/tiktok/save.d.ts +1 -0
  255. package/dist/clis/tiktok/save.js +29 -0
  256. package/dist/clis/tiktok/search.d.ts +1 -0
  257. package/dist/clis/tiktok/search.js +39 -0
  258. package/dist/clis/tiktok/unfollow.d.ts +1 -0
  259. package/dist/clis/tiktok/unfollow.js +44 -0
  260. package/dist/clis/tiktok/unlike.d.ts +1 -0
  261. package/dist/clis/tiktok/unlike.js +33 -0
  262. package/dist/clis/tiktok/unsave.d.ts +1 -0
  263. package/dist/clis/tiktok/unsave.js +31 -0
  264. package/dist/clis/tiktok/user.d.ts +1 -0
  265. package/dist/clis/tiktok/user.js +41 -0
  266. package/dist/clis/twitter/reply.js +3 -8
  267. package/dist/clis/twitter/reply.test.js +5 -5
  268. package/dist/clis/v2ex/hot.d.ts +1 -0
  269. package/dist/clis/v2ex/hot.js +25 -0
  270. package/dist/clis/v2ex/latest.d.ts +1 -0
  271. package/dist/clis/v2ex/latest.js +25 -0
  272. package/dist/clis/v2ex/member.d.ts +1 -0
  273. package/dist/clis/v2ex/member.js +27 -0
  274. package/dist/clis/v2ex/node.d.ts +1 -0
  275. package/dist/clis/v2ex/node.js +38 -0
  276. package/dist/clis/v2ex/nodes.d.ts +1 -0
  277. package/dist/clis/v2ex/nodes.js +25 -0
  278. package/dist/clis/v2ex/replies.d.ts +1 -0
  279. package/dist/clis/v2ex/replies.js +26 -0
  280. package/dist/clis/v2ex/topic.d.ts +1 -0
  281. package/dist/clis/v2ex/topic.js +30 -0
  282. package/dist/clis/v2ex/user.d.ts +1 -0
  283. package/dist/clis/v2ex/user.js +33 -0
  284. package/dist/clis/xiaoe/catalog.d.ts +1 -0
  285. package/dist/clis/xiaoe/catalog.js +125 -0
  286. package/dist/clis/xiaoe/content.d.ts +1 -0
  287. package/dist/clis/xiaoe/content.js +39 -0
  288. package/dist/clis/xiaoe/courses.d.ts +1 -0
  289. package/dist/clis/xiaoe/courses.js +69 -0
  290. package/dist/clis/xiaoe/detail.d.ts +1 -0
  291. package/dist/clis/xiaoe/detail.js +35 -0
  292. package/dist/clis/xiaoe/play-url.d.ts +1 -0
  293. package/dist/clis/xiaoe/play-url.js +120 -0
  294. package/dist/clis/xiaohongshu/feed.d.ts +1 -0
  295. package/dist/clis/xiaohongshu/feed.js +32 -0
  296. package/dist/clis/xiaohongshu/note.js +8 -3
  297. package/dist/clis/xiaohongshu/note.test.js +11 -0
  298. package/dist/clis/xiaohongshu/notifications.d.ts +1 -0
  299. package/dist/clis/xiaohongshu/notifications.js +38 -0
  300. package/dist/clis/xueqiu/earnings-date.d.ts +1 -0
  301. package/dist/clis/xueqiu/earnings-date.js +61 -0
  302. package/dist/clis/xueqiu/feed.d.ts +1 -0
  303. package/dist/clis/xueqiu/feed.js +48 -0
  304. package/dist/clis/xueqiu/groups.d.ts +1 -0
  305. package/dist/clis/xueqiu/groups.js +25 -0
  306. package/dist/clis/xueqiu/hot-stock.d.ts +1 -0
  307. package/dist/clis/xueqiu/hot-stock.js +44 -0
  308. package/dist/clis/xueqiu/hot.d.ts +1 -0
  309. package/dist/clis/xueqiu/hot.js +44 -0
  310. package/dist/clis/xueqiu/kline.d.ts +1 -0
  311. package/dist/clis/xueqiu/kline.js +64 -0
  312. package/dist/clis/xueqiu/search.d.ts +1 -0
  313. package/dist/clis/xueqiu/search.js +49 -0
  314. package/dist/clis/xueqiu/stock.d.ts +1 -0
  315. package/dist/clis/xueqiu/stock.js +72 -0
  316. package/dist/clis/xueqiu/watchlist.d.ts +1 -0
  317. package/dist/clis/xueqiu/watchlist.js +45 -0
  318. package/dist/clis/zhihu/answer.d.ts +1 -0
  319. package/dist/clis/zhihu/answer.js +194 -0
  320. package/dist/clis/zhihu/answer.test.d.ts +1 -0
  321. package/dist/clis/zhihu/answer.test.js +81 -0
  322. package/dist/clis/zhihu/comment.d.ts +1 -0
  323. package/dist/clis/zhihu/comment.js +335 -0
  324. package/dist/clis/zhihu/comment.test.d.ts +1 -0
  325. package/dist/clis/zhihu/comment.test.js +54 -0
  326. package/dist/clis/zhihu/favorite.d.ts +1 -0
  327. package/dist/clis/zhihu/favorite.js +224 -0
  328. package/dist/clis/zhihu/favorite.test.d.ts +1 -0
  329. package/dist/clis/zhihu/favorite.test.js +196 -0
  330. package/dist/clis/zhihu/follow.d.ts +1 -0
  331. package/dist/clis/zhihu/follow.js +80 -0
  332. package/dist/clis/zhihu/follow.test.d.ts +1 -0
  333. package/dist/clis/zhihu/follow.test.js +45 -0
  334. package/dist/clis/zhihu/hot.d.ts +1 -0
  335. package/dist/clis/zhihu/hot.js +43 -0
  336. package/dist/clis/zhihu/like.d.ts +1 -0
  337. package/dist/clis/zhihu/like.js +91 -0
  338. package/dist/clis/zhihu/like.test.d.ts +1 -0
  339. package/dist/clis/zhihu/like.test.js +64 -0
  340. package/dist/clis/zhihu/search.d.ts +1 -0
  341. package/dist/clis/zhihu/search.js +52 -0
  342. package/dist/clis/zhihu/target.d.ts +24 -0
  343. package/dist/clis/zhihu/target.js +91 -0
  344. package/dist/clis/zhihu/target.test.d.ts +1 -0
  345. package/dist/clis/zhihu/target.test.js +77 -0
  346. package/dist/clis/zhihu/write-shared.d.ts +32 -0
  347. package/dist/clis/zhihu/write-shared.js +221 -0
  348. package/dist/clis/zhihu/write-shared.test.d.ts +1 -0
  349. package/dist/clis/zhihu/write-shared.test.js +175 -0
  350. package/dist/src/browser/bridge.d.ts +2 -0
  351. package/dist/src/browser/bridge.js +30 -24
  352. package/dist/src/browser/daemon-client.d.ts +30 -10
  353. package/dist/src/browser/daemon-client.js +42 -27
  354. package/dist/src/browser/daemon-client.test.js +32 -25
  355. package/dist/src/browser/dom-helpers.test.js +3 -2
  356. package/dist/src/browser/errors.d.ts +26 -1
  357. package/dist/src/browser/errors.js +40 -7
  358. package/dist/src/browser/errors.test.d.ts +1 -0
  359. package/dist/src/browser/errors.test.js +51 -0
  360. package/dist/src/browser/index.d.ts +2 -1
  361. package/dist/src/browser/index.js +1 -1
  362. package/dist/src/browser/page.d.ts +9 -8
  363. package/dist/src/browser/page.js +33 -31
  364. package/dist/src/browser.test.js +27 -8
  365. package/dist/src/build-manifest.d.ts +5 -11
  366. package/dist/src/build-manifest.js +6 -75
  367. package/dist/src/build-manifest.test.js +1 -39
  368. package/dist/src/cascade.js +3 -2
  369. package/dist/src/cli.d.ts +3 -3
  370. package/dist/src/cli.js +73 -65
  371. package/dist/src/cli.test.js +20 -15
  372. package/dist/src/clis/binance/asks.d.ts +1 -0
  373. package/dist/src/clis/binance/asks.js +20 -0
  374. package/dist/src/clis/binance/commands.test.d.ts +3 -0
  375. package/dist/src/clis/binance/commands.test.js +58 -0
  376. package/dist/src/clis/binance/depth.d.ts +1 -0
  377. package/dist/src/clis/binance/depth.js +20 -0
  378. package/dist/src/clis/binance/gainers.d.ts +1 -0
  379. package/dist/src/clis/binance/gainers.js +21 -0
  380. package/dist/src/clis/binance/klines.d.ts +1 -0
  381. package/dist/src/clis/binance/klines.js +20 -0
  382. package/dist/src/clis/binance/losers.d.ts +1 -0
  383. package/dist/src/clis/binance/losers.js +21 -0
  384. package/dist/src/clis/binance/pairs.d.ts +1 -0
  385. package/dist/src/clis/binance/pairs.js +20 -0
  386. package/dist/src/clis/binance/price.d.ts +1 -0
  387. package/dist/src/clis/binance/price.js +17 -0
  388. package/dist/src/clis/binance/prices.d.ts +1 -0
  389. package/dist/src/clis/binance/prices.js +18 -0
  390. package/dist/src/clis/binance/ticker.d.ts +1 -0
  391. package/dist/src/clis/binance/ticker.js +20 -0
  392. package/dist/src/clis/binance/top.d.ts +1 -0
  393. package/dist/src/clis/binance/top.js +20 -0
  394. package/dist/src/clis/binance/trades.d.ts +1 -0
  395. package/dist/src/clis/binance/trades.js +19 -0
  396. package/dist/src/commanderAdapter.js +19 -6
  397. package/dist/src/completion-fast.d.ts +25 -0
  398. package/dist/src/completion-fast.js +140 -0
  399. package/dist/src/completion.d.ts +1 -0
  400. package/dist/src/completion.js +1 -0
  401. package/dist/src/diagnostic.d.ts +1 -0
  402. package/dist/src/diagnostic.js +64 -2
  403. package/dist/src/diagnostic.test.js +93 -3
  404. package/dist/src/discovery.d.ts +3 -3
  405. package/dist/src/discovery.js +34 -97
  406. package/dist/src/doctor.d.ts +2 -0
  407. package/dist/src/doctor.js +59 -31
  408. package/dist/src/doctor.test.js +89 -16
  409. package/dist/src/download/index.d.ts +1 -1
  410. package/dist/src/engine.test.js +4 -19
  411. package/dist/src/execution.js +1 -13
  412. package/dist/src/explore.js +1 -1
  413. package/dist/src/generate-verified.d.ts +105 -0
  414. package/dist/src/generate-verified.js +696 -0
  415. package/dist/src/generate-verified.test.d.ts +1 -0
  416. package/dist/src/generate-verified.test.js +925 -0
  417. package/dist/src/generate.d.ts +11 -6
  418. package/dist/src/generate.js +4 -7
  419. package/dist/src/main.js +65 -12
  420. package/dist/src/pipeline/steps/download.d.ts +1 -17
  421. package/dist/src/pipeline/steps/download.js +20 -31
  422. package/dist/src/pipeline/steps/intercept.d.ts +1 -1
  423. package/dist/src/pipeline/steps/intercept.js +1 -1
  424. package/dist/src/pipeline/steps/tap.d.ts +1 -1
  425. package/dist/src/pipeline/steps/tap.js +1 -1
  426. package/dist/src/plugin-scaffold.d.ts +2 -2
  427. package/dist/src/plugin-scaffold.js +24 -21
  428. package/dist/src/plugin-scaffold.test.js +1 -1
  429. package/dist/src/plugin.d.ts +3 -2
  430. package/dist/src/plugin.js +29 -14
  431. package/dist/src/plugin.test.js +47 -32
  432. package/dist/src/record.js +26 -25
  433. package/dist/src/runtime-detect.js +3 -7
  434. package/dist/src/scripts/framework.d.ts +3 -0
  435. package/dist/src/scripts/framework.js +8 -4
  436. package/dist/src/scripts/store.d.ts +5 -1
  437. package/dist/src/scripts/store.js +5 -1
  438. package/dist/src/skill-generate.d.ts +30 -0
  439. package/dist/src/skill-generate.js +75 -0
  440. package/dist/src/skill-generate.test.d.ts +1 -0
  441. package/dist/src/skill-generate.test.js +173 -0
  442. package/dist/src/synthesize.d.ts +1 -1
  443. package/dist/src/synthesize.js +7 -8
  444. package/dist/src/types.d.ts +3 -1
  445. package/package.json +5 -5
  446. package/dist/clis/bilibili/hot.yaml +0 -38
  447. package/dist/clis/bluesky/feeds.yaml +0 -29
  448. package/dist/clis/bluesky/followers.yaml +0 -33
  449. package/dist/clis/bluesky/following.yaml +0 -33
  450. package/dist/clis/bluesky/profile.yaml +0 -27
  451. package/dist/clis/bluesky/search.yaml +0 -34
  452. package/dist/clis/bluesky/starter-packs.yaml +0 -34
  453. package/dist/clis/bluesky/thread.yaml +0 -32
  454. package/dist/clis/bluesky/trending.yaml +0 -27
  455. package/dist/clis/bluesky/user.yaml +0 -34
  456. package/dist/clis/devto/tag.yaml +0 -34
  457. package/dist/clis/devto/top.yaml +0 -29
  458. package/dist/clis/devto/user.yaml +0 -33
  459. package/dist/clis/dictionary/examples.yaml +0 -25
  460. package/dist/clis/dictionary/search.yaml +0 -27
  461. package/dist/clis/dictionary/synonyms.yaml +0 -25
  462. package/dist/clis/douban/subject.yaml +0 -107
  463. package/dist/clis/douban/top250.yaml +0 -70
  464. package/dist/clis/facebook/add-friend.yaml +0 -43
  465. package/dist/clis/facebook/events.yaml +0 -44
  466. package/dist/clis/facebook/feed.yaml +0 -63
  467. package/dist/clis/facebook/friends.yaml +0 -42
  468. package/dist/clis/facebook/groups.yaml +0 -50
  469. package/dist/clis/facebook/join-group.yaml +0 -44
  470. package/dist/clis/facebook/memories.yaml +0 -39
  471. package/dist/clis/facebook/notifications.yaml +0 -40
  472. package/dist/clis/facebook/profile.yaml +0 -37
  473. package/dist/clis/facebook/search.yaml +0 -47
  474. package/dist/clis/hackernews/ask.yaml +0 -38
  475. package/dist/clis/hackernews/best.yaml +0 -38
  476. package/dist/clis/hackernews/jobs.yaml +0 -36
  477. package/dist/clis/hackernews/new.yaml +0 -38
  478. package/dist/clis/hackernews/search.yaml +0 -44
  479. package/dist/clis/hackernews/show.yaml +0 -38
  480. package/dist/clis/hackernews/top.yaml +0 -38
  481. package/dist/clis/hackernews/user.yaml +0 -25
  482. package/dist/clis/hupu/hot.yaml +0 -43
  483. package/dist/clis/instagram/comment.yaml +0 -52
  484. package/dist/clis/instagram/explore.yaml +0 -43
  485. package/dist/clis/instagram/follow.yaml +0 -41
  486. package/dist/clis/instagram/followers.yaml +0 -51
  487. package/dist/clis/instagram/following.yaml +0 -51
  488. package/dist/clis/instagram/like.yaml +0 -46
  489. package/dist/clis/instagram/profile.yaml +0 -42
  490. package/dist/clis/instagram/save.yaml +0 -46
  491. package/dist/clis/instagram/saved.yaml +0 -40
  492. package/dist/clis/instagram/search.yaml +0 -44
  493. package/dist/clis/instagram/unfollow.yaml +0 -38
  494. package/dist/clis/instagram/unlike.yaml +0 -46
  495. package/dist/clis/instagram/unsave.yaml +0 -46
  496. package/dist/clis/instagram/user.yaml +0 -54
  497. package/dist/clis/jike/post.yaml +0 -59
  498. package/dist/clis/jike/topic.yaml +0 -53
  499. package/dist/clis/jike/user.yaml +0 -52
  500. package/dist/clis/jimeng/generate.yaml +0 -85
  501. package/dist/clis/jimeng/history.yaml +0 -46
  502. package/dist/clis/linux-do/categories.yaml +0 -70
  503. package/dist/clis/linux-do/search.yaml +0 -48
  504. package/dist/clis/linux-do/tags.yaml +0 -41
  505. package/dist/clis/linux-do/topic.yaml +0 -62
  506. package/dist/clis/linux-do/user-posts.yaml +0 -67
  507. package/dist/clis/linux-do/user-topics.yaml +0 -54
  508. package/dist/clis/lobsters/active.yaml +0 -29
  509. package/dist/clis/lobsters/hot.yaml +0 -29
  510. package/dist/clis/lobsters/newest.yaml +0 -29
  511. package/dist/clis/lobsters/tag.yaml +0 -34
  512. package/dist/clis/pixiv/detail.yaml +0 -49
  513. package/dist/clis/pixiv/ranking.yaml +0 -53
  514. package/dist/clis/pixiv/user.yaml +0 -46
  515. package/dist/clis/reddit/frontpage.yaml +0 -30
  516. package/dist/clis/reddit/hot.yaml +0 -47
  517. package/dist/clis/reddit/popular.yaml +0 -40
  518. package/dist/clis/reddit/search.yaml +0 -61
  519. package/dist/clis/reddit/subreddit.yaml +0 -50
  520. package/dist/clis/reddit/user-comments.yaml +0 -46
  521. package/dist/clis/reddit/user-posts.yaml +0 -44
  522. package/dist/clis/reddit/user.yaml +0 -40
  523. package/dist/clis/stackoverflow/bounties.yaml +0 -29
  524. package/dist/clis/stackoverflow/hot.yaml +0 -28
  525. package/dist/clis/stackoverflow/search.yaml +0 -33
  526. package/dist/clis/stackoverflow/unanswered.yaml +0 -28
  527. package/dist/clis/steam/top-sellers.yaml +0 -29
  528. package/dist/clis/tiktok/comment.yaml +0 -66
  529. package/dist/clis/tiktok/explore.yaml +0 -39
  530. package/dist/clis/tiktok/follow.yaml +0 -39
  531. package/dist/clis/tiktok/following.yaml +0 -46
  532. package/dist/clis/tiktok/friends.yaml +0 -47
  533. package/dist/clis/tiktok/like.yaml +0 -38
  534. package/dist/clis/tiktok/live.yaml +0 -51
  535. package/dist/clis/tiktok/notifications.yaml +0 -52
  536. package/dist/clis/tiktok/profile.yaml +0 -45
  537. package/dist/clis/tiktok/save.yaml +0 -34
  538. package/dist/clis/tiktok/search.yaml +0 -47
  539. package/dist/clis/tiktok/unfollow.yaml +0 -44
  540. package/dist/clis/tiktok/unlike.yaml +0 -38
  541. package/dist/clis/tiktok/unsave.yaml +0 -36
  542. package/dist/clis/tiktok/user.yaml +0 -44
  543. package/dist/clis/v2ex/hot.yaml +0 -28
  544. package/dist/clis/v2ex/latest.yaml +0 -28
  545. package/dist/clis/v2ex/member.yaml +0 -29
  546. package/dist/clis/v2ex/node.yaml +0 -34
  547. package/dist/clis/v2ex/nodes.yaml +0 -31
  548. package/dist/clis/v2ex/replies.yaml +0 -32
  549. package/dist/clis/v2ex/topic.yaml +0 -33
  550. package/dist/clis/v2ex/user.yaml +0 -34
  551. package/dist/clis/xiaoe/catalog.yaml +0 -129
  552. package/dist/clis/xiaoe/content.yaml +0 -43
  553. package/dist/clis/xiaoe/courses.yaml +0 -73
  554. package/dist/clis/xiaoe/detail.yaml +0 -39
  555. package/dist/clis/xiaoe/play-url.yaml +0 -124
  556. package/dist/clis/xiaohongshu/feed.yaml +0 -31
  557. package/dist/clis/xiaohongshu/notifications.yaml +0 -37
  558. package/dist/clis/xueqiu/earnings-date.yaml +0 -69
  559. package/dist/clis/xueqiu/feed.yaml +0 -53
  560. package/dist/clis/xueqiu/groups.yaml +0 -23
  561. package/dist/clis/xueqiu/hot-stock.yaml +0 -49
  562. package/dist/clis/xueqiu/hot.yaml +0 -46
  563. package/dist/clis/xueqiu/kline.yaml +0 -65
  564. package/dist/clis/xueqiu/search.yaml +0 -55
  565. package/dist/clis/xueqiu/stock.yaml +0 -69
  566. package/dist/clis/xueqiu/watchlist.yaml +0 -46
  567. package/dist/clis/zhihu/hot.yaml +0 -46
  568. package/dist/clis/zhihu/search.yaml +0 -59
  569. package/dist/src/browser/discover.d.ts +0 -15
  570. package/dist/src/browser/discover.js +0 -19
  571. package/dist/src/yaml-schema.d.ts +0 -29
  572. package/dist/src/yaml-schema.js +0 -22
@@ -6,8 +6,9 @@ import { fileURLToPath } from 'node:url';
6
6
  import * as path from 'node:path';
7
7
  import * as fs from 'node:fs';
8
8
  import { Page } from './page.js';
9
- import { fetchDaemonStatus, isExtensionConnected } from './daemon-client.js';
9
+ import { getDaemonHealth } from './daemon-client.js';
10
10
  import { DEFAULT_DAEMON_PORT } from '../constants.js';
11
+ import { BrowserConnectError } from '../errors.js';
11
12
  const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
12
13
  /**
13
14
  * Browser factory: manages daemon lifecycle and provides IPage instances.
@@ -52,25 +53,22 @@ export class BrowserBridge {
52
53
  async _ensureDaemon(timeoutSeconds) {
53
54
  const effectiveSeconds = (timeoutSeconds && timeoutSeconds > 0) ? timeoutSeconds : Math.ceil(DAEMON_SPAWN_TIMEOUT / 1000);
54
55
  const timeoutMs = effectiveSeconds * 1000;
55
- // Single status check instead of two separate fetchDaemonStatus() calls
56
- const status = await fetchDaemonStatus();
57
- // Fast path: extension already connected
58
- if (status?.extensionConnected)
56
+ const health = await getDaemonHealth();
57
+ // Fast path: everything ready
58
+ if (health.state === 'ready')
59
59
  return;
60
60
  // Daemon running but no extension — wait for extension with progress
61
- if (status !== null) {
61
+ if (health.state === 'no-extension') {
62
62
  if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
63
63
  process.stderr.write('⏳ Waiting for Chrome/Chromium extension to connect...\n');
64
64
  process.stderr.write(' Make sure Chrome or Chromium is open and the OpenCLI extension is enabled.\n');
65
65
  }
66
- const deadline = Date.now() + timeoutMs;
67
- while (Date.now() < deadline) {
68
- await new Promise(resolve => setTimeout(resolve, 200));
69
- if (await isExtensionConnected())
70
- return;
71
- }
72
- throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
73
- 'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
66
+ if (await this._pollUntilReady(timeoutMs))
67
+ return;
68
+ throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
69
+ ' 1. Download: https://github.com/jackwener/opencli/releases\n' +
70
+ ' 2. In Chrome or Chromium, open chrome://extensions → Developer Mode → Load unpacked\n' +
71
+ ' Then run: opencli doctor', 'extension-not-connected');
74
72
  }
75
73
  // No daemon — spawn one
76
74
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -91,19 +89,27 @@ export class BrowserBridge {
91
89
  env: { ...process.env },
92
90
  });
93
91
  this._daemonProc.unref();
94
- // Wait for daemon + extension with faster polling
92
+ // Wait for daemon + extension
93
+ if (await this._pollUntilReady(timeoutMs))
94
+ return;
95
+ const finalHealth = await getDaemonHealth();
96
+ if (finalHealth.state === 'no-extension') {
97
+ throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
98
+ ' 1. Download: https://github.com/jackwener/opencli/releases\n' +
99
+ ' 2. In Chrome or Chromium, open chrome://extensions → Developer Mode → Load unpacked\n' +
100
+ ' Then run: opencli doctor', 'extension-not-connected');
101
+ }
102
+ throw new BrowserConnectError('Failed to start opencli daemon', `Try running manually:\n node ${daemonPath}\nMake sure port ${DEFAULT_DAEMON_PORT} is available.`, 'daemon-not-running');
103
+ }
104
+ /** Poll getDaemonHealth() until state is 'ready' or deadline is reached. */
105
+ async _pollUntilReady(timeoutMs) {
95
106
  const deadline = Date.now() + timeoutMs;
96
107
  while (Date.now() < deadline) {
97
108
  await new Promise(resolve => setTimeout(resolve, 200));
98
- if (await isExtensionConnected())
99
- return;
100
- }
101
- if ((await fetchDaemonStatus()) !== null) {
102
- throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
103
- 'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
109
+ const h = await getDaemonHealth();
110
+ if (h.state === 'ready')
111
+ return true;
104
112
  }
105
- throw new Error('Failed to start opencli daemon. Try running manually:\n' +
106
- ` node ${daemonPath}\n` +
107
- `Make sure port ${DEFAULT_DAEMON_PORT} is available.`);
113
+ return false;
108
114
  }
109
115
  }
@@ -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;
@@ -35,6 +38,8 @@ export interface DaemonResult {
35
38
  ok: boolean;
36
39
  data?: unknown;
37
40
  error?: string;
41
+ /** Page identity (targetId) — present on page-scoped command responses */
42
+ page?: string;
38
43
  }
39
44
  export interface DaemonStatus {
40
45
  ok: boolean;
@@ -50,23 +55,38 @@ export interface DaemonStatus {
50
55
  export declare function fetchDaemonStatus(opts?: {
51
56
  timeout?: number;
52
57
  }): Promise<DaemonStatus | null>;
58
+ export type DaemonHealth = {
59
+ state: 'stopped';
60
+ status: null;
61
+ } | {
62
+ state: 'no-extension';
63
+ status: DaemonStatus;
64
+ } | {
65
+ state: 'ready';
66
+ status: DaemonStatus;
67
+ };
68
+ /**
69
+ * Unified daemon health check — single entry point for all status queries.
70
+ * Replaces isDaemonRunning(), isExtensionConnected(), and checkDaemonStatus().
71
+ */
72
+ export declare function getDaemonHealth(opts?: {
73
+ timeout?: number;
74
+ }): Promise<DaemonHealth>;
53
75
  export declare function requestDaemonShutdown(opts?: {
54
76
  timeout?: number;
55
77
  }): Promise<boolean>;
56
78
  /**
57
- * Check if daemon is running.
79
+ * Send a command to the daemon and return the result data.
58
80
  */
59
- export declare function isDaemonRunning(): Promise<boolean>;
60
- /**
61
- * Check if daemon is running AND the extension is connected.
62
- */
63
- export declare function isExtensionConnected(): Promise<boolean>;
81
+ export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
64
82
  /**
65
- * Send a command to the daemon and wait for a result.
66
- * Retries up to 4 times: network errors retry at 500ms,
67
- * transient extension errors retry at 1500ms.
83
+ * Like sendCommand, but returns both data and page identity (targetId).
84
+ * Use this for page-scoped commands where the caller needs the page identity.
68
85
  */
69
- export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
86
+ export declare function sendCommandFull(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<{
87
+ data: unknown;
88
+ page?: string;
89
+ }>;
70
90
  export declare function listSessions(): Promise<BrowserSessionInfo[]>;
71
91
  export declare function bindCurrentTab(workspace: string, opts?: {
72
92
  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' };
@@ -39,6 +39,18 @@ export async function fetchDaemonStatus(opts) {
39
39
  return null;
40
40
  }
41
41
  }
42
+ /**
43
+ * Unified daemon health check — single entry point for all status queries.
44
+ * Replaces isDaemonRunning(), isExtensionConnected(), and checkDaemonStatus().
45
+ */
46
+ export async function getDaemonHealth(opts) {
47
+ const status = await fetchDaemonStatus(opts);
48
+ if (!status)
49
+ return { state: 'stopped', status: null };
50
+ if (!status.extensionConnected)
51
+ return { state: 'no-extension', status };
52
+ return { state: 'ready', status };
53
+ }
42
54
  export async function requestDaemonShutdown(opts) {
43
55
  try {
44
56
  const res = await requestDaemon('/shutdown', { method: 'POST', timeout: opts?.timeout ?? 5000 });
@@ -49,27 +61,17 @@ export async function requestDaemonShutdown(opts) {
49
61
  }
50
62
  }
51
63
  /**
52
- * Check if daemon is running.
53
- */
54
- export async function isDaemonRunning() {
55
- return (await fetchDaemonStatus()) !== null;
56
- }
57
- /**
58
- * Check if daemon is running AND the extension is connected.
59
- */
60
- export async function isExtensionConnected() {
61
- const status = await fetchDaemonStatus();
62
- return !!status?.extensionConnected;
63
- }
64
- /**
65
- * Send a command to the daemon and wait for a result.
66
- * Retries up to 4 times: network errors retry at 500ms,
67
- * 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()
68
71
  */
69
- export async function sendCommand(action, params = {}) {
72
+ async function sendCommandRaw(action, params) {
70
73
  const maxRetries = 4;
71
74
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
72
- // Generate a fresh ID per attempt to avoid daemon-side duplicate detection
73
75
  const id = generateId();
74
76
  const command = { id, action, ...params };
75
77
  try {
@@ -81,29 +83,42 @@ export async function sendCommand(action, params = {}) {
81
83
  });
82
84
  const result = (await res.json());
83
85
  if (!result.ok) {
84
- // Check if error is a transient extension issue worth retrying
85
- if (isTransientBrowserError(new Error(result.error ?? '')) && attempt < maxRetries) {
86
- // Longer delay for extension recovery (service worker restart)
87
- await sleep(1500);
86
+ const advice = classifyBrowserError(new Error(result.error ?? ''));
87
+ if (advice.retryable && attempt < maxRetries) {
88
+ await sleep(advice.delayMs);
88
89
  continue;
89
90
  }
90
91
  throw new Error(result.error ?? 'Daemon command failed');
91
92
  }
92
- return result.data;
93
+ return result;
93
94
  }
94
95
  catch (err) {
95
- const isRetryable = err instanceof TypeError // fetch network error
96
+ const isNetworkError = err instanceof TypeError
96
97
  || (err instanceof Error && err.name === 'AbortError');
97
- if (isRetryable && attempt < maxRetries) {
98
+ if (isNetworkError && attempt < maxRetries) {
98
99
  await sleep(500);
99
100
  continue;
100
101
  }
101
102
  throw err;
102
103
  }
103
104
  }
104
- // Unreachable — the loop always returns or throws
105
105
  throw new Error('sendCommand: max retries exhausted');
106
106
  }
107
+ /**
108
+ * Send a command to the daemon and return the result data.
109
+ */
110
+ export async function sendCommand(action, params = {}) {
111
+ const result = await sendCommandRaw(action, params);
112
+ return result.data;
113
+ }
114
+ /**
115
+ * Like sendCommand, but returns both data and page identity (targetId).
116
+ * Use this for page-scoped commands where the caller needs the page identity.
117
+ */
118
+ export async function sendCommandFull(action, params = {}) {
119
+ const result = await sendCommandRaw(action, params);
120
+ return { data: result.data, page: result.page };
121
+ }
107
122
  export async function listSessions() {
108
123
  const result = await sendCommand('sessions');
109
124
  return Array.isArray(result) ? result : [];
@@ -1,5 +1,5 @@
1
1
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
- import { fetchDaemonStatus, isDaemonRunning, isExtensionConnected, requestDaemonShutdown, } from './daemon-client.js';
2
+ import { fetchDaemonStatus, getDaemonHealth, requestDaemonShutdown, } from './daemon-client.js';
3
3
  describe('daemon-client', () => {
4
4
  beforeEach(() => {
5
5
  vi.stubGlobal('fetch', vi.fn());
@@ -42,36 +42,43 @@ describe('daemon-client', () => {
42
42
  headers: expect.objectContaining({ 'X-OpenCLI': '1' }),
43
43
  }));
44
44
  });
45
- it('isDaemonRunning reflects shared status availability', async () => {
45
+ it('getDaemonHealth returns stopped when daemon is not reachable', async () => {
46
+ vi.mocked(fetch).mockRejectedValue(new Error('ECONNREFUSED'));
47
+ await expect(getDaemonHealth()).resolves.toEqual({ state: 'stopped', status: null });
48
+ });
49
+ it('getDaemonHealth returns no-extension when daemon is running but extension disconnected', async () => {
50
+ const status = {
51
+ ok: true,
52
+ pid: 123,
53
+ uptime: 10,
54
+ extensionConnected: false,
55
+ pending: 0,
56
+ lastCliRequestTime: Date.now(),
57
+ memoryMB: 16,
58
+ port: 19825,
59
+ };
46
60
  vi.mocked(fetch).mockResolvedValue({
47
61
  ok: true,
48
- json: () => Promise.resolve({
49
- ok: true,
50
- pid: 123,
51
- uptime: 10,
52
- extensionConnected: false,
53
- pending: 0,
54
- lastCliRequestTime: Date.now(),
55
- memoryMB: 16,
56
- port: 19825,
57
- }),
62
+ json: () => Promise.resolve(status),
58
63
  });
59
- await expect(isDaemonRunning()).resolves.toBe(true);
64
+ await expect(getDaemonHealth()).resolves.toEqual({ state: 'no-extension', status });
60
65
  });
61
- it('isExtensionConnected reflects shared status payload', async () => {
66
+ it('getDaemonHealth returns ready when daemon and extension are both connected', async () => {
67
+ const status = {
68
+ ok: true,
69
+ pid: 123,
70
+ uptime: 10,
71
+ extensionConnected: true,
72
+ extensionVersion: '1.2.3',
73
+ pending: 0,
74
+ lastCliRequestTime: Date.now(),
75
+ memoryMB: 32,
76
+ port: 19825,
77
+ };
62
78
  vi.mocked(fetch).mockResolvedValue({
63
79
  ok: true,
64
- json: () => Promise.resolve({
65
- ok: true,
66
- pid: 123,
67
- uptime: 10,
68
- extensionConnected: false,
69
- pending: 0,
70
- lastCliRequestTime: Date.now(),
71
- memoryMB: 16,
72
- port: 19825,
73
- }),
80
+ json: () => Promise.resolve(status),
74
81
  });
75
- await expect(isExtensionConnected()).resolves.toBe(false);
82
+ await expect(getDaemonHealth()).resolves.toEqual({ state: 'ready', status });
76
83
  });
77
84
  });
@@ -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
+ });
@@ -7,7 +7,8 @@
7
7
  export { Page } from './page.js';
8
8
  export { BrowserBridge } from './bridge.js';
9
9
  export { CDPBridge } from './cdp.js';
10
- export { isDaemonRunning } from './daemon-client.js';
10
+ export { getDaemonHealth } from './daemon-client.js';
11
+ export type { DaemonHealth } from './daemon-client.js';
11
12
  export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
12
13
  export { generateStealthJs } from './stealth.js';
13
14
  export type { DomSnapshotOptions } from './dom-snapshot.js';
@@ -7,6 +7,6 @@
7
7
  export { Page } from './page.js';
8
8
  export { BrowserBridge } from './bridge.js';
9
9
  export { CDPBridge } from './cdp.js';
10
- export { isDaemonRunning } from './daemon-client.js';
10
+ export { getDaemonHealth } from './daemon-client.js';
11
11
  export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
12
12
  export { generateStealthJs } from './stealth.js';
@@ -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?: {