@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
@@ -9,8 +9,9 @@
9
9
  */
10
10
 
11
11
  import { WebSocket, type RawData } from 'ws';
12
- import type { IPage } from '../types.js';
12
+ import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
13
13
  import { wrapForEval } from './utils.js';
14
+ import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
14
15
  import {
15
16
  clickJs,
16
17
  typeTextJs,
@@ -28,13 +29,24 @@ export interface CDPTarget {
28
29
  webSocketDebuggerUrl?: string;
29
30
  }
30
31
 
32
+ interface RuntimeEvaluateResult {
33
+ result?: {
34
+ value?: unknown;
35
+ };
36
+ exceptionDetails?: {
37
+ exception?: {
38
+ description?: string;
39
+ };
40
+ };
41
+ }
42
+
31
43
  const CDP_SEND_TIMEOUT = 30_000; // 30s per command
32
44
 
33
45
  export class CDPBridge {
34
46
  private _ws: WebSocket | null = null;
35
47
  private _idCounter = 0;
36
- private _pending = new Map<number, { resolve: (val: any) => void; reject: (err: Error) => void; timer: ReturnType<typeof setTimeout> }>();
37
- private _eventListeners = new Map<string, Set<(params: any) => void>>();
48
+ private _pending = new Map<number, { resolve: (val: unknown) => void; reject: (err: Error) => void; timer: ReturnType<typeof setTimeout> }>();
49
+ private _eventListeners = new Map<string, Set<(params: unknown) => void>>();
38
50
 
39
51
  async connect(opts?: { timeout?: number; workspace?: string }): Promise<IPage> {
40
52
  const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
@@ -55,7 +67,8 @@ export class CDPBridge {
55
67
 
56
68
  return new Promise((resolve, reject) => {
57
69
  const ws = new WebSocket(wsUrl);
58
- const timeout = setTimeout(() => reject(new Error('CDP connect timeout')), opts?.timeout ?? 10000);
70
+ const timeoutMs = (opts?.timeout ?? 10) * 1000; // opts.timeout is in seconds
71
+ const timeout = setTimeout(() => reject(new Error('CDP connect timeout')), timeoutMs);
59
72
 
60
73
  ws.on('open', () => {
61
74
  clearTimeout(timeout);
@@ -110,7 +123,7 @@ export class CDPBridge {
110
123
  }
111
124
 
112
125
  /** Send a CDP command with timeout guard (P0 fix #4) */
113
- async send(method: string, params: any = {}, timeoutMs: number = CDP_SEND_TIMEOUT): Promise<any> {
126
+ async send(method: string, params: Record<string, unknown> = {}, timeoutMs: number = CDP_SEND_TIMEOUT): Promise<unknown> {
114
127
  if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
115
128
  throw new Error('CDP connection is not open');
116
129
  }
@@ -126,25 +139,25 @@ export class CDPBridge {
126
139
  }
127
140
 
128
141
  /** Listen for a CDP event */
129
- on(event: string, handler: (params: any) => void): void {
142
+ on(event: string, handler: (params: unknown) => void): void {
130
143
  let set = this._eventListeners.get(event);
131
144
  if (!set) { set = new Set(); this._eventListeners.set(event, set); }
132
145
  set.add(handler);
133
146
  }
134
147
 
135
148
  /** Remove a CDP event listener */
136
- off(event: string, handler: (params: any) => void): void {
149
+ off(event: string, handler: (params: unknown) => void): void {
137
150
  this._eventListeners.get(event)?.delete(handler);
138
151
  }
139
152
 
140
153
  /** Wait for a CDP event to fire (one-shot) */
141
- waitForEvent(event: string, timeoutMs: number = 15_000): Promise<any> {
154
+ waitForEvent(event: string, timeoutMs: number = 15_000): Promise<unknown> {
142
155
  return new Promise((resolve, reject) => {
143
156
  const timer = setTimeout(() => {
144
157
  this.off(event, handler);
145
158
  reject(new Error(`Timed out waiting for CDP event '${event}'`));
146
159
  }, timeoutMs);
147
- const handler = (params: any) => {
160
+ const handler = (params: unknown) => {
148
161
  clearTimeout(timer);
149
162
  this.off(event, handler);
150
163
  resolve(params);
@@ -171,30 +184,38 @@ class CDPPage implements IPage {
171
184
  }
172
185
  }
173
186
 
174
- async evaluate(js: string): Promise<any> {
187
+ async evaluate(js: string): Promise<unknown> {
175
188
  const expression = wrapForEval(js);
176
189
  const result = await this.bridge.send('Runtime.evaluate', {
177
190
  expression,
178
191
  returnByValue: true,
179
192
  awaitPromise: true
180
- });
193
+ }) as RuntimeEvaluateResult;
181
194
  if (result.exceptionDetails) {
182
195
  throw new Error('Evaluate error: ' + (result.exceptionDetails.exception?.description || 'Unknown exception'));
183
196
  }
184
197
  return result.result?.value;
185
198
  }
186
199
 
187
- async getCookies(opts: { domain?: string; url?: string } = {}): Promise<any[]> {
200
+ async getCookies(opts: { domain?: string; url?: string } = {}): Promise<BrowserCookie[]> {
188
201
  const result = await this.bridge.send('Network.getCookies', opts.url ? { urls: [opts.url] } : {});
189
- const cookies = Array.isArray(result?.cookies) ? result.cookies : [];
190
- return opts.domain
191
- ? cookies.filter((cookie: any) => typeof cookie.domain === 'string' && cookie.domain.includes(opts.domain!))
202
+ const cookies = isRecord(result) && Array.isArray(result.cookies) ? result.cookies : [];
203
+ const domain = opts.domain;
204
+ return domain
205
+ ? cookies.filter((cookie): cookie is BrowserCookie => isCookie(cookie) && cookie.domain.includes(domain))
192
206
  : cookies;
193
207
  }
194
208
 
195
- async snapshot(_opts?: any): Promise<any> {
196
- // CDP doesn't have a built-in accessibility tree equivalent without additional setup
197
- return '(snapshot not available in CDP mode)';
209
+ async snapshot(opts: SnapshotOptions = {}): Promise<unknown> {
210
+ const snapshotJs = generateSnapshotJs({
211
+ viewportExpand: opts.viewportExpand ?? 800,
212
+ maxDepth: Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200)),
213
+ interactiveOnly: opts.interactive ?? false,
214
+ maxTextLength: opts.maxTextLength ?? 120,
215
+ includeScrollInfo: true,
216
+ bboxDedup: true,
217
+ });
218
+ return this.evaluate(snapshotJs);
198
219
  }
199
220
 
200
221
  // ── Shared DOM operations (P1 fix #5 — using dom-helpers.ts) ──
@@ -211,13 +232,22 @@ class CDPPage implements IPage {
211
232
  await this.evaluate(pressKeyJs(key));
212
233
  }
213
234
 
214
- async wait(options: any): Promise<void> {
235
+ async scrollTo(ref: string): Promise<unknown> {
236
+ return this.evaluate(scrollToRefJs(ref));
237
+ }
238
+
239
+ async getFormState(): Promise<Record<string, unknown>> {
240
+ return (await this.evaluate(getFormStateJs())) as Record<string, unknown>;
241
+ }
242
+
243
+ async wait(options: number | WaitOptions): Promise<void> {
215
244
  if (typeof options === 'number') {
216
245
  await new Promise(resolve => setTimeout(resolve, options * 1000));
217
246
  return;
218
247
  }
219
- if (options.time) {
220
- await new Promise(resolve => setTimeout(resolve, options.time * 1000));
248
+ if (typeof options.time === 'number') {
249
+ const waitTime = options.time;
250
+ await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
221
251
  return;
222
252
  }
223
253
  if (options.text) {
@@ -238,13 +268,13 @@ class CDPPage implements IPage {
238
268
  await this.evaluate(autoScrollJs(times, delayMs));
239
269
  }
240
270
 
241
- async screenshot(options: any = {}): Promise<string> {
271
+ async screenshot(options: ScreenshotOptions = {}): Promise<string> {
242
272
  const result = await this.bridge.send('Page.captureScreenshot', {
243
273
  format: options.format ?? 'png',
244
274
  quality: options.format === 'jpeg' ? (options.quality ?? 80) : undefined,
245
275
  captureBeyondViewport: options.fullPage ?? false,
246
276
  });
247
- const base64 = result.data;
277
+ const base64 = isRecord(result) && typeof result.data === 'string' ? result.data : '';
248
278
  if (options.path) {
249
279
  const fs = await import('node:fs');
250
280
  const path = await import('node:path');
@@ -255,11 +285,12 @@ class CDPPage implements IPage {
255
285
  return base64;
256
286
  }
257
287
 
258
- async networkRequests(includeStatic: boolean = false): Promise<any> {
259
- return this.evaluate(networkRequestsJs(includeStatic));
288
+ async networkRequests(includeStatic: boolean = false): Promise<unknown[]> {
289
+ const result = await this.evaluate(networkRequestsJs(includeStatic));
290
+ return Array.isArray(result) ? result : [];
260
291
  }
261
292
 
262
- async tabs(): Promise<any> {
293
+ async tabs(): Promise<unknown[]> {
263
294
  return [];
264
295
  }
265
296
 
@@ -275,7 +306,7 @@ class CDPPage implements IPage {
275
306
  // Not supported in direct CDP mode
276
307
  }
277
308
 
278
- async consoleMessages(_level?: string): Promise<any> {
309
+ async consoleMessages(_level?: string): Promise<unknown[]> {
279
310
  return [];
280
311
  }
281
312
 
@@ -287,13 +318,24 @@ class CDPPage implements IPage {
287
318
  }));
288
319
  }
289
320
 
290
- async getInterceptedRequests(): Promise<any[]> {
321
+ async getInterceptedRequests(): Promise<unknown[]> {
291
322
  const { generateReadInterceptedJs } = await import('../interceptor.js');
292
323
  const result = await this.evaluate(generateReadInterceptedJs('__opencli_xhr'));
293
- return (result as any[]) || [];
324
+ return Array.isArray(result) ? result : [];
294
325
  }
295
326
  }
296
327
 
328
+ function isRecord(value: unknown): value is Record<string, unknown> {
329
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
330
+ }
331
+
332
+ function isCookie(value: unknown): value is BrowserCookie {
333
+ return isRecord(value)
334
+ && typeof value.name === 'string'
335
+ && typeof value.value === 'string'
336
+ && typeof value.domain === 'string';
337
+ }
338
+
297
339
  // ── CDP target selection (unchanged) ──
298
340
 
299
341
  function selectCDPTarget(targets: CDPTarget[]): CDPTarget | undefined {
@@ -343,7 +385,6 @@ function scoreCDPTarget(target: CDPTarget, preferredPattern?: RegExp): number {
343
385
  if (title.includes('chatwise')) score += 120;
344
386
  if (title.includes('notion')) score += 120;
345
387
  if (title.includes('discord')) score += 120;
346
- if (title.includes('netease')) score += 120;
347
388
 
348
389
  if (url.includes('antigravity')) score += 100;
349
390
  if (url.includes('codex')) score += 100;
@@ -351,7 +392,6 @@ function scoreCDPTarget(target: CDPTarget, preferredPattern?: RegExp): number {
351
392
  if (url.includes('chatwise')) score += 100;
352
393
  if (url.includes('notion')) score += 100;
353
394
  if (url.includes('discord')) score += 100;
354
- if (url.includes('netease')) score += 100;
355
395
 
356
396
  return score;
357
397
  }
@@ -7,6 +7,8 @@
7
7
  const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? '19825', 10);
8
8
  const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
9
9
 
10
+ import type { BrowserSessionInfo } from '../types.js';
11
+
10
12
  let _idCounter = 0;
11
13
 
12
14
  function generateId(): string {
@@ -69,17 +71,19 @@ export async function isExtensionConnected(): Promise<boolean> {
69
71
 
70
72
  /**
71
73
  * Send a command to the daemon and wait for a result.
72
- * Retries up to 3 times with 500ms delay for transient failures.
74
+ * Retries up to 4 times: network errors retry at 500ms,
75
+ * transient extension errors retry at 1500ms.
73
76
  */
74
77
  export async function sendCommand(
75
78
  action: DaemonCommand['action'],
76
79
  params: Omit<DaemonCommand, 'id' | 'action'> = {},
77
80
  ): Promise<unknown> {
78
- const id = generateId();
79
- const command: DaemonCommand = { id, action, ...params };
80
- const maxRetries = 3;
81
+ const maxRetries = 4;
81
82
 
82
83
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
84
+ // Generate a fresh ID per attempt to avoid daemon-side duplicate detection
85
+ const id = generateId();
86
+ const command: DaemonCommand = { id, action, ...params };
83
87
  try {
84
88
  const controller = new AbortController();
85
89
  const timer = setTimeout(() => controller.abort(), 30000);
@@ -95,6 +99,17 @@ export async function sendCommand(
95
99
  const result = (await res.json()) as DaemonResult;
96
100
 
97
101
  if (!result.ok) {
102
+ // Check if error is a transient extension issue worth retrying
103
+ const errMsg = result.error ?? '';
104
+ const isTransient = errMsg.includes('Extension disconnected')
105
+ || errMsg.includes('Extension not connected')
106
+ || errMsg.includes('attach failed')
107
+ || errMsg.includes('no longer exists');
108
+ if (isTransient && attempt < maxRetries) {
109
+ // Longer delay for extension recovery (service worker restart)
110
+ await new Promise(r => setTimeout(r, 1500));
111
+ continue;
112
+ }
98
113
  throw new Error(result.error ?? 'Daemon command failed');
99
114
  }
100
115
 
@@ -113,7 +128,8 @@ export async function sendCommand(
113
128
  throw new Error('sendCommand: max retries exhausted');
114
129
  }
115
130
 
116
- export async function listSessions(): Promise<any[]> {
131
+ export async function listSessions(): Promise<BrowserSessionInfo[]> {
117
132
  const result = await sendCommand('sessions');
118
133
  return Array.isArray(result) ? result : [];
119
134
  }
135
+
@@ -11,8 +11,21 @@ export function clickJs(ref: string): string {
11
11
  return `
12
12
  (() => {
13
13
  const ref = ${safeRef};
14
- const el = document.querySelector('[data-ref="' + ref + '"]')
15
- || document.querySelectorAll('a, button, input, [role="button"], [tabindex]')[parseInt(ref, 10) || 0];
14
+ // 1. data-opencli-ref (set by snapshot engine)
15
+ let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
16
+ // 2. data-ref (legacy)
17
+ if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
18
+ // 3. CSS selector
19
+ if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
20
+ try { el = document.querySelector(ref); } catch {}
21
+ }
22
+ // 4. Numeric index into interactive elements
23
+ if (!el) {
24
+ const idx = parseInt(ref, 10);
25
+ if (!isNaN(idx)) {
26
+ el = document.querySelectorAll('a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])')[idx];
27
+ }
28
+ }
16
29
  if (!el) throw new Error('Element not found: ' + ref);
17
30
  el.scrollIntoView({ behavior: 'instant', block: 'center' });
18
31
  el.click();
@@ -28,13 +41,31 @@ export function typeTextJs(ref: string, text: string): string {
28
41
  return `
29
42
  (() => {
30
43
  const ref = ${safeRef};
31
- const el = document.querySelector('[data-ref="' + ref + '"]')
32
- || document.querySelectorAll('input, textarea, [contenteditable]')[parseInt(ref, 10) || 0];
44
+ // 1. data-opencli-ref (set by snapshot engine)
45
+ let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
46
+ // 2. data-ref (legacy)
47
+ if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
48
+ // 3. CSS selector
49
+ if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
50
+ try { el = document.querySelector(ref); } catch {}
51
+ }
52
+ // 4. Numeric index into typeable elements
53
+ if (!el) {
54
+ const idx = parseInt(ref, 10);
55
+ if (!isNaN(idx)) {
56
+ el = document.querySelectorAll('input, textarea, [contenteditable="true"]')[idx];
57
+ }
58
+ }
33
59
  if (!el) throw new Error('Element not found: ' + ref);
34
60
  el.focus();
35
- el.value = ${safeText};
36
- el.dispatchEvent(new Event('input', { bubbles: true }));
37
- el.dispatchEvent(new Event('change', { bubbles: true }));
61
+ if (el.isContentEditable) {
62
+ el.textContent = ${safeText};
63
+ el.dispatchEvent(new Event('input', { bubbles: true }));
64
+ } else {
65
+ el.value = ${safeText};
66
+ el.dispatchEvent(new Event('input', { bubbles: true }));
67
+ el.dispatchEvent(new Event('change', { bubbles: true }));
68
+ }
38
69
  return 'typed';
39
70
  })()
40
71
  `;
@@ -0,0 +1,249 @@
1
+ /**
2
+ * Tests for dom-snapshot.ts: DOM snapshot engine.
3
+ *
4
+ * Since the engine generates JavaScript strings for in-page evaluation,
5
+ * these tests validate:
6
+ * 1. The generated code is syntactically valid JS
7
+ * 2. Options are correctly embedded
8
+ * 3. The output structure matches expected format
9
+ * 4. All features are present (Shadow DOM, iframe, table, diff, etc.)
10
+ */
11
+
12
+ import { describe, it, expect } from 'vitest';
13
+ import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
14
+
15
+ describe('generateSnapshotJs', () => {
16
+ it('returns a non-empty string', () => {
17
+ const js = generateSnapshotJs();
18
+ expect(typeof js).toBe('string');
19
+ expect(js.length).toBeGreaterThan(100);
20
+ });
21
+
22
+ it('generates syntactically valid JS (can be parsed)', () => {
23
+ const js = generateSnapshotJs();
24
+ expect(() => new Function(js)).not.toThrow();
25
+ });
26
+
27
+ it('embeds default options correctly', () => {
28
+ const js = generateSnapshotJs();
29
+ expect(js).toContain('VIEWPORT_EXPAND = 800');
30
+ expect(js).toContain('MAX_DEPTH = 50');
31
+ expect(js).toContain('INTERACTIVE_ONLY = false');
32
+ expect(js).toContain('MAX_TEXT_LEN = 120');
33
+ expect(js).toContain('INCLUDE_SCROLL_INFO = true');
34
+ expect(js).toContain('BBOX_DEDUP = true');
35
+ expect(js).toContain('INCLUDE_SHADOW_DOM = true');
36
+ expect(js).toContain('INCLUDE_IFRAMES = true');
37
+ expect(js).toContain('PAINT_ORDER_CHECK = true');
38
+ expect(js).toContain('ANNOTATE_REFS = true');
39
+ expect(js).toContain('REPORT_HIDDEN = true');
40
+ expect(js).toContain('FILTER_ADS = true');
41
+ expect(js).toContain('MARKDOWN_TABLES = true');
42
+ expect(js).toContain('PREV_HASHES = null');
43
+ });
44
+
45
+ it('embeds custom options correctly', () => {
46
+ const js = generateSnapshotJs({
47
+ viewportExpand: 2000,
48
+ maxDepth: 30,
49
+ interactiveOnly: true,
50
+ maxTextLength: 200,
51
+ includeScrollInfo: false,
52
+ bboxDedup: false,
53
+ includeShadowDom: false,
54
+ includeIframes: false,
55
+ maxIframes: 3,
56
+ paintOrderCheck: false,
57
+ annotateRefs: false,
58
+ reportHidden: false,
59
+ filterAds: false,
60
+ markdownTables: false,
61
+ });
62
+ expect(js).toContain('VIEWPORT_EXPAND = 2000');
63
+ expect(js).toContain('MAX_DEPTH = 30');
64
+ expect(js).toContain('INTERACTIVE_ONLY = true');
65
+ expect(js).toContain('MAX_TEXT_LEN = 200');
66
+ expect(js).toContain('INCLUDE_SCROLL_INFO = false');
67
+ expect(js).toContain('BBOX_DEDUP = false');
68
+ expect(js).toContain('INCLUDE_SHADOW_DOM = false');
69
+ expect(js).toContain('INCLUDE_IFRAMES = false');
70
+ expect(js).toContain('MAX_IFRAMES = 3');
71
+ expect(js).toContain('PAINT_ORDER_CHECK = false');
72
+ expect(js).toContain('ANNOTATE_REFS = false');
73
+ expect(js).toContain('REPORT_HIDDEN = false');
74
+ expect(js).toContain('FILTER_ADS = false');
75
+ expect(js).toContain('MARKDOWN_TABLES = false');
76
+ });
77
+
78
+ it('clamps maxDepth between 1 and 200', () => {
79
+ expect(generateSnapshotJs({ maxDepth: -5 })).toContain('MAX_DEPTH = 1');
80
+ expect(generateSnapshotJs({ maxDepth: 999 })).toContain('MAX_DEPTH = 200');
81
+ expect(generateSnapshotJs({ maxDepth: 75 })).toContain('MAX_DEPTH = 75');
82
+ });
83
+
84
+ it('wraps output as an IIFE', () => {
85
+ const js = generateSnapshotJs();
86
+ expect(js.startsWith('(() =>')).toBe(true);
87
+ expect(js.trimEnd().endsWith(')()')).toBe(true);
88
+ });
89
+
90
+ it('embeds previousHashes for incremental diff', () => {
91
+ const hashes = JSON.stringify(['12345', '67890']);
92
+ const js = generateSnapshotJs({ previousHashes: hashes });
93
+ expect(js).toContain('new Set(["12345","67890"])');
94
+ });
95
+
96
+ it('includes all core features in generated code', () => {
97
+ const js = generateSnapshotJs();
98
+
99
+ // Tag filtering
100
+ expect(js).toContain('SKIP_TAGS');
101
+ expect(js).toContain("'script'");
102
+ expect(js).toContain("'style'");
103
+
104
+ // SVG collapsing
105
+ expect(js).toContain('SVG_CHILDREN');
106
+
107
+ // Interactive detection
108
+ expect(js).toContain('INTERACTIVE_TAGS');
109
+ expect(js).toContain('INTERACTIVE_ROLES');
110
+ expect(js).toContain('isInteractive');
111
+
112
+ // Visibility
113
+ expect(js).toContain('isVisibleByCSS');
114
+ expect(js).toContain('isInExpandedViewport');
115
+
116
+ // BBox dedup
117
+ expect(js).toContain('isContainedBy');
118
+ expect(js).toContain('PROPAGATING_TAGS');
119
+
120
+ // Shadow DOM
121
+ expect(js).toContain('shadowRoot');
122
+ expect(js).toContain('|shadow|');
123
+
124
+ // iframe
125
+ expect(js).toContain('walkIframe');
126
+ expect(js).toContain('|iframe|');
127
+
128
+ // Paint order
129
+ expect(js).toContain('isOccludedByOverlay');
130
+ expect(js).toContain('elementFromPoint');
131
+
132
+ // Ad filtering
133
+ expect(js).toContain('isAdElement');
134
+ expect(js).toContain('AD_PATTERNS');
135
+
136
+ // data-ref annotation
137
+ expect(js).toContain('data-opencli-ref');
138
+
139
+ // Hidden elements report
140
+ expect(js).toContain('hiddenInteractives');
141
+ expect(js).toContain('hidden_interactive');
142
+
143
+ // Incremental diff
144
+ expect(js).toContain('hashElement');
145
+ expect(js).toContain('currentHashes');
146
+ expect(js).toContain('__opencli_prev_hashes');
147
+
148
+ // Table serialization
149
+ expect(js).toContain('serializeTable');
150
+ expect(js).toContain('|table|');
151
+
152
+ // Synthetic attributes
153
+ expect(js).toContain("'YYYY-MM-DD'");
154
+ expect(js).toContain('value=••••');
155
+
156
+ // Page metadata
157
+ expect(js).toContain('location.href');
158
+ expect(js).toContain('document.title');
159
+ });
160
+
161
+ it('contains proper attribute whitelist', () => {
162
+ const js = generateSnapshotJs();
163
+ const expectedAttrs = [
164
+ 'aria-label', 'aria-expanded', 'aria-checked', 'aria-selected',
165
+ 'placeholder', 'href', 'role', 'data-testid', 'autocomplete',
166
+ ];
167
+ for (const attr of expectedAttrs) {
168
+ expect(js).toContain(`'${attr}'`);
169
+ }
170
+ });
171
+
172
+ it('includes scroll info formatting', () => {
173
+ const js = generateSnapshotJs();
174
+ expect(js).toContain('scrollHeight');
175
+ expect(js).toContain('scrollTop');
176
+ expect(js).toContain('|scroll|');
177
+ expect(js).toContain('page_scroll');
178
+ });
179
+ });
180
+
181
+ describe('scrollToRefJs', () => {
182
+ it('generates valid JS', () => {
183
+ const js = scrollToRefJs('42');
184
+ expect(() => new Function(js)).not.toThrow();
185
+ });
186
+
187
+ it('targets data-opencli-ref', () => {
188
+ const js = scrollToRefJs('7');
189
+ expect(js).toContain('data-opencli-ref');
190
+ expect(js).toContain('scrollIntoView');
191
+ expect(js).toContain('"7"');
192
+ });
193
+
194
+ it('falls back to data-ref', () => {
195
+ const js = scrollToRefJs('3');
196
+ expect(js).toContain('data-ref');
197
+ });
198
+
199
+ it('returns scrolled info', () => {
200
+ const js = scrollToRefJs('1');
201
+ expect(js).toContain('scrolled: true');
202
+ expect(js).toContain('tag:');
203
+ });
204
+ });
205
+
206
+ describe('getFormStateJs', () => {
207
+ it('generates valid JS', () => {
208
+ const js = getFormStateJs();
209
+ expect(() => new Function(js)).not.toThrow();
210
+ });
211
+
212
+ it('collects form elements', () => {
213
+ const js = getFormStateJs();
214
+ expect(js).toContain('document.forms');
215
+ expect(js).toContain('form.elements');
216
+ });
217
+
218
+ it('collects orphan fields', () => {
219
+ const js = getFormStateJs();
220
+ expect(js).toContain('orphanFields');
221
+ expect(js).toContain('el.form');
222
+ });
223
+
224
+ it('handles different input types', () => {
225
+ const js = getFormStateJs();
226
+ expect(js).toContain('checkbox');
227
+ expect(js).toContain('radio');
228
+ expect(js).toContain('password');
229
+ expect(js).toContain('contenteditable');
230
+ });
231
+
232
+ it('extracts labels', () => {
233
+ const js = getFormStateJs();
234
+ expect(js).toContain('aria-label');
235
+ expect(js).toContain('label[for=');
236
+ expect(js).toContain('closest');
237
+ expect(js).toContain('placeholder');
238
+ });
239
+
240
+ it('masks passwords', () => {
241
+ const js = getFormStateJs();
242
+ expect(js).toContain('••••');
243
+ });
244
+
245
+ it('includes data-opencli-ref in output', () => {
246
+ const js = getFormStateJs();
247
+ expect(js).toContain('data-opencli-ref');
248
+ });
249
+ });