@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,38 @@
1
+ site: tiktok
2
+ name: unlike
3
+ description: Unlike a TikTok video
4
+ domain: www.tiktok.com
5
+
6
+ args:
7
+ url:
8
+ type: str
9
+ required: true
10
+ positional: true
11
+ description: TikTok video URL
12
+
13
+ pipeline:
14
+ - navigate:
15
+ url: ${{ args.url }}
16
+ settleMs: 6000
17
+
18
+ - evaluate: |
19
+ (async () => {
20
+ const url = ${{ args.url | json }};
21
+ const btn = document.querySelector('[data-e2e="like-icon"]');
22
+ if (!btn) throw new Error('Like button not found - make sure you are logged in');
23
+ const container = btn.closest('button') || btn.closest('[role="button"]') || btn;
24
+ const aria = (container.getAttribute('aria-label') || '').toLowerCase();
25
+ const color = window.getComputedStyle(btn).color;
26
+ const isLiked = aria.includes('unlike') || aria.includes('取消点赞') ||
27
+ (color && (color.includes('255, 65') || color.includes('fe2c55')));
28
+ if (!isLiked) {
29
+ const count = document.querySelector('[data-e2e="like-count"]');
30
+ return [{ status: 'Not liked', likes: count ? count.textContent.trim() : '-', url: url }];
31
+ }
32
+ container.click();
33
+ await new Promise(r => setTimeout(r, 2000));
34
+ const count = document.querySelector('[data-e2e="like-count"]');
35
+ return [{ status: 'Unliked', likes: count ? count.textContent.trim() : '-', url: url }];
36
+ })()
37
+
38
+ columns: [status, likes, url]
@@ -0,0 +1,36 @@
1
+ site: tiktok
2
+ name: unsave
3
+ description: Remove a TikTok video from Favorites
4
+ domain: www.tiktok.com
5
+
6
+ args:
7
+ url:
8
+ type: str
9
+ required: true
10
+ positional: true
11
+ description: TikTok video URL
12
+
13
+ pipeline:
14
+ - navigate:
15
+ url: ${{ args.url }}
16
+ settleMs: 6000
17
+
18
+ - evaluate: |
19
+ (async () => {
20
+ const url = ${{ args.url | json }};
21
+ const btn = document.querySelector('[data-e2e="bookmark-icon"]') ||
22
+ document.querySelector('[data-e2e="collect-icon"]');
23
+ if (!btn) throw new Error('Favorites button not found - make sure you are logged in');
24
+ const container = btn.closest('button') || btn.closest('[role="button"]') || btn;
25
+ const aria = (container.getAttribute('aria-label') || '').toLowerCase();
26
+ if (aria.includes('add to favorites') || aria.includes('收藏')) {
27
+ if (!aria.includes('remove') && !aria.includes('取消')) {
28
+ return [{ status: 'Not in Favorites', url: url }];
29
+ }
30
+ }
31
+ container.click();
32
+ await new Promise(r => setTimeout(r, 2000));
33
+ return [{ status: 'Removed from Favorites', url: url }];
34
+ })()
35
+
36
+ columns: [status, url]
@@ -0,0 +1,44 @@
1
+ site: tiktok
2
+ name: user
3
+ description: Get recent videos from a TikTok user
4
+ domain: www.tiktok.com
5
+
6
+ args:
7
+ username:
8
+ type: str
9
+ required: true
10
+ positional: true
11
+ description: TikTok username (without @)
12
+ limit:
13
+ type: int
14
+ default: 10
15
+ description: Number of videos
16
+
17
+ pipeline:
18
+ - navigate:
19
+ url: https://www.tiktok.com/@${{ args.username }}
20
+ settleMs: 6000
21
+
22
+ - evaluate: |
23
+ (() => {
24
+ const limit = ${{ args.limit }};
25
+ const username = ${{ args.username | json }};
26
+ const links = Array.from(document.querySelectorAll('a[href*="/video/"]'));
27
+ const seen = {};
28
+ const results = [];
29
+ for (const a of links) {
30
+ const href = a.href;
31
+ if (seen[href]) continue;
32
+ seen[href] = true;
33
+ results.push({
34
+ index: results.length + 1,
35
+ views: a.textContent.trim() || '-',
36
+ url: href,
37
+ });
38
+ if (results.length >= limit) break;
39
+ }
40
+ if (results.length === 0) throw new Error('No videos found for @' + username);
41
+ return results;
42
+ })()
43
+
44
+ columns: [index, views, url]
@@ -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
 
