@jackwener/opencli 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (509) hide show
  1. package/CONTRIBUTING.md +39 -1
  2. package/README.md +9 -10
  3. package/README.zh-CN.md +39 -17
  4. package/SKILL.md +10 -5
  5. package/dist/browser/cdp.d.ts +4 -4
  6. package/dist/browser/cdp.js +39 -16
  7. package/dist/browser/daemon-client.d.ts +2 -1
  8. package/dist/browser/dom-helpers.js +38 -7
  9. package/dist/browser/dom-snapshot.d.ts +86 -0
  10. package/dist/browser/dom-snapshot.js +729 -0
  11. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  12. package/dist/browser/dom-snapshot.test.js +212 -0
  13. package/dist/browser/index.d.ts +2 -0
  14. package/dist/browser/index.js +1 -0
  15. package/dist/browser/page.d.ts +14 -24
  16. package/dist/browser/page.js +37 -4
  17. package/dist/build-manifest.d.ts +11 -4
  18. package/dist/build-manifest.js +59 -21
  19. package/dist/build-manifest.test.js +58 -2
  20. package/dist/cli-manifest.json +3856 -1509
  21. package/dist/cli.js +66 -0
  22. package/dist/clis/barchart/greeks.js +1 -1
  23. package/dist/clis/barchart/options.js +1 -1
  24. package/dist/clis/barchart/quote.js +1 -1
  25. package/dist/clis/bilibili/download.js +1 -1
  26. package/dist/clis/bilibili/following.js +1 -1
  27. package/dist/clis/bilibili/subtitle.js +1 -1
  28. package/dist/clis/bilibili/user-videos.js +1 -1
  29. package/dist/clis/boss/batchgreet.js +10 -97
  30. package/dist/clis/boss/chatlist.js +8 -25
  31. package/dist/clis/boss/chatmsg.js +11 -42
  32. package/dist/clis/boss/common.d.ts +92 -0
  33. package/dist/clis/boss/common.js +223 -0
  34. package/dist/clis/boss/detail.js +7 -49
  35. package/dist/clis/boss/exchange.js +13 -79
  36. package/dist/clis/boss/greet.js +18 -145
  37. package/dist/clis/boss/invite.js +26 -121
  38. package/dist/clis/boss/joblist.js +6 -31
  39. package/dist/clis/boss/mark.js +12 -85
  40. package/dist/clis/boss/recommend.js +10 -49
  41. package/dist/clis/boss/resume.js +18 -118
  42. package/dist/clis/boss/search.js +12 -60
  43. package/dist/clis/boss/send.js +17 -151
  44. package/dist/clis/boss/stats.js +18 -69
  45. package/dist/clis/coupang/add-to-cart.js +1 -1
  46. package/dist/clis/devto/tag.yaml +34 -0
  47. package/dist/clis/devto/top.yaml +29 -0
  48. package/dist/clis/devto/user.yaml +33 -0
  49. package/dist/clis/douban/book-hot.d.ts +1 -0
  50. package/dist/clis/douban/book-hot.js +14 -0
  51. package/dist/clis/douban/marks.d.ts +1 -0
  52. package/dist/clis/douban/marks.js +115 -0
  53. package/dist/clis/douban/movie-hot.d.ts +1 -0
  54. package/dist/clis/douban/movie-hot.js +14 -0
  55. package/dist/clis/douban/reviews.d.ts +1 -0
  56. package/dist/clis/douban/reviews.js +106 -0
  57. package/dist/clis/douban/search.d.ts +1 -0
  58. package/dist/clis/douban/search.js +16 -0
  59. package/dist/clis/douban/shared.d.ts +4 -0
  60. package/dist/clis/douban/shared.js +155 -0
  61. package/dist/clis/douban/subject.yaml +76 -0
  62. package/dist/clis/douban/top250.yaml +70 -0
  63. package/dist/clis/douban/utils.d.ts +35 -0
  64. package/dist/clis/douban/utils.js +48 -0
  65. package/dist/clis/facebook/add-friend.yaml +43 -0
  66. package/dist/clis/facebook/events.yaml +44 -0
  67. package/dist/clis/facebook/feed.yaml +63 -0
  68. package/dist/clis/facebook/friends.yaml +42 -0
  69. package/dist/clis/facebook/groups.yaml +50 -0
  70. package/dist/clis/facebook/join-group.yaml +44 -0
  71. package/dist/clis/facebook/memories.yaml +39 -0
  72. package/dist/clis/facebook/notifications.yaml +40 -0
  73. package/dist/clis/facebook/profile.yaml +37 -0
  74. package/dist/clis/facebook/search.yaml +46 -0
  75. package/dist/clis/google/news.d.ts +5 -0
  76. package/dist/clis/google/news.js +58 -0
  77. package/dist/clis/google/search.d.ts +10 -0
  78. package/dist/clis/google/search.js +127 -0
  79. package/dist/clis/google/suggest.d.ts +5 -0
  80. package/dist/clis/google/suggest.js +34 -0
  81. package/dist/clis/google/trends.d.ts +5 -0
  82. package/dist/clis/google/trends.js +38 -0
  83. package/dist/clis/google/utils.d.ts +9 -0
  84. package/dist/clis/google/utils.js +23 -0
  85. package/dist/clis/google/utils.test.d.ts +1 -0
  86. package/dist/clis/google/utils.test.js +75 -0
  87. package/dist/clis/grok/ask.d.ts +14 -0
  88. package/dist/clis/grok/ask.js +257 -65
  89. package/dist/clis/grok/ask.test.d.ts +1 -0
  90. package/dist/clis/grok/ask.test.js +36 -0
  91. package/dist/clis/instagram/comment.yaml +52 -0
  92. package/dist/clis/instagram/explore.yaml +43 -0
  93. package/dist/clis/instagram/follow.yaml +41 -0
  94. package/dist/clis/instagram/followers.yaml +51 -0
  95. package/dist/clis/instagram/following.yaml +51 -0
  96. package/dist/clis/instagram/like.yaml +46 -0
  97. package/dist/clis/instagram/profile.yaml +42 -0
  98. package/dist/clis/instagram/save.yaml +46 -0
  99. package/dist/clis/instagram/saved.yaml +40 -0
  100. package/dist/clis/instagram/search.yaml +43 -0
  101. package/dist/clis/instagram/unfollow.yaml +38 -0
  102. package/dist/clis/instagram/unlike.yaml +46 -0
  103. package/dist/clis/instagram/unsave.yaml +46 -0
  104. package/dist/clis/instagram/user.yaml +54 -0
  105. package/dist/clis/jike/repost.js +1 -1
  106. package/dist/clis/jimeng/generate.yaml +1 -0
  107. package/dist/clis/linux-do/category.yaml +1 -0
  108. package/dist/clis/lobsters/active.yaml +29 -0
  109. package/dist/clis/lobsters/hot.yaml +29 -0
  110. package/dist/clis/lobsters/newest.yaml +29 -0
  111. package/dist/clis/lobsters/tag.yaml +34 -0
  112. package/dist/clis/medium/feed.d.ts +1 -0
  113. package/dist/clis/medium/feed.js +15 -0
  114. package/dist/clis/medium/search.d.ts +1 -0
  115. package/dist/clis/medium/search.js +15 -0
  116. package/dist/clis/medium/shared.d.ts +5 -0
  117. package/dist/clis/medium/shared.js +78 -0
  118. package/dist/clis/medium/user.d.ts +1 -0
  119. package/dist/clis/medium/user.js +15 -0
  120. package/dist/clis/reddit/comment.js +1 -1
  121. package/dist/clis/reddit/read.js +1 -1
  122. package/dist/clis/reddit/save.js +1 -1
  123. package/dist/clis/reddit/subreddit.yaml +1 -0
  124. package/dist/clis/reddit/subscribe.js +1 -1
  125. package/dist/clis/reddit/upvote.js +1 -1
  126. package/dist/clis/sinablog/article.d.ts +1 -0
  127. package/dist/clis/sinablog/article.js +14 -0
  128. package/dist/clis/sinablog/hot.d.ts +1 -0
  129. package/dist/clis/sinablog/hot.js +14 -0
  130. package/dist/clis/sinablog/search.d.ts +1 -0
  131. package/dist/clis/sinablog/search.js +51 -0
  132. package/dist/clis/sinablog/shared.d.ts +7 -0
  133. package/dist/clis/sinablog/shared.js +187 -0
  134. package/dist/clis/sinablog/user.d.ts +1 -0
  135. package/dist/clis/sinablog/user.js +15 -0
  136. package/dist/clis/substack/feed.d.ts +1 -0
  137. package/dist/clis/substack/feed.js +15 -0
  138. package/dist/clis/substack/publication.d.ts +1 -0
  139. package/dist/clis/substack/publication.js +15 -0
  140. package/dist/clis/substack/search.d.ts +1 -0
  141. package/dist/clis/substack/search.js +77 -0
  142. package/dist/clis/substack/shared.d.ts +4 -0
  143. package/dist/clis/substack/shared.js +129 -0
  144. package/dist/clis/tiktok/comment.yaml +66 -0
  145. package/dist/clis/tiktok/explore.yaml +39 -0
  146. package/dist/clis/tiktok/follow.yaml +39 -0
  147. package/dist/clis/tiktok/following.yaml +46 -0
  148. package/dist/clis/tiktok/friends.yaml +47 -0
  149. package/dist/clis/tiktok/like.yaml +38 -0
  150. package/dist/clis/tiktok/live.yaml +51 -0
  151. package/dist/clis/tiktok/notifications.yaml +52 -0
  152. package/dist/clis/tiktok/profile.yaml +45 -0
  153. package/dist/clis/tiktok/save.yaml +34 -0
  154. package/dist/clis/tiktok/search.yaml +46 -0
  155. package/dist/clis/tiktok/unfollow.yaml +44 -0
  156. package/dist/clis/tiktok/unlike.yaml +38 -0
  157. package/dist/clis/tiktok/unsave.yaml +36 -0
  158. package/dist/clis/tiktok/user.yaml +44 -0
  159. package/dist/clis/twitter/download.d.ts +1 -1
  160. package/dist/clis/twitter/download.js +3 -3
  161. package/dist/clis/twitter/followers.js +1 -1
  162. package/dist/clis/twitter/following.js +1 -1
  163. package/dist/clis/twitter/thread.js +1 -1
  164. package/dist/clis/twitter/timeline.d.ts +23 -0
  165. package/dist/clis/twitter/timeline.js +42 -14
  166. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  167. package/dist/clis/twitter/timeline.test.js +102 -0
  168. package/dist/clis/wikipedia/random.d.ts +1 -0
  169. package/dist/clis/wikipedia/random.js +19 -0
  170. package/dist/clis/wikipedia/search.js +3 -3
  171. package/dist/clis/wikipedia/summary.js +4 -9
  172. package/dist/clis/wikipedia/trending.d.ts +1 -0
  173. package/dist/clis/wikipedia/trending.js +35 -0
  174. package/dist/clis/wikipedia/utils.d.ts +28 -0
  175. package/dist/clis/wikipedia/utils.js +13 -0
  176. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  177. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  178. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  179. package/dist/clis/xiaohongshu/download.js +1 -1
  180. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  181. package/dist/clis/xueqiu/search.yaml +2 -1
  182. package/dist/clis/xueqiu/stock.yaml +2 -0
  183. package/dist/clis/yahoo-finance/quote.js +1 -1
  184. package/dist/commanderAdapter.js +13 -7
  185. package/dist/discovery.d.ts +8 -0
  186. package/dist/discovery.js +105 -19
  187. package/dist/doctor.js +3 -1
  188. package/dist/doctor.test.js +46 -2
  189. package/dist/engine.test.d.ts +0 -3
  190. package/dist/engine.test.js +74 -6
  191. package/dist/execution.d.ts +4 -2
  192. package/dist/execution.js +31 -7
  193. package/dist/explore.d.ts +76 -3
  194. package/dist/explore.js +11 -4
  195. package/dist/generate.d.ts +41 -2
  196. package/dist/generate.js +5 -4
  197. package/dist/main.js +2 -1
  198. package/dist/pipeline/executor.d.ts +2 -2
  199. package/dist/pipeline/executor.js +2 -2
  200. package/dist/pipeline/executor.test.js +33 -6
  201. package/dist/pipeline/registry.d.ts +1 -1
  202. package/dist/pipeline/steps/browser.d.ts +7 -7
  203. package/dist/pipeline/steps/browser.js +15 -7
  204. package/dist/pipeline/steps/fetch.d.ts +1 -1
  205. package/dist/pipeline/steps/fetch.js +11 -7
  206. package/dist/pipeline/steps/transform.d.ts +6 -5
  207. package/dist/pipeline/steps/transform.js +30 -9
  208. package/dist/pipeline/template.d.ts +6 -6
  209. package/dist/pipeline/template.js +43 -5
  210. package/dist/pipeline/template.test.js +18 -0
  211. package/dist/pipeline/transform.test.js +11 -0
  212. package/dist/plugin.d.ts +31 -0
  213. package/dist/plugin.js +216 -0
  214. package/dist/plugin.test.d.ts +4 -0
  215. package/dist/plugin.test.js +76 -0
  216. package/dist/registry-api.d.ts +11 -0
  217. package/dist/registry-api.js +9 -0
  218. package/dist/registry.d.ts +11 -0
  219. package/dist/registry.js +6 -1
  220. package/dist/synthesize.d.ts +94 -4
  221. package/dist/synthesize.js +5 -4
  222. package/dist/types.d.ts +39 -26
  223. package/dist/validate.js +8 -2
  224. package/docs/.vitepress/config.mts +6 -4
  225. package/docs/adapters/browser/barchart.md +6 -5
  226. package/docs/adapters/browser/bilibili.md +9 -0
  227. package/docs/adapters/browser/devto.md +35 -0
  228. package/docs/adapters/browser/douban.md +38 -0
  229. package/docs/adapters/browser/facebook.md +36 -0
  230. package/docs/adapters/browser/google.md +62 -0
  231. package/docs/adapters/browser/grok.md +26 -8
  232. package/docs/adapters/browser/instagram.md +46 -0
  233. package/docs/adapters/browser/lobsters.md +32 -0
  234. package/docs/adapters/browser/medium.md +32 -0
  235. package/docs/adapters/browser/reddit.md +9 -0
  236. package/docs/adapters/browser/sinablog.md +36 -0
  237. package/docs/adapters/browser/substack.md +38 -0
  238. package/docs/adapters/browser/tiktok.md +68 -0
  239. package/docs/adapters/browser/wikipedia.md +11 -2
  240. package/docs/adapters/browser/xueqiu.md +10 -0
  241. package/docs/adapters/browser/yahoo-finance.md +6 -5
  242. package/docs/adapters/desktop/antigravity.md +6 -0
  243. package/docs/adapters/desktop/chatgpt.md +2 -1
  244. package/docs/adapters/desktop/codex.md +5 -1
  245. package/docs/adapters/desktop/cursor.md +4 -0
  246. package/docs/adapters/desktop/discord.md +7 -7
  247. package/docs/adapters/index.md +1 -4
  248. package/docs/guide/getting-started.md +1 -0
  249. package/docs/guide/plugins.md +153 -0
  250. package/docs/zh/guide/plugins.md +107 -0
  251. package/extension/src/background.ts +18 -11
  252. package/package.json +10 -5
  253. package/scripts/clean-dist.cjs +13 -0
  254. package/src/browser/cdp.ts +71 -31
  255. package/src/browser/daemon-client.ts +2 -1
  256. package/src/browser/dom-helpers.ts +38 -7
  257. package/src/browser/dom-snapshot.test.ts +249 -0
  258. package/src/browser/dom-snapshot.ts +770 -0
  259. package/src/browser/index.ts +2 -0
  260. package/src/browser/page.ts +50 -19
  261. package/src/build-manifest.test.ts +70 -2
  262. package/src/build-manifest.ts +94 -26
  263. package/src/cli.ts +71 -2
  264. package/src/clis/barchart/greeks.ts +1 -1
  265. package/src/clis/barchart/options.ts +1 -1
  266. package/src/clis/barchart/quote.ts +1 -1
  267. package/src/clis/bilibili/download.ts +1 -1
  268. package/src/clis/bilibili/following.ts +1 -1
  269. package/src/clis/bilibili/subtitle.ts +1 -1
  270. package/src/clis/bilibili/user-videos.ts +1 -1
  271. package/src/clis/boss/batchgreet.ts +14 -106
  272. package/src/clis/boss/chatlist.ts +12 -26
  273. package/src/clis/boss/chatmsg.ts +16 -40
  274. package/src/clis/boss/common.ts +287 -0
  275. package/src/clis/boss/detail.ts +8 -54
  276. package/src/clis/boss/exchange.ts +15 -89
  277. package/src/clis/boss/greet.ts +23 -160
  278. package/src/clis/boss/invite.ts +36 -133
  279. package/src/clis/boss/joblist.ts +7 -36
  280. package/src/clis/boss/mark.ts +13 -94
  281. package/src/clis/boss/recommend.ts +12 -57
  282. package/src/clis/boss/resume.ts +19 -124
  283. package/src/clis/boss/search.ts +13 -66
  284. package/src/clis/boss/send.ts +21 -161
  285. package/src/clis/boss/stats.ts +19 -74
  286. package/src/clis/coupang/add-to-cart.ts +1 -1
  287. package/src/clis/devto/tag.yaml +34 -0
  288. package/src/clis/devto/top.yaml +29 -0
  289. package/src/clis/devto/user.yaml +33 -0
  290. package/src/clis/douban/book-hot.ts +15 -0
  291. package/src/clis/douban/marks.ts +135 -0
  292. package/src/clis/douban/movie-hot.ts +15 -0
  293. package/src/clis/douban/reviews.ts +127 -0
  294. package/src/clis/douban/search.ts +17 -0
  295. package/src/clis/douban/shared.ts +165 -0
  296. package/src/clis/douban/subject.yaml +76 -0
  297. package/src/clis/douban/top250.yaml +70 -0
  298. package/src/clis/douban/utils.ts +81 -0
  299. package/src/clis/facebook/add-friend.yaml +43 -0
  300. package/src/clis/facebook/events.yaml +44 -0
  301. package/src/clis/facebook/feed.yaml +63 -0
  302. package/src/clis/facebook/friends.yaml +42 -0
  303. package/src/clis/facebook/groups.yaml +50 -0
  304. package/src/clis/facebook/join-group.yaml +44 -0
  305. package/src/clis/facebook/memories.yaml +39 -0
  306. package/src/clis/facebook/notifications.yaml +40 -0
  307. package/src/clis/facebook/profile.yaml +37 -0
  308. package/src/clis/facebook/search.yaml +46 -0
  309. package/src/clis/google/news.ts +66 -0
  310. package/src/clis/google/search.ts +133 -0
  311. package/src/clis/google/suggest.ts +40 -0
  312. package/src/clis/google/trends.ts +44 -0
  313. package/src/clis/google/utils.test.ts +82 -0
  314. package/src/clis/google/utils.ts +24 -0
  315. package/src/clis/grok/ask.test.ts +53 -0
  316. package/src/clis/grok/ask.ts +300 -69
  317. package/src/clis/instagram/comment.yaml +52 -0
  318. package/src/clis/instagram/explore.yaml +43 -0
  319. package/src/clis/instagram/follow.yaml +41 -0
  320. package/src/clis/instagram/followers.yaml +51 -0
  321. package/src/clis/instagram/following.yaml +51 -0
  322. package/src/clis/instagram/like.yaml +46 -0
  323. package/src/clis/instagram/profile.yaml +42 -0
  324. package/src/clis/instagram/save.yaml +46 -0
  325. package/src/clis/instagram/saved.yaml +40 -0
  326. package/src/clis/instagram/search.yaml +43 -0
  327. package/src/clis/instagram/unfollow.yaml +38 -0
  328. package/src/clis/instagram/unlike.yaml +46 -0
  329. package/src/clis/instagram/unsave.yaml +46 -0
  330. package/src/clis/instagram/user.yaml +54 -0
  331. package/src/clis/jike/repost.ts +1 -1
  332. package/src/clis/jimeng/generate.yaml +1 -0
  333. package/src/clis/linux-do/category.yaml +1 -0
  334. package/src/clis/lobsters/active.yaml +29 -0
  335. package/src/clis/lobsters/hot.yaml +29 -0
  336. package/src/clis/lobsters/newest.yaml +29 -0
  337. package/src/clis/lobsters/tag.yaml +34 -0
  338. package/src/clis/medium/feed.ts +16 -0
  339. package/src/clis/medium/search.ts +16 -0
  340. package/src/clis/medium/shared.ts +83 -0
  341. package/src/clis/medium/user.ts +16 -0
  342. package/src/clis/reddit/comment.ts +1 -1
  343. package/src/clis/reddit/read.ts +1 -1
  344. package/src/clis/reddit/save.ts +1 -1
  345. package/src/clis/reddit/subreddit.yaml +1 -0
  346. package/src/clis/reddit/subscribe.ts +1 -1
  347. package/src/clis/reddit/upvote.ts +1 -1
  348. package/src/clis/sinablog/article.ts +15 -0
  349. package/src/clis/sinablog/hot.ts +15 -0
  350. package/src/clis/sinablog/search.ts +56 -0
  351. package/src/clis/sinablog/shared.ts +198 -0
  352. package/src/clis/sinablog/user.ts +16 -0
  353. package/src/clis/substack/feed.ts +16 -0
  354. package/src/clis/substack/publication.ts +16 -0
  355. package/src/clis/substack/search.ts +91 -0
  356. package/src/clis/substack/shared.ts +132 -0
  357. package/src/clis/tiktok/comment.yaml +66 -0
  358. package/src/clis/tiktok/explore.yaml +39 -0
  359. package/src/clis/tiktok/follow.yaml +39 -0
  360. package/src/clis/tiktok/following.yaml +46 -0
  361. package/src/clis/tiktok/friends.yaml +47 -0
  362. package/src/clis/tiktok/like.yaml +38 -0
  363. package/src/clis/tiktok/live.yaml +51 -0
  364. package/src/clis/tiktok/notifications.yaml +52 -0
  365. package/src/clis/tiktok/profile.yaml +45 -0
  366. package/src/clis/tiktok/save.yaml +34 -0
  367. package/src/clis/tiktok/search.yaml +46 -0
  368. package/src/clis/tiktok/unfollow.yaml +44 -0
  369. package/src/clis/tiktok/unlike.yaml +38 -0
  370. package/src/clis/tiktok/unsave.yaml +36 -0
  371. package/src/clis/tiktok/user.yaml +44 -0
  372. package/src/clis/twitter/download.ts +3 -3
  373. package/src/clis/twitter/followers.ts +1 -1
  374. package/src/clis/twitter/following.ts +1 -1
  375. package/src/clis/twitter/thread.ts +1 -1
  376. package/src/clis/twitter/timeline.test.ts +109 -0
  377. package/src/clis/twitter/timeline.ts +59 -19
  378. package/src/clis/wikipedia/random.ts +19 -0
  379. package/src/clis/wikipedia/search.ts +10 -4
  380. package/src/clis/wikipedia/summary.ts +4 -9
  381. package/src/clis/wikipedia/trending.ts +41 -0
  382. package/src/clis/wikipedia/utils.ts +31 -0
  383. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  384. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  385. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  386. package/src/clis/xiaohongshu/download.ts +1 -1
  387. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  388. package/src/clis/xueqiu/search.yaml +2 -1
  389. package/src/clis/xueqiu/stock.yaml +2 -0
  390. package/src/clis/yahoo-finance/quote.ts +1 -1
  391. package/src/commanderAdapter.ts +17 -10
  392. package/src/discovery.ts +134 -24
  393. package/src/doctor.test.ts +59 -2
  394. package/src/doctor.ts +4 -2
  395. package/src/engine.test.ts +79 -6
  396. package/src/execution.ts +42 -16
  397. package/src/explore.ts +77 -9
  398. package/src/generate.ts +58 -9
  399. package/src/main.ts +2 -1
  400. package/src/pipeline/executor.test.ts +35 -6
  401. package/src/pipeline/executor.ts +11 -7
  402. package/src/pipeline/registry.ts +3 -3
  403. package/src/pipeline/steps/browser.ts +24 -15
  404. package/src/pipeline/steps/fetch.ts +18 -13
  405. package/src/pipeline/steps/transform.ts +40 -15
  406. package/src/pipeline/template.test.ts +18 -0
  407. package/src/pipeline/template.ts +86 -13
  408. package/src/pipeline/transform.test.ts +15 -2
  409. package/src/plugin.test.ts +86 -0
  410. package/src/plugin.ts +254 -0
  411. package/src/registry-api.ts +12 -0
  412. package/src/registry.ts +19 -1
  413. package/src/synthesize.ts +102 -21
  414. package/src/types.ts +44 -12
  415. package/src/validate.ts +19 -4
  416. package/tests/e2e/browser-public.test.ts +11 -0
  417. package/tests/e2e/public-commands.test.ts +64 -0
  418. package/dist/clis/feishu/new.d.ts +0 -1
  419. package/dist/clis/feishu/new.js +0 -27
  420. package/dist/clis/feishu/read.d.ts +0 -1
  421. package/dist/clis/feishu/read.js +0 -40
  422. package/dist/clis/feishu/search.d.ts +0 -1
  423. package/dist/clis/feishu/search.js +0 -30
  424. package/dist/clis/feishu/send.d.ts +0 -1
  425. package/dist/clis/feishu/send.js +0 -39
  426. package/dist/clis/feishu/status.d.ts +0 -1
  427. package/dist/clis/feishu/status.js +0 -28
  428. package/dist/clis/neteasemusic/like.d.ts +0 -1
  429. package/dist/clis/neteasemusic/like.js +0 -25
  430. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  431. package/dist/clis/neteasemusic/lyrics.js +0 -47
  432. package/dist/clis/neteasemusic/next.d.ts +0 -1
  433. package/dist/clis/neteasemusic/next.js +0 -26
  434. package/dist/clis/neteasemusic/play.d.ts +0 -1
  435. package/dist/clis/neteasemusic/play.js +0 -26
  436. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  437. package/dist/clis/neteasemusic/playing.js +0 -59
  438. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  439. package/dist/clis/neteasemusic/playlist.js +0 -46
  440. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  441. package/dist/clis/neteasemusic/prev.js +0 -25
  442. package/dist/clis/neteasemusic/search.d.ts +0 -1
  443. package/dist/clis/neteasemusic/search.js +0 -52
  444. package/dist/clis/neteasemusic/status.d.ts +0 -1
  445. package/dist/clis/neteasemusic/status.js +0 -16
  446. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  447. package/dist/clis/neteasemusic/volume.js +0 -54
  448. package/dist/clis/wechat/chats.d.ts +0 -1
  449. package/dist/clis/wechat/chats.js +0 -28
  450. package/dist/clis/wechat/contacts.d.ts +0 -1
  451. package/dist/clis/wechat/contacts.js +0 -28
  452. package/dist/clis/wechat/read.d.ts +0 -1
  453. package/dist/clis/wechat/read.js +0 -58
  454. package/dist/clis/wechat/search.d.ts +0 -1
  455. package/dist/clis/wechat/search.js +0 -31
  456. package/dist/clis/wechat/send.d.ts +0 -1
  457. package/dist/clis/wechat/send.js +0 -42
  458. package/dist/clis/wechat/status.d.ts +0 -1
  459. package/dist/clis/wechat/status.js +0 -29
  460. package/dist/pipeline.d.ts +0 -7
  461. package/dist/pipeline.js +0 -7
  462. package/docs/adapters/browser/github.md +0 -26
  463. package/docs/adapters/desktop/feishu.md +0 -20
  464. package/docs/adapters/desktop/neteasemusic.md +0 -31
  465. package/docs/adapters/desktop/wechat.md +0 -28
  466. package/src/clis/antigravity/README.md +0 -5
  467. package/src/clis/antigravity/README.zh-CN.md +0 -51
  468. package/src/clis/chaoxing/README.md +0 -14
  469. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  470. package/src/clis/chatgpt/README.md +0 -5
  471. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  472. package/src/clis/chatwise/README.md +0 -5
  473. package/src/clis/chatwise/README.zh-CN.md +0 -38
  474. package/src/clis/codex/README.md +0 -5
  475. package/src/clis/codex/README.zh-CN.md +0 -33
  476. package/src/clis/cursor/README.md +0 -5
  477. package/src/clis/cursor/README.zh-CN.md +0 -33
  478. package/src/clis/discord-app/README.md +0 -5
  479. package/src/clis/discord-app/README.zh-CN.md +0 -28
  480. package/src/clis/feishu/README.md +0 -5
  481. package/src/clis/feishu/README.zh-CN.md +0 -20
  482. package/src/clis/feishu/new.ts +0 -32
  483. package/src/clis/feishu/read.ts +0 -48
  484. package/src/clis/feishu/search.ts +0 -35
  485. package/src/clis/feishu/send.ts +0 -46
  486. package/src/clis/feishu/status.ts +0 -34
  487. package/src/clis/neteasemusic/README.md +0 -5
  488. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  489. package/src/clis/neteasemusic/like.ts +0 -28
  490. package/src/clis/neteasemusic/lyrics.ts +0 -53
  491. package/src/clis/neteasemusic/next.ts +0 -30
  492. package/src/clis/neteasemusic/play.ts +0 -30
  493. package/src/clis/neteasemusic/playing.ts +0 -62
  494. package/src/clis/neteasemusic/playlist.ts +0 -51
  495. package/src/clis/neteasemusic/prev.ts +0 -29
  496. package/src/clis/neteasemusic/search.ts +0 -58
  497. package/src/clis/neteasemusic/status.ts +0 -18
  498. package/src/clis/neteasemusic/volume.ts +0 -61
  499. package/src/clis/notion/README.md +0 -5
  500. package/src/clis/notion/README.zh-CN.md +0 -29
  501. package/src/clis/wechat/README.md +0 -5
  502. package/src/clis/wechat/README.zh-CN.md +0 -28
  503. package/src/clis/wechat/chats.ts +0 -33
  504. package/src/clis/wechat/contacts.ts +0 -33
  505. package/src/clis/wechat/read.ts +0 -72
  506. package/src/clis/wechat/search.ts +0 -36
  507. package/src/clis/wechat/send.ts +0 -49
  508. package/src/clis/wechat/status.ts +0 -35
  509. package/src/pipeline.ts +0 -8
