@zenalexa/unicli 0.219.0 → 0.220.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/AGENTS.md +9 -9
  2. package/README.md +55 -249
  3. package/README.zh-CN.md +55 -249
  4. package/dist/adapters/1point3acres/forum.d.ts +58 -0
  5. package/dist/adapters/1point3acres/forum.d.ts.map +1 -0
  6. package/dist/adapters/1point3acres/forum.js +708 -0
  7. package/dist/adapters/1point3acres/forum.js.map +1 -0
  8. package/dist/adapters/aibase/news.d.ts +21 -0
  9. package/dist/adapters/aibase/news.d.ts.map +1 -0
  10. package/dist/adapters/aibase/news.js +96 -0
  11. package/dist/adapters/aibase/news.js.map +1 -0
  12. package/dist/adapters/arxiv/papers.d.ts +27 -0
  13. package/dist/adapters/arxiv/papers.d.ts.map +1 -0
  14. package/dist/adapters/arxiv/papers.js +193 -0
  15. package/dist/adapters/arxiv/papers.js.map +1 -0
  16. package/dist/adapters/bbc/topic.d.ts +24 -0
  17. package/dist/adapters/bbc/topic.d.ts.map +1 -0
  18. package/dist/adapters/bbc/topic.js +122 -0
  19. package/dist/adapters/bbc/topic.js.map +1 -0
  20. package/dist/adapters/chatgpt/web.d.ts +24 -0
  21. package/dist/adapters/chatgpt/web.d.ts.map +1 -0
  22. package/dist/adapters/chatgpt/web.js +242 -0
  23. package/dist/adapters/chatgpt/web.js.map +1 -0
  24. package/dist/adapters/claude/web.d.ts +24 -0
  25. package/dist/adapters/claude/web.d.ts.map +1 -0
  26. package/dist/adapters/claude/web.js +575 -0
  27. package/dist/adapters/claude/web.js.map +1 -0
  28. package/dist/adapters/codex/projects.d.ts +27 -0
  29. package/dist/adapters/codex/projects.d.ts.map +1 -0
  30. package/dist/adapters/codex/projects.js +147 -0
  31. package/dist/adapters/codex/projects.js.map +1 -0
  32. package/dist/adapters/coingecko/markets.d.ts +19 -0
  33. package/dist/adapters/coingecko/markets.d.ts.map +1 -0
  34. package/dist/adapters/coingecko/markets.js +474 -0
  35. package/dist/adapters/coingecko/markets.js.map +1 -0
  36. package/dist/adapters/coupang/product.d.ts +27 -0
  37. package/dist/adapters/coupang/product.d.ts.map +1 -0
  38. package/dist/adapters/coupang/product.js +211 -0
  39. package/dist/adapters/coupang/product.js.map +1 -0
  40. package/dist/adapters/crates/registry.d.ts +44 -0
  41. package/dist/adapters/crates/registry.d.ts.map +1 -0
  42. package/dist/adapters/crates/registry.js +186 -0
  43. package/dist/adapters/crates/registry.js.map +1 -0
  44. package/dist/adapters/ctrip/travel.d.ts +83 -0
  45. package/dist/adapters/ctrip/travel.d.ts.map +1 -0
  46. package/dist/adapters/ctrip/travel.js +630 -0
  47. package/dist/adapters/ctrip/travel.js.map +1 -0
  48. package/dist/adapters/dblp/publications.d.ts +41 -0
  49. package/dist/adapters/dblp/publications.d.ts.map +1 -0
  50. package/dist/adapters/dblp/publications.js +409 -0
  51. package/dist/adapters/dblp/publications.js.map +1 -0
  52. package/dist/adapters/deepseek/web.d.ts +1 -1
  53. package/dist/adapters/deepseek/web.d.ts.map +1 -1
  54. package/dist/adapters/deepseek/web.js +66 -1
  55. package/dist/adapters/deepseek/web.js.map +1 -1
  56. package/dist/adapters/defillama/protocols.d.ts +13 -0
  57. package/dist/adapters/defillama/protocols.d.ts.map +1 -0
  58. package/dist/adapters/defillama/protocols.js +218 -0
  59. package/dist/adapters/defillama/protocols.js.map +1 -0
  60. package/dist/adapters/devto/read.d.ts +26 -0
  61. package/dist/adapters/devto/read.d.ts.map +1 -0
  62. package/dist/adapters/devto/read.js +110 -0
  63. package/dist/adapters/devto/read.js.map +1 -0
  64. package/dist/adapters/dianping/shop.d.ts +38 -0
  65. package/dist/adapters/dianping/shop.d.ts.map +1 -0
  66. package/dist/adapters/dianping/shop.js +194 -0
  67. package/dist/adapters/dianping/shop.js.map +1 -0
  68. package/dist/adapters/dockerhub/registry.d.ts +36 -0
  69. package/dist/adapters/dockerhub/registry.d.ts.map +1 -0
  70. package/dist/adapters/dockerhub/registry.js +172 -0
  71. package/dist/adapters/dockerhub/registry.js.map +1 -0
  72. package/dist/adapters/endoflife/product.d.ts +11 -0
  73. package/dist/adapters/endoflife/product.d.ts.map +1 -0
  74. package/dist/adapters/endoflife/product.js +113 -0
  75. package/dist/adapters/endoflife/product.js.map +1 -0
  76. package/dist/adapters/facebook/marketplace-extra.d.ts +9 -0
  77. package/dist/adapters/facebook/marketplace-extra.d.ts.map +1 -0
  78. package/dist/adapters/facebook/marketplace-extra.js +170 -0
  79. package/dist/adapters/facebook/marketplace-extra.js.map +1 -0
  80. package/dist/adapters/flathub/apps.d.ts +17 -0
  81. package/dist/adapters/flathub/apps.d.ts.map +1 -0
  82. package/dist/adapters/flathub/apps.js +220 -0
  83. package/dist/adapters/flathub/apps.js.map +1 -0
  84. package/dist/adapters/goproxy/module.d.ts +24 -0
  85. package/dist/adapters/goproxy/module.d.ts.map +1 -0
  86. package/dist/adapters/goproxy/module.js +221 -0
  87. package/dist/adapters/goproxy/module.js.map +1 -0
  88. package/dist/adapters/grok/web.d.ts +29 -0
  89. package/dist/adapters/grok/web.d.ts.map +1 -0
  90. package/dist/adapters/grok/web.js +553 -0
  91. package/dist/adapters/grok/web.js.map +1 -0
  92. package/dist/adapters/hackernews/read.d.ts +31 -0
  93. package/dist/adapters/hackernews/read.d.ts.map +1 -0
  94. package/dist/adapters/hackernews/read.js +201 -0
  95. package/dist/adapters/hackernews/read.js.map +1 -0
  96. package/dist/adapters/hf/paper.d.ts +22 -0
  97. package/dist/adapters/hf/paper.d.ts.map +1 -0
  98. package/dist/adapters/hf/paper.js +112 -0
  99. package/dist/adapters/hf/paper.js.map +1 -0
  100. package/dist/adapters/homebrew/packages.d.ts +52 -0
  101. package/dist/adapters/homebrew/packages.d.ts.map +1 -0
  102. package/dist/adapters/homebrew/packages.js +240 -0
  103. package/dist/adapters/homebrew/packages.js.map +1 -0
  104. package/dist/adapters/indeed/jobs.d.ts +38 -0
  105. package/dist/adapters/indeed/jobs.d.ts.map +1 -0
  106. package/dist/adapters/indeed/jobs.js +300 -0
  107. package/dist/adapters/indeed/jobs.js.map +1 -0
  108. package/dist/adapters/instagram/collections.d.ts +9 -0
  109. package/dist/adapters/instagram/collections.d.ts.map +1 -0
  110. package/dist/adapters/instagram/collections.js +174 -0
  111. package/dist/adapters/instagram/collections.js.map +1 -0
  112. package/dist/adapters/lichess/players.d.ts +46 -0
  113. package/dist/adapters/lichess/players.d.ts.map +1 -0
  114. package/dist/adapters/lichess/players.js +221 -0
  115. package/dist/adapters/lichess/players.js.map +1 -0
  116. package/dist/adapters/lobsters/read-domain.d.ts +35 -0
  117. package/dist/adapters/lobsters/read-domain.d.ts.map +1 -0
  118. package/dist/adapters/lobsters/read-domain.js +306 -0
  119. package/dist/adapters/lobsters/read-domain.js.map +1 -0
  120. package/dist/adapters/maven/artifact.d.ts +30 -0
  121. package/dist/adapters/maven/artifact.d.ts.map +1 -0
  122. package/dist/adapters/maven/artifact.js +121 -0
  123. package/dist/adapters/maven/artifact.js.map +1 -0
  124. package/dist/adapters/mdn/search.d.ts +11 -0
  125. package/dist/adapters/mdn/search.d.ts.map +1 -0
  126. package/dist/adapters/mdn/search.js +115 -0
  127. package/dist/adapters/mdn/search.js.map +1 -0
  128. package/dist/adapters/medium/tag.d.ts +15 -0
  129. package/dist/adapters/medium/tag.d.ts.map +1 -0
  130. package/dist/adapters/medium/tag.js +148 -0
  131. package/dist/adapters/medium/tag.js.map +1 -0
  132. package/dist/adapters/npm/package.d.ts +32 -0
  133. package/dist/adapters/npm/package.d.ts.map +1 -0
  134. package/dist/adapters/npm/package.js +141 -0
  135. package/dist/adapters/npm/package.js.map +1 -0
  136. package/dist/adapters/nuget/package.d.ts +34 -0
  137. package/dist/adapters/nuget/package.d.ts.map +1 -0
  138. package/dist/adapters/nuget/package.js +135 -0
  139. package/dist/adapters/nuget/package.js.map +1 -0
  140. package/dist/adapters/nvd/cve.d.ts +42 -0
  141. package/dist/adapters/nvd/cve.d.ts.map +1 -0
  142. package/dist/adapters/nvd/cve.js +132 -0
  143. package/dist/adapters/nvd/cve.js.map +1 -0
  144. package/dist/adapters/oeis/sequences.d.ts +14 -0
  145. package/dist/adapters/oeis/sequences.d.ts.map +1 -0
  146. package/dist/adapters/oeis/sequences.js +219 -0
  147. package/dist/adapters/oeis/sequences.js.map +1 -0
  148. package/dist/adapters/openalex/works.d.ts +43 -0
  149. package/dist/adapters/openalex/works.d.ts.map +1 -0
  150. package/dist/adapters/openalex/works.js +267 -0
  151. package/dist/adapters/openalex/works.js.map +1 -0
  152. package/dist/adapters/openfda/records.d.ts +18 -0
  153. package/dist/adapters/openfda/records.d.ts.map +1 -0
  154. package/dist/adapters/openfda/records.js +209 -0
  155. package/dist/adapters/openfda/records.js.map +1 -0
  156. package/dist/adapters/openreview/papers.d.ts +34 -0
  157. package/dist/adapters/openreview/papers.d.ts.map +1 -0
  158. package/dist/adapters/openreview/papers.js +463 -0
  159. package/dist/adapters/openreview/papers.js.map +1 -0
  160. package/dist/adapters/osv/security.d.ts +36 -0
  161. package/dist/adapters/osv/security.d.ts.map +1 -0
  162. package/dist/adapters/osv/security.js +247 -0
  163. package/dist/adapters/osv/security.js.map +1 -0
  164. package/dist/adapters/packagist/package.d.ts +31 -0
  165. package/dist/adapters/packagist/package.d.ts.map +1 -0
  166. package/dist/adapters/packagist/package.js +108 -0
  167. package/dist/adapters/packagist/package.js.map +1 -0
  168. package/dist/adapters/pubmed/articles.d.ts +31 -0
  169. package/dist/adapters/pubmed/articles.d.ts.map +1 -0
  170. package/dist/adapters/pubmed/articles.js +385 -0
  171. package/dist/adapters/pubmed/articles.js.map +1 -0
  172. package/dist/adapters/pypi/package.d.ts +38 -0
  173. package/dist/adapters/pypi/package.d.ts.map +1 -0
  174. package/dist/adapters/pypi/package.js +235 -0
  175. package/dist/adapters/pypi/package.js.map +1 -0
  176. package/dist/adapters/qwen/web.d.ts +26 -0
  177. package/dist/adapters/qwen/web.d.ts.map +1 -0
  178. package/dist/adapters/qwen/web.js +672 -0
  179. package/dist/adapters/qwen/web.js.map +1 -0
  180. package/dist/adapters/reddit/account.d.ts +12 -0
  181. package/dist/adapters/reddit/account.d.ts.map +1 -0
  182. package/dist/adapters/reddit/account.js +409 -0
  183. package/dist/adapters/reddit/account.js.map +1 -0
  184. package/dist/adapters/rednote/web.d.ts +30 -0
  185. package/dist/adapters/rednote/web.d.ts.map +1 -0
  186. package/dist/adapters/rednote/web.js +858 -0
  187. package/dist/adapters/rednote/web.js.map +1 -0
  188. package/dist/adapters/rest-countries/countries.d.ts +14 -0
  189. package/dist/adapters/rest-countries/countries.d.ts.map +1 -0
  190. package/dist/adapters/rest-countries/countries.js +231 -0
  191. package/dist/adapters/rest-countries/countries.js.map +1 -0
  192. package/dist/adapters/reuters/article-detail.d.ts +37 -0
  193. package/dist/adapters/reuters/article-detail.d.ts.map +1 -0
  194. package/dist/adapters/reuters/article-detail.js +139 -0
  195. package/dist/adapters/reuters/article-detail.js.map +1 -0
  196. package/dist/adapters/rfc/rfc.d.ts +11 -0
  197. package/dist/adapters/rfc/rfc.d.ts.map +1 -0
  198. package/dist/adapters/rfc/rfc.js +121 -0
  199. package/dist/adapters/rfc/rfc.js.map +1 -0
  200. package/dist/adapters/rubygems/gem.d.ts +26 -0
  201. package/dist/adapters/rubygems/gem.d.ts.map +1 -0
  202. package/dist/adapters/rubygems/gem.js +96 -0
  203. package/dist/adapters/rubygems/gem.js.map +1 -0
  204. package/dist/adapters/stackoverflow/questions.d.ts +79 -0
  205. package/dist/adapters/stackoverflow/questions.d.ts.map +1 -0
  206. package/dist/adapters/stackoverflow/questions.js +504 -0
  207. package/dist/adapters/stackoverflow/questions.js.map +1 -0
  208. package/dist/adapters/steam/app.d.ts +39 -0
  209. package/dist/adapters/steam/app.d.ts.map +1 -0
  210. package/dist/adapters/steam/app.js +165 -0
  211. package/dist/adapters/steam/app.js.map +1 -0
  212. package/dist/adapters/tiktok/creator-videos.d.ts +52 -0
  213. package/dist/adapters/tiktok/creator-videos.d.ts.map +1 -0
  214. package/dist/adapters/tiktok/creator-videos.js +267 -0
  215. package/dist/adapters/tiktok/creator-videos.js.map +1 -0
  216. package/dist/adapters/tvmaze/shows.d.ts +13 -0
  217. package/dist/adapters/tvmaze/shows.d.ts.map +1 -0
  218. package/dist/adapters/tvmaze/shows.js +240 -0
  219. package/dist/adapters/tvmaze/shows.js.map +1 -0
  220. package/dist/adapters/twitter/bookmark-folders.d.ts +33 -0
  221. package/dist/adapters/twitter/bookmark-folders.d.ts.map +1 -0
  222. package/dist/adapters/twitter/bookmark-folders.js +290 -0
  223. package/dist/adapters/twitter/bookmark-folders.js.map +1 -0
  224. package/dist/adapters/twitter/quote.d.ts +18 -0
  225. package/dist/adapters/twitter/quote.d.ts.map +1 -0
  226. package/dist/adapters/twitter/quote.js +285 -0
  227. package/dist/adapters/twitter/quote.js.map +1 -0
  228. package/dist/adapters/twitter/tweet-actions.d.ts +12 -0
  229. package/dist/adapters/twitter/tweet-actions.d.ts.map +1 -0
  230. package/dist/adapters/twitter/tweet-actions.js +145 -0
  231. package/dist/adapters/twitter/tweet-actions.js.map +1 -0
  232. package/dist/adapters/twitter/tweet-url.d.ts +15 -0
  233. package/dist/adapters/twitter/tweet-url.d.ts.map +1 -0
  234. package/dist/adapters/twitter/tweet-url.js +57 -0
  235. package/dist/adapters/twitter/tweet-url.js.map +1 -0
  236. package/dist/adapters/uisdc/news.d.ts +22 -0
  237. package/dist/adapters/uisdc/news.d.ts.map +1 -0
  238. package/dist/adapters/uisdc/news.js +91 -0
  239. package/dist/adapters/uisdc/news.js.map +1 -0
  240. package/dist/adapters/weibo/favorites-publish.d.ts +28 -0
  241. package/dist/adapters/weibo/favorites-publish.d.ts.map +1 -0
  242. package/dist/adapters/weibo/favorites-publish.js +356 -0
  243. package/dist/adapters/weibo/favorites-publish.js.map +1 -0
  244. package/dist/adapters/wikidata/entities.d.ts +15 -0
  245. package/dist/adapters/wikidata/entities.d.ts.map +1 -0
  246. package/dist/adapters/wikidata/entities.js +219 -0
  247. package/dist/adapters/wikidata/entities.js.map +1 -0
  248. package/dist/adapters/wikipedia/page.d.ts +21 -0
  249. package/dist/adapters/wikipedia/page.d.ts.map +1 -0
  250. package/dist/adapters/wikipedia/page.js +116 -0
  251. package/dist/adapters/wikipedia/page.js.map +1 -0
  252. package/dist/adapters/wttr/weather.d.ts +12 -0
  253. package/dist/adapters/wttr/weather.d.ts.map +1 -0
  254. package/dist/adapters/wttr/weather.js +207 -0
  255. package/dist/adapters/wttr/weather.js.map +1 -0
  256. package/dist/adapters/xianyu/publish.d.ts +31 -0
  257. package/dist/adapters/xianyu/publish.d.ts.map +1 -0
  258. package/dist/adapters/xianyu/publish.js +349 -0
  259. package/dist/adapters/xianyu/publish.js.map +1 -0
  260. package/dist/adapters/xiaohongshu/user-helpers.d.ts +2 -2
  261. package/dist/adapters/xiaohongshu/user-helpers.d.ts.map +1 -1
  262. package/dist/adapters/xiaohongshu/user-helpers.js +5 -4
  263. package/dist/adapters/xiaohongshu/user-helpers.js.map +1 -1
  264. package/dist/adapters/yuanbao/web.d.ts +27 -0
  265. package/dist/adapters/yuanbao/web.d.ts.map +1 -0
  266. package/dist/adapters/yuanbao/web.js +365 -0
  267. package/dist/adapters/yuanbao/web.js.map +1 -0
  268. package/dist/adapters/zhihu/collection.d.ts +33 -0
  269. package/dist/adapters/zhihu/collection.d.ts.map +1 -0
  270. package/dist/adapters/zhihu/collection.js +185 -0
  271. package/dist/adapters/zhihu/collection.js.map +1 -0
  272. package/dist/adapters/zlibrary/web.d.ts +19 -0
  273. package/dist/adapters/zlibrary/web.d.ts.map +1 -0
  274. package/dist/adapters/zlibrary/web.js +153 -0
  275. package/dist/adapters/zlibrary/web.js.map +1 -0
  276. package/dist/browser/daemon-client.js +2 -2
  277. package/dist/browser/daemon-client.js.map +1 -1
  278. package/dist/discovery/macos-dynamic.d.ts.map +1 -1
  279. package/dist/discovery/macos-dynamic.js +17 -3
  280. package/dist/discovery/macos-dynamic.js.map +1 -1
  281. package/dist/manifest-compact.txt +10 -10
  282. package/dist/manifest-search.json +1 -1
  283. package/dist/manifest.json +5130 -112
  284. package/dist/transport/refs.d.ts +5 -3
  285. package/dist/transport/refs.d.ts.map +1 -1
  286. package/dist/transport/refs.js +8 -1
  287. package/dist/transport/refs.js.map +1 -1
  288. package/dist/transport/sidecar-binary.d.ts +7 -0
  289. package/dist/transport/sidecar-binary.d.ts.map +1 -1
  290. package/dist/transport/sidecar-binary.js +28 -8
  291. package/dist/transport/sidecar-binary.js.map +1 -1
  292. package/package.json +4 -3
  293. package/server.json +3 -3
  294. package/skills/unicli/SKILL.md +1 -1
  295. package/skills/unicli-claude-code/SKILL.md +1 -1
  296. package/skills/unicli-hermes/SKILL.md +1 -1
  297. package/src/adapters/1point3acres/forum.test.ts +300 -0
  298. package/src/adapters/1point3acres/forum.ts +852 -0
  299. package/src/adapters/aibase/news.test.ts +42 -0
  300. package/src/adapters/aibase/news.ts +118 -0
  301. package/src/adapters/arxiv/papers.test.ts +59 -0
  302. package/src/adapters/arxiv/papers.ts +226 -0
  303. package/src/adapters/bbc/topic.test.ts +52 -0
  304. package/src/adapters/bbc/topic.ts +149 -0
  305. package/src/adapters/chatgpt/web.test.ts +121 -0
  306. package/src/adapters/chatgpt/web.ts +286 -0
  307. package/src/adapters/claude/web.test.ts +206 -0
  308. package/src/adapters/claude/web.ts +684 -0
  309. package/src/adapters/codex/projects.test.ts +77 -0
  310. package/src/adapters/codex/projects.ts +178 -0
  311. package/src/adapters/coingecko/markets.test.ts +156 -0
  312. package/src/adapters/coingecko/markets.ts +574 -0
  313. package/src/adapters/coupang/product.test.ts +111 -0
  314. package/src/adapters/coupang/product.ts +256 -0
  315. package/src/adapters/crates/registry.test.ts +89 -0
  316. package/src/adapters/crates/registry.ts +247 -0
  317. package/src/adapters/ctrip/travel.test.ts +359 -0
  318. package/src/adapters/ctrip/travel.ts +792 -0
  319. package/src/adapters/dblp/publications.test.ts +123 -0
  320. package/src/adapters/dblp/publications.ts +494 -0
  321. package/src/adapters/deepseek/web.test.ts +69 -0
  322. package/src/adapters/deepseek/web.ts +78 -1
  323. package/src/adapters/defillama/protocols.test.ts +75 -0
  324. package/src/adapters/defillama/protocols.ts +253 -0
  325. package/src/adapters/devto/read.test.ts +49 -0
  326. package/src/adapters/devto/read.ts +145 -0
  327. package/src/adapters/dianping/shop.test.ts +134 -0
  328. package/src/adapters/dianping/shop.ts +261 -0
  329. package/src/adapters/dockerhub/registry.test.ts +97 -0
  330. package/src/adapters/dockerhub/registry.ts +223 -0
  331. package/src/adapters/endoflife/product.test.ts +50 -0
  332. package/src/adapters/endoflife/product.ts +128 -0
  333. package/src/adapters/facebook/marketplace-extra.test.ts +132 -0
  334. package/src/adapters/facebook/marketplace-extra.ts +213 -0
  335. package/src/adapters/flathub/apps.test.ts +85 -0
  336. package/src/adapters/flathub/apps.ts +254 -0
  337. package/src/adapters/goproxy/module.test.ts +72 -0
  338. package/src/adapters/goproxy/module.ts +258 -0
  339. package/src/adapters/grok/web.test.ts +181 -0
  340. package/src/adapters/grok/web.ts +640 -0
  341. package/src/adapters/hackernews/read.test.ts +68 -0
  342. package/src/adapters/hackernews/read.ts +265 -0
  343. package/src/adapters/hf/paper.test.ts +48 -0
  344. package/src/adapters/hf/paper.ts +138 -0
  345. package/src/adapters/homebrew/packages.test.ts +109 -0
  346. package/src/adapters/homebrew/packages.ts +304 -0
  347. package/src/adapters/indeed/jobs.test.ts +230 -0
  348. package/src/adapters/indeed/jobs.ts +375 -0
  349. package/src/adapters/instagram/collections.test.ts +94 -0
  350. package/src/adapters/instagram/collections.ts +206 -0
  351. package/src/adapters/lichess/players.test.ts +99 -0
  352. package/src/adapters/lichess/players.ts +277 -0
  353. package/src/adapters/lobsters/read-domain.test.ts +121 -0
  354. package/src/adapters/lobsters/read-domain.ts +400 -0
  355. package/src/adapters/maven/artifact.test.ts +67 -0
  356. package/src/adapters/maven/artifact.ts +155 -0
  357. package/src/adapters/mdn/search.test.ts +39 -0
  358. package/src/adapters/mdn/search.ts +133 -0
  359. package/src/adapters/medium/tag.test.ts +64 -0
  360. package/src/adapters/medium/tag.ts +164 -0
  361. package/src/adapters/npm/package.test.ts +53 -0
  362. package/src/adapters/npm/package.ts +177 -0
  363. package/src/adapters/nuget/package.test.ts +102 -0
  364. package/src/adapters/nuget/package.ts +193 -0
  365. package/src/adapters/nvd/cve.test.ts +66 -0
  366. package/src/adapters/nvd/cve.ts +182 -0
  367. package/src/adapters/oeis/sequences.test.ts +71 -0
  368. package/src/adapters/oeis/sequences.ts +234 -0
  369. package/src/adapters/openalex/works.test.ts +99 -0
  370. package/src/adapters/openalex/works.ts +319 -0
  371. package/src/adapters/openfda/records.test.ts +90 -0
  372. package/src/adapters/openfda/records.ts +239 -0
  373. package/src/adapters/openreview/papers.test.ts +139 -0
  374. package/src/adapters/openreview/papers.ts +560 -0
  375. package/src/adapters/osv/security.test.ts +91 -0
  376. package/src/adapters/osv/security.ts +298 -0
  377. package/src/adapters/packagist/package.test.ts +62 -0
  378. package/src/adapters/packagist/package.ts +146 -0
  379. package/src/adapters/pubmed/articles.test.ts +96 -0
  380. package/src/adapters/pubmed/articles.ts +497 -0
  381. package/src/adapters/pypi/package.test.ts +131 -0
  382. package/src/adapters/pypi/package.ts +297 -0
  383. package/src/adapters/qwen/web.test.ts +176 -0
  384. package/src/adapters/qwen/web.ts +758 -0
  385. package/src/adapters/reddit/account.test.ts +56 -0
  386. package/src/adapters/reddit/account.ts +493 -0
  387. package/src/adapters/rednote/web.test.ts +354 -0
  388. package/src/adapters/rednote/web.ts +968 -0
  389. package/src/adapters/rest-countries/countries.test.ts +80 -0
  390. package/src/adapters/rest-countries/countries.ts +271 -0
  391. package/src/adapters/reuters/article-detail.test.ts +65 -0
  392. package/src/adapters/reuters/article-detail.ts +186 -0
  393. package/src/adapters/rfc/rfc.test.ts +37 -0
  394. package/src/adapters/rfc/rfc.ts +133 -0
  395. package/src/adapters/rubygems/gem.test.ts +43 -0
  396. package/src/adapters/rubygems/gem.ts +126 -0
  397. package/src/adapters/stackoverflow/questions.test.ts +207 -0
  398. package/src/adapters/stackoverflow/questions.ts +765 -0
  399. package/src/adapters/steam/app.test.ts +68 -0
  400. package/src/adapters/steam/app.ts +218 -0
  401. package/src/adapters/tiktok/creator-videos.test.ts +158 -0
  402. package/src/adapters/tiktok/creator-videos.ts +370 -0
  403. package/src/adapters/tvmaze/shows.test.ts +93 -0
  404. package/src/adapters/tvmaze/shows.ts +271 -0
  405. package/src/adapters/twitter/bookmark-folders.test.ts +164 -0
  406. package/src/adapters/twitter/bookmark-folders.ts +366 -0
  407. package/src/adapters/twitter/quote.test.ts +157 -0
  408. package/src/adapters/twitter/quote.ts +332 -0
  409. package/src/adapters/twitter/tweet-actions.test.ts +51 -0
  410. package/src/adapters/twitter/tweet-actions.ts +187 -0
  411. package/src/adapters/twitter/tweet-url.ts +65 -0
  412. package/src/adapters/uisdc/news.test.ts +46 -0
  413. package/src/adapters/uisdc/news.ts +111 -0
  414. package/src/adapters/weibo/favorites-publish.test.ts +177 -0
  415. package/src/adapters/weibo/favorites-publish.ts +426 -0
  416. package/src/adapters/wikidata/entities.test.ts +103 -0
  417. package/src/adapters/wikidata/entities.ts +253 -0
  418. package/src/adapters/wikipedia/page.test.ts +49 -0
  419. package/src/adapters/wikipedia/page.ts +158 -0
  420. package/src/adapters/wttr/weather.test.ts +99 -0
  421. package/src/adapters/wttr/weather.ts +239 -0
  422. package/src/adapters/xianyu/publish.test.ts +210 -0
  423. package/src/adapters/xianyu/publish.ts +420 -0
  424. package/src/adapters/xiaohongshu/user-helpers.ts +5 -2
  425. package/src/adapters/yuanbao/web.test.ts +144 -0
  426. package/src/adapters/yuanbao/web.ts +435 -0
  427. package/src/adapters/zhihu/collection.test.ts +96 -0
  428. package/src/adapters/zhihu/collection.ts +239 -0
  429. package/src/adapters/zlibrary/web.test.ts +104 -0
  430. package/src/adapters/zlibrary/web.ts +192 -0
