@jackwener/opencli 1.1.1 → 1.2.1

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 (523) hide show
  1. package/.github/workflows/build-extension.yml +3 -3
  2. package/.github/workflows/ci.yml +6 -6
  3. package/.github/workflows/doc-check.yml +3 -3
  4. package/.github/workflows/e2e-headed.yml +2 -2
  5. package/.github/workflows/pkg-pr-new.yml +2 -2
  6. package/.github/workflows/release.yml +3 -3
  7. package/.github/workflows/security.yml +2 -2
  8. package/CONTRIBUTING.md +39 -1
  9. package/README.md +13 -10
  10. package/README.zh-CN.md +43 -17
  11. package/SKILL.md +10 -5
  12. package/dist/browser/cdp.d.ts +4 -4
  13. package/dist/browser/cdp.js +39 -16
  14. package/dist/browser/daemon-client.d.ts +4 -2
  15. package/dist/browser/daemon-client.js +17 -4
  16. package/dist/browser/dom-helpers.js +38 -7
  17. package/dist/browser/dom-snapshot.d.ts +86 -0
  18. package/dist/browser/dom-snapshot.js +729 -0
  19. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  20. package/dist/browser/dom-snapshot.test.js +212 -0
  21. package/dist/browser/index.d.ts +2 -0
  22. package/dist/browser/index.js +1 -0
  23. package/dist/browser/mcp.js +3 -1
  24. package/dist/browser/page.d.ts +14 -24
  25. package/dist/browser/page.js +46 -6
  26. package/dist/build-manifest.d.ts +11 -4
  27. package/dist/build-manifest.js +59 -21
  28. package/dist/build-manifest.test.js +58 -2
  29. package/dist/cli-manifest.json +3856 -1509
  30. package/dist/cli.js +66 -0
  31. package/dist/clis/barchart/greeks.js +1 -1
  32. package/dist/clis/barchart/options.js +1 -1
  33. package/dist/clis/barchart/quote.js +1 -1
  34. package/dist/clis/bilibili/download.js +1 -1
  35. package/dist/clis/bilibili/following.js +1 -1
  36. package/dist/clis/bilibili/subtitle.js +1 -1
  37. package/dist/clis/bilibili/user-videos.js +1 -1
  38. package/dist/clis/boss/batchgreet.js +10 -97
  39. package/dist/clis/boss/chatlist.js +8 -25
  40. package/dist/clis/boss/chatmsg.js +11 -42
  41. package/dist/clis/boss/common.d.ts +92 -0
  42. package/dist/clis/boss/common.js +223 -0
  43. package/dist/clis/boss/detail.js +7 -49
  44. package/dist/clis/boss/exchange.js +13 -79
  45. package/dist/clis/boss/greet.js +18 -145
  46. package/dist/clis/boss/invite.js +26 -121
  47. package/dist/clis/boss/joblist.js +6 -31
  48. package/dist/clis/boss/mark.js +12 -85
  49. package/dist/clis/boss/recommend.js +10 -49
  50. package/dist/clis/boss/resume.js +18 -118
  51. package/dist/clis/boss/search.js +12 -60
  52. package/dist/clis/boss/send.js +17 -151
  53. package/dist/clis/boss/stats.js +18 -69
  54. package/dist/clis/coupang/add-to-cart.js +1 -1
  55. package/dist/clis/devto/tag.yaml +34 -0
  56. package/dist/clis/devto/top.yaml +29 -0
  57. package/dist/clis/devto/user.yaml +33 -0
  58. package/dist/clis/douban/book-hot.d.ts +1 -0
  59. package/dist/clis/douban/book-hot.js +14 -0
  60. package/dist/clis/douban/marks.d.ts +1 -0
  61. package/dist/clis/douban/marks.js +115 -0
  62. package/dist/clis/douban/movie-hot.d.ts +1 -0
  63. package/dist/clis/douban/movie-hot.js +14 -0
  64. package/dist/clis/douban/reviews.d.ts +1 -0
  65. package/dist/clis/douban/reviews.js +106 -0
  66. package/dist/clis/douban/search.d.ts +1 -0
  67. package/dist/clis/douban/search.js +16 -0
  68. package/dist/clis/douban/shared.d.ts +4 -0
  69. package/dist/clis/douban/shared.js +155 -0
  70. package/dist/clis/douban/subject.yaml +76 -0
  71. package/dist/clis/douban/top250.yaml +70 -0
  72. package/dist/clis/douban/utils.d.ts +35 -0
  73. package/dist/clis/douban/utils.js +48 -0
  74. package/dist/clis/facebook/add-friend.yaml +43 -0
  75. package/dist/clis/facebook/events.yaml +44 -0
  76. package/dist/clis/facebook/feed.yaml +63 -0
  77. package/dist/clis/facebook/friends.yaml +42 -0
  78. package/dist/clis/facebook/groups.yaml +50 -0
  79. package/dist/clis/facebook/join-group.yaml +44 -0
  80. package/dist/clis/facebook/memories.yaml +39 -0
  81. package/dist/clis/facebook/notifications.yaml +40 -0
  82. package/dist/clis/facebook/profile.yaml +37 -0
  83. package/dist/clis/facebook/search.yaml +46 -0
  84. package/dist/clis/google/news.d.ts +5 -0
  85. package/dist/clis/google/news.js +58 -0
  86. package/dist/clis/google/search.d.ts +10 -0
  87. package/dist/clis/google/search.js +127 -0
  88. package/dist/clis/google/suggest.d.ts +5 -0
  89. package/dist/clis/google/suggest.js +34 -0
  90. package/dist/clis/google/trends.d.ts +5 -0
  91. package/dist/clis/google/trends.js +38 -0
  92. package/dist/clis/google/utils.d.ts +9 -0
  93. package/dist/clis/google/utils.js +23 -0
  94. package/dist/clis/google/utils.test.d.ts +1 -0
  95. package/dist/clis/google/utils.test.js +75 -0
  96. package/dist/clis/grok/ask.d.ts +14 -0
  97. package/dist/clis/grok/ask.js +257 -65
  98. package/dist/clis/grok/ask.test.d.ts +1 -0
  99. package/dist/clis/grok/ask.test.js +36 -0
  100. package/dist/clis/instagram/comment.yaml +52 -0
  101. package/dist/clis/instagram/explore.yaml +43 -0
  102. package/dist/clis/instagram/follow.yaml +41 -0
  103. package/dist/clis/instagram/followers.yaml +51 -0
  104. package/dist/clis/instagram/following.yaml +51 -0
  105. package/dist/clis/instagram/like.yaml +46 -0
  106. package/dist/clis/instagram/profile.yaml +42 -0
  107. package/dist/clis/instagram/save.yaml +46 -0
  108. package/dist/clis/instagram/saved.yaml +40 -0
  109. package/dist/clis/instagram/search.yaml +43 -0
  110. package/dist/clis/instagram/unfollow.yaml +38 -0
  111. package/dist/clis/instagram/unlike.yaml +46 -0
  112. package/dist/clis/instagram/unsave.yaml +46 -0
  113. package/dist/clis/instagram/user.yaml +54 -0
  114. package/dist/clis/jike/repost.js +1 -1
  115. package/dist/clis/jimeng/generate.yaml +1 -0
  116. package/dist/clis/linux-do/category.yaml +1 -0
  117. package/dist/clis/lobsters/active.yaml +29 -0
  118. package/dist/clis/lobsters/hot.yaml +29 -0
  119. package/dist/clis/lobsters/newest.yaml +29 -0
  120. package/dist/clis/lobsters/tag.yaml +34 -0
  121. package/dist/clis/medium/feed.d.ts +1 -0
  122. package/dist/clis/medium/feed.js +15 -0
  123. package/dist/clis/medium/search.d.ts +1 -0
  124. package/dist/clis/medium/search.js +15 -0
  125. package/dist/clis/medium/shared.d.ts +5 -0
  126. package/dist/clis/medium/shared.js +78 -0
  127. package/dist/clis/medium/user.d.ts +1 -0
  128. package/dist/clis/medium/user.js +15 -0
  129. package/dist/clis/reddit/comment.js +1 -1
  130. package/dist/clis/reddit/read.js +1 -1
  131. package/dist/clis/reddit/save.js +1 -1
  132. package/dist/clis/reddit/subreddit.yaml +1 -0
  133. package/dist/clis/reddit/subscribe.js +1 -1
  134. package/dist/clis/reddit/upvote.js +1 -1
  135. package/dist/clis/sinablog/article.d.ts +1 -0
  136. package/dist/clis/sinablog/article.js +14 -0
  137. package/dist/clis/sinablog/hot.d.ts +1 -0
  138. package/dist/clis/sinablog/hot.js +14 -0
  139. package/dist/clis/sinablog/search.d.ts +1 -0
  140. package/dist/clis/sinablog/search.js +51 -0
  141. package/dist/clis/sinablog/shared.d.ts +7 -0
  142. package/dist/clis/sinablog/shared.js +187 -0
  143. package/dist/clis/sinablog/user.d.ts +1 -0
  144. package/dist/clis/sinablog/user.js +15 -0
  145. package/dist/clis/substack/feed.d.ts +1 -0
  146. package/dist/clis/substack/feed.js +15 -0
  147. package/dist/clis/substack/publication.d.ts +1 -0
  148. package/dist/clis/substack/publication.js +15 -0
  149. package/dist/clis/substack/search.d.ts +1 -0
  150. package/dist/clis/substack/search.js +77 -0
  151. package/dist/clis/substack/shared.d.ts +4 -0
  152. package/dist/clis/substack/shared.js +129 -0
  153. package/dist/clis/tiktok/comment.yaml +66 -0
  154. package/dist/clis/tiktok/explore.yaml +39 -0
  155. package/dist/clis/tiktok/follow.yaml +39 -0
  156. package/dist/clis/tiktok/following.yaml +46 -0
  157. package/dist/clis/tiktok/friends.yaml +47 -0
  158. package/dist/clis/tiktok/like.yaml +38 -0
  159. package/dist/clis/tiktok/live.yaml +51 -0
  160. package/dist/clis/tiktok/notifications.yaml +52 -0
  161. package/dist/clis/tiktok/profile.yaml +45 -0
  162. package/dist/clis/tiktok/save.yaml +34 -0
  163. package/dist/clis/tiktok/search.yaml +46 -0
  164. package/dist/clis/tiktok/unfollow.yaml +44 -0
  165. package/dist/clis/tiktok/unlike.yaml +38 -0
  166. package/dist/clis/tiktok/unsave.yaml +36 -0
  167. package/dist/clis/tiktok/user.yaml +44 -0
  168. package/dist/clis/twitter/download.d.ts +1 -1
  169. package/dist/clis/twitter/download.js +3 -3
  170. package/dist/clis/twitter/followers.js +1 -1
  171. package/dist/clis/twitter/following.js +1 -1
  172. package/dist/clis/twitter/thread.js +1 -1
  173. package/dist/clis/twitter/timeline.d.ts +23 -0
  174. package/dist/clis/twitter/timeline.js +42 -14
  175. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  176. package/dist/clis/twitter/timeline.test.js +102 -0
  177. package/dist/clis/wikipedia/random.d.ts +1 -0
  178. package/dist/clis/wikipedia/random.js +19 -0
  179. package/dist/clis/wikipedia/search.js +3 -3
  180. package/dist/clis/wikipedia/summary.js +4 -9
  181. package/dist/clis/wikipedia/trending.d.ts +1 -0
  182. package/dist/clis/wikipedia/trending.js +35 -0
  183. package/dist/clis/wikipedia/utils.d.ts +28 -0
  184. package/dist/clis/wikipedia/utils.js +13 -0
  185. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  186. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  187. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  188. package/dist/clis/xiaohongshu/download.js +1 -1
  189. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  190. package/dist/clis/xueqiu/search.yaml +2 -1
  191. package/dist/clis/xueqiu/stock.yaml +2 -0
  192. package/dist/clis/yahoo-finance/quote.js +1 -1
  193. package/dist/commanderAdapter.js +13 -7
  194. package/dist/daemon.js +21 -0
  195. package/dist/discovery.d.ts +8 -0
  196. package/dist/discovery.js +105 -19
  197. package/dist/doctor.js +3 -1
  198. package/dist/doctor.test.js +46 -2
  199. package/dist/engine.test.d.ts +0 -3
  200. package/dist/engine.test.js +74 -6
  201. package/dist/execution.d.ts +4 -2
  202. package/dist/execution.js +31 -7
  203. package/dist/explore.d.ts +76 -3
  204. package/dist/explore.js +11 -4
  205. package/dist/generate.d.ts +41 -2
  206. package/dist/generate.js +5 -4
  207. package/dist/main.js +2 -1
  208. package/dist/pipeline/executor.d.ts +4 -2
  209. package/dist/pipeline/executor.js +54 -15
  210. package/dist/pipeline/executor.test.js +33 -6
  211. package/dist/pipeline/registry.d.ts +1 -1
  212. package/dist/pipeline/steps/browser.d.ts +7 -7
  213. package/dist/pipeline/steps/browser.js +15 -7
  214. package/dist/pipeline/steps/fetch.d.ts +1 -1
  215. package/dist/pipeline/steps/fetch.js +11 -7
  216. package/dist/pipeline/steps/transform.d.ts +6 -5
  217. package/dist/pipeline/steps/transform.js +30 -9
  218. package/dist/pipeline/template.d.ts +6 -6
  219. package/dist/pipeline/template.js +43 -5
  220. package/dist/pipeline/template.test.js +18 -0
  221. package/dist/pipeline/transform.test.js +11 -0
  222. package/dist/plugin.d.ts +31 -0
  223. package/dist/plugin.js +216 -0
  224. package/dist/plugin.test.d.ts +4 -0
  225. package/dist/plugin.test.js +76 -0
  226. package/dist/registry-api.d.ts +11 -0
  227. package/dist/registry-api.js +9 -0
  228. package/dist/registry.d.ts +11 -0
  229. package/dist/registry.js +6 -1
  230. package/dist/synthesize.d.ts +94 -4
  231. package/dist/synthesize.js +5 -4
  232. package/dist/types.d.ts +39 -26
  233. package/dist/validate.js +8 -2
  234. package/docs/.vitepress/config.mts +6 -4
  235. package/docs/adapters/browser/barchart.md +6 -5
  236. package/docs/adapters/browser/bilibili.md +9 -0
  237. package/docs/adapters/browser/devto.md +35 -0
  238. package/docs/adapters/browser/douban.md +38 -0
  239. package/docs/adapters/browser/facebook.md +36 -0
  240. package/docs/adapters/browser/google.md +62 -0
  241. package/docs/adapters/browser/grok.md +26 -8
  242. package/docs/adapters/browser/instagram.md +46 -0
  243. package/docs/adapters/browser/lobsters.md +32 -0
  244. package/docs/adapters/browser/medium.md +32 -0
  245. package/docs/adapters/browser/reddit.md +9 -0
  246. package/docs/adapters/browser/sinablog.md +36 -0
  247. package/docs/adapters/browser/substack.md +38 -0
  248. package/docs/adapters/browser/tiktok.md +68 -0
  249. package/docs/adapters/browser/wikipedia.md +11 -2
  250. package/docs/adapters/browser/xueqiu.md +10 -0
  251. package/docs/adapters/browser/yahoo-finance.md +6 -5
  252. package/docs/adapters/desktop/antigravity.md +6 -0
  253. package/docs/adapters/desktop/chatgpt.md +2 -1
  254. package/docs/adapters/desktop/codex.md +5 -1
  255. package/docs/adapters/desktop/cursor.md +4 -0
  256. package/docs/adapters/desktop/discord.md +7 -7
  257. package/docs/adapters/index.md +1 -4
  258. package/docs/guide/getting-started.md +1 -0
  259. package/docs/guide/plugins.md +153 -0
  260. package/docs/zh/guide/plugins.md +107 -0
  261. package/extension/dist/background.js +91 -23
  262. package/extension/src/background.ts +82 -29
  263. package/extension/src/cdp.ts +42 -1
  264. package/package.json +10 -5
  265. package/scripts/clean-dist.cjs +13 -0
  266. package/src/browser/cdp.ts +71 -31
  267. package/src/browser/daemon-client.ts +21 -5
  268. package/src/browser/dom-helpers.ts +38 -7
  269. package/src/browser/dom-snapshot.test.ts +249 -0
  270. package/src/browser/dom-snapshot.ts +770 -0
  271. package/src/browser/index.ts +2 -0
  272. package/src/browser/mcp.ts +3 -1
  273. package/src/browser/page.ts +57 -21
  274. package/src/build-manifest.test.ts +70 -2
  275. package/src/build-manifest.ts +94 -26
  276. package/src/cli.ts +71 -2
  277. package/src/clis/barchart/greeks.ts +1 -1
  278. package/src/clis/barchart/options.ts +1 -1
  279. package/src/clis/barchart/quote.ts +1 -1
  280. package/src/clis/bilibili/download.ts +1 -1
  281. package/src/clis/bilibili/following.ts +1 -1
  282. package/src/clis/bilibili/subtitle.ts +1 -1
  283. package/src/clis/bilibili/user-videos.ts +1 -1
  284. package/src/clis/boss/batchgreet.ts +14 -106
  285. package/src/clis/boss/chatlist.ts +12 -26
  286. package/src/clis/boss/chatmsg.ts +16 -40
  287. package/src/clis/boss/common.ts +287 -0
  288. package/src/clis/boss/detail.ts +8 -54
  289. package/src/clis/boss/exchange.ts +15 -89
  290. package/src/clis/boss/greet.ts +23 -160
  291. package/src/clis/boss/invite.ts +36 -133
  292. package/src/clis/boss/joblist.ts +7 -36
  293. package/src/clis/boss/mark.ts +13 -94
  294. package/src/clis/boss/recommend.ts +12 -57
  295. package/src/clis/boss/resume.ts +19 -124
  296. package/src/clis/boss/search.ts +13 -66
  297. package/src/clis/boss/send.ts +21 -161
  298. package/src/clis/boss/stats.ts +19 -74
  299. package/src/clis/coupang/add-to-cart.ts +1 -1
  300. package/src/clis/devto/tag.yaml +34 -0
  301. package/src/clis/devto/top.yaml +29 -0
  302. package/src/clis/devto/user.yaml +33 -0
  303. package/src/clis/douban/book-hot.ts +15 -0
  304. package/src/clis/douban/marks.ts +135 -0
  305. package/src/clis/douban/movie-hot.ts +15 -0
  306. package/src/clis/douban/reviews.ts +127 -0
  307. package/src/clis/douban/search.ts +17 -0
  308. package/src/clis/douban/shared.ts +165 -0
  309. package/src/clis/douban/subject.yaml +76 -0
  310. package/src/clis/douban/top250.yaml +70 -0
  311. package/src/clis/douban/utils.ts +81 -0
  312. package/src/clis/facebook/add-friend.yaml +43 -0
  313. package/src/clis/facebook/events.yaml +44 -0
  314. package/src/clis/facebook/feed.yaml +63 -0
  315. package/src/clis/facebook/friends.yaml +42 -0
  316. package/src/clis/facebook/groups.yaml +50 -0
  317. package/src/clis/facebook/join-group.yaml +44 -0
  318. package/src/clis/facebook/memories.yaml +39 -0
  319. package/src/clis/facebook/notifications.yaml +40 -0
  320. package/src/clis/facebook/profile.yaml +37 -0
  321. package/src/clis/facebook/search.yaml +46 -0
  322. package/src/clis/google/news.ts +66 -0
  323. package/src/clis/google/search.ts +133 -0
  324. package/src/clis/google/suggest.ts +40 -0
  325. package/src/clis/google/trends.ts +44 -0
  326. package/src/clis/google/utils.test.ts +82 -0
  327. package/src/clis/google/utils.ts +24 -0
  328. package/src/clis/grok/ask.test.ts +53 -0
  329. package/src/clis/grok/ask.ts +300 -69
  330. package/src/clis/instagram/comment.yaml +52 -0
  331. package/src/clis/instagram/explore.yaml +43 -0
  332. package/src/clis/instagram/follow.yaml +41 -0
  333. package/src/clis/instagram/followers.yaml +51 -0
  334. package/src/clis/instagram/following.yaml +51 -0
  335. package/src/clis/instagram/like.yaml +46 -0
  336. package/src/clis/instagram/profile.yaml +42 -0
  337. package/src/clis/instagram/save.yaml +46 -0
  338. package/src/clis/instagram/saved.yaml +40 -0
  339. package/src/clis/instagram/search.yaml +43 -0
  340. package/src/clis/instagram/unfollow.yaml +38 -0
  341. package/src/clis/instagram/unlike.yaml +46 -0
  342. package/src/clis/instagram/unsave.yaml +46 -0
  343. package/src/clis/instagram/user.yaml +54 -0
  344. package/src/clis/jike/repost.ts +1 -1
  345. package/src/clis/jimeng/generate.yaml +1 -0
  346. package/src/clis/linux-do/category.yaml +1 -0
  347. package/src/clis/lobsters/active.yaml +29 -0
  348. package/src/clis/lobsters/hot.yaml +29 -0
  349. package/src/clis/lobsters/newest.yaml +29 -0
  350. package/src/clis/lobsters/tag.yaml +34 -0
  351. package/src/clis/medium/feed.ts +16 -0
  352. package/src/clis/medium/search.ts +16 -0
  353. package/src/clis/medium/shared.ts +83 -0
  354. package/src/clis/medium/user.ts +16 -0
  355. package/src/clis/reddit/comment.ts +1 -1
  356. package/src/clis/reddit/read.ts +1 -1
  357. package/src/clis/reddit/save.ts +1 -1
  358. package/src/clis/reddit/subreddit.yaml +1 -0
  359. package/src/clis/reddit/subscribe.ts +1 -1
  360. package/src/clis/reddit/upvote.ts +1 -1
  361. package/src/clis/sinablog/article.ts +15 -0
  362. package/src/clis/sinablog/hot.ts +15 -0
  363. package/src/clis/sinablog/search.ts +56 -0
  364. package/src/clis/sinablog/shared.ts +198 -0
  365. package/src/clis/sinablog/user.ts +16 -0
  366. package/src/clis/substack/feed.ts +16 -0
  367. package/src/clis/substack/publication.ts +16 -0
  368. package/src/clis/substack/search.ts +91 -0
  369. package/src/clis/substack/shared.ts +132 -0
  370. package/src/clis/tiktok/comment.yaml +66 -0
  371. package/src/clis/tiktok/explore.yaml +39 -0
  372. package/src/clis/tiktok/follow.yaml +39 -0
  373. package/src/clis/tiktok/following.yaml +46 -0
  374. package/src/clis/tiktok/friends.yaml +47 -0
  375. package/src/clis/tiktok/like.yaml +38 -0
  376. package/src/clis/tiktok/live.yaml +51 -0
  377. package/src/clis/tiktok/notifications.yaml +52 -0
  378. package/src/clis/tiktok/profile.yaml +45 -0
  379. package/src/clis/tiktok/save.yaml +34 -0
  380. package/src/clis/tiktok/search.yaml +46 -0
  381. package/src/clis/tiktok/unfollow.yaml +44 -0
  382. package/src/clis/tiktok/unlike.yaml +38 -0
  383. package/src/clis/tiktok/unsave.yaml +36 -0
  384. package/src/clis/tiktok/user.yaml +44 -0
  385. package/src/clis/twitter/download.ts +3 -3
  386. package/src/clis/twitter/followers.ts +1 -1
  387. package/src/clis/twitter/following.ts +1 -1
  388. package/src/clis/twitter/thread.ts +1 -1
  389. package/src/clis/twitter/timeline.test.ts +109 -0
  390. package/src/clis/twitter/timeline.ts +59 -19
  391. package/src/clis/wikipedia/random.ts +19 -0
  392. package/src/clis/wikipedia/search.ts +10 -4
  393. package/src/clis/wikipedia/summary.ts +4 -9
  394. package/src/clis/wikipedia/trending.ts +41 -0
  395. package/src/clis/wikipedia/utils.ts +31 -0
  396. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  397. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  398. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  399. package/src/clis/xiaohongshu/download.ts +1 -1
  400. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  401. package/src/clis/xueqiu/search.yaml +2 -1
  402. package/src/clis/xueqiu/stock.yaml +2 -0
  403. package/src/clis/yahoo-finance/quote.ts +1 -1
  404. package/src/commanderAdapter.ts +17 -10
  405. package/src/daemon.ts +23 -0
  406. package/src/discovery.ts +134 -24
  407. package/src/doctor.test.ts +59 -2
  408. package/src/doctor.ts +4 -2
  409. package/src/engine.test.ts +79 -6
  410. package/src/execution.ts +42 -16
  411. package/src/explore.ts +77 -9
  412. package/src/generate.ts +58 -9
  413. package/src/main.ts +2 -1
  414. package/src/pipeline/executor.test.ts +35 -6
  415. package/src/pipeline/executor.ts +68 -19
  416. package/src/pipeline/registry.ts +3 -3
  417. package/src/pipeline/steps/browser.ts +24 -15
  418. package/src/pipeline/steps/fetch.ts +18 -13
  419. package/src/pipeline/steps/transform.ts +40 -15
  420. package/src/pipeline/template.test.ts +18 -0
  421. package/src/pipeline/template.ts +86 -13
  422. package/src/pipeline/transform.test.ts +15 -2
  423. package/src/plugin.test.ts +86 -0
  424. package/src/plugin.ts +254 -0
  425. package/src/registry-api.ts +12 -0
  426. package/src/registry.ts +19 -1
  427. package/src/synthesize.ts +102 -21
  428. package/src/types.ts +44 -12
  429. package/src/validate.ts +19 -4
  430. package/tests/e2e/browser-public.test.ts +11 -0
  431. package/tests/e2e/public-commands.test.ts +64 -0
  432. package/dist/clis/feishu/new.d.ts +0 -1
  433. package/dist/clis/feishu/new.js +0 -27
  434. package/dist/clis/feishu/read.d.ts +0 -1
  435. package/dist/clis/feishu/read.js +0 -40
  436. package/dist/clis/feishu/search.d.ts +0 -1
  437. package/dist/clis/feishu/search.js +0 -30
  438. package/dist/clis/feishu/send.d.ts +0 -1
  439. package/dist/clis/feishu/send.js +0 -39
  440. package/dist/clis/feishu/status.d.ts +0 -1
  441. package/dist/clis/feishu/status.js +0 -28
  442. package/dist/clis/neteasemusic/like.d.ts +0 -1
  443. package/dist/clis/neteasemusic/like.js +0 -25
  444. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  445. package/dist/clis/neteasemusic/lyrics.js +0 -47
  446. package/dist/clis/neteasemusic/next.d.ts +0 -1
  447. package/dist/clis/neteasemusic/next.js +0 -26
  448. package/dist/clis/neteasemusic/play.d.ts +0 -1
  449. package/dist/clis/neteasemusic/play.js +0 -26
  450. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  451. package/dist/clis/neteasemusic/playing.js +0 -59
  452. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  453. package/dist/clis/neteasemusic/playlist.js +0 -46
  454. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  455. package/dist/clis/neteasemusic/prev.js +0 -25
  456. package/dist/clis/neteasemusic/search.d.ts +0 -1
  457. package/dist/clis/neteasemusic/search.js +0 -52
  458. package/dist/clis/neteasemusic/status.d.ts +0 -1
  459. package/dist/clis/neteasemusic/status.js +0 -16
  460. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  461. package/dist/clis/neteasemusic/volume.js +0 -54
  462. package/dist/clis/wechat/chats.d.ts +0 -1
  463. package/dist/clis/wechat/chats.js +0 -28
  464. package/dist/clis/wechat/contacts.d.ts +0 -1
  465. package/dist/clis/wechat/contacts.js +0 -28
  466. package/dist/clis/wechat/read.d.ts +0 -1
  467. package/dist/clis/wechat/read.js +0 -58
  468. package/dist/clis/wechat/search.d.ts +0 -1
  469. package/dist/clis/wechat/search.js +0 -31
  470. package/dist/clis/wechat/send.d.ts +0 -1
  471. package/dist/clis/wechat/send.js +0 -42
  472. package/dist/clis/wechat/status.d.ts +0 -1
  473. package/dist/clis/wechat/status.js +0 -29
  474. package/dist/pipeline.d.ts +0 -7
  475. package/dist/pipeline.js +0 -7
  476. package/docs/adapters/browser/github.md +0 -26
  477. package/docs/adapters/desktop/feishu.md +0 -20
  478. package/docs/adapters/desktop/neteasemusic.md +0 -31
  479. package/docs/adapters/desktop/wechat.md +0 -28
  480. package/src/clis/antigravity/README.md +0 -5
  481. package/src/clis/antigravity/README.zh-CN.md +0 -51
  482. package/src/clis/chaoxing/README.md +0 -14
  483. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  484. package/src/clis/chatgpt/README.md +0 -5
  485. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  486. package/src/clis/chatwise/README.md +0 -5
  487. package/src/clis/chatwise/README.zh-CN.md +0 -38
  488. package/src/clis/codex/README.md +0 -5
  489. package/src/clis/codex/README.zh-CN.md +0 -33
  490. package/src/clis/cursor/README.md +0 -5
  491. package/src/clis/cursor/README.zh-CN.md +0 -33
  492. package/src/clis/discord-app/README.md +0 -5
  493. package/src/clis/discord-app/README.zh-CN.md +0 -28
  494. package/src/clis/feishu/README.md +0 -5
  495. package/src/clis/feishu/README.zh-CN.md +0 -20
  496. package/src/clis/feishu/new.ts +0 -32
  497. package/src/clis/feishu/read.ts +0 -48
  498. package/src/clis/feishu/search.ts +0 -35
  499. package/src/clis/feishu/send.ts +0 -46
  500. package/src/clis/feishu/status.ts +0 -34
  501. package/src/clis/neteasemusic/README.md +0 -5
  502. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  503. package/src/clis/neteasemusic/like.ts +0 -28
  504. package/src/clis/neteasemusic/lyrics.ts +0 -53
  505. package/src/clis/neteasemusic/next.ts +0 -30
  506. package/src/clis/neteasemusic/play.ts +0 -30
  507. package/src/clis/neteasemusic/playing.ts +0 -62
  508. package/src/clis/neteasemusic/playlist.ts +0 -51
  509. package/src/clis/neteasemusic/prev.ts +0 -29
  510. package/src/clis/neteasemusic/search.ts +0 -58
  511. package/src/clis/neteasemusic/status.ts +0 -18
  512. package/src/clis/neteasemusic/volume.ts +0 -61
  513. package/src/clis/notion/README.md +0 -5
  514. package/src/clis/notion/README.zh-CN.md +0 -29
  515. package/src/clis/wechat/README.md +0 -5
  516. package/src/clis/wechat/README.zh-CN.md +0 -28
  517. package/src/clis/wechat/chats.ts +0 -33
  518. package/src/clis/wechat/contacts.ts +0 -33
  519. package/src/clis/wechat/read.ts +0 -72
  520. package/src/clis/wechat/search.ts +0 -36
  521. package/src/clis/wechat/send.ts +0 -49
  522. package/src/clis/wechat/status.ts +0 -35
  523. package/src/pipeline.ts +0 -8
