@jackwener/opencli 1.0.6 → 1.1.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 (400) hide show
  1. package/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
  2. package/.github/pull_request_template.md +7 -0
  3. package/.github/workflows/doc-check.yml +36 -0
  4. package/.github/workflows/docs.yml +7 -42
  5. package/CHANGELOG.md +23 -0
  6. package/CLI-EXPLORER.md +9 -8
  7. package/README.md +51 -10
  8. package/README.zh-CN.md +29 -11
  9. package/SKILL.md +102 -33
  10. package/dist/browser/cdp.js +6 -1
  11. package/dist/browser/page.d.ts +4 -1
  12. package/dist/browser/page.js +7 -1
  13. package/dist/build-manifest.js +23 -16
  14. package/dist/cli-manifest.json +951 -296
  15. package/dist/cli.d.ts +6 -0
  16. package/dist/cli.js +225 -148
  17. package/dist/clis/antigravity/serve.js +296 -47
  18. package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
  19. package/dist/clis/apple-podcasts/commands.test.js +76 -0
  20. package/dist/clis/apple-podcasts/search.js +2 -2
  21. package/dist/clis/apple-podcasts/top.js +9 -2
  22. package/dist/clis/arxiv/paper.js +21 -0
  23. package/dist/clis/arxiv/search.js +24 -0
  24. package/dist/clis/arxiv/utils.d.ts +18 -0
  25. package/dist/clis/arxiv/utils.js +49 -0
  26. package/dist/clis/bilibili/dynamic.js +1 -1
  27. package/dist/clis/bilibili/favorite.js +1 -1
  28. package/dist/clis/bilibili/feed.js +1 -1
  29. package/dist/clis/bilibili/following.js +1 -1
  30. package/dist/clis/bilibili/history.js +1 -1
  31. package/dist/clis/bilibili/me.js +1 -1
  32. package/dist/clis/bilibili/ranking.js +1 -1
  33. package/dist/clis/bilibili/search.js +3 -3
  34. package/dist/clis/bilibili/subtitle.js +1 -1
  35. package/dist/clis/bilibili/user-videos.js +1 -1
  36. package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
  37. package/dist/clis/bloomberg/businessweek.d.ts +1 -0
  38. package/dist/clis/bloomberg/businessweek.js +17 -0
  39. package/dist/clis/bloomberg/economics.d.ts +1 -0
  40. package/dist/clis/bloomberg/economics.js +17 -0
  41. package/dist/clis/bloomberg/feeds.d.ts +1 -0
  42. package/dist/clis/bloomberg/feeds.js +15 -0
  43. package/dist/clis/bloomberg/industries.d.ts +1 -0
  44. package/dist/clis/bloomberg/industries.js +17 -0
  45. package/dist/clis/bloomberg/main.d.ts +1 -0
  46. package/dist/clis/bloomberg/main.js +17 -0
  47. package/dist/clis/bloomberg/markets.d.ts +1 -0
  48. package/dist/clis/bloomberg/markets.js +17 -0
  49. package/dist/clis/bloomberg/news.d.ts +1 -0
  50. package/dist/clis/bloomberg/news.js +105 -0
  51. package/dist/clis/bloomberg/opinions.d.ts +1 -0
  52. package/dist/clis/bloomberg/opinions.js +17 -0
  53. package/dist/clis/bloomberg/politics.d.ts +1 -0
  54. package/dist/clis/bloomberg/politics.js +17 -0
  55. package/dist/clis/bloomberg/tech.d.ts +1 -0
  56. package/dist/clis/bloomberg/tech.js +17 -0
  57. package/dist/clis/bloomberg/utils.d.ts +34 -0
  58. package/dist/clis/bloomberg/utils.js +364 -0
  59. package/dist/clis/bloomberg/utils.test.d.ts +1 -0
  60. package/dist/clis/bloomberg/utils.test.js +129 -0
  61. package/dist/clis/boss/batchgreet.d.ts +1 -0
  62. package/dist/clis/boss/batchgreet.js +147 -0
  63. package/dist/clis/boss/chatlist.js +2 -2
  64. package/dist/clis/boss/detail.js +2 -2
  65. package/dist/clis/boss/exchange.d.ts +1 -0
  66. package/dist/clis/boss/exchange.js +111 -0
  67. package/dist/clis/boss/greet.d.ts +1 -0
  68. package/dist/clis/boss/greet.js +175 -0
  69. package/dist/clis/boss/invite.d.ts +1 -0
  70. package/dist/clis/boss/invite.js +158 -0
  71. package/dist/clis/boss/joblist.d.ts +1 -0
  72. package/dist/clis/boss/joblist.js +55 -0
  73. package/dist/clis/boss/mark.d.ts +1 -0
  74. package/dist/clis/boss/mark.js +141 -0
  75. package/dist/clis/boss/recommend.d.ts +1 -0
  76. package/dist/clis/boss/recommend.js +83 -0
  77. package/dist/clis/boss/search.js +1 -1
  78. package/dist/clis/boss/send.js +1 -1
  79. package/dist/clis/boss/stats.d.ts +1 -0
  80. package/dist/clis/boss/stats.js +116 -0
  81. package/dist/clis/chaoxing/assignments.js +1 -1
  82. package/dist/clis/chaoxing/exams.js +1 -1
  83. package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
  84. package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
  85. package/dist/clis/chaoxing/utils.test.d.ts +1 -0
  86. package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
  87. package/dist/clis/chatgpt/read.js +1 -1
  88. package/dist/clis/chatwise/export.js +1 -1
  89. package/dist/clis/chatwise/model.js +2 -2
  90. package/dist/clis/chatwise/screenshot.js +1 -1
  91. package/dist/clis/codex/export.js +1 -1
  92. package/dist/clis/codex/model.js +2 -2
  93. package/dist/clis/codex/screenshot.js +1 -1
  94. package/dist/clis/coupang/add-to-cart.js +3 -4
  95. package/dist/clis/coupang/search.js +2 -4
  96. package/dist/clis/coupang/utils.test.d.ts +1 -0
  97. package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
  98. package/dist/clis/ctrip/search.js +1 -1
  99. package/dist/clis/cursor/export.js +1 -1
  100. package/dist/clis/cursor/model.js +2 -2
  101. package/dist/clis/cursor/screenshot.js +1 -1
  102. package/dist/clis/jike/comment.js +2 -3
  103. package/dist/clis/jike/create.js +1 -2
  104. package/dist/clis/jike/feed.js +0 -1
  105. package/dist/clis/jike/like.js +1 -2
  106. package/dist/clis/jike/notifications.js +0 -1
  107. package/dist/clis/jike/post.yaml +1 -0
  108. package/dist/clis/jike/repost.js +1 -2
  109. package/dist/clis/jike/search.js +2 -3
  110. package/dist/clis/jike/topic.yaml +1 -0
  111. package/dist/clis/jike/user.yaml +1 -0
  112. package/dist/clis/jimeng/history.yaml +0 -1
  113. package/dist/clis/linkedin/search.js +7 -7
  114. package/dist/clis/linux-do/category.yaml +1 -0
  115. package/dist/clis/linux-do/search.yaml +4 -3
  116. package/dist/clis/linux-do/topic.yaml +1 -0
  117. package/dist/clis/notion/export.js +1 -1
  118. package/dist/clis/reddit/comment.js +3 -4
  119. package/dist/clis/reddit/read.js +4 -5
  120. package/dist/clis/reddit/save.js +2 -3
  121. package/dist/clis/reddit/saved.js +0 -1
  122. package/dist/clis/reddit/search.yaml +1 -0
  123. package/dist/clis/reddit/subscribe.js +0 -1
  124. package/dist/clis/reddit/upvote.js +2 -3
  125. package/dist/clis/reddit/upvoted.js +0 -1
  126. package/dist/clis/reddit/user-comments.yaml +1 -0
  127. package/dist/clis/reddit/user-posts.yaml +1 -0
  128. package/dist/clis/reddit/user.yaml +1 -0
  129. package/dist/clis/reuters/search.js +1 -1
  130. package/dist/clis/sinafinance/news.d.ts +7 -0
  131. package/dist/clis/sinafinance/news.js +61 -0
  132. package/dist/clis/smzdm/search.js +2 -3
  133. package/dist/clis/stackoverflow/search.yaml +1 -0
  134. package/dist/clis/steam/top-sellers.yaml +29 -0
  135. package/dist/clis/twitter/accept.js +2 -2
  136. package/dist/clis/twitter/article.js +2 -2
  137. package/dist/clis/twitter/block.d.ts +1 -0
  138. package/dist/clis/twitter/block.js +88 -0
  139. package/dist/clis/twitter/delete.js +1 -1
  140. package/dist/clis/twitter/hide-reply.d.ts +1 -0
  141. package/dist/clis/twitter/hide-reply.js +66 -0
  142. package/dist/clis/twitter/like.js +1 -1
  143. package/dist/clis/twitter/post.js +1 -1
  144. package/dist/clis/twitter/reply-dm.js +1 -1
  145. package/dist/clis/twitter/reply.js +2 -2
  146. package/dist/clis/twitter/search.js +1 -1
  147. package/dist/clis/twitter/thread.js +2 -2
  148. package/dist/clis/twitter/trending.d.ts +1 -0
  149. package/dist/clis/twitter/trending.js +91 -0
  150. package/dist/clis/twitter/unblock.d.ts +1 -0
  151. package/dist/clis/twitter/unblock.js +71 -0
  152. package/dist/clis/v2ex/topic.yaml +1 -0
  153. package/dist/clis/weibo/hot.js +0 -1
  154. package/dist/clis/weread/book.js +1 -1
  155. package/dist/clis/weread/highlights.js +1 -1
  156. package/dist/clis/weread/notes.js +1 -1
  157. package/dist/clis/weread/search.js +1 -1
  158. package/dist/clis/wikipedia/search.d.ts +1 -0
  159. package/dist/clis/wikipedia/search.js +30 -0
  160. package/dist/clis/wikipedia/summary.d.ts +1 -0
  161. package/dist/clis/wikipedia/summary.js +28 -0
  162. package/dist/clis/wikipedia/utils.d.ts +8 -0
  163. package/dist/clis/wikipedia/utils.js +18 -0
  164. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +79 -5
  165. package/dist/clis/xiaohongshu/creator-note-detail.js +323 -70
  166. package/dist/clis/xiaohongshu/creator-note-detail.test.d.ts +1 -0
  167. package/dist/clis/xiaohongshu/creator-note-detail.test.js +258 -0
  168. package/dist/clis/xiaohongshu/creator-notes-summary.d.ts +28 -0
  169. package/dist/clis/xiaohongshu/creator-notes-summary.js +92 -0
  170. package/dist/clis/xiaohongshu/creator-notes-summary.test.d.ts +1 -0
  171. package/dist/clis/xiaohongshu/creator-notes-summary.test.js +49 -0
  172. package/dist/clis/xiaohongshu/creator-notes.d.ts +18 -5
  173. package/dist/clis/xiaohongshu/creator-notes.js +189 -71
  174. package/dist/clis/xiaohongshu/creator-notes.test.d.ts +1 -0
  175. package/dist/clis/xiaohongshu/creator-notes.test.js +191 -0
  176. package/dist/clis/xiaohongshu/creator-profile.js +0 -1
  177. package/dist/clis/xiaohongshu/creator-stats.js +0 -1
  178. package/dist/clis/xiaohongshu/download.js +2 -3
  179. package/dist/clis/xiaohongshu/feed.yaml +0 -1
  180. package/dist/clis/xiaohongshu/notifications.yaml +0 -1
  181. package/dist/clis/xiaohongshu/search.js +2 -2
  182. package/dist/clis/xiaohongshu/user.js +1 -2
  183. package/dist/clis/yahoo-finance/quote.js +0 -1
  184. package/dist/clis/youtube/search.js +1 -1
  185. package/dist/clis/youtube/transcript.js +1 -1
  186. package/dist/clis/youtube/video.js +1 -1
  187. package/dist/clis/zhihu/download.js +1 -2
  188. package/dist/clis/zhihu/question.js +1 -1
  189. package/dist/clis/zhihu/search.yaml +4 -3
  190. package/dist/commanderAdapter.d.ts +21 -0
  191. package/dist/commanderAdapter.js +111 -0
  192. package/dist/{engine.d.ts → discovery.d.ts} +0 -6
  193. package/dist/{engine.js → discovery.js} +1 -98
  194. package/dist/download/index.d.ts +2 -6
  195. package/dist/download/index.js +19 -46
  196. package/dist/engine.test.d.ts +1 -1
  197. package/dist/engine.test.js +8 -7
  198. package/dist/execution.d.ts +22 -0
  199. package/dist/execution.js +129 -0
  200. package/dist/explore.js +121 -107
  201. package/dist/external-clis.yaml +48 -0
  202. package/dist/external.d.ts +25 -0
  203. package/dist/external.js +156 -0
  204. package/dist/main.js +1 -1
  205. package/dist/pipeline/steps/browser.js +8 -2
  206. package/dist/registry.d.ts +2 -0
  207. package/dist/registry.js +2 -0
  208. package/dist/runtime.d.ts +5 -0
  209. package/dist/runtime.js +8 -0
  210. package/dist/serialization.d.ts +34 -0
  211. package/dist/serialization.js +63 -0
  212. package/dist/types.d.ts +4 -1
  213. package/docs/.vitepress/config.mts +14 -3
  214. package/docs/adapters/browser/arxiv.md +27 -0
  215. package/docs/adapters/browser/barchart.md +32 -0
  216. package/docs/adapters/browser/bloomberg.md +70 -0
  217. package/docs/adapters/browser/chaoxing.md +39 -0
  218. package/docs/adapters/browser/grok.md +35 -0
  219. package/docs/adapters/browser/hf.md +42 -0
  220. package/docs/adapters/browser/jike.md +45 -0
  221. package/docs/adapters/browser/jimeng.md +39 -0
  222. package/docs/adapters/browser/linux-do.md +45 -0
  223. package/docs/adapters/browser/sinafinance.md +35 -0
  224. package/docs/adapters/browser/stackoverflow.md +35 -0
  225. package/docs/adapters/browser/steam.md +26 -0
  226. package/docs/adapters/browser/twitter.md +3 -0
  227. package/docs/adapters/browser/weread.md +48 -0
  228. package/docs/adapters/browser/wikipedia.md +30 -0
  229. package/docs/adapters/browser/xiaohongshu.md +5 -1
  230. package/docs/adapters/desktop/chatgpt.md +3 -3
  231. package/docs/adapters/index.md +13 -0
  232. package/docs/advanced/download.md +4 -4
  233. package/docs/developer/architecture.md +17 -4
  234. package/package.json +1 -1
  235. package/scripts/check-doc-coverage.sh +69 -0
  236. package/scripts/copy-yaml.cjs +7 -0
  237. package/src/browser/cdp.ts +9 -4
  238. package/src/browser/page.ts +7 -1
  239. package/src/build-manifest.ts +25 -19
  240. package/src/cli.ts +253 -119
  241. package/src/clis/antigravity/serve.ts +323 -50
  242. package/src/clis/apple-podcasts/commands.test.ts +95 -0
  243. package/src/clis/apple-podcasts/search.ts +2 -2
  244. package/src/clis/apple-podcasts/top.ts +12 -2
  245. package/src/clis/arxiv/paper.ts +21 -0
  246. package/src/clis/arxiv/search.ts +24 -0
  247. package/src/clis/arxiv/utils.ts +63 -0
  248. package/src/clis/bilibili/dynamic.ts +1 -1
  249. package/src/clis/bilibili/favorite.ts +1 -1
  250. package/src/clis/bilibili/feed.ts +1 -1
  251. package/src/clis/bilibili/following.ts +1 -1
  252. package/src/clis/bilibili/history.ts +1 -1
  253. package/src/clis/bilibili/me.ts +1 -1
  254. package/src/clis/bilibili/ranking.ts +1 -1
  255. package/src/clis/bilibili/search.ts +3 -3
  256. package/src/clis/bilibili/subtitle.ts +1 -1
  257. package/src/clis/bilibili/user-videos.ts +1 -1
  258. package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
  259. package/src/clis/bloomberg/businessweek.ts +18 -0
  260. package/src/clis/bloomberg/economics.ts +18 -0
  261. package/src/clis/bloomberg/feeds.ts +16 -0
  262. package/src/clis/bloomberg/industries.ts +18 -0
  263. package/src/clis/bloomberg/main.ts +18 -0
  264. package/src/clis/bloomberg/markets.ts +18 -0
  265. package/src/clis/bloomberg/news.ts +136 -0
  266. package/src/clis/bloomberg/opinions.ts +18 -0
  267. package/src/clis/bloomberg/politics.ts +18 -0
  268. package/src/clis/bloomberg/tech.ts +18 -0
  269. package/src/clis/bloomberg/utils.test.ts +135 -0
  270. package/src/clis/bloomberg/utils.ts +429 -0
  271. package/src/clis/boss/batchgreet.ts +167 -0
  272. package/src/clis/boss/chatlist.ts +2 -2
  273. package/src/clis/boss/detail.ts +2 -2
  274. package/src/clis/boss/exchange.ts +126 -0
  275. package/src/clis/boss/greet.ts +198 -0
  276. package/src/clis/boss/invite.ts +177 -0
  277. package/src/clis/boss/joblist.ts +63 -0
  278. package/src/clis/boss/mark.ts +155 -0
  279. package/src/clis/boss/recommend.ts +94 -0
  280. package/src/clis/boss/search.ts +1 -1
  281. package/src/clis/boss/send.ts +1 -1
  282. package/src/clis/boss/stats.ts +130 -0
  283. package/src/clis/chaoxing/assignments.ts +1 -1
  284. package/src/clis/chaoxing/exams.ts +1 -1
  285. package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
  286. package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
  287. package/src/clis/chatgpt/README.zh-CN.md +3 -3
  288. package/src/clis/chatgpt/read.ts +1 -1
  289. package/src/clis/chatwise/export.ts +1 -1
  290. package/src/clis/chatwise/model.ts +2 -2
  291. package/src/clis/chatwise/screenshot.ts +1 -1
  292. package/src/clis/codex/export.ts +1 -1
  293. package/src/clis/codex/model.ts +2 -2
  294. package/src/clis/codex/screenshot.ts +1 -1
  295. package/src/clis/coupang/add-to-cart.ts +3 -4
  296. package/src/clis/coupang/search.ts +2 -4
  297. package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
  298. package/src/clis/ctrip/search.ts +1 -1
  299. package/src/clis/cursor/export.ts +1 -1
  300. package/src/clis/cursor/model.ts +2 -2
  301. package/src/clis/cursor/screenshot.ts +1 -1
  302. package/src/clis/jike/comment.ts +2 -3
  303. package/src/clis/jike/create.ts +1 -2
  304. package/src/clis/jike/feed.ts +0 -1
  305. package/src/clis/jike/like.ts +1 -2
  306. package/src/clis/jike/notifications.ts +0 -1
  307. package/src/clis/jike/post.yaml +1 -0
  308. package/src/clis/jike/repost.ts +1 -2
  309. package/src/clis/jike/search.ts +2 -3
  310. package/src/clis/jike/topic.yaml +1 -0
  311. package/src/clis/jike/user.yaml +1 -0
  312. package/src/clis/jimeng/history.yaml +0 -1
  313. package/src/clis/linkedin/search.ts +7 -7
  314. package/src/clis/linux-do/category.yaml +1 -0
  315. package/src/clis/linux-do/search.yaml +4 -3
  316. package/src/clis/linux-do/topic.yaml +1 -0
  317. package/src/clis/notion/export.ts +1 -1
  318. package/src/clis/reddit/comment.ts +3 -4
  319. package/src/clis/reddit/read.ts +4 -5
  320. package/src/clis/reddit/save.ts +2 -3
  321. package/src/clis/reddit/saved.ts +0 -1
  322. package/src/clis/reddit/search.yaml +1 -0
  323. package/src/clis/reddit/subscribe.ts +0 -1
  324. package/src/clis/reddit/upvote.ts +2 -3
  325. package/src/clis/reddit/upvoted.ts +0 -1
  326. package/src/clis/reddit/user-comments.yaml +1 -0
  327. package/src/clis/reddit/user-posts.yaml +1 -0
  328. package/src/clis/reddit/user.yaml +1 -0
  329. package/src/clis/reuters/search.ts +1 -1
  330. package/src/clis/sinafinance/news.ts +76 -0
  331. package/src/clis/smzdm/search.ts +2 -3
  332. package/src/clis/stackoverflow/search.yaml +1 -0
  333. package/src/clis/steam/top-sellers.yaml +29 -0
  334. package/src/clis/twitter/accept.ts +2 -2
  335. package/src/clis/twitter/article.ts +2 -2
  336. package/src/clis/twitter/block.ts +92 -0
  337. package/src/clis/twitter/delete.ts +1 -1
  338. package/src/clis/twitter/hide-reply.ts +70 -0
  339. package/src/clis/twitter/like.ts +1 -1
  340. package/src/clis/twitter/post.ts +1 -1
  341. package/src/clis/twitter/reply-dm.ts +1 -1
  342. package/src/clis/twitter/reply.ts +2 -2
  343. package/src/clis/twitter/search.ts +1 -1
  344. package/src/clis/twitter/thread.ts +2 -2
  345. package/src/clis/twitter/trending.ts +113 -0
  346. package/src/clis/twitter/unblock.ts +75 -0
  347. package/src/clis/v2ex/topic.yaml +1 -0
  348. package/src/clis/weibo/hot.ts +0 -1
  349. package/src/clis/weread/book.ts +1 -1
  350. package/src/clis/weread/highlights.ts +1 -1
  351. package/src/clis/weread/notes.ts +1 -1
  352. package/src/clis/weread/search.ts +1 -1
  353. package/src/clis/wikipedia/search.ts +32 -0
  354. package/src/clis/wikipedia/summary.ts +28 -0
  355. package/src/clis/wikipedia/utils.ts +20 -0
  356. package/src/clis/xiaohongshu/creator-note-detail.test.ts +272 -0
  357. package/src/clis/xiaohongshu/creator-note-detail.ts +425 -73
  358. package/src/clis/xiaohongshu/creator-notes-summary.test.ts +54 -0
  359. package/src/clis/xiaohongshu/creator-notes-summary.ts +120 -0
  360. package/src/clis/xiaohongshu/creator-notes.test.ts +211 -0
  361. package/src/clis/xiaohongshu/creator-notes.ts +254 -75
  362. package/src/clis/xiaohongshu/creator-profile.ts +0 -1
  363. package/src/clis/xiaohongshu/creator-stats.ts +0 -1
  364. package/src/clis/xiaohongshu/download.ts +2 -3
  365. package/src/clis/xiaohongshu/feed.yaml +0 -1
  366. package/src/clis/xiaohongshu/notifications.yaml +0 -1
  367. package/src/clis/xiaohongshu/search.ts +2 -2
  368. package/src/clis/xiaohongshu/user.ts +1 -2
  369. package/src/clis/yahoo-finance/quote.ts +0 -1
  370. package/src/clis/youtube/search.ts +1 -1
  371. package/src/clis/youtube/transcript.ts +1 -1
  372. package/src/clis/youtube/video.ts +1 -1
  373. package/src/clis/zhihu/download.ts +1 -2
  374. package/src/clis/zhihu/question.ts +1 -1
  375. package/src/clis/zhihu/search.yaml +4 -3
  376. package/src/commanderAdapter.ts +113 -0
  377. package/src/daemon.ts +3 -3
  378. package/src/{engine.ts → discovery.ts} +1 -108
  379. package/src/download/index.ts +21 -54
  380. package/src/engine.test.ts +8 -7
  381. package/src/execution.ts +138 -0
  382. package/src/explore.ts +135 -109
  383. package/src/external-clis.yaml +48 -0
  384. package/src/external.ts +185 -0
  385. package/src/main.ts +1 -1
  386. package/src/pipeline/steps/browser.ts +7 -2
  387. package/src/registry.ts +5 -0
  388. package/src/runtime.ts +9 -0
  389. package/src/serialization.ts +79 -0
  390. package/src/types.ts +1 -1
  391. package/tests/e2e/browser-public.test.ts +25 -0
  392. package/tests/e2e/public-commands.test.ts +55 -1
  393. package/dist/clis/twitter/trending.yaml +0 -46
  394. package/src/clis/twitter/trending.yaml +0 -46
  395. /package/dist/{chaoxing.test.d.ts → clis/arxiv/paper.d.ts} +0 -0
  396. /package/dist/{coupang.test.d.ts → clis/arxiv/search.d.ts} +0 -0
  397. /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
  398. /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
  399. /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
  400. /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