@@ -2,7 +2,7 @@
2
2
  * Twitter/X download — download images and videos from tweets.
3
3
  *
4
4
  * Usage:
5
- * opencli twitter download --username elonmusk --limit 10 --output ./twitter
5
+ * opencli twitter download elonmusk --limit 10 --output ./twitter
6
6
  * opencli twitter download --tweet-url https://x.com/xxx/status/123 --output ./twitter
7
7
  */
8
8
  export {};
@@ -2,7 +2,7 @@
2
2
  * Twitter/X download — download images and videos from tweets.
3
3
  *
4
4
  * Usage:
5
- * opencli twitter download --username elonmusk --limit 10 --output ./twitter
5
+ * opencli twitter download elonmusk --limit 10 --output ./twitter
6
6
  * opencli twitter download --tweet-url https://x.com/xxx/status/123 --output ./twitter
7
7
  */
8
8
  import * as fs from 'node:fs';
@@ -17,7 +17,7 @@ cli({
17
17
  domain: 'x.com',
18
18
  strategy: Strategy.COOKIE,
19
19
  args: [
20
- { name: 'username', help: 'Twitter username (downloads from media tab)' },
20
+ { name: 'username', positional: true, help: 'Twitter username (downloads from media tab)' },
21
21
  { name: 'tweet-url', help: 'Single tweet URL to download' },
22
22
  { name: 'limit', type: 'int', default: 10, help: 'Number of tweets to scan' },
23
23
  { name: 'output', default: './twitter-downloads', help: 'Output directory' },
@@ -33,7 +33,7 @@ cli({
33
33
  index: 0,
34
34
  type: '-',
35
35
  status: 'failed',
36
- size: 'Must provide --username or --tweet-url',
36
+ size: 'Must provide a username or --tweet-url',
37
37
  }];
38
38
  }
39
39
  // Navigate to the appropriate page
@@ -7,7 +7,7 @@ cli({
7
7
  strategy: Strategy.INTERCEPT,
8
8
  browser: true,
9
9
  args: [
10
- { name: 'user', type: 'string', required: false },
10
+ { name: 'user', positional: true, type: 'string', required: false },
11
11
  { name: 'limit', type: 'int', default: 50 },
12
12
  ],
13
13
  columns: ['screen_name', 'name', 'bio', 'followers'],
@@ -7,7 +7,7 @@ cli({
7
7
  strategy: Strategy.INTERCEPT,
8
8
  browser: true,
9
9
  args: [
10
- { name: 'user', type: 'string', required: false },
10
+ { name: 'user', positional: true, type: 'string', required: false },
11
11
  { name: 'limit', type: 'int', default: 50 },
12
12
  ],
13
13
  columns: ['screen_name', 'name', 'bio', 'followers'],
@@ -97,7 +97,7 @@ cli({
97
97
  strategy: Strategy.COOKIE,
98
98
  browser: true,
99
99
  args: [
100
- { name: 'tweet-id', type: 'string', required: true },
100
+ { name: 'tweet-id', positional: true, type: 'string', required: true },
101
101
  { name: 'limit', type: 'int', default: 50 },
102
102
  ],
103
103
  columns: ['id', 'author', 'text', 'likes', 'retweets', 'url'],
@@ -1 +1,24 @@
1
+ type TimelineType = 'for-you' | 'following';
2
+ interface TimelineTweet {
3
+ id: string;
4
+ author: string;
5
+ text: string;
6
+ likes: number;
7
+ retweets: number;
8
+ replies: number;
9
+ views: number;
10
+ created_at: string;
11
+ url: string;
12
+ }
13
+ declare function buildTimelineVariables(type: TimelineType, count: number, cursor?: string | null): Record<string, unknown>;
14
+ declare function buildHomeTimelineUrl(queryId: string, endpoint: string, vars: Record<string, unknown>): string;
15
+ declare function parseHomeTimeline(data: any, seen: Set<string>): {
16
+ tweets: TimelineTweet[];
17
+ nextCursor: string | null;
18
+ };
19
+ export declare const __test__: {
20
+ buildTimelineVariables: typeof buildTimelineVariables;
21
+ buildHomeTimelineUrl: typeof buildHomeTimelineUrl;
22
+ parseHomeTimeline: typeof parseHomeTimeline;
23
+ };
1
24
  export {};
@@ -2,6 +2,12 @@ import { cli, Strategy } from '../../registry.js';
2
2
  // ── Twitter GraphQL constants ──────────────────────────────────────────
3
3
  const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
4
4
  const HOME_TIMELINE_QUERY_ID = 'c-CzHF1LboFilMpsx4ZCrQ';
5
+ const HOME_LATEST_TIMELINE_QUERY_ID = 'BKB7oi212Fi7kQtCBGE4zA';
6
+ // Endpoint config: for-you uses GET HomeTimeline, following uses POST HomeLatestTimeline
7
+ const TIMELINE_ENDPOINTS = {
8
+ 'for-you': { endpoint: 'HomeTimeline', method: 'GET', fallbackQueryId: HOME_TIMELINE_QUERY_ID },
9
+ following: { endpoint: 'HomeLatestTimeline', method: 'POST', fallbackQueryId: HOME_LATEST_TIMELINE_QUERY_ID },
10
+ };
5
11
  const FEATURES = {
6
12
  rweb_video_screen_enabled: false,
7
13
  profile_label_improvements_pcf_label_in_post_enabled: true,
@@ -35,19 +41,25 @@ const FEATURES = {
35
41
  responsive_web_grok_image_annotation_enabled: true,
36
42
  responsive_web_enhance_cards_enabled: false,
37
43
  };
38
- function buildHomeTimelineUrl(count, cursor) {
44
+ function buildTimelineVariables(type, count, cursor) {
39
45
  const vars = {
40
46
  count,
41
47
  includePromotedContent: false,
42
48
  latestControlAvailable: true,
43
49
  requestContext: 'launch',
44
- withCommunity: true,
45
50
  };
51
+ if (type === 'for-you')
52
+ vars.withCommunity = true;
53
+ if (type === 'following')
54
+ vars.seenTweetIds = [];
46
55
  if (cursor)
47
56
  vars.cursor = cursor;
48
- return `/i/api/graphql/${HOME_TIMELINE_QUERY_ID}/HomeTimeline`
49
- + `?variables=${encodeURIComponent(JSON.stringify(vars))}`
50
- + `&features=${encodeURIComponent(JSON.stringify(FEATURES))}`;
57
+ return vars;
58
+ }
59
+ function buildHomeTimelineUrl(queryId, endpoint, vars) {
60
+ return (`/i/api/graphql/${queryId}/${endpoint}` +
61
+ `?variables=${encodeURIComponent(JSON.stringify(vars))}` +
62
+ `&features=${encodeURIComponent(JSON.stringify(FEATURES))}`);
51
63
  }
52
64
  function extractTweet(result, seen) {
53
65
  if (!result)
@@ -76,6 +88,7 @@ function extractTweet(result, seen) {
76
88
  function parseHomeTimeline(data, seen) {
77
89
  const tweets = [];
78
90
  let nextCursor = null;
91
+ // Both HomeTimeline and HomeLatestTimeline share the same response envelope
79
92
  const instructions = data?.data?.home?.home_timeline_urt?.instructions || [];
80
93
  for (const inst of instructions) {
81
94
  for (const entry of inst.entries || []) {
@@ -120,16 +133,24 @@ function parseHomeTimeline(data, seen) {
120
133
  cli({
121
134
  site: 'twitter',
122
135
  name: 'timeline',
123
- description: 'Fetch Twitter Home Timeline',
136
+ description: 'Fetch Twitter timeline (for-you or following)',
124
137
  domain: 'x.com',
125
138
  strategy: Strategy.COOKIE,
126
139
  browser: true,
127
140
  args: [
141
+ {
142
+ name: 'type',
143
+ default: 'for-you',
144
+ choices: ['for-you', 'following'],
145
+ help: 'Timeline type: for-you (algorithmic) or following (chronological)',
146
+ },
128
147
  { name: 'limit', type: 'int', default: 20 },
129
148
  ],
130
149
  columns: ['id', 'author', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url'],
131
150
  func: async (page, kwargs) => {
132
151
  const limit = kwargs.limit || 20;
152
+ const timelineType = kwargs.type === 'following' ? 'following' : 'for-you';
153
+ const { endpoint, method, fallbackQueryId } = TIMELINE_ENDPOINTS[timelineType];
133
154
  // Navigate to x.com for cookie context
134
155
  await page.goto('https://x.com');
135
156
  await page.wait(3);
@@ -139,21 +160,23 @@ cli({
139
160
  }`);
140
161
  if (!ct0)
141
162
  throw new Error('Not logged into x.com (no ct0 cookie)');
142
- // Dynamically resolve queryId
143
- const queryId = await page.evaluate(`async () => {
163
+ // Dynamically resolve queryId for the selected endpoint
164
+ const resolved = await page.evaluate(`async () => {
144
165
  try {
145
166
  const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
146
167
  if (ghResp.ok) {
147
168
  const data = await ghResp.json();
148
- const entry = data['HomeTimeline'];
169
+ const entry = data['${endpoint}'];
149
170
  if (entry && entry.queryId) return entry.queryId;
150
171
  }
151
172
  } catch {}
152
173
  return null;
153
- }`) || HOME_TIMELINE_QUERY_ID;
174
+ }`);
175
+ // Validate queryId format to prevent injection from untrusted upstream
176
+ const queryId = typeof resolved === 'string' && /^[A-Za-z0-9_-]+$/.test(resolved) ? resolved : fallbackQueryId;
154
177
  // Build auth headers
155
178
  const headers = JSON.stringify({
156
- 'Authorization': `Bearer ${decodeURIComponent(BEARER_TOKEN)}`,
179
+ Authorization: `Bearer ${decodeURIComponent(BEARER_TOKEN)}`,
157
180
  'X-Csrf-Token': ct0,
158
181
  'X-Twitter-Auth-Type': 'OAuth2Session',
159
182
  'X-Twitter-Active-User': 'yes',
@@ -164,10 +187,10 @@ cli({
164
187
  let cursor = null;
165
188
  for (let i = 0; i < 5 && allTweets.length < limit; i++) {
166
189
  const fetchCount = Math.min(40, limit - allTweets.length + 5); // over-fetch slightly for promoted filtering
167
- const apiUrl = buildHomeTimelineUrl(fetchCount, cursor)
168
- .replace(HOME_TIMELINE_QUERY_ID, queryId);
190
+ const variables = buildTimelineVariables(timelineType, fetchCount, cursor);
191
+ const apiUrl = buildHomeTimelineUrl(queryId, endpoint, variables);
169
192
  const data = await page.evaluate(`async () => {
170
- const r = await fetch("${apiUrl}", { headers: ${headers}, credentials: 'include' });
193
+ const r = await fetch("${apiUrl}", { method: "${method}", headers: ${headers}, credentials: 'include' });
171
194
  return r.ok ? await r.json() : { error: r.status };
172
195
  }`);
173
196
  if (data?.error) {
@@ -184,3 +207,8 @@ cli({
184
207
  return allTweets.slice(0, limit);
185
208
  },
186
209
  });
210
+ export const __test__ = {
211
+ buildTimelineVariables,
212
+ buildHomeTimelineUrl,
213
+ parseHomeTimeline,
214
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,102 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './timeline.js';
3
+ describe('twitter timeline helpers', () => {
4
+ it('builds for-you variables with withCommunity', () => {
5
+ expect(__test__.buildTimelineVariables('for-you', 20)).toEqual({
6
+ count: 20,
7
+ includePromotedContent: false,
8
+ latestControlAvailable: true,
9
+ requestContext: 'launch',
10
+ withCommunity: true,
11
+ });
12
+ });
13
+ it('builds following variables with seenTweetIds instead of withCommunity', () => {
14
+ expect(__test__.buildTimelineVariables('following', 20, 'cursor-1')).toEqual({
15
+ count: 20,
16
+ includePromotedContent: false,
17
+ latestControlAvailable: true,
18
+ requestContext: 'launch',
19
+ seenTweetIds: [],
20
+ cursor: 'cursor-1',
21
+ });
22
+ });
23
+ it('encodes variables into timeline url', () => {
24
+ const url = __test__.buildHomeTimelineUrl('query123', 'HomeLatestTimeline', {
25
+ count: 20,
26
+ seenTweetIds: [],
27
+ });
28
+ expect(url).toContain('/i/api/graphql/query123/HomeLatestTimeline');
29
+ expect(url).toContain('variables=');
30
+ expect(url).toContain('features=');
31
+ expect(decodeURIComponent(url)).toContain('"seenTweetIds":[]');
32
+ });
33
+ it('parses tweets and bottom cursor from home timeline payload', () => {
34
+ const payload = {
35
+ data: {
36
+ home: {
37
+ home_timeline_urt: {
38
+ instructions: [
39
+ {
40
+ entries: [
41
+ {
42
+ entryId: 'tweet-1',
43
+ content: {
44
+ itemContent: {
45
+ tweet_results: {
46
+ result: {
47
+ rest_id: '1',
48
+ legacy: {
49
+ full_text: 'hello',
50
+ favorite_count: 3,
51
+ retweet_count: 2,
52
+ reply_count: 1,
53
+ created_at: 'now',
54
+ },
55
+ core: {
56
+ user_results: {
57
+ result: {
58
+ legacy: {
59
+ screen_name: 'alice',
60
+ },
61
+ },
62
+ },
63
+ },
64
+ views: {
65
+ count: '9',
66
+ },
67
+ },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ {
73
+ entryId: 'cursor-bottom-1',
74
+ content: {
75
+ entryType: 'TimelineTimelineCursor',
76
+ cursorType: 'Bottom',
77
+ value: 'cursor-next',
78
+ },
79
+ },
80
+ ],
81
+ },
82
+ ],
83
+ },
84
+ },
85
+ },
86
+ };
87
+ const result = __test__.parseHomeTimeline(payload, new Set());
88
+ expect(result.nextCursor).toBe('cursor-next');
89
+ expect(result.tweets).toHaveLength(1);
90
+ expect(result.tweets[0]).toMatchObject({
91
+ id: '1',
92
+ author: 'alice',
93
+ text: 'hello',
94
+ likes: 3,
95
+ retweets: 2,
96
+ replies: 1,
97
+ views: 9,
98
+ created_at: 'now',
99
+ url: 'https://x.com/alice/status/1',
100
+ });
101
+ });
102
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { CliError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import { formatSummaryRow, wikiFetch } from './utils.js';
4
+ cli({
5
+ site: 'wikipedia',
6
+ name: 'random',
7
+ description: 'Get a random Wikipedia article',
8
+ strategy: Strategy.PUBLIC,
9
+ browser: false,
10
+ args: [{ name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' }],
11
+ columns: ['title', 'description', 'extract', 'url'],
12
+ func: async (_page, args) => {
13
+ const lang = args.lang || 'en';
14
+ const data = (await wikiFetch(lang, '/api/rest_v1/page/random/summary'));
15
+ if (!data?.title)
16
+ throw new CliError('NOT_FOUND', 'No random article returned', 'Try again');
17
+ return [formatSummaryRow(data, lang)];
18
+ },
19
+ });
@@ -1,5 +1,5 @@
1
- import { cli, Strategy } from '../../registry.js';
2
1
  import { CliError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
3
  import { wikiFetch } from './utils.js';
4
4
  cli({
5
5
  site: 'wikipedia',
@@ -16,8 +16,8 @@ cli({
16
16
  func: async (_page, args) => {
17
17
  const limit = Math.max(1, Math.min(Number(args.limit), 50));
18
18
  const lang = args.lang || 'en';
19
- const q = encodeURIComponent(args.keyword);
20
- const data = await wikiFetch(lang, `/w/api.php?action=query&list=search&srsearch=${q}&srlimit=${limit}&format=json&utf8=1`);
19
+ const q = encodeURIComponent(args.query);
20
+ const data = (await wikiFetch(lang, `/w/api.php?action=query&list=search&srsearch=${q}&srlimit=${limit}&format=json&utf8=1`));
21
21
  const results = data?.query?.search;
22
22
  if (!results?.length)
23
23
  throw new CliError('NOT_FOUND', 'No articles found', 'Try a different keyword');
@@ -1,6 +1,6 @@
1
- import { cli, Strategy } from '../../registry.js';
2
1
  import { CliError } from '../../errors.js';
3
- import { wikiFetch } from './utils.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import { formatSummaryRow, wikiFetch } from './utils.js';
4
4
  cli({
5
5
  site: 'wikipedia',
6
6
  name: 'summary',
@@ -15,14 +15,9 @@ cli({
15
15
  func: async (_page, args) => {
16
16
  const lang = args.lang || 'en';
17
17
  const title = encodeURIComponent(args.title.replace(/ /g, '_'));
18
- const data = await wikiFetch(lang, `/api/rest_v1/page/summary/${title}`);
18
+ const data = (await wikiFetch(lang, `/api/rest_v1/page/summary/${title}`));
19
19
  if (!data?.title)
20
20
  throw new CliError('NOT_FOUND', `Article "${args.title}" not found`, 'Try searching first: opencli wikipedia search <keyword>');
21
- return [{
22
- title: data.title,
23
- description: data.description ?? '-',
24
- extract: (data.extract ?? '').slice(0, 300),
25
- url: data.content_urls?.desktop?.page ?? `https://${lang}.wikipedia.org/wiki/${title}`,
26
- }];
21
+ return [formatSummaryRow(data, lang)];
27
22
  },
28
23
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,35 @@
1
+ import { CliError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import { DESC_MAX_LEN, wikiFetch } from './utils.js';
4
+ cli({
5
+ site: 'wikipedia',
6
+ name: 'trending',
7
+ description: 'Most-read Wikipedia articles (yesterday)',
8
+ strategy: Strategy.PUBLIC,
9
+ browser: false,
10
+ args: [
11
+ { name: 'limit', type: 'int', default: 10, help: 'Max results' },
12
+ { name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' },
13
+ ],
14
+ columns: ['rank', 'title', 'description', 'views'],
15
+ func: async (_page, args) => {
16
+ const lang = args.lang || 'en';
17
+ const limit = Math.max(1, Math.min(Number(args.limit), 50));
18
+ // Use yesterday's UTC date — Wikipedia API expects UTC and yesterday
19
+ // guarantees data availability (today's aggregation may be incomplete).
20
+ const d = new Date(Date.now() - 86_400_000);
21
+ const yyyy = d.getUTCFullYear();
22
+ const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
23
+ const dd = String(d.getUTCDate()).padStart(2, '0');
24
+ const data = (await wikiFetch(lang, `/api/rest_v1/feed/featured/${yyyy}/${mm}/${dd}`));
25
+ const articles = data?.mostread?.articles;
26
+ if (!articles?.length)
27
+ throw new CliError('NOT_FOUND', 'No trending articles available', 'Try a different language with --lang');
28
+ return articles.slice(0, limit).map((a, i) => ({
29
+ rank: i + 1,
30
+ title: a.title ?? '-',
31
+ description: (a.description ?? '-').slice(0, DESC_MAX_LEN),
32
+ views: a.views ?? 0,
33
+ }));
34
+ },
35
+ });
@@ -5,4 +5,32 @@
5
5
  * REST API: https://en.wikipedia.org/api/rest_v1/
6
6
  * Action API: https://en.wikipedia.org/w/api.php
7
7
  */
8
+ /** Maximum character length for article extract fields. */
9
+ export declare const EXTRACT_MAX_LEN = 300;
10
+ /** Maximum character length for short description fields. */
11
+ export declare const DESC_MAX_LEN = 80;
12
+ /** Response shape shared by /page/summary and /page/random/summary endpoints. */
13
+ export interface WikiSummary {
14
+ title?: string;
15
+ description?: string;
16
+ extract?: string;
17
+ content_urls?: {
18
+ desktop?: {
19
+ page?: string;
20
+ };
21
+ };
22
+ }
23
+ /** Article entry returned by the /feed/featured most-read endpoint. */
24
+ export interface WikiMostReadArticle {
25
+ title?: string;
26
+ description?: string;
27
+ views?: number;
28
+ }
8
29
  export declare function wikiFetch(lang: string, path: string): Promise<unknown>;
30
+ /** Map a WikiSummary API response to the standard output row. */
31
+ export declare function formatSummaryRow(data: WikiSummary, lang: string): {
32
+ title: string;
33
+ description: string;
34
+ extract: string;
35
+ url: string;
36
+ };
@@ -6,6 +6,10 @@
6
6
  * Action API: https://en.wikipedia.org/w/api.php
7
7
  */
8
8
  import { CliError } from '../../errors.js';
9
+ /** Maximum character length for article extract fields. */
10
+ export const EXTRACT_MAX_LEN = 300;
11
+ /** Maximum character length for short description fields. */
12
+ export const DESC_MAX_LEN = 80;
9
13
  export async function wikiFetch(lang, path) {
10
14
  const url = `https://${lang}.wikipedia.org${path}`;
11
15
  const resp = await fetch(url, {
@@ -16,3 +20,12 @@ export async function wikiFetch(lang, path) {
16
20
  }
17
21
  return resp.json();
18
22
  }
23
+ /** Map a WikiSummary API response to the standard output row. */
24
+ export function formatSummaryRow(data, lang) {
25
+ return {
26
+ title: data.title,
27
+ description: data.description ?? '-',
28
+ extract: (data.extract ?? '').slice(0, EXTRACT_MAX_LEN),
29
+ url: data.content_urls?.desktop?.page ?? `https://${lang}.wikipedia.org`,
30
+ };
31
+ }
@@ -326,7 +326,7 @@ cli({
326
326
  strategy: Strategy.COOKIE,
327
327
  browser: true,
328
328
  args: [
329
- { name: 'note-id', type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
329
+ { name: 'note-id', positional: true, type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
330
330
  ],
331
331
  columns: ['section', 'metric', 'value', 'extra'],
332
332
  func: async (page, kwargs) => {
@@ -15,6 +15,8 @@ function createPageMock(evaluateResult) {
15
15
  click: vi.fn().mockResolvedValue(undefined),
16
16
  typeText: vi.fn().mockResolvedValue(undefined),
17
17
  pressKey: vi.fn().mockResolvedValue(undefined),
18
+ scrollTo: vi.fn().mockResolvedValue(undefined),
19
+ getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
18
20
  wait: vi.fn().mockResolvedValue(undefined),
19
21
  tabs: vi.fn().mockResolvedValue([]),
20
22
  closeTab: vi.fn().mockResolvedValue(undefined),
@@ -18,6 +18,8 @@ function createPageMock(evaluateResult, interceptedRequests = []) {
18
18
  click: vi.fn().mockResolvedValue(undefined),
19
19
  typeText: vi.fn().mockResolvedValue(undefined),
20
20
  pressKey: vi.fn().mockResolvedValue(undefined),
21
+ scrollTo: vi.fn().mockResolvedValue(undefined),
22
+ getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
21
23
  wait: vi.fn().mockResolvedValue(undefined),
22
24
  tabs: vi.fn().mockResolvedValue([]),
23
25
  closeTab: vi.fn().mockResolvedValue(undefined),
@@ -16,7 +16,7 @@ cli({
16
16
  domain: 'www.xiaohongshu.com',
17
17
  strategy: Strategy.COOKIE,
18
18
  args: [
19
- { name: 'note-id', required: true, help: 'Note ID (from URL)' },
19
+ { name: 'note-id', positional: true, required: true, help: 'Note ID (from URL)' },
20
20
  { name: 'output', default: './xiaohongshu-downloads', help: 'Output directory' },
21
21
  ],
22
22
  columns: ['index', 'type', 'status', 'size'],
@@ -0,0 +1,69 @@
1
+ site: xueqiu
2
+ name: earnings-date
3
+ description: 获取股票预计财报发布日期(公司大事)
4
+ domain: xueqiu.com
5
+ browser: true
6
+
7
+ args:
8
+ symbol:
9
+ positional: true
10
+ type: str
11
+ required: true
12
+ description: 股票代码,如 SH600519、SZ000858、00700
13
+ next:
14
+ type: bool
15
+ default: false
16
+ description: 仅返回最近一次未发布的财报日期
17
+ limit:
18
+ type: int
19
+ default: 10
20
+ description: 返回数量,默认 10
21
+
22
+ pipeline:
23
+ - navigate: https://xueqiu.com
24
+ - evaluate: |
25
+ (async () => {
26
+ const symbol = (${{ args.symbol | json }} || '').toUpperCase();
27
+ const onlyNext = ${{ args.next }};
28
+ if (!symbol) throw new Error('Missing argument: symbol');
29
+ const resp = await fetch(
30
+ `https://stock.xueqiu.com/v5/stock/screener/event/list.json?symbol=${encodeURIComponent(symbol)}&page=1&size=100`,
31
+ { credentials: 'include' }
32
+ );
33
+ if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?');
34
+ const d = await resp.json();
35
+ if (!d.data || !d.data.items) throw new Error('获取失败: ' + JSON.stringify(d));
36
+
37
+ // subtype 2 = 预计财报发布
38
+ let items = d.data.items.filter(item => item.subtype === 2);
39
+
40
+ const now = Date.now();
41
+ let results = items.map(item => {
42
+ const ts = item.timestamp;
43
+ const dateStr = ts ? new Date(ts).toISOString().split('T')[0] : null;
44
+ const isFuture = ts && ts > now;
45
+ return {
46
+ date: dateStr,
47
+ report: item.message,
48
+ status: isFuture ? '⏳ 未发布' : '✅ 已发布',
49
+ _ts: ts,
50
+ _future: isFuture
51
+ };
52
+ });
53
+
54
+ if (onlyNext) {
55
+ const future = results.filter(r => r._future).sort((a, b) => a._ts - b._ts);
56
+ results = future.length ? [future[0]] : [];
57
+ }
58
+
59
+ return results;
60
+ })()
61
+
62
+ - map:
63
+ date: ${{ item.date }}
64
+ report: ${{ item.report }}
65
+ status: ${{ item.status }}
66
+
67
+ - limit: ${{ args.limit }}
68
+
69
+ columns: [date, report, status]
@@ -6,7 +6,9 @@ browser: true
6
6
 
7
7
  args:
8
8
  query:
9
+ positional: true
9
10
  type: str
11
+ required: true
10
12
  description: 搜索关键词,如 茅台、AAPL、腾讯
11
13
  limit:
12
14
  type: int
@@ -19,7 +21,6 @@ pipeline:
19
21
  (async () => {
20
22
  const query = ${{ args.query | json }};
21
23
  const count = ${{ args.limit }};
22
- if (!query) throw new Error('Missing argument: query');
23
24
  const resp = await fetch(`https://xueqiu.com/stock/search.json?code=${encodeURIComponent(query)}&size=${count}`, {credentials: 'include'});
24
25
  if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?');
25
26
  const d = await resp.json();
@@ -6,7 +6,9 @@ browser: true
6
6
 
7
7
  args:
8
8
  symbol:
9
+ positional: true
9
10
  type: str
11
+ required: true
10
12
  description: 股票代码,如 SH600519、SZ000858、AAPL、00700
11
13
 
12
14
  pipeline:
@@ -10,7 +10,7 @@ cli({
10
10
  domain: 'finance.yahoo.com',
11
11
  strategy: Strategy.COOKIE,
12
12
  args: [
13
- { name: 'symbol', required: true, help: 'Stock ticker (e.g. AAPL, MSFT, TSLA)' },
13
+ { name: 'symbol', required: true, positional: true, help: 'Stock ticker (e.g. AAPL, MSFT, TSLA)' },
14
14
  ],
15
15
  columns: ['symbol', 'name', 'price', 'change', 'changePercent', 'open', 'high', 'low', 'volume', 'marketCap'],
16
16
  func: async (page, kwargs) => {