@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
@@ -0,0 +1,42 @@
1
+ site: facebook
2
+ name: friends
3
+ description: Get Facebook friend suggestions
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 10
10
+ description: Number of friend suggestions
11
+
12
+ pipeline:
13
+ - navigate:
14
+ url: https://www.facebook.com/friends
15
+ settleMs: 3000
16
+
17
+ - evaluate: |
18
+ (() => {
19
+ const limit = ${{ args.limit }};
20
+ const items = document.querySelectorAll('[role="listitem"]');
21
+ return Array.from(items)
22
+ .slice(0, limit)
23
+ .map((el, i) => {
24
+ const text = el.textContent.trim().replace(/\s+/g, ' ');
25
+ // Extract mutual info if present (before name extraction to avoid pollution)
26
+ const mutualMatch = text.match(/([\d,]+)\s*位.*(?:关注|共同|mutual)/);
27
+ // Extract name: remove mutual info, action buttons, etc.
28
+ let name = text
29
+ .replace(/[\d,]+\s*位.*(?:关注了|共同好友|mutual friends?)/, '')
30
+ .replace(/加好友.*/, '').replace(/Add [Ff]riend.*/, '')
31
+ .replace(/移除$/, '').replace(/Remove$/, '')
32
+ .trim();
33
+ return {
34
+ index: i + 1,
35
+ name: name.substring(0, 50),
36
+ mutual: mutualMatch ? mutualMatch[1] : '-',
37
+ };
38
+ })
39
+ .filter(item => item.name.length > 0);
40
+ })()
41
+
42
+ columns: [index, name, mutual]
@@ -0,0 +1,50 @@
1
+ site: facebook
2
+ name: groups
3
+ description: List your Facebook groups
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 20
10
+ description: Number of groups
11
+
12
+ pipeline:
13
+ - navigate:
14
+ url: https://www.facebook.com/groups/feed/
15
+ settleMs: 3000
16
+
17
+ - evaluate: |
18
+ (() => {
19
+ const limit = ${{ args.limit }};
20
+ const links = Array.from(document.querySelectorAll('a'))
21
+ .filter(a => {
22
+ const href = a.href || '';
23
+ return href.includes('/groups/') &&
24
+ !href.includes('/feed') &&
25
+ !href.includes('/discover') &&
26
+ !href.includes('/joins') &&
27
+ !href.includes('category=create') &&
28
+ a.textContent.trim().length > 2;
29
+ });
30
+
31
+ // Deduplicate by href
32
+ const seen = new Set();
33
+ const groups = [];
34
+ for (const a of links) {
35
+ const href = a.href.split('?')[0];
36
+ if (seen.has(href)) continue;
37
+ seen.add(href);
38
+ const raw = a.textContent.trim().replace(/\s+/g, ' ');
39
+ // Split name from "上次发帖" info
40
+ const parts = raw.split(/上次发帖|Last post/);
41
+ groups.push({
42
+ name: (parts[0] || '').trim().substring(0, 60),
43
+ last_post: parts[1] ? parts[1].replace(/^[::]/, '').trim() : '-',
44
+ url: href,
45
+ });
46
+ }
47
+ return groups.slice(0, limit).map((g, i) => ({ index: i + 1, ...g }));
48
+ })()
49
+
50
+ columns: [index, name, last_post, url]
@@ -0,0 +1,44 @@
1
+ site: facebook
2
+ name: join-group
3
+ description: Join a Facebook group
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ group:
8
+ type: str
9
+ required: true
10
+ positional: true
11
+ description: Group ID or URL path (e.g. '1876150192925481' or group name)
12
+
13
+ pipeline:
14
+ - navigate:
15
+ url: https://www.facebook.com/groups/${{ args.group }}
16
+ settleMs: 3000
17
+
18
+ - evaluate: |
19
+ (async () => {
20
+ const group = ${{ args.group | json }};
21
+ const groupName = document.querySelector('h1')?.textContent?.trim() || group;
22
+
23
+ // Find "Join Group" button
24
+ const buttons = Array.from(document.querySelectorAll('[role="button"]'));
25
+ const joinBtn = buttons.find(b => {
26
+ const text = b.textContent.trim();
27
+ return text === '加入小组' || text === 'Join group' || text === 'Join Group';
28
+ });
29
+
30
+ if (!joinBtn) {
31
+ const isMember = buttons.some(b => {
32
+ const t = b.textContent.trim();
33
+ return t === '已加入' || t === 'Joined' || t === '成员' || t === 'Member';
34
+ });
35
+ if (isMember) return [{ status: 'Already a member', group: groupName }];
36
+ return [{ status: 'Join button not found', group: groupName }];
37
+ }
38
+
39
+ joinBtn.click();
40
+ await new Promise(r => setTimeout(r, 1500));
41
+ return [{ status: 'Join request sent', group: groupName }];
42
+ })()
43
+
44
+ columns: [status, group]
@@ -0,0 +1,39 @@
1
+ site: facebook
2
+ name: memories
3
+ description: Get your Facebook memories (On This Day)
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 10
10
+ description: Number of memories
11
+
12
+ pipeline:
13
+ - navigate:
14
+ url: https://www.facebook.com/onthisday
15
+ settleMs: 4000
16
+
17
+ - evaluate: |
18
+ (() => {
19
+ const limit = ${{ args.limit }};
20
+ const articles = document.querySelectorAll('[role="article"]');
21
+ return Array.from(articles)
22
+ .slice(0, limit)
23
+ .map((el, i) => {
24
+ const headerLink = el.querySelector('h2 a, h3 a, h4 a, strong a');
25
+ const spans = Array.from(el.querySelectorAll('div[dir="auto"]'))
26
+ .map(s => s.textContent.trim())
27
+ .filter(t => t.length > 5 && t.length < 500);
28
+ const timeEl = el.querySelector('a[href*="/posts/"] span, a[href*="story_fbid"] span');
29
+ return {
30
+ index: i + 1,
31
+ source: headerLink ? headerLink.textContent.trim().substring(0, 50) : '-',
32
+ content: (spans[0] || '').replace(/\n/g, ' ').substring(0, 150),
33
+ time: timeEl ? timeEl.textContent.trim() : '-',
34
+ };
35
+ })
36
+ .filter(item => item.content.length > 0 || item.source !== '-');
37
+ })()
38
+
39
+ columns: [index, source, content, time]
@@ -0,0 +1,40 @@
1
+ site: facebook
2
+ name: notifications
3
+ description: Get recent Facebook notifications
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 15
10
+ description: Number of notifications
11
+
12
+ pipeline:
13
+ - navigate:
14
+ url: https://www.facebook.com/notifications
15
+ settleMs: 3000
16
+
17
+ - evaluate: |
18
+ (() => {
19
+ const limit = ${{ args.limit }};
20
+ const items = document.querySelectorAll('[role="listitem"]');
21
+ return Array.from(items)
22
+ .filter(el => el.querySelectorAll('a').length > 0)
23
+ .slice(0, limit)
24
+ .map((el, i) => {
25
+ const raw = el.textContent.trim().replace(/\s+/g, ' ');
26
+ // Remove leading "未读" and trailing "标记为已读"
27
+ const cleaned = raw.replace(/^未读/, '').replace(/标记为已读$/, '').replace(/^Unread/, '').replace(/Mark as read$/, '').trim();
28
+ // Try to extract time (last segment like "11小时", "5天", "1周")
29
+ const timeMatch = cleaned.match(/(\d+\s*(?:分钟|小时|天|周|个月|minutes?|hours?|days?|weeks?|months?))\s*$/);
30
+ const time = timeMatch ? timeMatch[1] : '';
31
+ const text = timeMatch ? cleaned.slice(0, -timeMatch[0].length).trim() : cleaned;
32
+ return {
33
+ index: i + 1,
34
+ text: text.substring(0, 150),
35
+ time: time || '-',
36
+ };
37
+ });
38
+ })()
39
+
40
+ columns: [index, text, time]
@@ -0,0 +1,37 @@
1
+ site: facebook
2
+ name: profile
3
+ description: Get Facebook user/page profile info
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ username:
8
+ type: str
9
+ required: true
10
+ positional: true
11
+ description: Facebook username or page name
12
+
13
+ pipeline:
14
+ - navigate:
15
+ url: https://www.facebook.com/${{ args.username }}
16
+ settleMs: 3000
17
+
18
+ - evaluate: |
19
+ (() => {
20
+ const h1 = document.querySelector('h1');
21
+ let name = h1 ? h1.textContent.trim() : '';
22
+
23
+ // Find friends/followers links
24
+ const links = Array.from(document.querySelectorAll('a'));
25
+ const friendsLink = links.find(a => a.href && a.href.includes('/friends'));
26
+ const followersLink = links.find(a => a.href && a.href.includes('/followers'));
27
+
28
+ return [{
29
+ name: name,
30
+ username: ${{ args.username | json }},
31
+ friends: friendsLink ? friendsLink.textContent.trim() : '-',
32
+ followers: followersLink ? followersLink.textContent.trim() : '-',
33
+ url: window.location.href,
34
+ }];
35
+ })()
36
+
37
+ columns: [name, username, friends, followers, url]
@@ -0,0 +1,46 @@
1
+ site: facebook
2
+ name: search
3
+ description: Search Facebook for people, pages, or posts
4
+ domain: www.facebook.com
5
+
6
+ args:
7
+ query:
8
+ type: str
9
+ required: true
10
+ positional: true
11
+ description: Search query
12
+ limit:
13
+ type: int
14
+ default: 10
15
+ description: Number of results
16
+
17
+ pipeline:
18
+ - navigate: https://www.facebook.com
19
+
20
+ - evaluate: |
21
+ (async () => {
22
+ const query = ${{ args.query | json }};
23
+ const limit = ${{ args.limit }};
24
+ window.location.href = 'https://www.facebook.com/search/top?q=' + encodeURIComponent(query);
25
+ await new Promise(r => setTimeout(r, 4000));
26
+ // Search results are typically in role="article" or role="listitem"
27
+ let items = document.querySelectorAll('[role="article"]');
28
+ if (items.length === 0) {
29
+ items = document.querySelectorAll('[role="listitem"]');
30
+ }
31
+ return Array.from(items)
32
+ .filter(el => el.textContent.trim().length > 20)
33
+ .slice(0, limit)
34
+ .map((el, i) => {
35
+ const link = el.querySelector('a[href*="facebook.com/"]');
36
+ const heading = el.querySelector('h2, h3, h4, strong');
37
+ return {
38
+ index: i + 1,
39
+ title: heading ? heading.textContent.trim().substring(0, 80) : '',
40
+ text: el.textContent.trim().replace(/\s+/g, ' ').substring(0, 150),
41
+ url: link ? link.href.split('?')[0] : '',
42
+ };
43
+ });
44
+ })()
45
+
46
+ columns: [index, title, text, url]
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Google News via public RSS feed.
3
+ * Supports top stories (no keyword) and search (with keyword).
4
+ */
5
+
6
+ import { cli, Strategy } from '../../registry.js';
7
+ import { CliError } from '../../errors.js';
8
+ import { parseRssItems } from './utils.js';
9
+
10
+ cli({
11
+ site: 'google',
12
+ name: 'news',
13
+ description: 'Get Google News headlines',
14
+ strategy: Strategy.PUBLIC,
15
+ browser: false,
16
+ args: [
17
+ { name: 'keyword', positional: true, help: 'Search query (omit for top stories)' },
18
+ { name: 'limit', type: 'int', default: 10, help: 'Number of results' },
19
+ { name: 'lang', default: 'en', help: 'Language short code (e.g. en, zh)' },
20
+ { name: 'region', default: 'US', help: 'Region code (e.g. US, CN)' },
21
+ ],
22
+ columns: ['title', 'source', 'date', 'url'],
23
+ func: async (_page, args) => {
24
+ const limit = Math.max(1, Math.min(Number(args.limit), 100));
25
+ const lang = encodeURIComponent(args.lang);
26
+ const region = encodeURIComponent(args.region);
27
+ const ceid = `${args.region}:${args.lang}`;
28
+
29
+ // Top stories or search
30
+ const base = args.keyword
31
+ ? `https://news.google.com/rss/search?q=${encodeURIComponent(args.keyword)}&hl=${lang}&gl=${region}&ceid=${ceid}`
32
+ : `https://news.google.com/rss?hl=${lang}&gl=${region}&ceid=${ceid}`;
33
+
34
+ const resp = await fetch(base);
35
+ if (!resp.ok) {
36
+ throw new CliError('FETCH_ERROR', `HTTP ${resp.status}`, 'Check your network connection');
37
+ }
38
+
39
+ const xml = await resp.text();
40
+ const items = parseRssItems(xml, ['title', 'link', 'pubDate', 'source']);
41
+
42
+ if (!items.length) {
43
+ throw new CliError('NOT_FOUND', 'No news articles found', 'Try a different keyword or region');
44
+ }
45
+
46
+ return items.slice(0, limit).map(item => {
47
+ // Extract source: prefer <source> element, fallback to parsing title
48
+ let title = item['title'] || '';
49
+ let source = item['source'] || '';
50
+ if (!source) {
51
+ const idx = title.lastIndexOf(' - ');
52
+ if (idx !== -1) {
53
+ source = title.slice(idx + 3);
54
+ title = title.slice(0, idx);
55
+ }
56
+ }
57
+
58
+ return {
59
+ title,
60
+ source,
61
+ date: item['pubDate'] || '',
62
+ url: item['link'] || '',
63
+ };
64
+ });
65
+ },
66
+ });
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Google Web Search via browser DOM extraction.
3
+ * Uses browser mode to navigate google.com and extract results from the DOM.
4
+ *
5
+ * Extraction strategy (2026-03): Google no longer uses `.g` class containers.
6
+ * Instead, we find all `a` tags containing `h3` within `#rso`, then walk up
7
+ * to the result container (`div.tF2Cxc` or closest `div[data-hveid]`) to find
8
+ * snippets. This approach is resilient to class name changes.
9
+ */
10
+
11
+ import { cli, Strategy } from '../../registry.js';
12
+ import { CliError } from '../../errors.js';
13
+
14
+ cli({
15
+ site: 'google',
16
+ name: 'search',
17
+ description: 'Search Google',
18
+ domain: 'google.com',
19
+ strategy: Strategy.PUBLIC,
20
+ browser: true,
21
+ args: [
22
+ { name: 'keyword', positional: true, required: true, help: 'Search query' },
23
+ { name: 'limit', type: 'int', default: 10, help: 'Number of results (1-100)' },
24
+ { name: 'lang', default: 'en', help: 'Language short code (e.g. en, zh)' },
25
+ ],
26
+ columns: ['type', 'title', 'url', 'snippet'],
27
+ func: async (page, args) => {
28
+ const limit = Math.max(1, Math.min(Number(args.limit), 100));
29
+ const keyword = encodeURIComponent(args.keyword);
30
+ const lang = encodeURIComponent(args.lang);
31
+ const url = `https://www.google.com/search?q=${keyword}&hl=${lang}&num=${limit}`;
32
+
33
+ await page.goto(url);
34
+ await page.wait(2);
35
+
36
+ const results = await page.evaluate(`
37
+ (function() {
38
+ var results = [];
39
+ var seenUrls = {};
40
+ var rso = document.querySelector('#rso');
41
+ if (!rso) return results;
42
+
43
+ // -- Featured snippet (scoped to #rso to avoid matching unrelated elements) --
44
+ var featuredEl = rso.querySelector('.xpdopen .hgKElc')
45
+ || rso.querySelector('.IZ6rdc');
46
+ if (featuredEl) {
47
+ var parentBlock = featuredEl.closest('[data-hveid]') || featuredEl.parentElement;
48
+ var fLink = parentBlock ? parentBlock.querySelector('a[href]') : null;
49
+ var fUrl = fLink ? fLink.href : '';
50
+ if (fUrl) seenUrls[fUrl] = true;
51
+ results.push({
52
+ type: 'snippet',
53
+ title: featuredEl.textContent.trim().slice(0, 200),
54
+ url: fUrl,
55
+ snippet: '',
56
+ });
57
+ }
58
+
59
+ // -- Standard search results --
60
+ // Strategy: find all links containing h3 within #rso
61
+ var allLinks = rso.querySelectorAll('a');
62
+ for (var i = 0; i < allLinks.length; i++) {
63
+ var link = allLinks[i];
64
+ var h3 = link.querySelector('h3');
65
+ if (!h3) continue;
66
+
67
+ var href = link.href || '';
68
+ // Skip non-http, Google internal links, and duplicates
69
+ if (!href.match(/^https?:\\/\\//)) continue;
70
+ if (href.indexOf('google.com/search') !== -1) continue;
71
+ if (seenUrls[href]) continue;
72
+ seenUrls[href] = true;
73
+
74
+ // Walk up to find result container for snippet extraction
75
+ var container = link;
76
+ for (var j = 0; j < 6; j++) {
77
+ if (container.parentElement && container.parentElement !== rso) {
78
+ container = container.parentElement;
79
+ }
80
+ // Stop at a known result boundary
81
+ if (container.getAttribute && container.getAttribute('data-hveid')) break;
82
+ }
83
+
84
+ // Find snippet: look for descriptive text, skip breadcrumbs and metadata
85
+ var snippetText = '';
86
+ var titleText = h3.textContent.trim();
87
+ var candidates = container.querySelectorAll('span, div');
88
+ for (var k = 0; k < candidates.length; k++) {
89
+ var el = candidates[k];
90
+ if (el.querySelector('h3') || el.querySelector('a[href]')) continue;
91
+ var text = el.textContent.trim();
92
+ if (text.length < 40 || text.length > 500) continue;
93
+ if (text === titleText) continue;
94
+ // Skip URL breadcrumbs (e.g. "https://example.com › path..." or "Site Namehttps://...")
95
+ if (text.indexOf('\u203A') !== -1) continue;
96
+ if (new RegExp('https?://').test(text.slice(0, 60))) continue;
97
+ snippetText = text;
98
+ break;
99
+ }
100
+
101
+ results.push({
102
+ type: 'result',
103
+ title: h3.textContent.trim(),
104
+ url: href,
105
+ snippet: snippetText.slice(0, 300),
106
+ });
107
+ }
108
+
109
+ // -- People Also Ask --
110
+ var paaContainers = document.querySelectorAll('[data-sgrd="true"]');
111
+ for (var i = 0; i < paaContainers.length; i++) {
112
+ var questionEl = paaContainers[i].querySelector('span.CSkcDe');
113
+ if (questionEl) {
114
+ results.push({
115
+ type: 'paa',
116
+ title: questionEl.textContent.trim(),
117
+ url: '',
118
+ snippet: '',
119
+ });
120
+ }
121
+ }
122
+
123
+ return results;
124
+ })()
125
+ `);
126
+
127
+ if (!Array.isArray(results) || results.length === 0) {
128
+ throw new CliError('NOT_FOUND', 'No search results found', 'Try a different keyword or check for CAPTCHA');
129
+ }
130
+
131
+ return results;
132
+ },
133
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Google Search Suggestions via public JSON API.
3
+ * Uses suggestqueries.google.com with client=firefox for pure JSON (not JSONP).
4
+ */
5
+
6
+ import { cli, Strategy } from '../../registry.js';
7
+ import { CliError } from '../../errors.js';
8
+
9
+ cli({
10
+ site: 'google',
11
+ name: 'suggest',
12
+ description: 'Get Google search suggestions',
13
+ strategy: Strategy.PUBLIC,
14
+ browser: false,
15
+ args: [
16
+ { name: 'keyword', positional: true, required: true, help: 'Search query' },
17
+ { name: 'lang', default: 'zh-CN', help: 'Language code' },
18
+ ],
19
+ columns: ['suggestion'],
20
+ func: async (_page, args) => {
21
+ const keyword = encodeURIComponent(args.keyword);
22
+ const lang = encodeURIComponent(args.lang);
23
+ const url = `https://suggestqueries.google.com/complete/search?client=firefox&q=${keyword}&hl=${lang}`;
24
+
25
+ const resp = await fetch(url);
26
+ if (!resp.ok) {
27
+ throw new CliError('FETCH_ERROR', `HTTP ${resp.status}`, 'Check your network connection');
28
+ }
29
+
30
+ const data = await resp.json();
31
+ // Response format: ["query", ["suggestion1", "suggestion2", ...]]
32
+ const suggestions: string[] = Array.isArray(data) && Array.isArray(data[1]) ? data[1] : [];
33
+
34
+ if (!suggestions.length) {
35
+ throw new CliError('NOT_FOUND', 'No suggestions found', 'Try a different keyword');
36
+ }
37
+
38
+ return suggestions.map(s => ({ suggestion: s }));
39
+ },
40
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Google Trends via public RSS feed.
3
+ * Shows daily trending searches for a given region.
4
+ */
5
+
6
+ import { cli, Strategy } from '../../registry.js';
7
+ import { CliError } from '../../errors.js';
8
+ import { parseRssItems } from './utils.js';
9
+
10
+ cli({
11
+ site: 'google',
12
+ name: 'trends',
13
+ description: 'Get Google Trends daily trending searches',
14
+ strategy: Strategy.PUBLIC,
15
+ browser: false,
16
+ args: [
17
+ { name: 'region', default: 'US', help: 'Region code (e.g. US, CN, JP)' },
18
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
19
+ ],
20
+ columns: ['title', 'traffic', 'date'],
21
+ func: async (_page, args) => {
22
+ const limit = Math.max(1, Math.min(Number(args.limit), 100));
23
+ const region = encodeURIComponent(args.region);
24
+ const url = `https://trends.google.com/trending/rss?geo=${region}`;
25
+
26
+ const resp = await fetch(url);
27
+ if (!resp.ok) {
28
+ throw new CliError('FETCH_ERROR', `HTTP ${resp.status}`, 'Check your network connection or region code');
29
+ }
30
+
31
+ const xml = await resp.text();
32
+ const items = parseRssItems(xml, ['title', 'pubDate', 'ht:approx_traffic']);
33
+
34
+ if (!items.length) {
35
+ throw new CliError('NOT_FOUND', 'No trending data found', 'Try a different region code');
36
+ }
37
+
38
+ return items.slice(0, limit).map(item => ({
39
+ title: item['title'],
40
+ traffic: item['ht:approx_traffic'], // raw string e.g. "1,000,000+", no numeric conversion
41
+ date: item['pubDate'],
42
+ }));
43
+ },
44
+ });
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseRssItems } from './utils.js';
3
+
4
+ describe('parseRssItems', () => {
5
+ it('extracts plain text fields', () => {
6
+ const xml = `
7
+ <channel>
8
+ <item><title>Hello</title><link>https://example.com</link></item>
9
+ <item><title>World</title><link>https://test.com</link></item>
10
+ </channel>
11
+ `;
12
+ const items = parseRssItems(xml, ['title', 'link']);
13
+ expect(items).toEqual([
14
+ { title: 'Hello', link: 'https://example.com' },
15
+ { title: 'World', link: 'https://test.com' },
16
+ ]);
17
+ });
18
+
19
+ it('handles CDATA-wrapped content', () => {
20
+ const xml = `
21
+ <item><title><![CDATA[Breaking News]]></title><link>https://news.com</link></item>
22
+ `;
23
+ const items = parseRssItems(xml, ['title', 'link']);
24
+ expect(items).toEqual([
25
+ { title: 'Breaking News', link: 'https://news.com' },
26
+ ]);
27
+ });
28
+
29
+ it('handles namespaced fields like ht:approx_traffic', () => {
30
+ const xml = `
31
+ <item>
32
+ <title>AI</title>
33
+ <ht:approx_traffic>500,000+</ht:approx_traffic>
34
+ <pubDate>Mon, 20 Mar 2026</pubDate>
35
+ </item>
36
+ `;
37
+ const items = parseRssItems(xml, ['title', 'ht:approx_traffic', 'pubDate']);
38
+ expect(items).toEqual([
39
+ { title: 'AI', 'ht:approx_traffic': '500,000+', pubDate: 'Mon, 20 Mar 2026' },
40
+ ]);
41
+ });
42
+
43
+ it('returns empty string for missing fields', () => {
44
+ const xml = `<item><title>Test</title></item>`;
45
+ const items = parseRssItems(xml, ['title', 'missing']);
46
+ expect(items).toEqual([{ title: 'Test', missing: '' }]);
47
+ });
48
+
49
+ it('handles tags with attributes (e.g. <source url="...">)', () => {
50
+ const xml = `
51
+ <item>
52
+ <title><![CDATA[AI reshapes everything - Reuters]]></title>
53
+ <source url="https://reuters.com">Reuters</source>
54
+ <link>https://news.google.com/123</link>
55
+ </item>
56
+ `;
57
+ const items = parseRssItems(xml, ['title', 'source', 'link']);
58
+ expect(items).toEqual([
59
+ { title: 'AI reshapes everything - Reuters', source: 'Reuters', link: 'https://news.google.com/123' },
60
+ ]);
61
+ });
62
+
63
+ it('handles mixed CDATA and plain text in the same item', () => {
64
+ const xml = `
65
+ <item>
66
+ <title><![CDATA[Breaking: Major event]]></title>
67
+ <link>https://example.com/article</link>
68
+ <pubDate>Fri, 21 Mar 2026</pubDate>
69
+ </item>
70
+ `;
71
+ const items = parseRssItems(xml, ['title', 'link', 'pubDate']);
72
+ expect(items).toEqual([
73
+ { title: 'Breaking: Major event', link: 'https://example.com/article', pubDate: 'Fri, 21 Mar 2026' },
74
+ ]);
75
+ });
76
+
77
+ it('returns empty array for no items', () => {
78
+ const xml = `<channel><title>Empty</title></channel>`;
79
+ const items = parseRssItems(xml, ['title']);
80
+ expect(items).toEqual([]);
81
+ });
82
+ });