@@ -1,95 +1,447 @@
1
1
  /**
2
- * Xiaohongshu Creator Note Detail — per-note analytics breakdown.
2
+ * Xiaohongshu Creator Note Detail — per-note analytics from the creator detail page.
3
3
  *
4
- * Uses the creator.xiaohongshu.com internal API (cookie auth).
5
- * Returns total reads, engagement, likes, collects, comments, shares
6
- * for a specific note, split by channel (organic vs promoted vs video).
4
+ * The current creator center no longer serves stable single-note metrics from the legacy
5
+ * `/api/galaxy/creator/data/note_detail` endpoint. The real note detail page loads data
6
+ * through the newer `datacenter/note/*` API family, so this command navigates to the
7
+ * detail page and parses the rendered metrics that are backed by those APIs.
7
8
  *
8
9
  * Requires: logged into creator.xiaohongshu.com in Chrome.
9
10
  */
10
11
 
11
12
  import { cli, Strategy } from '../../registry.js';
13
+ import type { IPage } from '../../types.js';
14
+
15
+ type CreatorNoteDetailRow = {
16
+ section: string;
17
+ metric: string;
18
+ value: string;
19
+ extra: string;
20
+ };
21
+
22
+ export type { CreatorNoteDetailRow };
23
+
24
+ type CreatorNoteDetailDomMetric = {
25
+ label: string;
26
+ value: string;
27
+ extra: string;
28
+ };
29
+
30
+ type CreatorNoteDetailDomSection = {
31
+ title: string;
32
+ metrics: CreatorNoteDetailDomMetric[];
33
+ };
34
+
35
+ type CreatorNoteDetailDomData = {
36
+ title: string;
37
+ infoText: string;
38
+ sections: CreatorNoteDetailDomSection[];
39
+ };
40
+
41
+ type AudienceSourceItem = {
42
+ title?: string;
43
+ value_with_double?: number;
44
+ info?: {
45
+ imp_count?: number;
46
+ view_count?: number;
47
+ interaction_count?: number;
48
+ };
49
+ };
50
+
51
+ type AudiencePortraitItem = {
52
+ title?: string;
53
+ value?: number;
54
+ };
55
+
56
+ type NoteTrendPoint = {
57
+ date?: number;
58
+ count?: number;
59
+ count_with_double?: number;
60
+ };
61
+
62
+ type NoteTrendBucket = {
63
+ imp_list?: NoteTrendPoint[];
64
+ view_list?: NoteTrendPoint[];
65
+ view_time_list?: NoteTrendPoint[];
66
+ like_list?: NoteTrendPoint[];
67
+ comment_list?: NoteTrendPoint[];
68
+ collect_list?: NoteTrendPoint[];
69
+ share_list?: NoteTrendPoint[];
70
+ rise_fans_list?: NoteTrendPoint[];
71
+ };
72
+
73
+ type NoteDetailApiPayload = {
74
+ noteBase?: {
75
+ hour?: NoteTrendBucket;
76
+ day?: NoteTrendBucket;
77
+ };
78
+ audienceTrend?: {
79
+ no_data?: boolean;
80
+ no_data_tip_msg?: string;
81
+ };
82
+ audienceSource?: {
83
+ source?: AudienceSourceItem[];
84
+ };
85
+ audienceSourceDetail?: {
86
+ gender?: AudiencePortraitItem[];
87
+ age?: AudiencePortraitItem[];
88
+ city?: AudiencePortraitItem[];
89
+ interest?: AudiencePortraitItem[];
90
+ };
91
+ };
92
+
93
+ const NOTE_DETAIL_DATETIME_RE = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
94
+ const NOTE_DETAIL_METRICS = [
95
+ { label: '曝光数', section: '基础数据' },
96
+ { label: '观看数', section: '基础数据' },
97
+ { label: '封面点击率', section: '基础数据' },
98
+ { label: '平均观看时长', section: '基础数据' },
99
+ { label: '涨粉数', section: '基础数据' },
100
+ { label: '点赞数', section: '互动数据' },
101
+ { label: '评论数', section: '互动数据' },
102
+ { label: '收藏数', section: '互动数据' },
103
+ { label: '分享数', section: '互动数据' },
104
+ ] as const;
105
+
106
+ const NOTE_DETAIL_METRIC_LABELS = new Set<string>(NOTE_DETAIL_METRICS.map((metric) => metric.label));
107
+ const NOTE_DETAIL_SECTIONS = new Set<string>(NOTE_DETAIL_METRICS.map((metric) => metric.section));
108
+ const NOTE_DETAIL_NOISE_LINES = new Set([
109
+ '切换笔记',
110
+ '笔记诊断',
111
+ '核心数据',
112
+ '观看来源',
113
+ '观众画像',
114
+ '提升建议',
115
+ '基础数据',
116
+ '互动数据',
117
+ '导出数据',
118
+ '实时',
119
+ '按小时',
120
+ '按天',
121
+ ]);
122
+
123
+ function findNoteTitle(lines: string[]): string {
124
+ const detailIndex = lines.indexOf('笔记数据详情');
125
+ if (detailIndex < 0) return '';
126
+
127
+ for (let i = detailIndex + 1; i < lines.length; i++) {
128
+ const line = lines[i];
129
+ if (!line || line.startsWith('#') || NOTE_DETAIL_DATETIME_RE.test(line)) continue;
130
+ if (NOTE_DETAIL_NOISE_LINES.has(line)) continue;
131
+ return line;
132
+ }
133
+
134
+ return '';
135
+ }
136
+
137
+ function findMetricValue(lines: string[], startIndex: number): { value: string; extra: string } {
138
+ let value = '';
139
+ let extra = '';
140
+
141
+ for (let i = startIndex + 1; i < lines.length; i++) {
142
+ const line = lines[i];
143
+ if (!line) continue;
144
+ if (NOTE_DETAIL_METRIC_LABELS.has(line)) break;
145
+ if (NOTE_DETAIL_NOISE_LINES.has(line) || line.startsWith('数据更新至') || line.startsWith('部分数据统计中')) continue;
146
+
147
+ if (!value) {
148
+ value = line;
149
+ continue;
150
+ }
151
+
152
+ if (!extra && line.startsWith('粉丝')) {
153
+ extra = line;
154
+ break;
155
+ }
156
+
157
+ if (line === '0' || /^\d/.test(line) || line.endsWith('%') || line.endsWith('秒')) {
158
+ break;
159
+ }
160
+ }
161
+
162
+ return { value, extra };
163
+ }
164
+
165
+ function findPublishedAt(text: string): string {
166
+ const match = text.match(/\b\d{4}-\d{2}-\d{2} \d{2}:\d{2}\b/);
167
+ return match?.[0] ?? '';
168
+ }
169
+
170
+ export function parseCreatorNoteDetailText(bodyText: string, noteId: string): CreatorNoteDetailRow[] {
171
+ const lines = bodyText
172
+ .split('\n')
173
+ .map((line) => line.trim())
174
+ .filter(Boolean);
175
+
176
+ const title = findNoteTitle(lines);
177
+ const publishedAt = lines.find((line) => NOTE_DETAIL_DATETIME_RE.test(line)) ?? '';
178
+ const rows: CreatorNoteDetailRow[] = [
179
+ { section: '笔记信息', metric: 'note_id', value: noteId, extra: '' },
180
+ { section: '笔记信息', metric: 'title', value: title, extra: '' },
181
+ { section: '笔记信息', metric: 'published_at', value: publishedAt, extra: '' },
182
+ ];
183
+
184
+ for (const metric of NOTE_DETAIL_METRICS) {
185
+ const index = lines.indexOf(metric.label);
186
+ if (index < 0) continue;
187
+ const { value, extra } = findMetricValue(lines, index);
188
+ rows.push({
189
+ section: metric.section,
190
+ metric: metric.label,
191
+ value,
192
+ extra,
193
+ });
194
+ }
195
+
196
+ return rows;
197
+ }
198
+
199
+ export function parseCreatorNoteDetailDomData(dom: CreatorNoteDetailDomData | null | undefined, noteId: string): CreatorNoteDetailRow[] {
200
+ if (!dom) return [];
201
+ const title = typeof dom.title === 'string' ? dom.title.trim() : '';
202
+ const infoText = typeof dom.infoText === 'string' ? dom.infoText : '';
203
+ const sections = Array.isArray(dom.sections) ? dom.sections : [];
204
+
205
+ const rows: CreatorNoteDetailRow[] = [
206
+ { section: '笔记信息', metric: 'note_id', value: noteId, extra: '' },
207
+ { section: '笔记信息', metric: 'title', value: title, extra: '' },
208
+ { section: '笔记信息', metric: 'published_at', value: findPublishedAt(infoText), extra: '' },
209
+ ];
210
+
211
+ for (const section of sections) {
212
+ if (!NOTE_DETAIL_SECTIONS.has(section.title)) continue;
213
+ for (const metric of section.metrics) {
214
+ if (!NOTE_DETAIL_METRIC_LABELS.has(metric.label)) continue;
215
+ rows.push({
216
+ section: section.title,
217
+ metric: metric.label,
218
+ value: metric.value,
219
+ extra: metric.extra,
220
+ });
221
+ }
222
+ }
223
+
224
+ const hasMetric = rows.some((row) => row.section !== '笔记信息' && row.value);
225
+ return hasMetric ? rows : [];
226
+ }
227
+
228
+ function toPercentString(value?: number): string {
229
+ return value == null ? '' : `${value}%`;
230
+ }
231
+
232
+ function appendAudienceSourceRows(rows: CreatorNoteDetailRow[], payload?: NoteDetailApiPayload): CreatorNoteDetailRow[] {
233
+ const sourceItems = payload?.audienceSource?.source ?? [];
234
+ for (const item of sourceItems) {
235
+ if (!item.title) continue;
236
+ const extras: string[] = [];
237
+ if (item.info?.imp_count != null) extras.push(`曝光 ${item.info.imp_count}`);
238
+ if (item.info?.view_count != null) extras.push(`观看 ${item.info.view_count}`);
239
+ if (item.info?.interaction_count != null) extras.push(`互动 ${item.info.interaction_count}`);
240
+ rows.push({
241
+ section: '观看来源',
242
+ metric: item.title,
243
+ value: toPercentString(item.value_with_double),
244
+ extra: extras.join(' · '),
245
+ });
246
+ }
247
+ return rows;
248
+ }
249
+
250
+ function appendAudiencePortraitGroup(
251
+ rows: CreatorNoteDetailRow[],
252
+ groupLabel: string,
253
+ items?: AudiencePortraitItem[],
254
+ ): CreatorNoteDetailRow[] {
255
+ for (const item of items ?? []) {
256
+ if (!item.title) continue;
257
+ rows.push({
258
+ section: '观众画像',
259
+ metric: `${groupLabel}/${item.title}`,
260
+ value: toPercentString(item.value),
261
+ extra: '',
262
+ });
263
+ }
264
+ return rows;
265
+ }
266
+
267
+ export function appendAudienceRows(rows: CreatorNoteDetailRow[], payload?: NoteDetailApiPayload): CreatorNoteDetailRow[] {
268
+ appendAudienceSourceRows(rows, payload);
269
+ appendAudiencePortraitGroup(rows, '性别', payload?.audienceSourceDetail?.gender);
270
+ appendAudiencePortraitGroup(rows, '年龄', payload?.audienceSourceDetail?.age);
271
+ appendAudiencePortraitGroup(rows, '城市', payload?.audienceSourceDetail?.city);
272
+ appendAudiencePortraitGroup(rows, '兴趣', payload?.audienceSourceDetail?.interest);
273
+ return rows;
274
+ }
275
+
276
+ function formatTrendTimestamp(ts: number | undefined, granularity: 'hour' | 'day'): string {
277
+ if (!ts) return '';
278
+ // Use fixed UTC+8 offset to ensure consistent output regardless of CI server timezone.
279
+ const CST_OFFSET_MS = 8 * 60 * 60 * 1000;
280
+ const cstDate = new Date(ts + CST_OFFSET_MS);
281
+ const pad = (value: number) => String(value).padStart(2, '0');
282
+ if (granularity === 'hour') {
283
+ return `${pad(cstDate.getUTCMonth() + 1)}-${pad(cstDate.getUTCDate())} ${pad(cstDate.getUTCHours())}:00`;
284
+ }
285
+ return `${cstDate.getUTCFullYear()}-${pad(cstDate.getUTCMonth() + 1)}-${pad(cstDate.getUTCDate())}`;
286
+ }
287
+
288
+ function formatTrendSeries(points: NoteTrendPoint[] | undefined, granularity: 'hour' | 'day'): string {
289
+ if (!points?.length) return '';
290
+ return points
291
+ .map((point) => {
292
+ const label = formatTrendTimestamp(point.date, granularity);
293
+ const value = point.count_with_double ?? point.count;
294
+ return label && value != null ? `${label}=${value}` : '';
295
+ })
296
+ .filter(Boolean)
297
+ .join(' | ');
298
+ }
299
+
300
+ const TREND_SERIES_CONFIG = [
301
+ { key: 'imp_list', label: '曝光数' },
302
+ { key: 'view_list', label: '观看数' },
303
+ { key: 'view_time_list', label: '平均观看时长' },
304
+ { key: 'like_list', label: '点赞数' },
305
+ { key: 'comment_list', label: '评论数' },
306
+ { key: 'collect_list', label: '收藏数' },
307
+ { key: 'share_list', label: '分享数' },
308
+ { key: 'rise_fans_list', label: '涨粉数' },
309
+ ] as const;
310
+
311
+ export function appendTrendRows(rows: CreatorNoteDetailRow[], payload?: NoteDetailApiPayload): CreatorNoteDetailRow[] {
312
+ if (payload?.audienceTrend?.no_data_tip_msg) {
313
+ rows.push({
314
+ section: '趋势说明',
315
+ metric: '观众趋势',
316
+ value: payload.audienceTrend.no_data ? '暂不可用' : '可用',
317
+ extra: payload.audienceTrend.no_data_tip_msg,
318
+ });
319
+ }
320
+
321
+ const buckets: Array<{ label: string; granularity: 'hour' | 'day'; data?: NoteTrendBucket }> = [
322
+ { label: '按小时', granularity: 'hour', data: payload?.noteBase?.hour },
323
+ { label: '按天', granularity: 'day', data: payload?.noteBase?.day },
324
+ ];
325
+
326
+ for (const bucket of buckets) {
327
+ for (const series of TREND_SERIES_CONFIG) {
328
+ const points = bucket.data?.[series.key];
329
+ const formatted = formatTrendSeries(points, bucket.granularity);
330
+ if (!formatted) continue;
331
+ rows.push({
332
+ section: '趋势数据',
333
+ metric: `${bucket.label}/${series.label}`,
334
+ value: `${points!.length} points`,
335
+ extra: formatted,
336
+ });
337
+ }
338
+ }
339
+
340
+ return rows;
341
+ }
342
+
343
+ const DETAIL_API_ENDPOINTS: Array<{ suffix: string; key: keyof NoteDetailApiPayload }> = [
344
+ { suffix: '/api/galaxy/creator/datacenter/note/base', key: 'noteBase' },
345
+ { suffix: '/api/galaxy/creator/datacenter/note/analyze/audience/trend', key: 'audienceTrend' },
346
+ { suffix: '/api/galaxy/creator/datacenter/note/audience/source/detail', key: 'audienceSourceDetail' },
347
+ { suffix: '/api/galaxy/creator/datacenter/note/audience', key: 'audienceSource' },
348
+ ];
349
+
350
+ async function captureNoteDetailPayload(page: IPage, noteId: string): Promise<NoteDetailApiPayload | null> {
351
+ const payload: NoteDetailApiPayload = {};
352
+ let captured = 0;
353
+
354
+ // Try to fetch each API endpoint through the page context (uses the browser's cookies)
355
+ for (const { suffix, key } of DETAIL_API_ENDPOINTS) {
356
+ const apiUrl = `${suffix}?note_id=${noteId}`;
357
+ try {
358
+ const data = await page.evaluate(`
359
+ async () => {
360
+ try {
361
+ const resp = await fetch(${JSON.stringify(apiUrl)}, { credentials: 'include' });
362
+ if (!resp.ok) return null;
363
+ const json = await resp.json();
364
+ return JSON.stringify(json.data ?? {});
365
+ } catch { return null; }
366
+ }
367
+ `);
368
+ if (data && typeof data === 'string') {
369
+ try {
370
+ payload[key] = JSON.parse(data);
371
+ captured++;
372
+ } catch {}
373
+ }
374
+ } catch {}
375
+ }
376
+
377
+ return captured > 0 ? payload : null;
378
+ }
379
+
380
+ async function captureNoteDetailDomData(page: IPage): Promise<CreatorNoteDetailDomData | null> {
381
+ const result = await page.evaluate(`() => {
382
+ const norm = (value) => (value || '').trim();
383
+ const sections = Array.from(document.querySelectorAll('.shell-container')).map((container) => {
384
+ const containerText = norm(container.innerText);
385
+ const title = containerText.startsWith('互动数据')
386
+ ? '互动数据'
387
+ : containerText.includes('基础数据')
388
+ ? '基础数据'
389
+ : '';
390
+ const metrics = Array.from(container.querySelectorAll('.block-container.block')).map((block) => ({
391
+ label: norm(block.querySelector('.des')?.innerText),
392
+ value: norm(block.querySelector('.content')?.innerText),
393
+ extra: norm(block.querySelector('.text-with-fans')?.innerText),
394
+ })).filter((metric) => metric.label && metric.value);
395
+ return { title, metrics };
396
+ }).filter((section) => section.title && section.metrics.length > 0);
397
+
398
+ return {
399
+ title: norm(document.querySelector('.note-title')?.innerText),
400
+ infoText: norm(document.querySelector('.note-info-content')?.innerText),
401
+ sections,
402
+ };
403
+ }`);
404
+
405
+ if (!result || typeof result !== 'object') return null;
406
+ return result as CreatorNoteDetailDomData;
407
+ }
408
+
409
+ export async function fetchCreatorNoteDetailRows(page: IPage, noteId: string): Promise<CreatorNoteDetailRow[]> {
410
+ await page.goto(`https://creator.xiaohongshu.com/statistics/note-detail?noteId=${encodeURIComponent(noteId)}`);
411
+
412
+ const domData = await captureNoteDetailDomData(page).catch(() => null);
413
+ let rows = parseCreatorNoteDetailDomData(domData, noteId);
414
+ if (rows.length === 0) {
415
+ const bodyText = await page.evaluate('() => document.body.innerText');
416
+ rows = parseCreatorNoteDetailText(typeof bodyText === 'string' ? bodyText : '', noteId);
417
+ }
418
+ const apiPayload = await captureNoteDetailPayload(page, noteId).catch(() => null);
419
+ appendTrendRows(rows, apiPayload ?? undefined);
420
+ appendAudienceRows(rows, apiPayload ?? undefined);
421
+
422
+ return rows;
423
+ }
12
424
 