@@ -27,7 +27,7 @@ cli({
27
27
  domain: 'x.com',
28
28
  strategy: Strategy.COOKIE,
29
29
  args: [
30
- { name: 'username', help: 'Twitter username (downloads from media tab)' },
30
+ { name: 'username', positional: true, help: 'Twitter username (downloads from media tab)' },
31
31
  { name: 'tweet-url', help: 'Single tweet URL to download' },
32
32
  { name: 'limit', type: 'int', default: 10, help: 'Number of tweets to scan' },
33
33
  { name: 'output', default: './twitter-downloads', help: 'Output directory' },
@@ -44,7 +44,7 @@ cli({
44
44
  index: 0,
45
45
  type: '-',
46
46
  status: 'failed',
47
- size: 'Must provide --username or --tweet-url',
47
+ size: 'Must provide a username or --tweet-url',
48
48
  }];
49
49
  }
50
50
 
@@ -8,7 +8,7 @@ cli({
8
8
  strategy: Strategy.INTERCEPT,
9
9
  browser: true,
10
10
  args: [
11
- { name: 'user', type: 'string', required: false },
11
+ { name: 'user', positional: true, type: 'string', required: false },
12
12
  { name: 'limit', type: 'int', default: 50 },
13
13
  ],
14
14
  columns: ['screen_name', 'name', 'bio', 'followers'],
@@ -8,7 +8,7 @@ cli({
8
8
  strategy: Strategy.INTERCEPT,
9
9
  browser: true,
10
10
  args: [
11
- { name: 'user', type: 'string', required: false },
11
+ { name: 'user', positional: true, type: 'string', required: false },
12
12
  { name: 'limit', type: 'int', default: 50 },
13
13
  ],
14
14
  columns: ['screen_name', 'name', 'bio', 'followers'],
@@ -122,7 +122,7 @@ cli({
122
122
  strategy: Strategy.COOKIE,
123
123
  browser: true,
124
124
  args: [
125
- { name: 'tweet-id', type: 'string', required: true },
125
+ { name: 'tweet-id', positional: true, type: 'string', required: true },
126
126
  { name: 'limit', type: 'int', default: 50 },
127
127
  ],
128
128
  columns: ['id', 'author', 'text', 'likes', 'retweets', 'url'],
@@ -0,0 +1,109 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './timeline.js';
3
+
4
+ describe('twitter timeline helpers', () => {
5
+ it('builds for-you variables with withCommunity', () => {
6
+ expect(__test__.buildTimelineVariables('for-you', 20)).toEqual({
7
+ count: 20,
8
+ includePromotedContent: false,
9
+ latestControlAvailable: true,
10
+ requestContext: 'launch',
11
+ withCommunity: true,
12
+ });
13
+ });
14
+
15
+ it('builds following variables with seenTweetIds instead of withCommunity', () => {
16
+ expect(__test__.buildTimelineVariables('following', 20, 'cursor-1')).toEqual({
17
+ count: 20,
18
+ includePromotedContent: false,
19
+ latestControlAvailable: true,
20
+ requestContext: 'launch',
21
+ seenTweetIds: [],
22
+ cursor: 'cursor-1',
23
+ });
24
+ });
25
+
26
+ it('encodes variables into timeline url', () => {
27
+ const url = __test__.buildHomeTimelineUrl('query123', 'HomeLatestTimeline', {
28
+ count: 20,
29
+ seenTweetIds: [],
30
+ });
31
+
32
+ expect(url).toContain('/i/api/graphql/query123/HomeLatestTimeline');
33
+ expect(url).toContain('variables=');
34
+ expect(url).toContain('features=');
35
+ expect(decodeURIComponent(url)).toContain('"seenTweetIds":[]');
36
+ });
37
+
38
+ it('parses tweets and bottom cursor from home timeline payload', () => {
39
+ const payload = {
40
+ data: {
41
+ home: {
42
+ home_timeline_urt: {
43
+ instructions: [
44
+ {
45
+ entries: [
46
+ {
47
+ entryId: 'tweet-1',
48
+ content: {
49
+ itemContent: {
50
+ tweet_results: {
51
+ result: {
52
+ rest_id: '1',
53
+ legacy: {
54
+ full_text: 'hello',
55
+ favorite_count: 3,
56
+ retweet_count: 2,
57
+ reply_count: 1,
58
+ created_at: 'now',
59
+ },
60
+ core: {
61
+ user_results: {
62
+ result: {
63
+ legacy: {
64
+ screen_name: 'alice',
65
+ },
66
+ },
67
+ },
68
+ },
69
+ views: {
70
+ count: '9',
71
+ },
72
+ },
73
+ },
74
+ },
75
+ },
76
+ },
77
+ {
78
+ entryId: 'cursor-bottom-1',
79
+ content: {
80
+ entryType: 'TimelineTimelineCursor',
81
+ cursorType: 'Bottom',
82
+ value: 'cursor-next',
83
+ },
84
+ },
85
+ ],
86
+ },
87
+ ],
88
+ },
89
+ },
90
+ },
91
+ };
92
+
93
+ const result = __test__.parseHomeTimeline(payload, new Set());
94
+
95
+ expect(result.nextCursor).toBe('cursor-next');
96
+ expect(result.tweets).toHaveLength(1);
97
+ expect(result.tweets[0]).toMatchObject({
98
+ id: '1',
99
+ author: 'alice',
100
+ text: 'hello',
101
+ likes: 3,
102
+ retweets: 2,
103
+ replies: 1,
104
+ views: 9,
105
+ created_at: 'now',
106
+ url: 'https://x.com/alice/status/1',
107
+ });
108
+ });
109
+ });
@@ -2,8 +2,24 @@ import { cli, Strategy } from '../../registry.js';
2
2
 
3
3
  // ── Twitter GraphQL constants ──────────────────────────────────────────
4
4
 
5
- const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
5
+ const BEARER_TOKEN =
6
+ 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
6
7
  const HOME_TIMELINE_QUERY_ID = 'c-CzHF1LboFilMpsx4ZCrQ';
8
+ const HOME_LATEST_TIMELINE_QUERY_ID = 'BKB7oi212Fi7kQtCBGE4zA';
9
+
10
+ type TimelineType = 'for-you' | 'following';
11
+
12
+ interface TimelineEndpointConfig {
13
+ endpoint: string;
14
+ method: 'GET' | 'POST';
15
+ fallbackQueryId: string;
16
+ }
17
+
18
+ // Endpoint config: for-you uses GET HomeTimeline, following uses POST HomeLatestTimeline
19
+ const TIMELINE_ENDPOINTS: Record<TimelineType, TimelineEndpointConfig> = {
20
+ 'for-you': { endpoint: 'HomeTimeline', method: 'GET', fallbackQueryId: HOME_TIMELINE_QUERY_ID },
21
+ following: { endpoint: 'HomeLatestTimeline', method: 'POST', fallbackQueryId: HOME_LATEST_TIMELINE_QUERY_ID },
22
+ };
7
23
 
8
24
  const FEATURES = {
9
25
  rweb_video_screen_enabled: false,
@@ -53,19 +69,26 @@ interface TimelineTweet {
53
69
  url: string;
54
70
  }
55
71
 
56
- function buildHomeTimelineUrl(count: number, cursor?: string | null): string {
57
- const vars: Record<string, any> = {
72
+ function buildTimelineVariables(type: TimelineType, count: number, cursor?: string | null): Record<string, unknown> {
73
+ const vars: Record<string, unknown> = {
58
74
  count,
59
75
  includePromotedContent: false,
60
76
  latestControlAvailable: true,
61
77
  requestContext: 'launch',
62
- withCommunity: true,
63
78
  };
79
+ if (type === 'for-you') vars.withCommunity = true;
80
+ if (type === 'following') vars.seenTweetIds = [];
64
81
  if (cursor) vars.cursor = cursor;
82
+ return vars;
83
+ }
84
+
85
+ function buildHomeTimelineUrl(queryId: string, endpoint: string, vars: Record<string, unknown>): string {
65
86
 
66
- return `/i/api/graphql/${HOME_TIMELINE_QUERY_ID}/HomeTimeline`
67
- + `?variables=${encodeURIComponent(JSON.stringify(vars))}`
68
- + `&features=${encodeURIComponent(JSON.stringify(FEATURES))}`;
87
+ return (
88
+ `/i/api/graphql/${queryId}/${endpoint}` +
89
+ `?variables=${encodeURIComponent(JSON.stringify(vars))}` +
90
+ `&features=${encodeURIComponent(JSON.stringify(FEATURES))}`
91
+ );
69
92
  }
70
93
 
71
94
  function extractTweet(result: any, seen: Set<string>): TimelineTweet | null {
@@ -97,8 +120,8 @@ function parseHomeTimeline(data: any, seen: Set<string>): { tweets: TimelineTwee
97
120
  const tweets: TimelineTweet[] = [];
98
121
  let nextCursor: string | null = null;
99
122
 
100
- const instructions =
101
- data?.data?.home?.home_timeline_urt?.instructions || [];
123
+ // Both HomeTimeline and HomeLatestTimeline share the same response envelope
124
+ const instructions = data?.data?.home?.home_timeline_urt?.instructions || [];
102
125
 
103
126
  for (const inst of instructions) {
104
127
  for (const entry of inst.entries || []) {
@@ -144,16 +167,24 @@ function parseHomeTimeline(data: any, seen: Set<string>): { tweets: TimelineTwee
144
167
  cli({
145
168
  site: 'twitter',
146
169
  name: 'timeline',
147
- description: 'Fetch Twitter Home Timeline',
170
+ description: 'Fetch Twitter timeline (for-you or following)',
148
171
  domain: 'x.com',
149
172
  strategy: Strategy.COOKIE,
150
173
  browser: true,
151
174
  args: [
175
+ {
176
+ name: 'type',
177
+ default: 'for-you',
178
+ choices: ['for-you', 'following'],
179
+ help: 'Timeline type: for-you (algorithmic) or following (chronological)',
180
+ },
152
181
  { name: 'limit', type: 'int', default: 20 },
153
182
  ],
154
183
  columns: ['id', 'author', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url'],
155
184
  func: async (page, kwargs) => {
156
185
  const limit = kwargs.limit || 20;
186
+ const timelineType: TimelineType = kwargs.type === 'following' ? 'following' : 'for-you';
187
+ const { endpoint, method, fallbackQueryId } = TIMELINE_ENDPOINTS[timelineType];
157
188
 
158
189
  // Navigate to x.com for cookie context
159
190
  await page.goto('https://x.com');
@@ -165,22 +196,24 @@ cli({
165
196
  }`);
166
197
  if (!ct0) throw new Error('Not logged into x.com (no ct0 cookie)');
167
198
 
168
- // Dynamically resolve queryId
169
- const queryId = await page.evaluate(`async () => {
199
+ // Dynamically resolve queryId for the selected endpoint
200
+ const resolved = await page.evaluate(`async () => {
170
201
  try {
171
202
  const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
172
203
  if (ghResp.ok) {
173
204
  const data = await ghResp.json();
174
- const entry = data['HomeTimeline'];
205
+ const entry = data['${endpoint}'];
175
206
  if (entry && entry.queryId) return entry.queryId;
176
207
  }
177
208
  } catch {}
178
209
  return null;
179
- }`) || HOME_TIMELINE_QUERY_ID;
210
+ }`);
211
+ // Validate queryId format to prevent injection from untrusted upstream
212
+ const queryId = typeof resolved === 'string' && /^[A-Za-z0-9_-]+$/.test(resolved) ? resolved : fallbackQueryId;
180
213
 
181
214
  // Build auth headers
182
215
  const headers = JSON.stringify({
183
- 'Authorization': `Bearer ${decodeURIComponent(BEARER_TOKEN)}`,
216
+ Authorization: `Bearer ${decodeURIComponent(BEARER_TOKEN)}`,
184
217
  'X-Csrf-Token': ct0,
185
218
  'X-Twitter-Auth-Type': 'OAuth2Session',
186
219
  'X-Twitter-Active-User': 'yes',
@@ -193,16 +226,17 @@ cli({
193
226
 
194
227
  for (let i = 0; i < 5 && allTweets.length < limit; i++) {
195
228
  const fetchCount = Math.min(40, limit - allTweets.length + 5); // over-fetch slightly for promoted filtering
196
- const apiUrl = buildHomeTimelineUrl(fetchCount, cursor)
197
- .replace(HOME_TIMELINE_QUERY_ID, queryId);
229
+ const variables = buildTimelineVariables(timelineType, fetchCount, cursor);
230
+ const apiUrl = buildHomeTimelineUrl(queryId, endpoint, variables);
198
231
 
199
232
  const data = await page.evaluate(`async () => {
200
- const r = await fetch("${apiUrl}", { headers: ${headers}, credentials: 'include' });
233
+ const r = await fetch("${apiUrl}", { method: "${method}", headers: ${headers}, credentials: 'include' });
201
234
  return r.ok ? await r.json() : { error: r.status };
202
235
  }`);
203
236
 
204
237
  if (data?.error) {
205
- if (allTweets.length === 0) throw new Error(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
238
+ if (allTweets.length === 0)
239
+ throw new Error(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
206
240
  break;
207
241
  }
208
242
 
@@ -216,3 +250,9 @@ cli({
216
250
  return allTweets.slice(0, limit);
217
251
  },
218
252
  });
253
+
254
+ export const __test__ = {
255
+ buildTimelineVariables,
256
+ buildHomeTimelineUrl,
257
+ parseHomeTimeline,
258
+ };
@@ -0,0 +1,19 @@
1
+ import { CliError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import { type WikiSummary, formatSummaryRow, wikiFetch } from './utils.js';
4
+
5
+ cli({
6
+ site: 'wikipedia',
7
+ name: 'random',
8
+ description: 'Get a random Wikipedia article',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [{ name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' }],
12
+ columns: ['title', 'description', 'extract', 'url'],
13
+ func: async (_page, args) => {
14
+ const lang = args.lang || 'en';
15
+ const data = (await wikiFetch(lang, '/api/rest_v1/page/random/summary')) as WikiSummary;
16
+ if (!data?.title) throw new CliError('NOT_FOUND', 'No random article returned', 'Try again');
17
+ return [formatSummaryRow(data, lang)];
18
+ },
19
+ });
@@ -1,8 +1,11 @@
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
 
5
- interface WikiSearchResult { title: string; snippet: string; }
5
+ interface WikiSearchResult {
6
+ title: string;
7
+ snippet: string;
8
+ }
6
9
 
7
10
  cli({
8
11
  site: 'wikipedia',
@@ -19,8 +22,11 @@ cli({
19
22
  func: async (_page, args) => {
20
23
  const limit = Math.max(1, Math.min(Number(args.limit), 50));
21
24
  const lang = args.lang || 'en';
22
- const q = encodeURIComponent(args.keyword);
23
- const data = await wikiFetch(lang, `/w/api.php?action=query&list=search&srsearch=${q}&srlimit=${limit}&format=json&utf8=1`) as { query?: { search?: WikiSearchResult[] } };
25
+ const q = encodeURIComponent(args.query);
26
+ const data = (await wikiFetch(
27
+ lang,
28
+ `/w/api.php?action=query&list=search&srsearch=${q}&srlimit=${limit}&format=json&utf8=1`,
29
+ )) as { query?: { search?: WikiSearchResult[] } };
24
30
  const results = data?.query?.search;
25
31
  if (!results?.length) throw new CliError('NOT_FOUND', 'No articles found', 'Try a different keyword');
26
32
  return results.map((r) => ({
@@ -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 { type WikiSummary, formatSummaryRow, wikiFetch } from './utils.js';
4
4
 
5
5
  cli({
6
6
  site: 'wikipedia',
@@ -16,13 +16,8 @@ cli({
16
16
  func: async (_page, args) => {
17
17
  const lang = args.lang || 'en';
18
18
  const title = encodeURIComponent(args.title.replace(/ /g, '_'));
19
- const data = await wikiFetch(lang, `/api/rest_v1/page/summary/${title}`) as { title?: string; description?: string; extract?: string; content_urls?: { desktop?: { page?: string } } };
19
+ const data = (await wikiFetch(lang, `/api/rest_v1/page/summary/${title}`)) as WikiSummary;
20
20
  if (!data?.title) 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,41 @@
1
+ import { CliError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import { DESC_MAX_LEN, type WikiMostReadArticle, wikiFetch } from './utils.js';
4
+
5
+ cli({
6
+ site: 'wikipedia',
7
+ name: 'trending',
8
+ description: 'Most-read Wikipedia articles (yesterday)',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 10, help: 'Max results' },
13
+ { name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' },
14
+ ],
15
+ columns: ['rank', 'title', 'description', 'views'],
16
+ func: async (_page, args) => {
17
+ const lang = args.lang || 'en';
18
+ const limit = Math.max(1, Math.min(Number(args.limit), 50));
19
+
20
+ // Use yesterday's UTC date — Wikipedia API expects UTC and yesterday
21
+ // guarantees data availability (today's aggregation may be incomplete).
22
+ const d = new Date(Date.now() - 86_400_000);
23
+ const yyyy = d.getUTCFullYear();
24
+ const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
25
+ const dd = String(d.getUTCDate()).padStart(2, '0');
26
+
27
+ const data = (await wikiFetch(lang, `/api/rest_v1/feed/featured/${yyyy}/${mm}/${dd}`)) as {
28
+ mostread?: { articles?: WikiMostReadArticle[] };
29
+ };
30
+ const articles = data?.mostread?.articles;
31
+ if (!articles?.length)
32
+ throw new CliError('NOT_FOUND', 'No trending articles available', 'Try a different language with --lang');
33
+
34
+ return articles.slice(0, limit).map((a, i) => ({
35
+ rank: i + 1,
36
+ title: a.title ?? '-',
37
+ description: (a.description ?? '-').slice(0, DESC_MAX_LEN),
38
+ views: a.views ?? 0,
39
+ }));
40
+ },
41
+ });
@@ -8,6 +8,27 @@
8
8
 
9
9
  import { CliError } from '../../errors.js';
10
10
 
11
+ /** Maximum character length for article extract fields. */
12
+ export const EXTRACT_MAX_LEN = 300;
13
+
14
+ /** Maximum character length for short description fields. */
15
+ export const DESC_MAX_LEN = 80;
16
+
17
+ /** Response shape shared by /page/summary and /page/random/summary endpoints. */
18
+ export interface WikiSummary {
19
+ title?: string;
20
+ description?: string;
21
+ extract?: string;
22
+ content_urls?: { desktop?: { page?: string } };
23
+ }
24
+
25
+ /** Article entry returned by the /feed/featured most-read endpoint. */
26
+ export interface WikiMostReadArticle {
27
+ title?: string;
28
+ description?: string;
29
+ views?: number;
30
+ }
31
+
11
32
  export async function wikiFetch(lang: string, path: string): Promise<unknown> {
12
33
  const url = `https://${lang}.wikipedia.org${path}`;
13
34
  const resp = await fetch(url, {
@@ -18,3 +39,13 @@ export async function wikiFetch(lang: string, path: string): Promise<unknown> {
18
39
  }
19
40
  return resp.json();
20
41
  }
42
+
43
+ /** Map a WikiSummary API response to the standard output row. */
44
+ export function formatSummaryRow(data: WikiSummary, lang: string) {
45
+ return {
46
+ title: data.title!,
47
+ description: data.description ?? '-',
48
+ extract: (data.extract ?? '').slice(0, EXTRACT_MAX_LEN),
49
+ url: data.content_urls?.desktop?.page ?? `https://${lang}.wikipedia.org`,
50
+ };
51
+ }
@@ -18,6 +18,8 @@ function createPageMock(evaluateResult: any): IPage {
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),
@@ -430,7 +430,7 @@ cli({
430
430
  strategy: Strategy.COOKIE,
431
431
  browser: true,
432
432
  args: [
433
- { name: 'note-id', type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
433
+ { name: 'note-id', positional: true, type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
434
434
  ],
435
435
  columns: ['section', 'metric', 'value', 'extra'],
436
436
  func: async (page, kwargs) => {
@@ -22,6 +22,8 @@ function createPageMock(evaluateResult: any, interceptedRequests: any[] = []): I
22
22
  click: vi.fn().mockResolvedValue(undefined),
23
23
  typeText: vi.fn().mockResolvedValue(undefined),
24
24
  pressKey: vi.fn().mockResolvedValue(undefined),
25
+ scrollTo: vi.fn().mockResolvedValue(undefined),
26
+ getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
25
27
  wait: vi.fn().mockResolvedValue(undefined),
26
28
  tabs: vi.fn().mockResolvedValue([]),
27
29
  closeTab: vi.fn().mockResolvedValue(undefined),
@@ -22,7 +22,7 @@ cli({
22
22
  domain: 'www.xiaohongshu.com',
23
23
  strategy: Strategy.COOKIE,
24
24
  args: [
25
- { name: 'note-id', required: true, help: 'Note ID (from URL)' },
25
+ { name: 'note-id', positional: true, required: true, help: 'Note ID (from URL)' },
26
26
  { name: 'output', default: './xiaohongshu-downloads', help: 'Output directory' },
27
27
  ],
28
28
  columns: ['index', 'type', 'status', 'size'],