@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,83 @@
1
+ import type { IPage } from '../../types.js';
2
+
3
+ export function buildMediumTagUrl(topic?: string): string {
4
+ return topic ? `https://medium.com/tag/${encodeURIComponent(topic)}` : 'https://medium.com/tag/technology';
5
+ }
6
+
7
+ export function buildMediumSearchUrl(keyword: string): string {
8
+ return `https://medium.com/search?q=${encodeURIComponent(keyword)}`;
9
+ }
10
+
11
+ export function buildMediumUserUrl(username: string): string {
12
+ return username.startsWith('@') ? `https://medium.com/${username}` : `https://medium.com/@${username}`;
13
+ }
14
+
15
+ export async function loadMediumPosts(page: IPage, url: string, limit: number): Promise<any[]> {
16
+ if (!page) throw new Error('Requires browser session');
17
+ await page.goto(url);
18
+ await page.wait(5);
19
+ const data = await page.evaluate(`
20
+ (async () => {
21
+ await new Promise((resolve) => setTimeout(resolve, 3000));
22
+
23
+ const limit = ${Math.max(1, Math.min(limit, 50))};
24
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
25
+ const posts = [];
26
+ const seen = new Set();
27
+
28
+ for (const article of Array.from(document.querySelectorAll('article'))) {
29
+ try {
30
+ const titleEl = article.querySelector('h2, h3, h1');
31
+ const title = normalize(titleEl?.textContent);
32
+ if (!title) continue;
33
+
34
+ const linkEl = titleEl?.closest('a') || article.querySelector('a[href*="/@"], a[href*="/p/"]');
35
+ let url = linkEl?.getAttribute('href') || '';
36
+ if (!url) continue;
37
+ if (!url.startsWith('http')) url = 'https://medium.com' + url;
38
+ if (seen.has(url)) continue;
39
+
40
+ const author = normalize(
41
+ Array.from(article.querySelectorAll('a[href^="/@"]'))
42
+ .map((node) => normalize(node.textContent))
43
+ .find((text) => text && text !== title),
44
+ );
45
+
46
+ const allText = normalize(article.textContent);
47
+ const dateEl = article.querySelector('time');
48
+ const date = normalize(dateEl?.textContent) ||
49
+ dateEl?.getAttribute('datetime') ||
50
+ allText.match(/\\b(?:[A-Z][a-z]{2}\\s+\\d{1,2}|\\d+[dhmw]\\s+ago)\\b/)?.[0] ||
51
+ '';
52
+
53
+ const readTime = allText.match(/(\\d+)\\s*min\\s*read/i)?.[0] || '';
54
+ const claps = allText.match(/\\b(\\d+(?:\\.\\d+)?[KkMm]?)\\s*claps?\\b/i)?.[1] || '';
55
+
56
+ const description = normalize(
57
+ Array.from(article.querySelectorAll('h3, p'))
58
+ .map((node) => normalize(node.textContent))
59
+ .find((text) => text && text !== title && text !== author && !/member-only story|response icon/i.test(text)),
60
+ );
61
+
62
+ seen.add(url);
63
+ posts.push({
64
+ rank: posts.length + 1,
65
+ title,
66
+ author,
67
+ date,
68
+ readTime,
69
+ claps,
70
+ description: description ? description.slice(0, 150) : '',
71
+ url,
72
+ });
73
+
74
+ if (posts.length >= limit) break;
75
+ } catch {}
76
+ }
77
+
78
+ return posts;
79
+ })()
80
+ `);
81
+
82
+ return Array.isArray(data) ? data : [];
83
+ }
@@ -0,0 +1,16 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { buildMediumUserUrl, loadMediumPosts } from './shared.js';
3
+
4
+ cli({
5
+ site: 'medium',
6
+ name: 'user',
7
+ description: '获取 Medium 用户的文章列表',
8
+ domain: 'medium.com',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'username', required: true, positional: true, help: 'Medium 用户名(如 @username 或 username)' },
12
+ { name: 'limit', type: 'int', default: 20, help: '返回的文章数量' },
13
+ ],
14
+ columns: ['rank', 'title', 'date', 'readTime', 'claps', 'url'],
15
+ func: async (page, args) => loadMediumPosts(page, buildMediumUserUrl(args.username), Number(args.limit) || 20),
16
+ });
@@ -8,7 +8,7 @@ cli({
8
8
  strategy: Strategy.COOKIE,
9
9
  browser: true,
10
10
  args: [
11
- { name: 'post-id', type: 'string', required: true, help: 'Post ID (e.g. 1abc123) or fullname (t3_xxx)' },
11
+ { name: 'post-id', type: 'string', required: true, positional: true, help: 'Post ID (e.g. 1abc123) or fullname (t3_xxx)' },
12
12
  { name: 'text', type: 'string', required: true, positional: true, help: 'Comment text' },
13
13
  ],
14
14
  columns: ['status', 'message'],
@@ -15,7 +15,7 @@ cli({
15
15
  domain: 'reddit.com',
16
16
  strategy: Strategy.COOKIE,
17
17
  args: [
18
- { name: 'post-id', required: true, help: 'Post ID (e.g. 1abc123) or full URL' },
18
+ { name: 'post-id', required: true, positional: true, help: 'Post ID (e.g. 1abc123) or full URL' },
19
19
  { name: 'sort', default: 'best', help: 'Comment sort: best, top, new, controversial, old, qa' },
20
20
  { name: 'limit', type: 'int', default: 25, help: 'Number of top-level comments' },
21
21
  { name: 'depth', type: 'int', default: 2, help: 'Max reply depth (1=no replies, 2=one level of replies, etc.)' },
@@ -8,7 +8,7 @@ cli({
8
8
  strategy: Strategy.COOKIE,
9
9
  browser: true,
10
10
  args: [
11
- { name: 'post-id', type: 'string', required: true, help: 'Post ID (e.g. 1abc123) or fullname (t3_xxx)' },
11
+ { name: 'post-id', type: 'string', required: true, positional: true, help: 'Post ID (e.g. 1abc123) or fullname (t3_xxx)' },
12
12
  { name: 'undo', type: 'boolean', default: false, help: 'Unsave instead of save' },
13
13
  ],
14
14
  columns: ['status', 'message'],
@@ -8,6 +8,7 @@ browser: true
8
8
  args:
9
9
  name:
10
10
  type: string
11
+ positional: true
11
12
  required: true
12
13
  sort:
13
14
  type: string
@@ -8,7 +8,7 @@ cli({
8
8
  strategy: Strategy.COOKIE,
9
9
  browser: true,
10
10
  args: [
11
- { name: 'subreddit', type: 'string', required: true, help: 'Subreddit name (e.g. python)' },
11
+ { name: 'subreddit', type: 'string', required: true, positional: true, help: 'Subreddit name (e.g. python)' },
12
12
  { name: 'undo', type: 'boolean', default: false, help: 'Unsubscribe instead of subscribe' },
13
13
  ],
14
14
  columns: ['status', 'message'],
@@ -8,7 +8,7 @@ cli({
8
8
  strategy: Strategy.COOKIE,
9
9
  browser: true,
10
10
  args: [
11
- { name: 'post-id', type: 'string', required: true, help: 'Post ID (e.g. 1abc123) or fullname (t3_xxx)' },
11
+ { name: 'post-id', type: 'string', required: true, positional: true, help: 'Post ID (e.g. 1abc123) or fullname (t3_xxx)' },
12
12
  { name: 'direction', type: 'string', default: 'up', help: 'Vote direction: up, down, none' },
13
13
  ],
14
14
  columns: ['status', 'message'],
@@ -0,0 +1,15 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadSinaBlogArticle } from './shared.js';
3
+
4
+ cli({
5
+ site: 'sinablog',
6
+ name: 'article',
7
+ description: '获取新浪博客单篇文章详情',
8
+ domain: 'blog.sina.com.cn',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'url', required: true, positional: true, help: '文章URL(如 https://blog.sina.com.cn/s/blog_xxx.html)' },
12
+ ],
13
+ columns: ['title', 'author', 'date', 'category', 'readCount', 'commentCount'],
14
+ func: async (page, args) => loadSinaBlogArticle(page, args.url),
15
+ });
@@ -0,0 +1,15 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadSinaBlogHot } from './shared.js';
3
+
4
+ cli({
5
+ site: 'sinablog',
6
+ name: 'hot',
7
+ description: '获取新浪博客热门文章/推荐',
8
+ domain: 'blog.sina.com.cn',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'limit', type: 'int', default: 20, help: '返回的文章数量' },
12
+ ],
13
+ columns: ['rank', 'title', 'author', 'date', 'readCount', 'url'],
14
+ func: async (page, args) => loadSinaBlogHot(page, Number(args.limit) || 20),
15
+ });
@@ -0,0 +1,56 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+
3
+ function normalize(value: unknown): string {
4
+ return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
5
+ }
6
+
7
+ function stripHtml(value: string): string {
8
+ return value.replace(/<[^>]+>/g, '');
9
+ }
10
+
11
+ async function searchSinaBlog(keyword: string, limit: number): Promise<any[]> {
12
+ const url = new URL('https://search.sina.com.cn/api/search');
13
+ url.searchParams.set('q', keyword);
14
+ url.searchParams.set('tp', 'mix');
15
+ url.searchParams.set('sort', '0');
16
+ url.searchParams.set('page', '1');
17
+ url.searchParams.set('size', String(Math.max(limit, 10)));
18
+ url.searchParams.set('from', 'search_result');
19
+
20
+ const resp = await fetch(url, {
21
+ headers: {
22
+ 'User-Agent': 'Mozilla/5.0',
23
+ Accept: 'application/json',
24
+ },
25
+ });
26
+ if (!resp.ok) throw new Error(`Sina blog search failed: HTTP ${resp.status}`);
27
+
28
+ const data = await resp.json() as { data?: { list?: any[] } };
29
+ const list = Array.isArray(data?.data?.list) ? data.data.list : [];
30
+ return list
31
+ .filter((item) => normalize(item?.url).includes('blog.sina.com.cn/s/blog_'))
32
+ .slice(0, limit)
33
+ .map((item, index) => ({
34
+ rank: index + 1,
35
+ title: normalize(stripHtml(item?.title || '')),
36
+ author: normalize(item?.media_show || item?.author),
37
+ date: normalize(item?.time || item?.dataTime),
38
+ description: normalize(item?.intro || item?.searchSummary).slice(0, 150),
39
+ url: normalize(item?.url),
40
+ }));
41
+ }
42
+
43
+ cli({
44
+ site: 'sinablog',
45
+ name: 'search',
46
+ description: '搜索新浪博客文章(通过新浪搜索)',
47
+ domain: 'blog.sina.com.cn',
48
+ strategy: Strategy.PUBLIC,
49
+ browser: false,
50
+ args: [
51
+ { name: 'keyword', required: true, positional: true, help: '搜索关键词' },
52
+ { name: 'limit', type: 'int', default: 20, help: '返回的文章数量' },
53
+ ],
54
+ columns: ['rank', 'title', 'author', 'date', 'description', 'url'],
55
+ func: async (_page, args) => searchSinaBlog(args.keyword, Math.max(1, Math.min(Number(args.limit) || 20, 50))),
56
+ });
@@ -0,0 +1,198 @@
1
+ import type { IPage } from '../../types.js';
2
+
3
+ function clampLimit(limit: number): number {
4
+ return Math.max(1, Math.min(limit || 20, 50));
5
+ }
6
+
7
+ export function buildSinaBlogSearchUrl(keyword: string): string {
8
+ return `https://search.sina.com.cn/search?q=${encodeURIComponent(keyword)}&tp=mix`;
9
+ }
10
+
11
+ export function buildSinaBlogUserUrl(uid: string): string {
12
+ return `https://blog.sina.com.cn/s/articlelist_${encodeURIComponent(uid)}_0_1.html`;
13
+ }
14
+
15
+ export async function loadSinaBlogArticle(page: IPage, url: string): Promise<any> {
16
+ await page.goto(url);
17
+ await page.wait(3);
18
+ return page.evaluate(`
19
+ (async () => {
20
+ await new Promise((resolve) => setTimeout(resolve, 1500));
21
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
22
+ const title = normalize(document.querySelector('.articalTitle h2, .title h2, h1, h2.titName')?.textContent);
23
+ const titleParts = normalize(document.title).split('_').map((part) => normalize(part)).filter(Boolean);
24
+ const author = titleParts[1] || title.split(/[::]/)[0] || '';
25
+ const timeText = normalize(document.querySelector('.time, .articalInfo .time')?.textContent).replace(/[()]/g, '');
26
+ const date = timeText || normalize(document.body.innerText.match(/\\b\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}:\\d{2})?\\b/)?.[0]);
27
+ const category = normalize(document.querySelector('.articalTag .blog_class a, .blog_class a')?.textContent);
28
+ const tags = Array.from(document.querySelectorAll('.blog_tag h3, .blog_tag a, .tag a, .artical_tag a'))
29
+ .map((node) => normalize(node.textContent))
30
+ .filter(Boolean);
31
+ const content = normalize(document.querySelector('.articalContent, .blog_content, .content, #sina_keyword_ad_area2')?.textContent).slice(0, 500);
32
+ const images = Array.from(document.querySelectorAll('.articalContent img, .blog_content img, .content img'))
33
+ .map((img) => img.getAttribute('src') || img.getAttribute('real_src') || '')
34
+ .filter((src) => src && !src.includes('icon'))
35
+ .slice(0, 5);
36
+ return {
37
+ title,
38
+ author,
39
+ date,
40
+ category,
41
+ tags: tags.join(', '),
42
+ readCount: '',
43
+ commentCount: '',
44
+ content: content + (content.length >= 500 ? '...' : ''),
45
+ images: images.join(', '),
46
+ url: ${JSON.stringify(url)},
47
+ };
48
+ })()
49
+ `);
50
+ }
51
+
52
+ export async function loadSinaBlogHot(page: IPage, limit: number): Promise<any[]> {
53
+ const safeLimit = clampLimit(limit);
54
+ await page.goto('https://blog.sina.com.cn/');
55
+ await page.wait(3);
56
+ const data = await page.evaluate(`
57
+ (async () => {
58
+ await new Promise((resolve) => setTimeout(resolve, 1500));
59
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
60
+ const limit = ${safeLimit};
61
+ const abs = (href) => {
62
+ if (!href) return '';
63
+ if (href.startsWith('//')) return 'https:' + href;
64
+ if (href.startsWith('http')) return href;
65
+ return 'https://blog.sina.com.cn' + (href.startsWith('/') ? '' : '/') + href;
66
+ };
67
+ const parseArticle = (doc, fallback) => {
68
+ const title = normalize(doc.querySelector('.articalTitle h2, .title h2, h1, h2.titName')?.textContent) || fallback.title;
69
+ const titleParts = normalize(doc.title).split('_').map((part) => normalize(part)).filter(Boolean);
70
+ const timeText = normalize(doc.querySelector('.time, .articalInfo .time')?.textContent).replace(/[()]/g, '');
71
+ const articleId = fallback.url.match(/blog_([a-zA-Z0-9]+)\\.html/)?.[1] || '';
72
+ return {
73
+ articleId,
74
+ title,
75
+ author: titleParts[1] || title.split(/[::]/)[0] || '',
76
+ date: timeText || '',
77
+ readCount: '',
78
+ description: normalize(doc.querySelector('.articalContent, .blog_content, .content, #sina_keyword_ad_area2')?.textContent).slice(0, 150),
79
+ };
80
+ };
81
+
82
+ const seeds = [];
83
+ const seen = new Set();
84
+ for (const link of Array.from(document.querySelectorAll('.day-hot-rank .art-list a[href*="/s/blog_"], .hot-rank .art-list a[href*="/s/blog_"]'))) {
85
+ const title = normalize(link.textContent);
86
+ const url = abs(link.getAttribute('href') || '');
87
+ if (!title || !url || seen.has(url)) continue;
88
+ seen.add(url);
89
+ seeds.push({ rank: seeds.length + 1, title, url });
90
+ if (seeds.length >= limit) break;
91
+ }
92
+
93
+ const results = [];
94
+ for (const item of seeds) {
95
+ let merged = {
96
+ rank: item.rank,
97
+ articleId: item.url.match(/blog_([a-zA-Z0-9]+)\\.html/)?.[1] || '',
98
+ title: item.title,
99
+ author: '',
100
+ date: '',
101
+ readCount: '',
102
+ description: '',
103
+ url: item.url,
104
+ };
105
+ try {
106
+ const resp = await fetch(item.url, { credentials: 'include' });
107
+ if (resp.ok) {
108
+ const html = await resp.text();
109
+ const doc = new DOMParser().parseFromString(html, 'text/html');
110
+ merged = Object.assign(merged, parseArticle(doc, item));
111
+ }
112
+ } catch {}
113
+ results.push(merged);
114
+ }
115
+ return results;
116
+ })()
117
+ `);
118
+
119
+ return Array.isArray(data) ? data : [];
120
+ }
121
+
122
+ export async function loadSinaBlogSearch(page: IPage, keyword: string, limit: number): Promise<any[]> {
123
+ const safeLimit = clampLimit(limit);
124
+ await page.goto(buildSinaBlogSearchUrl(keyword));
125
+ await page.wait(5);
126
+ const data = await page.evaluate(`
127
+ (async () => {
128
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
129
+ for (let i = 0; i < 20; i += 1) {
130
+ if (document.querySelector('.result-item')) break;
131
+ await sleep(500);
132
+ }
133
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
134
+ const limit = ${safeLimit};
135
+ const items = Array.from(document.querySelectorAll('.result-item'));
136
+ const results = [];
137
+ for (const item of items) {
138
+ const link = item.querySelector('.result-title a[href*="blog.sina.com.cn/s/blog_"]');
139
+ const title = normalize(link?.textContent);
140
+ const url = link?.getAttribute('href') || '';
141
+ if (!title || !url) continue;
142
+ results.push({
143
+ rank: results.length + 1,
144
+ title,
145
+ author: normalize(item.querySelector('.result-meta .source')?.textContent),
146
+ date: normalize(item.querySelector('.result-meta .time')?.textContent),
147
+ description: normalize(item.querySelector('.result-intro')?.textContent).slice(0, 150),
148
+ url,
149
+ });
150
+ if (results.length >= limit) break;
151
+ }
152
+ return results;
153
+ })()
154
+ `);
155
+
156
+ return Array.isArray(data) ? data : [];
157
+ }
158
+
159
+ export async function loadSinaBlogUser(page: IPage, uid: string, limit: number): Promise<any[]> {
160
+ const safeLimit = clampLimit(limit);
161
+ await page.goto(buildSinaBlogUserUrl(uid));
162
+ await page.wait(3);
163
+ const data = await page.evaluate(`
164
+ (async () => {
165
+ await new Promise((resolve) => setTimeout(resolve, 1000));
166
+ const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
167
+ const limit = ${safeLimit};
168
+ const author = normalize(document.title).split('_').map((part) => normalize(part)).filter(Boolean)[1] || '';
169
+ const abs = (href) => {
170
+ if (!href) return '';
171
+ if (href.startsWith('//')) return 'https:' + href;
172
+ if (href.startsWith('http')) return href;
173
+ return 'https://blog.sina.com.cn' + (href.startsWith('/') ? '' : '/') + href;
174
+ };
175
+ const results = [];
176
+ for (const item of Array.from(document.querySelectorAll('.articleList .articleCell'))) {
177
+ const link = item.querySelector('.atc_title a[href*="/s/blog_"]');
178
+ const title = normalize(link?.textContent);
179
+ const url = abs(link?.getAttribute('href') || '');
180
+ if (!title || !url) continue;
181
+ results.push({
182
+ rank: results.length + 1,
183
+ articleId: url.match(/blog_([a-zA-Z0-9]+)\\.html/)?.[1] || '',
184
+ title,
185
+ author,
186
+ date: normalize(item.querySelector('.atc_tm')?.textContent),
187
+ readCount: '',
188
+ description: '',
189
+ url,
190
+ });
191
+ if (results.length >= limit) break;
192
+ }
193
+ return results;
194
+ })()
195
+ `);
196
+
197
+ return Array.isArray(data) ? data : [];
198
+ }
@@ -0,0 +1,16 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadSinaBlogUser } from './shared.js';
3
+
4
+ cli({
5
+ site: 'sinablog',
6
+ name: 'user',
7
+ description: '获取新浪博客用户的文章列表',
8
+ domain: 'blog.sina.com.cn',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'uid', required: true, positional: true, help: '新浪博客用户ID(如 1234567890)' },
12
+ { name: 'limit', type: 'int', default: 20, help: '返回的文章数量' },
13
+ ],
14
+ columns: ['rank', 'title', 'author', 'date', 'readCount', 'url'],
15
+ func: async (page, args) => loadSinaBlogUser(page, args.uid, Number(args.limit) || 20),
16
+ });
@@ -0,0 +1,16 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { buildSubstackBrowseUrl, loadSubstackFeed } from './shared.js';
3
+
4
+ cli({
5
+ site: 'substack',
6
+ name: 'feed',
7
+ description: 'Substack 热门文章 Feed',
8
+ domain: 'substack.com',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'category', default: 'all', help: '文章分类: all, tech, business, culture, politics, science, health' },
12
+ { name: 'limit', type: 'int', default: 20, help: '返回的文章数量' },
13
+ ],
14
+ columns: ['rank', 'title', 'author', 'date', 'readTime', 'url'],
15
+ func: async (page, args) => loadSubstackFeed(page, buildSubstackBrowseUrl(args.category), Number(args.limit) || 20),
16
+ });
@@ -0,0 +1,16 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { loadSubstackArchive } from './shared.js';
3
+
4
+ cli({
5
+ site: 'substack',
6
+ name: 'publication',
7
+ description: '获取特定 Substack Newsletter 的最新文章',
8
+ domain: 'substack.com',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'url', required: true, positional: true, help: 'Newsletter URL(如 https://example.substack.com)' },
12
+ { name: 'limit', type: 'int', default: 20, help: '返回的文章数量' },
13
+ ],
14
+ columns: ['rank', 'title', 'date', 'description', 'url'],
15
+ func: async (page, args) => loadSubstackArchive(page, args.url.replace(/\/$/, ''), Number(args.limit) || 20),
16
+ });
@@ -0,0 +1,91 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+
3
+ type SubstackPostResult = {
4
+ title: string;
5
+ author: string;
6
+ date: string;
7
+ description: string;
8
+ url: string;
9
+ };
10
+
11
+ function headers(): HeadersInit {
12
+ return {
13
+ 'User-Agent': 'Mozilla/5.0',
14
+ Accept: 'application/json',
15
+ };
16
+ }
17
+
18
+ function trim(value: unknown): string {
19
+ return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
20
+ }
21
+
22
+ function publicationBaseUrl(publication: any): string {
23
+ if (publication?.custom_domain) return `https://${publication.custom_domain}`;
24
+ if (publication?.subdomain) return `https://${publication.subdomain}.substack.com`;
25
+ return '';
26
+ }
27
+
28
+ async function searchPosts(keyword: string, limit: number): Promise<SubstackPostResult[]> {
29
+ const url = new URL('https://substack.com/api/v1/post/search');
30
+ url.searchParams.set('query', keyword);
31
+ url.searchParams.set('page', '0');
32
+ url.searchParams.set('includePlatformResults', 'true');
33
+
34
+ const resp = await fetch(url, { headers: headers() });
35
+ if (!resp.ok) throw new Error(`Substack post search failed: HTTP ${resp.status}`);
36
+
37
+ const data = await resp.json() as { results?: any[] };
38
+ const results = Array.isArray(data?.results) ? data.results : [];
39
+ return results.slice(0, limit).map((item, index) => ({
40
+ rank: index + 1,
41
+ title: trim(item?.title),
42
+ author: trim(item?.publishedBylines?.[0]?.name),
43
+ date: trim(item?.post_date).split('T')[0] || trim(item?.post_date),
44
+ description: trim(item?.description || item?.subtitle || item?.truncated_body_text).slice(0, 150),
45
+ url: trim(item?.canonical_url),
46
+ }));
47
+ }
48
+
49
+ async function searchPublications(keyword: string, limit: number): Promise<SubstackPostResult[]> {
50
+ const url = new URL('https://substack.com/api/v1/profile/search');
51
+ url.searchParams.set('query', keyword);
52
+ url.searchParams.set('page', '0');
53
+
54
+ const resp = await fetch(url, { headers: headers() });
55
+ if (!resp.ok) throw new Error(`Substack publication search failed: HTTP ${resp.status}`);
56
+
57
+ const data = await resp.json() as { results?: any[] };
58
+ const results = Array.isArray(data?.results) ? data.results : [];
59
+ return results.slice(0, limit).map((item, index) => {
60
+ const publication = item?.primaryPublication || item?.publicationUsers?.[0]?.publication || {};
61
+ return {
62
+ rank: index + 1,
63
+ title: trim(publication?.name || item?.name),
64
+ author: trim(item?.name),
65
+ date: '',
66
+ description: trim(publication?.hero_text || item?.bio).slice(0, 150),
67
+ url: publicationBaseUrl(publication),
68
+ };
69
+ });
70
+ }
71
+
72
+ cli({
73
+ site: 'substack',
74
+ name: 'search',
75
+ description: '搜索 Substack 文章和 Newsletter',
76
+ domain: 'substack.com',
77
+ strategy: Strategy.PUBLIC,
78
+ browser: false,
79
+ args: [
80
+ { name: 'keyword', required: true, positional: true, help: '搜索关键词' },
81
+ { name: 'type', default: 'posts', choices: ['posts', 'publications'], help: '搜索类型(posts=文章, publications=Newsletter)' },
82
+ { name: 'limit', type: 'int', default: 20, help: '返回结果数量' },
83
+ ],
84
+ columns: ['rank', 'title', 'author', 'date', 'description', 'url'],
85
+ func: async (_page, args) => {
86
+ const limit = Math.max(1, Math.min(Number(args.limit) || 20, 50));
87
+ return args.type === 'publications'
88
+ ? searchPublications(args.keyword, limit)
89
+ : searchPosts(args.keyword, limit);
90
+ },
91
+ });