package/dist/explore.d.ts CHANGED
@@ -5,8 +5,80 @@
5
5
  * captures network traffic, analyzes JSON responses, and automatically
6
6
  * infers CLI capabilities from discovered API endpoints.
7
7
  */
8
+ import type { IBrowserFactory } from './runtime.js';
8
9
  export declare function detectSiteName(url: string): string;
9
10
  export declare function slugify(value: string): string;
11
+ interface InferredCapability {
12
+ name: string;
13
+ description: string;
14
+ strategy: string;
15
+ confidence: number;
16
+ endpoint: string;
17
+ itemPath: string | null;
18
+ recommendedColumns: string[];
19
+ recommendedArgs: Array<{
20
+ name: string;
21
+ type: string;
22
+ required: boolean;
23
+ default?: unknown;
24
+ }>;
25
+ storeHint?: {
26
+ store: string;
27
+ action: string;
28
+ };
29
+ }
30
+ export interface ExploreManifest {
31
+ site: string;
32
+ target_url: string;
33
+ final_url: string;
34
+ title: string;
35
+ framework: Record<string, boolean>;
36
+ stores: Array<{
37
+ type: DiscoveredStore['type'];
38
+ id: string;
39
+ actions: string[];
40
+ }>;
41
+ top_strategy: string;
42
+ explored_at?: string;
43
+ }
44
+ export interface ExploreAuthSummary {
45
+ top_strategy: string;
46
+ indicators: string[];
47
+ framework: Record<string, boolean>;
48
+ }
49
+ export interface ExploreEndpointArtifact {
50
+ pattern: string;
51
+ method: string;
52
+ url: string;
53
+ status: number | null;
54
+ contentType: string;
55
+ score: number;
56
+ queryParams: string[];
57
+ itemPath: string | null;
58
+ itemCount: number;
59
+ detectedFields: Record<string, string>;
60
+ authIndicators: string[];
61
+ }
62
+ export interface ExploreResult {
63
+ site: string;
64
+ target_url: string;
65
+ final_url: string;
66
+ title: string;
67
+ framework: Record<string, boolean>;
68
+ stores: DiscoveredStore[];
69
+ top_strategy: string;
70
+ endpoint_count: number;
71
+ api_endpoint_count: number;
72
+ capabilities: InferredCapability[];
73
+ auth_indicators: string[];
74
+ out_dir: string;
75
+ }
76
+ export interface ExploreBundle {
77
+ manifest: ExploreManifest;
78
+ endpoints: ExploreEndpointArtifact[];
79
+ capabilities: InferredCapability[];
80
+ auth: ExploreAuthSummary;
81
+ }
10
82
  export interface DiscoveredStore {
11
83
  type: 'pinia' | 'vuex';
12
84
  id: string;
@@ -14,7 +86,7 @@ export interface DiscoveredStore {
14
86
  stateKeys: string[];
15
87
  }
16
88
  export declare function exploreUrl(url: string, opts: {
17
- BrowserFactory: new () => any;
89
+ BrowserFactory: new () => IBrowserFactory;
18
90
  site?: string;
19
91
  goal?: string;
20
92
  authenticated?: boolean;
@@ -24,5 +96,6 @@ export declare function exploreUrl(url: string, opts: {
24
96
  clickLabels?: string[];
25
97
  auto?: boolean;
26
98
  workspace?: string;
27
- }): Promise<Record<string, any>>;
28
- export declare function renderExploreSummary(result: Record<string, any>): string;
99
+ }): Promise<ExploreResult>;
100
+ export declare function renderExploreSummary(result: ExploreResult): string;
101
+ export {};
package/dist/explore.js CHANGED
@@ -147,6 +147,9 @@ function flattenFields(obj, prefix, maxDepth) {
147
147
  }
148
148
  return names;
149
149
  }