13
425
  cli({
14
426
  site: 'xiaohongshu',
15
427
  name: 'creator-note-detail',
16
- description: '小红书单篇笔记详细数据 (阅读/互动/点赞/收藏/评论/分享,区分自然流量/推广/视频)',
428
+ description: '小红书单篇笔记详情页数据 (笔记信息 + 核心/互动数据 + 观看来源 + 观众画像 + 趋势数据)',
17
429
  domain: 'creator.xiaohongshu.com',
18
430
  strategy: Strategy.COOKIE,
19
431
  browser: true,
20
432
  args: [
21
- { name: 'note_id', type: 'string', required: true, help: 'Note ID (from note URL or creator-notes command)' },
433
+ { name: 'note-id', type: 'string', required: true, help: 'Note ID (from creator-notes or note-detail page URL)' },
22
434
  ],
23
- columns: ['channel', 'reads', 'engagement', 'likes', 'collects', 'comments', 'shares'],
435
+ columns: ['section', 'metric', 'value', 'extra'],
24
436
  func: async (page, kwargs) => {
25
- const noteId: string = kwargs.note_id;
26
- const encodedNoteId = encodeURIComponent(noteId);
27
-
28
- // Navigate for cookie context
29
- await page.goto('https://creator.xiaohongshu.com/new/home');
30
- await page.wait(2);
31
-
32
- const data = await page.evaluate(`
33
- async () => {
34
- try {
35
- const resp = await fetch(
36
- '/api/galaxy/creator/data/note_detail?note_id=${encodedNoteId}',
37
- { credentials: 'include' }
38
- );
39
- if (!resp.ok) return { error: 'HTTP ' + resp.status };
40
- return await resp.json();
41
- } catch (e) {
42
- return { error: e.message };
43
- }
44
- }
45
- `);
437
+ const noteId: string = kwargs['note-id'];
438
+ const rows = await fetchCreatorNoteDetailRows(page, noteId);
46
439
 
47
- if (data?.error) {
48
- throw new Error(data.error + '. Check note_id and login status.');
49
- }
50
- if (!data?.data) {
51
- throw new Error('Unexpected response structure');
440
+ const hasCoreMetric = rows.some((row) => row.section !== '笔记信息' && row.value);
441
+ if (!hasCoreMetric) {
442
+ throw new Error('No note detail data found. Check note_id and login status for creator.xiaohongshu.com.');
52
443
  }
53
444
 
54
- const d = data.data;
55
-
56
- return [
57
- {
58
- channel: 'Total',
59
- reads: d.total_read ?? 0,
60
- engagement: d.total_engage ?? 0,
61
- likes: d.total_like ?? 0,
62
- collects: d.total_fav ?? 0,
63
- comments: d.total_cmt ?? 0,
64
- shares: d.total_share ?? 0,
65
- },
66
- {
67
- channel: 'Organic',
68
- reads: d.normal_read ?? 0,
69
- engagement: d.normal_engage ?? 0,
70
- likes: d.normal_like ?? 0,
71
- collects: d.normal_fav ?? 0,
72
- comments: d.normal_cmt ?? 0,
73
- shares: d.normal_share ?? 0,
74
- },
75
- {
76
- channel: 'Promoted',
77
- reads: d.total_promo_read ?? 0,
78
- engagement: 0,
79
- likes: 0,
80
- collects: 0,
81
- comments: 0,
82
- shares: 0,
83
- },
84
- {
85
- channel: 'Video',
86
- reads: d.video_read ?? 0,
87
- engagement: d.video_engage ?? 0,
88
- likes: d.video_like ?? 0,
89
- collects: d.video_fav ?? 0,
90
- comments: d.video_cmt ?? 0,
91
- shares: d.video_share ?? 0,
92
- },
93
- ];
445
+ return rows;
94
446
  },
95
447
  });
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { summarizeCreatorNote } from './creator-notes-summary.js';
3
+ import type { CreatorNoteRow } from './creator-notes.js';
4
+ import type { CreatorNoteDetailRow } from './creator-note-detail.js';
5
+ import './creator-notes-summary.js';
6
+
7
+ describe('xiaohongshu creator-notes-summary', () => {
8
+ it('summarizes note list row and detail rows into one compact row', () => {
9
+ const note: CreatorNoteRow = {
10
+ id: '69ba940500000000200384db',
11
+ title: '一张图讲清 诡秘之主·耕种者途径',
12
+ date: '2026年03月18日 20:01',
13
+ views: 549,
14
+ likes: 19,
15
+ collects: 10,
16
+ comments: 7,
17
+ url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=69ba940500000000200384db',
18
+ };
19
+
20
+ const rows: CreatorNoteDetailRow[] = [
21
+ { section: '笔记信息', metric: 'published_at', value: '2026-03-18 20:01', extra: '' },
22
+ { section: '基础数据', metric: '观看数', value: '549', extra: '' },
23
+ { section: '互动数据', metric: '点赞数', value: '19', extra: '' },
24
+ { section: '互动数据', metric: '收藏数', value: '10', extra: '' },
25
+ { section: '互动数据', metric: '评论数', value: '7', extra: '' },
26
+ { section: '互动数据', metric: '分享数', value: '6', extra: '' },
27
+ { section: '基础数据', metric: '平均观看时长', value: '51.5秒', extra: '' },
28
+ { section: '基础数据', metric: '涨粉数', value: '3', extra: '' },
29
+ { section: '观看来源', metric: '首页推荐', value: '89.9%', extra: '' },
30
+ { section: '观看来源', metric: '搜索', value: '0.3%', extra: '' },
31
+ { section: '观众画像', metric: '兴趣/二次元', value: '13%', extra: '' },
32
+ { section: '观众画像', metric: '兴趣/游戏', value: '11%', extra: '' },
33
+ ];
34
+
35
+ expect(summarizeCreatorNote(note, rows, 1)).toEqual({
36
+ rank: 1,
37
+ id: '69ba940500000000200384db',
38
+ title: '一张图讲清 诡秘之主·耕种者途径',
39
+ published_at: '2026-03-18 20:01',
40
+ views: '549',
41
+ likes: '19',
42
+ collects: '10',
43
+ comments: '7',
44
+ shares: '6',
45
+ avg_view_time: '51.5秒',
46
+ rise_fans: '3',
47
+ top_source: '首页推荐',
48
+ top_source_pct: '89.9%',
49
+ top_interest: '二次元',
50
+ top_interest_pct: '13%',
51
+ url: 'https://creator.xiaohongshu.com/statistics/note-detail?noteId=69ba940500000000200384db',
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Xiaohongshu Creator Notes Summary — batch summary for recent notes.
3
+ *
4
+ * Combines creator-notes and creator-note-detail into a single command that
5
+ * returns one summary row per note, suitable for quick review or downstream JSON use.
6
+ */
7
+
8
+ import { cli, Strategy } from '../../registry.js';
9
+ import { fetchCreatorNotes, type CreatorNoteRow } from './creator-notes.js';
10
+ import { fetchCreatorNoteDetailRows, type CreatorNoteDetailRow } from './creator-note-detail.js';
11
+
12
+ type CreatorNoteSummaryRow = {
13
+ rank: number;
14
+ id: string;
15
+ title: string;
16
+ published_at: string;
17
+ views: string;
18
+ likes: string;
19
+ collects: string;
20
+ comments: string;
21
+ shares: string;
22
+ avg_view_time: string;
23
+ rise_fans: string;
24
+ top_source: string;
25
+ top_source_pct: string;
26
+ top_interest: string;
27
+ top_interest_pct: string;
28
+ url: string;
29
+ };
30
+
31
+ function findDetailValue(rows: CreatorNoteDetailRow[], metric: string): string {
32
+ return rows.find((row) => row.metric === metric)?.value ?? '';
33
+ }
34
+
35
+ function findTopBySectionPrefix(rows: CreatorNoteDetailRow[], section: string, prefix: string): { label: string; value: string } {
36
+ const matches = rows.filter((row) => row.section === section && row.metric.startsWith(prefix) && row.value);
37
+ if (matches.length === 0) return { label: '', value: '' };
38
+ const sorted = [...matches].sort((a, b) => parseFloat(b.value) - parseFloat(a.value));
39
+ const top = sorted[0];
40
+ return {
41
+ label: top.metric.slice(prefix.length),
42
+ value: top.value,
43
+ };
44
+ }
45
+
46
+ export function summarizeCreatorNote(note: CreatorNoteRow, rows: CreatorNoteDetailRow[], rank: number): CreatorNoteSummaryRow {
47
+ const topSource = findTopBySectionPrefix(rows, '观看来源', '');
48
+ const topInterest = findTopBySectionPrefix(rows, '观众画像', '兴趣/');
49
+
50
+ return {
51
+ rank,
52
+ id: note.id,
53
+ title: note.title,
54
+ published_at: findDetailValue(rows, 'published_at') || note.date,
55
+ views: findDetailValue(rows, '观看数') || String(note.views),
56
+ likes: findDetailValue(rows, '点赞数') || String(note.likes),
57
+ collects: findDetailValue(rows, '收藏数') || String(note.collects),
58
+ comments: findDetailValue(rows, '评论数') || String(note.comments),
59
+ shares: findDetailValue(rows, '分享数'),
60
+ avg_view_time: findDetailValue(rows, '平均观看时长'),
61
+ rise_fans: findDetailValue(rows, '涨粉数'),
62
+ top_source: topSource.label,
63
+ top_source_pct: topSource.value,
64
+ top_interest: topInterest.label,
65
+ top_interest_pct: topInterest.value,
66
+ url: note.url,
67
+ };
68
+ }
69
+
70
+ cli({
71
+ site: 'xiaohongshu',
72
+ name: 'creator-notes-summary',
73
+ description: '小红书最近笔记批量摘要 (列表 + 单篇关键数据汇总)',
74
+ domain: 'creator.xiaohongshu.com',
75
+ strategy: Strategy.COOKIE,
76
+ browser: true,
77
+ args: [
78
+ { name: 'limit', type: 'int', default: 3, help: 'Number of recent notes to summarize' },
79
+ ],
80
+ columns: ['rank', 'id', 'title', 'views', 'likes', 'collects', 'comments', 'shares', 'avg_view_time', 'rise_fans', 'top_source', 'top_interest', 'url'],
81
+ timeoutSeconds: 180,
82
+ func: async (page, kwargs) => {
83
+ const limit = kwargs.limit || 3;
84
+ const notes = await fetchCreatorNotes(page, limit);
85
+
86
+ if (!notes.length) {
87
+ throw new Error('No notes found. Are you logged into creator.xiaohongshu.com?');
88
+ }
89
+
90
+ const results: CreatorNoteSummaryRow[] = [];
91
+ for (const [index, note] of notes.entries()) {
92
+ if (!note.id) {
93
+ results.push({
94
+ rank: index + 1,
95
+ id: note.id,
96
+ title: note.title,
97
+ published_at: note.date,
98
+ views: String(note.views),
99
+ likes: String(note.likes),
100
+ collects: String(note.collects),
101
+ comments: String(note.comments),
102
+ shares: '',
103
+ avg_view_time: '',
104
+ rise_fans: '',
105
+ top_source: '',
106
+ top_source_pct: '',
107
+ top_interest: '',
108
+ top_interest_pct: '',
109
+ url: note.url,
110
+ });
111
+ continue;
112
+ }
113
+
114
+ const detailRows = await fetchCreatorNoteDetailRows(page, note.id);
115
+ results.push(summarizeCreatorNote(note, detailRows, index + 1));
116
+ }
117
+
118
+ return results;
119
+ },
120
+ });