@@ -0,0 +1,80 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ joinCountryCurrencies,
4
+ joinCountryLanguages,
5
+ mapCountryRows,
6
+ requireRestCountriesLimit,
7
+ requireRestCountriesRegion,
8
+ } from "./countries.js";
9
+
10
+ describe("rest-countries agent-facing commands", () => {
11
+ it("validates region and limit arguments", () => {
12
+ expect(requireRestCountriesRegion(" Asia ")).toBe("asia");
13
+ expect(() => requireRestCountriesRegion("middle-earth")).toThrow(
14
+ "not supported",
15
+ );
16
+ expect(requireRestCountriesLimit(undefined, 25, 250)).toBe(25);
17
+ expect(requireRestCountriesLimit("250", 25, 250)).toBe(250);
18
+ expect(() => requireRestCountriesLimit("251", 25, 250)).toThrow(
19
+ "rest-countries limit must",
20
+ );
21
+ });
22
+
23
+ it("joins currencies and languages from REST Countries maps", () => {
24
+ expect(
25
+ joinCountryCurrencies({
26
+ JPY: { name: "Japanese yen", symbol: "yen" },
27
+ USD: {},
28
+ }),
29
+ ).toBe("JPY (Japanese yen), USD");
30
+ expect(joinCountryLanguages({ jpn: "Japanese", eng: "English" })).toBe(
31
+ "Japanese, English",
32
+ );
33
+ });
34
+
35
+ it("maps and sorts country rows by population", () => {
36
+ expect(
37
+ mapCountryRows(
38
+ [
39
+ {
40
+ name: { common: "Small", official: "Small State" },
41
+ cca2: "SS",
42
+ cca3: "SML",
43
+ population: 10,
44
+ latlng: [1, 2],
45
+ capital: ["One"],
46
+ timezones: ["UTC"],
47
+ languages: { eng: "English" },
48
+ currencies: { USD: { name: "United States dollar" } },
49
+ independent: true,
50
+ unMember: false,
51
+ landlocked: true,
52
+ },
53
+ {
54
+ name: { common: "Large", official: "Large State" },
55
+ cca2: "LS",
56
+ cca3: "LRG",
57
+ population: "20",
58
+ },
59
+ ],
60
+ 2,
61
+ ),
62
+ ).toMatchObject([
63
+ {
64
+ rank: 1,
65
+ commonName: "Large",
66
+ cca3: "LRG",
67
+ population: 20,
68
+ url: "https://restcountries.com/v3.1/alpha/lrg",
69
+ },
70
+ {
71
+ rank: 2,
72
+ commonName: "Small",
73
+ latitude: 1,
74
+ languages: "English",
75
+ currencies: "USD (United States dollar)",
76
+ independent: true,
77
+ },
78
+ ]);
79
+ });
80
+ });
@@ -0,0 +1,271 @@
1
+ /**
2
+ * @owner src/adapters/rest-countries/countries.ts
3
+ * @does Register agent-facing REST Countries country and region commands.
4
+ * @needs REST Countries v3.1 public API, bounded row limits, validated region names.
5
+ * @feeds surface coverage ledger, country metadata rows, public reference data search.
6
+ * @breaks REST Countries API drift, unbounded queries, or silent empty rows hide lookup failures.
7
+ */
8
+
9
+ import { cli, Strategy } from "../../registry.js";
10
+
11
+ const API_BASE = "https://restcountries.com/v3.1";
12
+ const COUNTRY_FIELDS = [
13
+ "name",
14
+ "cca2",
15
+ "cca3",
16
+ "ccn3",
17
+ "capital",
18
+ "region",
19
+ "subregion",
20
+ "population",
21
+ "area",
22
+ "languages",
23
+ "currencies",
24
+ "flag",
25
+ "latlng",
26
+ "timezones",
27
+ "independent",
28
+ "unMember",
29
+ "landlocked",
30
+ ].join(",");
31
+ const REGIONS = new Set([
32
+ "africa",
33
+ "americas",
34
+ "asia",
35
+ "europe",
36
+ "oceania",
37
+ "antarctic",
38
+ ]);
39
+ const COLUMNS = [
40
+ "rank",
41
+ "commonName",
42
+ "officialName",
43
+ "cca2",
44
+ "cca3",
45
+ "ccn3",
46
+ "capital",
47
+ "region",
48
+ "subregion",
49
+ "population",
50
+ "area",
51
+ "languages",
52
+ "currencies",
53
+ "latitude",
54
+ "longitude",
55
+ "timezones",
56
+ "independent",
57
+ "unMember",
58
+ "landlocked",
59
+ "flag",
60
+ "url",
61
+ ];
62
+
63
+ function nonEmptyString(value: unknown, label: string): string {
64
+ const text = String(value ?? "").trim();
65
+ if (!text) throw new Error(`rest-countries ${label} cannot be empty.`);
66
+ return text;
67
+ }
68
+
69
+ export function requireRestCountriesLimit(
70
+ value: unknown,
71
+ fallback: number,
72
+ max: number,
73
+ label = "limit",
74
+ ): number {
75
+ if (value === undefined || value === null || value === "") return fallback;
76
+ const n = Number(value);
77
+ if (!Number.isInteger(n) || n < 1 || n > max) {
78
+ throw new Error(
79
+ `rest-countries ${label} must be an integer in [1, ${max}]. Got: ${String(value)}`,
80
+ );
81
+ }
82
+ return n;
83
+ }
84
+
85
+ export function requireRestCountriesRegion(value: unknown): string {
86
+ const region = nonEmptyString(value, "region").toLowerCase();
87
+ if (!REGIONS.has(region)) {
88
+ throw new Error(
89
+ `rest-countries region "${String(value)}" is not supported. Allowed: ${[...REGIONS].join(", ")}.`,
90
+ );
91
+ }
92
+ return region;
93
+ }
94
+
95
+ function stringField(value: unknown): string {
96
+ return typeof value === "string" ? value : "";
97
+ }
98
+
99
+ function numberField(value: unknown): number | null {
100
+ const n =
101
+ typeof value === "number"
102
+ ? value
103
+ : typeof value === "string" && value.trim()
104
+ ? Number(value)
105
+ : NaN;
106
+ return Number.isFinite(n) ? n : null;
107
+ }
108
+
109
+ function booleanField(value: unknown): boolean | null {
110
+ return typeof value === "boolean" ? value : null;
111
+ }
112
+
113
+ function joinStringArray(value: unknown): string {
114
+ return Array.isArray(value)
115
+ ? value.filter((item) => typeof item === "string" && item.trim()).join(", ")
116
+ : "";
117
+ }
118
+
119
+ export function joinCountryCurrencies(value: unknown): string {
120
+ if (!value || typeof value !== "object") return "";
121
+ return Object.entries(value as Record<string, { name?: unknown }>)
122
+ .map(([code, info]) => {
123
+ const name =
124
+ info && typeof info.name === "string" && info.name.trim()
125
+ ? info.name.trim()
126
+ : "";
127
+ return name ? `${code} (${name})` : code;
128
+ })
129
+ .join(", ");
130
+ }
131
+
132
+ export function joinCountryLanguages(value: unknown): string {
133
+ if (!value || typeof value !== "object") return "";
134
+ return Object.values(value as Record<string, unknown>)
135
+ .filter(
136
+ (item): item is string =>
137
+ typeof item === "string" && item.trim().length > 0,
138
+ )
139
+ .join(", ");
140
+ }
141
+
142
+ export function mapCountryRow(
143
+ country: Record<string, unknown>,
144
+ ): Record<string, unknown> {
145
+ const name = (country.name ?? {}) as Record<string, unknown>;
146
+ const latlng = Array.isArray(country.latlng) ? country.latlng : [];
147
+ const cca3 = stringField(country.cca3);
148
+ return {
149
+ commonName: stringField(name.common),
150
+ officialName: stringField(name.official),
151
+ cca2: stringField(country.cca2),
152
+ cca3,
153
+ ccn3: stringField(country.ccn3),
154
+ capital: joinStringArray(country.capital),
155
+ region: stringField(country.region),
156
+ subregion: stringField(country.subregion),
157
+ population: numberField(country.population),
158
+ area: numberField(country.area),
159
+ languages: joinCountryLanguages(country.languages),
160
+ currencies: joinCountryCurrencies(country.currencies),
161
+ latitude: numberField(latlng[0]),
162
+ longitude: numberField(latlng[1]),
163
+ timezones: joinStringArray(country.timezones),
164
+ independent: booleanField(country.independent),
165
+ unMember: booleanField(country.unMember),
166
+ landlocked: booleanField(country.landlocked),
167
+ flag: stringField(country.flag),
168
+ url: cca3
169
+ ? `https://restcountries.com/v3.1/alpha/${cca3.toLowerCase()}`
170
+ : "",
171
+ };
172
+ }
173
+
174
+ export function mapCountryRows(
175
+ rows: Array<Record<string, unknown>>,
176
+ limit: number,
177
+ ): Array<Record<string, unknown>> {
178
+ const sorted = [...rows].sort(
179
+ (a, b) =>
180
+ (numberField(b.population) ?? 0) - (numberField(a.population) ?? 0),
181
+ );
182
+ return sorted.slice(0, limit).map((country, index) => ({
183
+ rank: index + 1,
184
+ ...mapCountryRow(country),
185
+ }));
186
+ }
187
+
188
+ async function fetchJson(url: URL | string, label: string): Promise<unknown> {
189
+ const response = await fetch(url, {
190
+ headers: {
191
+ accept: "application/json",
192
+ "user-agent":
193
+ "unicli-rest-countries (https://github.com/olo-dot-io/Uni-CLI)",
194
+ },
195
+ });
196
+ if (response.status === 404) throw new Error(`${label} returned no result.`);
197
+ if (response.status === 429) throw new Error(`${label} returned HTTP 429.`);
198
+ if (!response.ok)
199
+ throw new Error(`${label} returned HTTP ${response.status}.`);
200
+ return response.json();
201
+ }
202
+
203
+ cli({
204
+ site: "rest-countries",
205
+ name: "country",
206
+ description: "Look up countries by common or official name",
207
+ domain: "restcountries.com",
208
+ strategy: Strategy.PUBLIC,
209
+ args: [
210
+ {
211
+ name: "name",
212
+ type: "str",
213
+ required: true,
214
+ positional: true,
215
+ description: "Country name",
216
+ },
217
+ { name: "limit", type: "int", default: 25, description: "Max rows" },
218
+ ],
219
+ columns: COLUMNS,
220
+ func: async (_page, kwargs) => {
221
+ const name = nonEmptyString(kwargs.name, "name");
222
+ const limit = requireRestCountriesLimit(kwargs.limit, 25, 250);
223
+ const url = new URL(`${API_BASE}/name/${encodeURIComponent(name)}`);
224
+ url.searchParams.set("fields", COUNTRY_FIELDS);
225
+ const body = await fetchJson(url, "rest-countries country");
226
+ const rows = mapCountryRows(
227
+ Array.isArray(body) ? (body as Array<Record<string, unknown>>) : [],
228
+ limit,
229
+ );
230
+ if (rows.length === 0) {
231
+ throw new Error(`rest-countries country returned no rows for "${name}".`);
232
+ }
233
+ return rows;
234
+ },
235
+ });
236
+
237
+ cli({
238
+ site: "rest-countries",
239
+ name: "region",
240
+ description: "List countries in a REST Countries region",
241
+ domain: "restcountries.com",
242
+ strategy: Strategy.PUBLIC,
243
+ args: [
244
+ {
245
+ name: "region",
246
+ type: "str",
247
+ required: true,
248
+ positional: true,
249
+ description: "Region name",
250
+ },
251
+ { name: "limit", type: "int", default: 250, description: "Max rows" },
252
+ ],
253
+ columns: COLUMNS,
254
+ func: async (_page, kwargs) => {
255
+ const region = requireRestCountriesRegion(kwargs.region);
256
+ const limit = requireRestCountriesLimit(kwargs.limit, 250, 250);
257
+ const url = new URL(`${API_BASE}/region/${encodeURIComponent(region)}`);
258
+ url.searchParams.set("fields", COUNTRY_FIELDS);
259
+ const body = await fetchJson(url, "rest-countries region");
260
+ const rows = mapCountryRows(
261
+ Array.isArray(body) ? (body as Array<Record<string, unknown>>) : [],
262
+ limit,
263
+ );
264
+ if (rows.length === 0) {
265
+ throw new Error(
266
+ `rest-countries region returned no rows for "${region}".`,
267
+ );
268
+ }
269
+ return rows;
270
+ },
271
+ });
@@ -0,0 +1,65 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ extractReutersBody,
4
+ mapReutersArticleDetail,
5
+ requireReutersArticleUrl,
6
+ reutersPathFromUrl,
7
+ } from "./article-detail.js";
8
+
9
+ describe("reuters agent-facing article-detail command", () => {
10
+ it("validates Reuters URLs and extracts API URL paths", () => {
11
+ const url =
12
+ "https://www.reuters.com/world/us/fed-chair-says-rates-2026-05-12/?utm_source=x";
13
+ expect(requireReutersArticleUrl(` ${url} `)).toBe(url);
14
+ expect(reutersPathFromUrl(url)).toBe(
15
+ "/world/us/fed-chair-says-rates-2026-05-12/?utm_source=x",
16
+ );
17
+ expect(() => requireReutersArticleUrl("https://example.com/a")).toThrow(
18
+ "reuters.com",
19
+ );
20
+ });
21
+
22
+ it("maps article metadata and body to stable columns", () => {
23
+ expect(
24
+ mapReutersArticleDetail(
25
+ {
26
+ title: "Fed chair says rates hold",
27
+ display_date: "2026-05-12T01:02:03Z",
28
+ taxonomy: { section: { name: "Markets", path: "/markets/" } },
29
+ authors: [{ name: "Jane Doe" }, { byline: "John Roe" }],
30
+ description: { basic: "Short summary" },
31
+ word_count: 450,
32
+ canonical_url: "/markets/rates-2026-05-12/",
33
+ content_elements: [
34
+ { type: "text", content: "First paragraph." },
35
+ { type: "image", content: "ignored" },
36
+ { type: "text", content: "Second paragraph." },
37
+ ],
38
+ },
39
+ "https://www.reuters.com/fallback",
40
+ ),
41
+ ).toEqual({
42
+ title: "Fed chair says rates hold",
43
+ date: "2026-05-12",
44
+ section: "Markets",
45
+ section_path: "/markets/",
46
+ authors: "Jane Doe, John Roe",
47
+ description: "Short summary",
48
+ word_count: 450,
49
+ url: "https://www.reuters.com/markets/rates-2026-05-12/",
50
+ body: "First paragraph.\n\nSecond paragraph.",
51
+ });
52
+ });
53
+
54
+ it("extracts only text content elements", () => {
55
+ expect(
56
+ extractReutersBody({
57
+ content_elements: [
58
+ { type: "text", content: "A" },
59
+ { type: "raw_html", content: "<p>B</p>" },
60
+ { type: "text", content: "C" },
61
+ ],
62
+ }),
63
+ ).toBe("A\n\nC");
64
+ });
65
+ });
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @owner src/adapters/reuters/article-detail.ts
3
+ * @does Register agent-facing Reuters article-detail using the public Fusion content API.
4
+ * @needs Reuters article URLs, article-by-id-or-url-v1 envelope, robust article body extraction.
5
+ * @feeds surface coverage ledger, Reuters search-to-detail workflow, article reading surfaces.
6
+ * @breaks Reuters API query shape drift or content element changes can hide article bodies.
7
+ */
8
+
9
+ import { cli, Strategy } from "../../registry.js";
10
+
11
+ const REUTERS_API =
12
+ "https://www.reuters.com/pf/api/v3/content/fetch/article-by-id-or-url-v1";
13
+ const REUTERS_HOST = /^https?:\/\/(?:www\.)?reuters\.com\//i;
14
+
15
+ interface ReutersAuthor {
16
+ name?: unknown;
17
+ byline?: unknown;
18
+ }
19
+
20
+ interface ReutersContentElement {
21
+ type?: unknown;
22
+ content?: unknown;
23
+ }
24
+
25
+ interface ReutersArticle {
26
+ title?: unknown;
27
+ headlines?: { basic?: unknown };
28
+ display_date?: unknown;
29
+ published_time?: unknown;
30
+ taxonomy?: {
31
+ section?: {
32
+ name?: unknown;
33
+ path?: unknown;
34
+ };
35
+ };
36
+ authors?: unknown;
37
+ description?: { basic?: unknown };
38
+ subheadlines?: { basic?: unknown };
39
+ word_count?: unknown;
40
+ canonical_url?: unknown;
41
+ content_elements?: unknown;
42
+ }
43
+
44
+ interface ReutersEnvelope {
45
+ result?: ReutersArticle;
46
+ }
47
+
48
+ function stringField(value: unknown): string {
49
+ return typeof value === "string" ? value.trim() : "";
50
+ }
51
+
52
+ function dateOnly(value: unknown): string {
53
+ return stringField(value).split("T")[0] ?? "";
54
+ }
55
+
56
+ function absoluteReutersUrl(value: unknown, fallback: string): string {
57
+ const raw = stringField(value);
58
+ if (!raw) return fallback;
59
+ return /^https?:\/\//i.test(raw) ? raw : `https://www.reuters.com${raw}`;
60
+ }
61
+
62
+ function authorNames(value: unknown): string {
63
+ if (!Array.isArray(value)) return "";
64
+ return value
65
+ .map((author: ReutersAuthor | string) => {
66
+ return typeof author === "string"
67
+ ? author
68
+ : stringField(author.name) || stringField(author.byline);
69
+ })
70
+ .map((name) => name.trim())
71
+ .filter(Boolean)
72
+ .join(", ");
73
+ }
74
+
75
+ export function requireReutersArticleUrl(value: unknown): string {
76
+ const url = String(value ?? "").trim();
77
+ if (!url) throw new Error("Reuters article URL cannot be empty.");
78
+ if (!REUTERS_HOST.test(url)) {
79
+ throw new Error(`Reuters article URL must be on reuters.com: ${url}`);
80
+ }
81
+ return url;
82
+ }
83
+
84
+ export function reutersPathFromUrl(url: string): string {
85
+ const parsed = new URL(url);
86
+ return `${parsed.pathname}${parsed.search}` || "/";
87
+ }
88
+
89
+ export function extractReutersBody(article: ReutersArticle): string {
90
+ const elements = Array.isArray(article.content_elements)
91
+ ? article.content_elements
92
+ : [];
93
+ return elements
94
+ .filter((element): element is ReutersContentElement => {
95
+ return typeof element === "object" && element !== null;
96
+ })
97
+ .filter((element) => element.type === "text")
98
+ .map((element) => stringField(element.content))
99
+ .filter(Boolean)
100
+ .join("\n\n");
101
+ }
102
+
103
+ export function mapReutersArticleDetail(
104
+ article: ReutersArticle | undefined,
105
+ fallbackUrl: string,
106
+ ): Record<string, unknown> {
107
+ if (!article) throw new Error("Reuters article-detail returned no result.");
108
+ const body = extractReutersBody(article);
109
+ const title =
110
+ stringField(article.title) || stringField(article.headlines?.basic);
111
+ if (!title && !body) {
112
+ throw new Error("Reuters article-detail returned no article body.");
113
+ }
114
+ return {
115
+ title,
116
+ date: dateOnly(article.display_date || article.published_time),
117
+ section: stringField(article.taxonomy?.section?.name),
118
+ section_path: stringField(article.taxonomy?.section?.path),
119
+ authors: authorNames(article.authors),
120
+ description:
121
+ stringField(article.description?.basic) ||
122
+ stringField(article.subheadlines?.basic),
123
+ word_count:
124
+ typeof article.word_count === "number" &&
125
+ Number.isFinite(article.word_count)
126
+ ? article.word_count
127
+ : null,
128
+ url: absoluteReutersUrl(article.canonical_url, fallbackUrl),
129
+ body,
130
+ };
131
+ }
132
+
133
+ async function fetchReutersArticle(url: string): Promise<ReutersArticle> {
134
+ const query = JSON.stringify({
135
+ url: reutersPathFromUrl(url),
136
+ website: "reuters",
137
+ });
138
+ const apiUrl = new URL(REUTERS_API);
139
+ apiUrl.searchParams.set("query", query);
140
+ const response = await fetch(apiUrl, {
141
+ headers: {
142
+ Accept: "application/json",
143
+ "User-Agent": "Mozilla/5.0 (compatible; Uni-CLI)",
144
+ },
145
+ });
146
+ if (!response.ok) {
147
+ throw new Error(`Reuters article-detail returned HTTP ${response.status}.`);
148
+ }
149
+ const envelope = (await response.json()) as ReutersEnvelope;
150
+ if (!envelope.result)
151
+ throw new Error("Reuters article-detail returned no result.");
152
+ return envelope.result;
153
+ }
154
+
155
+ cli({
156
+ site: "reuters",
157
+ name: "article-detail",
158
+ description: "Reuters article detail with title, metadata, and body text",
159
+ domain: "www.reuters.com",
160
+ strategy: Strategy.PUBLIC,
161
+ browser: false,
162
+ args: [
163
+ {
164
+ name: "url",
165
+ type: "str",
166
+ required: true,
167
+ positional: true,
168
+ description: "Reuters article URL",
169
+ },
170
+ ],
171
+ columns: [
172
+ "title",
173
+ "date",
174
+ "section",
175
+ "section_path",
176
+ "authors",
177
+ "description",
178
+ "word_count",
179
+ "url",
180
+ "body",
181
+ ],
182
+ func: async (_page, kwargs) => {
183
+ const url = requireReutersArticleUrl(kwargs.url);
184
+ return [mapReutersArticleDetail(await fetchReutersArticle(url), url)];
185
+ },
186
+ });
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { mapRfcRow, requireRfcNumber, trimRfcDate } from "./rfc.js";
3
+
4
+ describe("rfc agent-facing metadata command", () => {
5
+ it("validates RFC numbers", () => {
6
+ expect(requireRfcNumber("rfc9000")).toBe(9000);
7
+ expect(requireRfcNumber(791)).toBe(791);
8
+ expect(() => requireRfcNumber("9000bis")).toThrow("not valid");
9
+ });
10
+
11
+ it("normalizes RFC dates", () => {
12
+ expect(trimRfcDate("2022-02-19 08:46:51")).toBe("2022-02-19");
13
+ expect(trimRfcDate("no date")).toBeNull();
14
+ });
15
+
16
+ it("maps RFC metadata rows", () => {
17
+ expect(
18
+ mapRfcRow(9000, {
19
+ title: "QUIC: A UDP-Based Multiplexed and Secure Transport",
20
+ state: "RFC",
21
+ std_level: "Proposed Standard",
22
+ group: { name: "QUIC", type: "wg" },
23
+ pages: "151",
24
+ time: "2021-05-27T00:00:00+00:00",
25
+ authors: [{ name: "J. Iyengar" }, { name: "M. Thomson" }],
26
+ abstract: "This document defines the core of QUIC.",
27
+ }),
28
+ ).toMatchObject({
29
+ rfc: 9000,
30
+ group: "QUIC",
31
+ pages: 151,
32
+ published: "2021-05-27",
33
+ authors: "J. Iyengar, M. Thomson",
34
+ rfcEditorUrl: "https://www.rfc-editor.org/rfc/rfc9000",
35
+ });
36
+ });
37
+ });