150
+ function isBooleanRecord(value) {
151
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
152
+ }
150
153
  function scoreEndpoint(ep) {
151
154
  let s = 0;
152
155
  if (ep.contentType.includes('json'))
@@ -401,7 +404,7 @@ export async function exploreUrl(url, opts) {
401
404
  let framework = {};
402
405
  try {
403
406
  const fw = await page.evaluate(FRAMEWORK_DETECT_JS);
404
- if (fw && typeof fw === 'object')
407
+ if (isBooleanRecord(fw))
405
408
  framework = fw;
406
409
  }
407
410
  catch { }
@@ -458,11 +461,15 @@ export function renderExploreSummary(result) {
458
461
  lines.push(`Output: ${result.out_dir}`);
459
462
  return lines.join('\n');
460
463
  }
461
- async function readPageMetadata(page /* IPage */) {
464
+ async function readPageMetadata(page) {
462
465
  try {
463
466
  const result = await page.evaluate(`() => ({ url: window.location.href, title: document.title || '' })`);
464
- if (result && typeof result === 'object')
465
- return { url: String(result.url ?? ''), title: String(result.title ?? '') };
467
+ if (result && typeof result === 'object' && !Array.isArray(result)) {
468
+ return {
469
+ url: String(result.url ?? ''),
470
+ title: String(result.title ?? ''),
471
+ };
472
+ }
466
473
  }
467
474
  catch { }
468
475
  return { url: '', title: '' };
@@ -7,5 +7,44 @@
7
7
  * Includes Strategy Cascade: if the initial strategy fails,
8
8
  * automatically downgrades and retries.
9
9
  */
10
- export declare function generateCliFromUrl(opts: any): Promise<any>;
11
- export declare function renderGenerateSummary(r: any): string;
10
+ import type { IBrowserFactory } from './runtime.js';
11
+ import { type SynthesizeCandidateSummary } from './synthesize.js';
12
+ interface RegisterCandidatesResult {
13
+ ok: boolean;
14
+ count: number;
15
+ }
16
+ export interface GenerateCliOptions {
17
+ url: string;
18
+ BrowserFactory: new () => IBrowserFactory;
19
+ builtinClis?: string;
20
+ userClis?: string;
21
+ goal?: string | null;
22
+ site?: string;
23
+ waitSeconds?: number;
24
+ top?: number;
25
+ register?: boolean;
26
+ workspace?: string;
27
+ }
28
+ export interface GenerateCliResult {
29
+ ok: boolean;
30
+ goal?: string | null;
31
+ normalized_goal?: string | null;
32
+ site: string;
33
+ selected_candidate: SynthesizeCandidateSummary | null;
34
+ selected_command: string;
35
+ explore: {
36
+ endpoint_count: number;
37
+ api_endpoint_count: number;
38
+ capability_count: number;
39
+ top_strategy: string;
40
+ framework: Record<string, boolean>;
41
+ };
42
+ synthesize: {
43
+ candidate_count: number;
44
+ candidates: Array<Pick<SynthesizeCandidateSummary, 'name' | 'strategy' | 'confidence'>>;
45
+ };
46
+ register: RegisterCandidatesResult | null;
47
+ }
48
+ export declare function generateCliFromUrl(opts: GenerateCliOptions): Promise<GenerateCliResult>;
49
+ export declare function renderGenerateSummary(r: GenerateCliResult): string;
50
+ export {};
package/dist/generate.js CHANGED
@@ -9,8 +9,9 @@
9
9
  */
10
10
  import { exploreUrl } from './explore.js';
11
11
  import { synthesizeFromExplore } from './synthesize.js';
12
- // TODO: implement real CLI registration (copy candidate YAML to user clis dir)
13
- function registerCandidates(_opts) { return { ok: true, count: 0 }; }
12
+ function registerCandidates(_opts) {
13
+ return { ok: true, count: 0 };
14
+ }
14
15
  const CAPABILITY_ALIASES = {
15
16
  search: ['search', '搜索', '查找', 'query', 'keyword'],
16
17
  hot: ['hot', '热门', '热榜', '热搜', 'popular', 'top', 'ranking'],
@@ -58,7 +59,7 @@ export async function generateCliFromUrl(opts) {
58
59
  const exploreResult = await exploreUrl(opts.url, {
59
60
  BrowserFactory: opts.BrowserFactory,
60
61
  site: opts.site,
61
- goal: normalizeGoal(opts.goal) ?? opts.goal,
62
+ goal: normalizeGoal(opts.goal) ?? opts.goal ?? undefined,
62
63
  waitSeconds: opts.waitSeconds ?? 3,
63
64
  workspace: opts.workspace,
64
65
  });
@@ -68,7 +69,7 @@ export async function generateCliFromUrl(opts) {
68
69
  });
69
70
  // Step 3: Select best candidate for goal
70
71
  const selected = selectCandidate(synthesizeResult.candidates ?? [], opts.goal);
71
- const selectedSite = selected?.site ?? synthesizeResult.site ?? exploreResult.site;
72
+ const selectedSite = synthesizeResult.site ?? exploreResult.site;
72
73
  // Step 4: Register (if requested)
73
74
  let registerResult = null;
74
75
  if (opts.register !== false && synthesizeResult.candidate_count > 0) {
package/dist/main.js CHANGED
@@ -5,7 +5,7 @@
5
5
  import * as os from 'node:os';
6
6
  import * as path from 'node:path';
7
7
  import { fileURLToPath } from 'node:url';
8
- import { discoverClis } from './discovery.js';
8
+ import { discoverClis, discoverPlugins } from './discovery.js';
9
9
  import { getCompletions } from './completion.js';
10
10
  import { runCli } from './cli.js';
11
11
  const __filename = fileURLToPath(import.meta.url);
@@ -13,6 +13,7 @@ const __dirname = path.dirname(__filename);
13
13
  const BUILTIN_CLIS = path.resolve(__dirname, 'clis');
14
14
  const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
15
15
  await discoverClis(BUILTIN_CLIS, USER_CLIS);
16
+ await discoverPlugins();
16
17
  // ── Fast-path: handle --get-completions before commander parses ─────────
17
18
  // Usage: opencli --get-completions --cursor <N> [word1 word2 ...]
18
19
  const getCompIdx = process.argv.indexOf('--get-completions');
@@ -3,7 +3,9 @@
3
3
  */
4
4
  import type { IPage } from '../types.js';
5
5
  export interface PipelineContext {
6
- args?: Record<string, any>;
6
+ args?: Record<string, unknown>;
7
7
  debug?: boolean;
8
+ /** Max retry attempts per step (default: 2 for browser steps, 0 for others) */
9
+ stepRetries?: number;
8
10
  }
9
- export declare function executePipeline(page: IPage | null, pipeline: any[], ctx?: PipelineContext): Promise<any>;
11
+ export declare function executePipeline(page: IPage | null, pipeline: unknown[], ctx?: PipelineContext): Promise<unknown>;
@@ -3,32 +3,71 @@
3
3
  */
4
4
  import { getStep } from './registry.js';
5
5
  import { log } from '../logger.js';
6
+ import { ConfigError } from '../errors.js';
7
+ /** Steps that interact with the browser and may fail transiently */
8
+ const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot', 'scroll']);
6
9
  export async function executePipeline(page, pipeline, ctx = {}) {
7
10
  const args = ctx.args ?? {};
8
11
  const debug = ctx.debug ?? false;
9
12
  let data = null;
10
13
  const total = pipeline.length;
11
- for (let i = 0; i < pipeline.length; i++) {
12
- const step = pipeline[i];
13
- if (!step || typeof step !== 'object')
14
- continue;
15
- for (const [op, params] of Object.entries(step)) {
16
- if (debug)
17
- debugStepStart(i + 1, total, op, params);
18
- const handler = getStep(op);
19
- if (handler) {
20
- data = await handler(page, params, data, args);
21
- }
22
- else {
14
+ try {
15
+ for (let i = 0; i < pipeline.length; i++) {
16
+ const step = pipeline[i];
17
+ if (!step || typeof step !== 'object')
18
+ continue;
19
+ for (const [op, params] of Object.entries(step)) {
20
+ if (debug)
21
+ debugStepStart(i + 1, total, op, params);
22
+ const handler = getStep(op);
23
+ if (handler) {
24
+ data = await executeStepWithRetry(handler, page, params, data, args, op, ctx.stepRetries);
25
+ }
26
+ else {
27
+ throw new ConfigError(`Unknown pipeline step "${op}" at index ${i}.`, 'Check the YAML pipeline step name or register the custom step before execution.');
28
+ }
23
29
  if (debug)
24
- log.warn(`Unknown step: ${op}`);
30
+ debugStepResult(op, data);
31
+ }
32
+ }
33
+ }
34
+ catch (err) {
35
+ // Attempt cleanup: close automation window on pipeline failure
36
+ if (page && typeof page.closeWindow === 'function') {
37
+ try {
38
+ await page.closeWindow();
25
39
  }
26
- if (debug)
27
- debugStepResult(op, data);
40
+ catch { /* ignore */ }
28
41
  }
42
+ throw err;
29
43
  }
30
44
  return data;
31
45
  }
46
+ async function executeStepWithRetry(handler, page, params, data, args, op, configRetries) {
47
+ const maxRetries = configRetries ?? (BROWSER_STEPS.has(op) ? 2 : 0);
48
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
49
+ try {
50
+ return await handler(page, params, data, args);
51
+ }
52
+ catch (err) {
53
+ if (attempt >= maxRetries)
54
+ throw err;
55
+ // Only retry on transient browser errors
56
+ const msg = err instanceof Error ? err.message : '';
57
+ const isTransient = msg.includes('Extension disconnected')
58
+ || msg.includes('attach failed')
59
+ || msg.includes('no longer exists')
60
+ || msg.includes('CDP connection')
61
+ || msg.includes('Daemon command failed');
62
+ if (!isTransient)
63
+ throw err;
64
+ // Brief delay before retry
65
+ await new Promise(resolve => setTimeout(resolve, 1000));
66
+ }
67
+ }
68
+ // Unreachable
69
+ throw new Error(`Step "${op}" failed after ${maxRetries} retries`);
70
+ }
32
71
  function debugStepStart(stepNum, total, op, params) {
33
72
  let preview = '';
34
73
  if (typeof params === 'string') {
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import { describe, it, expect, vi } from 'vitest';
5
5
  import { executePipeline } from './index.js';
6
+ import { ConfigError } from '../errors.js';
6
7
  /** Create a minimal mock page for testing */
7
8
  function createMockPage(overrides = {}) {
8
9
  return {
@@ -13,6 +14,7 @@ function createMockPage(overrides = {}) {
13
14
  click: vi.fn(),
14
15
  typeText: vi.fn(),
15
16
  pressKey: vi.fn(),
17
+ getFormState: vi.fn().mockResolvedValue({}),
16
18
  wait: vi.fn(),
17
19
  tabs: vi.fn().mockResolvedValue([]),
18
20
  closeTab: vi.fn(),
@@ -21,6 +23,7 @@ function createMockPage(overrides = {}) {
21
23
  networkRequests: vi.fn().mockResolvedValue([]),
22
24
  consoleMessages: vi.fn().mockResolvedValue(''),
23
25
  scroll: vi.fn(),
26
+ scrollTo: vi.fn(),
24
27
  autoScroll: vi.fn(),
25
28
  installInterceptor: vi.fn(),
26
29
  getInterceptedRequests: vi.fn().mockResolvedValue([]),
@@ -70,6 +73,30 @@ describe('executePipeline', () => {
70
73
  { name: 'World', score: 20 },
71
74
  ]);
72
75
  });
76
+ it('runs inline select inside map step', async () => {
77
+ const page = createMockPage({
78
+ evaluate: vi.fn().mockResolvedValue({
79
+ posts: [
80
+ { title: 'First', rank: 1 },
81
+ { title: 'Second', rank: 2 },
82
+ ],
83
+ }),
84
+ });
85
+ const result = await executePipeline(page, [
86
+ { evaluate: 'test' },
87
+ {
88
+ map: {
89
+ select: 'posts',
90
+ title: '${{ item.title }}',
91
+ rank: '${{ item.rank }}',
92
+ },
93
+ },
94
+ ]);
95
+ expect(result).toEqual([
96
+ { title: 'First', rank: 1 },
97
+ { title: 'Second', rank: 2 },
98
+ ]);
99
+ });
73
100
  it('executes limit step', async () => {
74
101
  const page = createMockPage({
75
102
  evaluate: vi.fn().mockResolvedValue([1, 2, 3, 4, 5]),
@@ -107,13 +134,13 @@ describe('executePipeline', () => {
107
134
  ]);
108
135
  expect(page.wait).toHaveBeenCalledWith(2);
109
136
  });
110
- it('handles unknown steps gracefully in debug mode', async () => {
111
- const stderr = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
112
- await executePipeline(null, [
137
+ it('fails fast on unknown steps', async () => {
138
+ await expect(executePipeline(null, [
139
+ { unknownStep: 'test' },
140
+ ], { debug: true })).rejects.toBeInstanceOf(ConfigError);
141
+ await expect(executePipeline(null, [
113
142
  { unknownStep: 'test' },
114
- ], { debug: true });
115
- expect(stderr).toHaveBeenCalledWith(expect.stringContaining('Unknown step'));
116
- stderr.mockRestore();
143
+ ], { debug: true })).rejects.toThrow('Unknown pipeline step "unknownStep"');
117
144
  });
118
145
  it('passes args through template rendering', async () => {
119
146
  const page = createMockPage({
@@ -8,7 +8,7 @@ import type { IPage } from '../types.js';
8
8
  * TData is the type of the `data` state flowing into the step.
9
9
  * TResult is the expected return type.
10
10
  */
11
- export type StepHandler<TData = any, TResult = any> = (page: IPage | null, params: any, data: TData, args: Record<string, any>) => Promise<TResult>;
11
+ export type StepHandler<TData = unknown, TResult = unknown, TParams = unknown> = (page: IPage | null, params: TParams, data: TData, args: Record<string, unknown>) => Promise<TResult>;
12
12
  /**
13
13
  * Get a registered step handler by name.
14
14
  */
@@ -3,10 +3,10 @@
3
3
  * Browser interaction primitives.
4
4
  */
5
5
  import type { IPage } from '../../types.js';
6
- export declare function stepNavigate(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
7
- export declare function stepClick(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
8
- export declare function stepType(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
9
- export declare function stepWait(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
10
- export declare function stepPress(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
11
- export declare function stepSnapshot(page: IPage | null, params: any, _data: any, _args: Record<string, any>): Promise<any>;
12
- export declare function stepEvaluate(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
6
+ export declare function stepNavigate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
7
+ export declare function stepClick(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
8
+ export declare function stepType(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
9
+ export declare function stepWait(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
10
+ export declare function stepPress(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
11
+ export declare function stepSnapshot(page: IPage | null, params: unknown, _data: unknown, _args: Record<string, unknown>): Promise<unknown>;
12
+ export declare function stepEvaluate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
@@ -3,10 +3,13 @@
3
3
  * Browser interaction primitives.
4
4
  */
5
5
  import { render } from '../template.js';
6
+ function isRecord(value) {
7
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
8
+ }
6
9
  export async function stepNavigate(page, params, data, args) {
7
- if (typeof params === 'object' && params && 'url' in params) {
10
+ if (isRecord(params) && 'url' in params) {
8
11
  const url = String(render(params.url, { args, data }));
9
- await page.goto(url, { waitUntil: params.waitUntil, settleMs: params.settleMs });
12
+ await page.goto(url, { waitUntil: params.waitUntil, settleMs: typeof params.settleMs === 'number' ? params.settleMs : undefined });
10
13
  }
11
14
  else {
12
15
  const url = render(params, { args, data });
@@ -19,7 +22,7 @@ export async function stepClick(page, params, data, args) {
19
22
  return data;
20
23
  }
21
24
  export async function stepType(page, params, data, args) {
22
- if (typeof params === 'object' && params) {
25
+ if (isRecord(params)) {
23
26
  const ref = String(render(params.ref ?? '', { args, data })).replace(/^@/, '');
24
27
  const text = String(render(params.text ?? '', { args, data }));
25
28
  await page.typeText(ref, text);
@@ -31,11 +34,11 @@ export async function stepType(page, params, data, args) {
31
34
  export async function stepWait(page, params, data, args) {
32
35
  if (typeof params === 'number')
33
36
  await page.wait(params);
34
- else if (typeof params === 'object' && params) {
37
+ else if (isRecord(params)) {
35
38
  if ('text' in params) {
36
39
  await page.wait({
37
40
  text: String(render(params.text, { args, data })),
38
- timeout: params.timeout
41
+ timeout: typeof params.timeout === 'number' ? params.timeout : undefined,
39
42
  });
40
43
  }
41
44
  else if ('time' in params)
@@ -50,8 +53,13 @@ export async function stepPress(page, params, data, args) {
50
53
  return data;
51
54
  }
52
55
  export async function stepSnapshot(page, params, _data, _args) {
53
- const opts = (typeof params === 'object' && params) ? params : {};
54
- return page.snapshot({ interactive: opts.interactive ?? false, compact: opts.compact ?? false, maxDepth: opts.max_depth, raw: opts.raw ?? false });
56
+ const opts = isRecord(params) ? params : {};
57
+ return page.snapshot({
58
+ interactive: typeof opts.interactive === 'boolean' ? opts.interactive : false,
59
+ compact: typeof opts.compact === 'boolean' ? opts.compact : false,
60
+ maxDepth: typeof opts.max_depth === 'number' ? opts.max_depth : undefined,
61
+ raw: typeof opts.raw === 'boolean' ? opts.raw : false,
62
+ });
55
63
  }
56
64
  export async function stepEvaluate(page, params, data, args) {
57
65
  const js = String(render(params, { args, data }));
@@ -2,4 +2,4 @@
2
2
  * Pipeline step: fetch — HTTP API requests.
3
3
  */
4
4
  import type { IPage } from '../../types.js';
5
- export declare function stepFetch(page: IPage | null, params: any, data: any, args: Record<string, any>): Promise<any>;
5
+ export declare function stepFetch(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
@@ -2,6 +2,9 @@
2
2
  * Pipeline step: fetch — HTTP API requests.
3
3
  */
4
4
  import { render } from '../template.js';
5
+ function isRecord(value) {
6
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ }
5
8
  /** Simple async concurrency limiter */
6
9
  async function mapConcurrent(items, limit, fn) {
7
10
  const results = new Array(items.length);
@@ -53,7 +56,7 @@ async function fetchSingle(page, url, method, queryParams, headers, args, data)
53
56
  async function fetchBatchInBrowser(page, urls, method, headers, concurrency) {
54
57
  const headersJs = JSON.stringify(headers);
55
58
  const urlsJs = JSON.stringify(urls);
56
- return page.evaluate(`
59
+ return (await page.evaluate(`
57
60
  async () => {
58
61
  const urls = ${urlsJs};
59
62
  const method = "${method}";
@@ -79,17 +82,18 @@ async function fetchBatchInBrowser(page, urls, method, headers, concurrency) {
79
82
  await Promise.all(workers);
80
83
  return results;
81
84
  }
82
- `);
85
+ `));
83
86
  }
84
87
  export async function stepFetch(page, params, data, args) {
85
- const urlOrObj = typeof params === 'string' ? params : (params?.url ?? '');
86
- const method = params?.method ?? 'GET';
87
- const queryParams = params?.params ?? {};
88
- const headers = params?.headers ?? {};
88
+ const paramObject = isRecord(params) ? params : {};
89
+ const urlOrObj = typeof params === 'string' ? params : (paramObject.url ?? '');
90
+ const method = typeof paramObject.method === 'string' ? paramObject.method : 'GET';
91
+ const queryParams = isRecord(paramObject.params) ? paramObject.params : {};
92
+ const headers = isRecord(paramObject.headers) ? paramObject.headers : {};
89
93
  const urlTemplate = String(urlOrObj);
90
94
  // Per-item fetch when data is array and URL references item
91
95
  if (Array.isArray(data) && urlTemplate.includes('item')) {
92
- const concurrency = typeof params?.concurrency === 'number' ? params.concurrency : 5;
96
+ const concurrency = typeof paramObject.concurrency === 'number' ? paramObject.concurrency : 5;
93
97
  // Render all URLs upfront
94
98
  const renderedHeaders = {};
95
99
  for (const [k, v] of Object.entries(headers))
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Pipeline steps: data transforms — select, map, filter, sort, limit.
3
3
  */
4
- export declare function stepSelect(_page: any, params: any, data: any, args: Record<string, any>): Promise<any>;
5
- export declare function stepMap(_page: any, params: any, data: any, args: Record<string, any>): Promise<any>;
6
- export declare function stepFilter(_page: any, params: any, data: any, args: Record<string, any>): Promise<any>;
7
- export declare function stepSort(_page: any, params: any, data: any, _args: Record<string, any>): Promise<any>;
8
- export declare function stepLimit(_page: any, params: any, data: any, args: Record<string, any>): Promise<any>;
4
+ import type { IPage } from '../../types.js';
5
+ export declare function stepSelect(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
6
+ export declare function stepMap(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
7
+ export declare function stepFilter(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
8
+ export declare function stepSort(_page: IPage | null, params: unknown, data: unknown, _args: Record<string, unknown>): Promise<unknown>;
9
+ export declare function stepLimit(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
@@ -2,12 +2,15 @@
2
2
  * Pipeline steps: data transforms — select, map, filter, sort, limit.
3
3
  */
4
4
  import { render, evalExpr } from '../template.js';
5
+ function isRecord(value) {
6
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ }
5
8
  export async function stepSelect(_page, params, data, args) {
6
9
  const pathStr = String(render(params, { args, data }));
7
10
  if (data && typeof data === 'object') {
8
11
  let current = data;
9
12
  for (const part of pathStr.split('.')) {
10
- if (current && typeof current === 'object' && !Array.isArray(current))
13
+ if (isRecord(current))
11
14
  current = current[part];
12
15
  else if (Array.isArray(current) && /^\d+$/.test(part))
13
16
  current = current[parseInt(part, 10)];
@@ -21,15 +24,26 @@ export async function stepSelect(_page, params, data, args) {
21
24
  export async function stepMap(_page, params, data, args) {
22
25
  if (!data || typeof data !== 'object')
23
26
  return data;
24
- let items = Array.isArray(data) ? data : [data];
25
- if (!Array.isArray(data) && typeof data === 'object' && 'data' in data)
26
- items = data.data;
27
+ let source = data;
28
+ // Support inline select: { map: { select: 'path', key: '${{ item.x }}' } }
29
+ if (isRecord(params) && 'select' in params) {
30
+ source = await stepSelect(null, params.select, data, args);
31
+ }
32
+ if (!source || typeof source !== 'object')
33
+ return source;
34
+ let items = Array.isArray(source) ? source : [source];
35
+ if (isRecord(source) && Array.isArray(source.data))
36
+ items = source.data;
27
37
  const result = [];
38
+ const templateParams = isRecord(params) ? params : {};
28
39
  for (let i = 0; i < items.length; i++) {
29
40
  const item = items[i];
30
41
  const row = {};
31
- for (const [key, template] of Object.entries(params))
32
- row[key] = render(template, { args, data, item, index: i });
42
+ for (const [key, template] of Object.entries(templateParams)) {
43
+ if (key === 'select')
44
+ continue;
45
+ row[key] = render(template, { args, data: source, item, index: i });
46
+ }
33
47
  result.push(row);
34
48
  }
35
49
  return result;
@@ -42,9 +56,16 @@ export async function stepFilter(_page, params, data, args) {
42
56
  export async function stepSort(_page, params, data, _args) {
43
57
  if (!Array.isArray(data))
44
58
  return data;
45
- const key = typeof params === 'object' ? (params.by ?? '') : String(params);
46
- const reverse = typeof params === 'object' ? params.order === 'desc' : false;
47
- return [...data].sort((a, b) => { const va = a[key] ?? ''; const vb = b[key] ?? ''; const cmp = va < vb ? -1 : va > vb ? 1 : 0; return reverse ? -cmp : cmp; });
59
+ const key = isRecord(params) ? String(params.by ?? '') : String(params);
60
+ const reverse = isRecord(params) ? params.order === 'desc' : false;
61
+ return [...data].sort((a, b) => {
62
+ const left = isRecord(a) ? a[key] : undefined;
63
+ const right = isRecord(b) ? b[key] : undefined;
64
+ const va = left ?? '';
65
+ const vb = right ?? '';
66
+ const cmp = va < vb ? -1 : va > vb ? 1 : 0;
67
+ return reverse ? -cmp : cmp;
68
+ });
48
69
  }
49
70
  export async function stepLimit(_page, params, data, args) {
50
71
  if (!Array.isArray(data))
@@ -2,14 +2,14 @@
2
2
  * Pipeline template engine: ${{ ... }} expression rendering.
3
3
  */
4
4
  export interface RenderContext {
5
- args?: Record<string, any>;
6
- data?: any;
7
- item?: any;
5
+ args?: Record<string, unknown>;
6
+ data?: unknown;
7
+ item?: unknown;
8
8
  index?: number;
9
9
  }
10
- export declare function render(template: any, ctx: RenderContext): any;
11
- export declare function evalExpr(expr: string, ctx: RenderContext): any;
12
- export declare function resolvePath(pathStr: string, ctx: RenderContext): any;
10
+ export declare function render(template: unknown, ctx: RenderContext): unknown;
11
+ export declare function evalExpr(expr: string, ctx: RenderContext): unknown;
12
+ export declare function resolvePath(pathStr: string, ctx: RenderContext): unknown;
13
13
  /**
14
14
  * Normalize JavaScript source for browser evaluate() calls.
15
15
  */