@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,213 @@
1
+ /**
2
+ * @owner src/adapters/facebook/marketplace-extra.ts
3
+ * @does Register agent-facing Facebook Marketplace seller listings and inbox readers.
4
+ * @needs Logged-in www.facebook.com browser session with Marketplace access.
5
+ * @feeds surface coverage ledger and Marketplace selling/conversation workflows.
6
+ * @breaks Facebook Marketplace DOM text changes, login redirects, or unavailable Marketplace account access.
7
+ */
8
+
9
+ import { cli, Strategy } from "../../registry.js";
10
+ import type { IPage } from "../../types.js";
11
+
12
+ interface MarketplaceListingsResult {
13
+ authRequired?: unknown;
14
+ rows?: unknown;
15
+ }
16
+
17
+ interface MarketplaceListingRow {
18
+ title?: unknown;
19
+ price?: unknown;
20
+ status?: unknown;
21
+ listed?: unknown;
22
+ clicks?: unknown;
23
+ actions?: unknown;
24
+ }
25
+
26
+ interface MarketplaceInboxRow {
27
+ buyer?: unknown;
28
+ listing?: unknown;
29
+ snippet?: unknown;
30
+ time?: unknown;
31
+ unread?: unknown;
32
+ }
33
+
34
+ export function requireFacebookMarketplaceLimit(value: unknown): number {
35
+ const limit = Number(value ?? 20);
36
+ if (!Number.isInteger(limit) || limit <= 0) {
37
+ throw new Error("Facebook Marketplace limit must be a positive integer.");
38
+ }
39
+ if (limit > 100) {
40
+ throw new Error("Facebook Marketplace limit must be <= 100.");
41
+ }
42
+ return limit;
43
+ }
44
+
45
+ function requireBrowserPage(page: unknown, command: string): IPage {
46
+ if (!page || typeof page !== "object") {
47
+ throw new Error(`Browser session required for facebook ${command}.`);
48
+ }
49
+ return page as IPage;
50
+ }
51
+
52
+ function text(value: unknown): string {
53
+ return value == null ? "" : String(value);
54
+ }
55
+
56
+ function readRows(
57
+ result: MarketplaceListingsResult,
58
+ command: string,
59
+ ): unknown[] {
60
+ if (result?.authRequired) {
61
+ throw new Error(
62
+ `Facebook Marketplace ${command} requires an active signed-in Facebook session.`,
63
+ );
64
+ }
65
+ const rows = Array.isArray(result?.rows) ? result.rows : [];
66
+ if (!rows.length) {
67
+ throw new Error(
68
+ `No Facebook Marketplace ${command} rows were visible. Check Marketplace access for this account.`,
69
+ );
70
+ }
71
+ return rows;
72
+ }
73
+
74
+ function marketplaceListingsScript(): string {
75
+ return String.raw`(() => {
76
+ const clean = (value) => String(value || '').replace(/\u00a0/g, ' ').replace(/\s+/g, ' ').trim();
77
+ const allText = document.body?.innerText || '';
78
+ if (/log in|sign in/i.test(allText) && !/Marketplace/i.test(allText)) {
79
+ return { authRequired: true, rows: [] };
80
+ }
81
+ const lines = allText.split(/\n+/).map(clean).filter(Boolean);
82
+ const seen = new Set();
83
+ const rows = [];
84
+ for (let index = 1; index < lines.length; index += 1) {
85
+ if (!/^(?:CA\$|\$)\s*\d+/.test(lines[index])) continue;
86
+ const title = lines[index - 1];
87
+ if (!title || /^(Hide|All listings|Needs attention|Marketplace|Selling)$/i.test(title)) continue;
88
+ if (seen.has(title)) continue;
89
+ seen.add(title);
90
+ const windowLines = lines.slice(index, index + 12);
91
+ const status = windowLines.find((line) => /^(Active|Sold|Pending|Draft)$/i.test(line)) || '';
92
+ const listed = windowLines.find((line) => /Listed on\b/i.test(line))?.replace(/^·\s*/, '') || '';
93
+ const clickLine = windowLines.find((line) => /clicks? on listing/i.test(line)) || '';
94
+ const clickMatch = clickLine.match(/([\d,.]+)\s+clicks? on listing/i);
95
+ const actions = windowLines.filter((line) => /^(Mark as sold|Mark as available|Relist this item|Share|Boost listing)$/i.test(line));
96
+ rows.push({
97
+ title,
98
+ price: lines[index],
99
+ status,
100
+ listed,
101
+ clicks: clickMatch ? clickMatch[1] : '',
102
+ actions,
103
+ });
104
+ }
105
+ return { authRequired: false, rows };
106
+ })()`;
107
+ }
108
+
109
+ function marketplaceInboxScript(): string {
110
+ return String.raw`(() => {
111
+ const clean = (value) => String(value || '').replace(/[\u00a0\u202f]/g, ' ').replace(/\s+/g, ' ').trim();
112
+ const timeRe = /^(?:\d{1,2}:\d{2}\s?(?:AM|PM|am|pm|上午|下午)?|Mon|Tue|Wed|Thu|Fri|Sat|Sun|Today|Yesterday|\d+[mhdw]|\d+\s*(?:min|h|d|w))$/;
113
+ const allText = document.body?.innerText || '';
114
+ if (/log in|sign in/i.test(allText) && !/Marketplace/i.test(allText)) {
115
+ return { authRequired: true, rows: [] };
116
+ }
117
+ const lines = allText.split(/\n+/).map(clean).filter(Boolean);
118
+ const rows = [];
119
+ const seen = new Set();
120
+ const skipBuyer = /^(Marketplace|Browse all|Notifications|Inbox|Marketplace access|Buying|Selling|Create new listing|Create multiple listings|Location|Categories|Vehicles|Property Rentals|All|Pending payment|Paid|To be shipped|Shipped|Cash on delivery|Completed|Filter by label)$/i;
121
+ for (let index = 0; index < lines.length - 2; index += 1) {
122
+ const buyer = lines[index];
123
+ const meta = lines[index + 1];
124
+ if (skipBuyer.test(buyer) || !/^·\s+/.test(meta)) continue;
125
+ const listing = meta.replace(/^·\s*/, '');
126
+ if (!listing || /^Within\b/i.test(listing)) continue;
127
+ const snippet = lines[index + 2] || '';
128
+ const time = timeRe.test(lines[index + 3] || '') ? lines[index + 3] : '';
129
+ const key = buyer + '|' + listing;
130
+ if (seen.has(key)) continue;
131
+ seen.add(key);
132
+ const nearby = lines.slice(Math.max(0, index - 2), index + 5).join(' ');
133
+ rows.push({
134
+ buyer,
135
+ listing,
136
+ snippet,
137
+ time,
138
+ unread: /Unread/i.test(nearby),
139
+ });
140
+ }
141
+ return { authRequired: false, rows };
142
+ })()`;
143
+ }
144
+
145
+ cli({
146
+ site: "facebook",
147
+ name: "marketplace-listings",
148
+ description: "List your Facebook Marketplace seller listings",
149
+ domain: "www.facebook.com",
150
+ strategy: Strategy.COOKIE,
151
+ browser: true,
152
+ args: [{ name: "limit", type: "int", default: 20 }],
153
+ columns: ["index", "title", "price", "status", "listed", "clicks", "actions"],
154
+ func: async (page, kwargs) => {
155
+ const p = requireBrowserPage(page, "marketplace-listings");
156
+ const limit = requireFacebookMarketplaceLimit(kwargs.limit);
157
+ await p.goto("https://www.facebook.com/marketplace/you/selling/");
158
+ await p.wait(4);
159
+ const result = (await p.evaluate(
160
+ marketplaceListingsScript(),
161
+ )) as MarketplaceListingsResult;
162
+ return readRows(result, "seller listings")
163
+ .slice(0, limit)
164
+ .map((item, index) => {
165
+ const row = item as MarketplaceListingRow;
166
+ return {
167
+ index: index + 1,
168
+ title: text(row.title),
169
+ price: text(row.price),
170
+ status: text(row.status),
171
+ listed: text(row.listed),
172
+ clicks: text(row.clicks),
173
+ actions: Array.isArray(row.actions)
174
+ ? row.actions.map(text).join(", ")
175
+ : text(row.actions),
176
+ };
177
+ });
178
+ },
179
+ });
180
+
181
+ cli({
182
+ site: "facebook",
183
+ name: "marketplace-inbox",
184
+ description:
185
+ "List recent Facebook Marketplace buyer and seller conversations",
186
+ domain: "www.facebook.com",
187
+ strategy: Strategy.COOKIE,
188
+ browser: true,
189
+ args: [{ name: "limit", type: "int", default: 20 }],
190
+ columns: ["index", "buyer", "listing", "snippet", "time", "unread"],
191
+ func: async (page, kwargs) => {
192
+ const p = requireBrowserPage(page, "marketplace-inbox");
193
+ const limit = requireFacebookMarketplaceLimit(kwargs.limit);
194
+ await p.goto("https://www.facebook.com/marketplace/inbox/");
195
+ await p.wait(4);
196
+ const result = (await p.evaluate(
197
+ marketplaceInboxScript(),
198
+ )) as MarketplaceListingsResult;
199
+ return readRows(result, "inbox conversations")
200
+ .slice(0, limit)
201
+ .map((item, index) => {
202
+ const row = item as MarketplaceInboxRow;
203
+ return {
204
+ index: index + 1,
205
+ buyer: text(row.buyer),
206
+ listing: text(row.listing),
207
+ snippet: text(row.snippet),
208
+ time: text(row.time),
209
+ unread: Boolean(row.unread),
210
+ };
211
+ });
212
+ },
213
+ });
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ joinFlathubList,
4
+ mapFlathubAppRow,
5
+ mapFlathubSearchRows,
6
+ pickLatestFlathubRelease,
7
+ requireFlathubAppId,
8
+ requireFlathubLimit,
9
+ } from "./apps.js";
10
+
11
+ describe("flathub agent-facing app commands", () => {
12
+ it("validates limits and AppStream ids", () => {
13
+ expect(requireFlathubLimit(undefined)).toBe(25);
14
+ expect(requireFlathubLimit("100")).toBe(100);
15
+ expect(() => requireFlathubLimit("101")).toThrow("flathub limit must");
16
+ expect(requireFlathubAppId("org.mozilla.firefox")).toBe(
17
+ "org.mozilla.firefox",
18
+ );
19
+ expect(() => requireFlathubAppId("firefox")).toThrow("not valid");
20
+ });
21
+
22
+ it("joins lists and picks latest releases", () => {
23
+ expect(joinFlathubList(["one", "two"], 1)).toBe("one, (+1)");
24
+ expect(
25
+ pickLatestFlathubRelease([
26
+ { version: "1.0", timestamp: 1 },
27
+ { version: "2.0", timestamp: "2" },
28
+ ]),
29
+ ).toEqual({ date: "1970-01-01", version: "2.0" });
30
+ });
31
+
32
+ it("maps search rows", () => {
33
+ expect(
34
+ mapFlathubSearchRows(
35
+ [
36
+ {
37
+ app_id: "org.mozilla.firefox",
38
+ name: "Firefox",
39
+ summary: "Browser",
40
+ developer_name: "Mozilla",
41
+ project_license: "MPL-2.0",
42
+ is_free_license: true,
43
+ main_categories: "Network",
44
+ installs_last_month: "100",
45
+ updated_at: 1,
46
+ },
47
+ ],
48
+ 25,
49
+ ),
50
+ ).toMatchObject([
51
+ {
52
+ rank: 1,
53
+ appId: "org.mozilla.firefox",
54
+ name: "Firefox",
55
+ isFreeLicense: true,
56
+ installsLastMonth: 100,
57
+ updatedAt: "1970-01-01",
58
+ },
59
+ ]);
60
+ });
61
+
62
+ it("maps app rows", () => {
63
+ expect(
64
+ mapFlathubAppRow("org.mozilla.firefox", {
65
+ id: "org.mozilla.firefox",
66
+ name: "Firefox",
67
+ summary: "Browser",
68
+ developer_name: "Mozilla",
69
+ project_license: "MPL-2.0",
70
+ is_free_license: true,
71
+ is_eol: false,
72
+ categories: ["Network", "WebBrowser"],
73
+ keywords: ["web", "browser"],
74
+ releases: [{ version: "1.0", timestamp: 1 }],
75
+ urls: { homepage: "https://www.mozilla.org" },
76
+ }),
77
+ ).toMatchObject({
78
+ appId: "org.mozilla.firefox",
79
+ categories: "Network, WebBrowser",
80
+ latestVersion: "1.0",
81
+ homepage: "https://www.mozilla.org",
82
+ url: "https://flathub.org/apps/org.mozilla.firefox",
83
+ });
84
+ });
85
+ });
@@ -0,0 +1,254 @@
1
+ /**
2
+ * @owner src/adapters/flathub/apps.ts
3
+ * @does Register agent-facing Flathub app search and detail commands.
4
+ * @needs Flathub public API, AppStream id validation, bounded search limits.
5
+ * @feeds surface coverage ledger, Linux app registry search rows, appstream metadata rows.
6
+ * @breaks Flathub API drift, weak app id validation, or silent empty rows hide app registry data.
7
+ */
8
+
9
+ import { cli, Strategy } from "../../registry.js";
10
+
11
+ const API_BASE = "https://flathub.org/api/v2";
12
+ const APP_BASE = "https://flathub.org/apps";
13
+ const APP_ID_RE =
14
+ /^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z0-9_][A-Za-z0-9_-]*){1,}$/;
15
+
16
+ function requireNonEmpty(value: unknown, label: string): string {
17
+ const text = String(value ?? "").trim();
18
+ if (!text) throw new Error(`flathub ${label} cannot be empty.`);
19
+ return text;
20
+ }
21
+
22
+ export function requireFlathubLimit(value: unknown): number {
23
+ const raw =
24
+ value === undefined || value === null || value === "" ? 25 : value;
25
+ const limit = Number(raw);
26
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
27
+ throw new Error("flathub limit must be an integer in [1, 100].");
28
+ }
29
+ return limit;
30
+ }
31
+
32
+ export function requireFlathubAppId(value: unknown): string {
33
+ const id = String(value ?? "").trim();
34
+ if (!id) throw new Error("flathub appId cannot be empty.");
35
+ if (!APP_ID_RE.test(id)) {
36
+ throw new Error(`flathub appId "${String(value)}" is not valid.`);
37
+ }
38
+ return id;
39
+ }
40
+
41
+ function stringField(value: unknown): string {
42
+ return typeof value === "string" ? value.trim() : "";
43
+ }
44
+
45
+ function numberField(value: unknown): number | null {
46
+ const n =
47
+ typeof value === "number"
48
+ ? value
49
+ : typeof value === "string" && value.trim()
50
+ ? Number(value)
51
+ : NaN;
52
+ return Number.isFinite(n) ? n : null;
53
+ }
54
+
55
+ function objectField(value: unknown): Record<string, unknown> {
56
+ return value && typeof value === "object"
57
+ ? (value as Record<string, unknown>)
58
+ : {};
59
+ }
60
+
61
+ export function joinFlathubList(value: unknown, max = 10): string {
62
+ if (!Array.isArray(value)) return "";
63
+ const items = value.filter((item) => typeof item === "string" && item.trim());
64
+ return items.length > max
65
+ ? [...items.slice(0, max), `(+${items.length - max})`].join(", ")
66
+ : items.join(", ");
67
+ }
68
+
69
+ function timestampToDate(value: unknown): string | null {
70
+ const n = numberField(value);
71
+ if (n == null || n <= 0) return null;
72
+ const date = new Date(n * 1000);
73
+ return Number.isNaN(date.getTime()) ? null : date.toISOString().slice(0, 10);
74
+ }
75
+
76
+ export function pickLatestFlathubRelease(value: unknown): {
77
+ date: string | null;
78
+ version: string | null;
79
+ } {
80
+ if (!Array.isArray(value)) return { date: null, version: null };
81
+ const releases = [...(value as Array<Record<string, unknown>>)].sort(
82
+ (a, b) => (numberField(b.timestamp) ?? 0) - (numberField(a.timestamp) ?? 0),
83
+ );
84
+ const latest = releases[0];
85
+ if (!latest) return { date: null, version: null };
86
+ return {
87
+ version: stringField(latest.version) || null,
88
+ date: timestampToDate(latest.timestamp) ?? stringField(latest.date) ?? null,
89
+ };
90
+ }
91
+
92
+ export function mapFlathubSearchRows(
93
+ hits: Array<Record<string, unknown>>,
94
+ limit: number,
95
+ ): Array<Record<string, unknown>> {
96
+ return hits.slice(0, limit).map((hit, index) => {
97
+ const appId = stringField(hit.app_id);
98
+ return {
99
+ rank: index + 1,
100
+ appId,
101
+ name: stringField(hit.name),
102
+ summary: stringField(hit.summary),
103
+ developer: stringField(hit.developer_name),
104
+ license: stringField(hit.project_license),
105
+ isFreeLicense: hit.is_free_license === true,
106
+ mainCategories:
107
+ stringField(hit.main_categories) ||
108
+ joinFlathubList(hit.main_categories),
109
+ installsLastMonth: numberField(hit.installs_last_month),
110
+ updatedAt: timestampToDate(hit.updated_at) ?? stringField(hit.updated_at),
111
+ url: appId ? `${APP_BASE}/${appId}` : "",
112
+ };
113
+ });
114
+ }
115
+
116
+ export function mapFlathubAppRow(
117
+ appId: string,
118
+ body: Record<string, unknown>,
119
+ ): Record<string, unknown> {
120
+ const urls = objectField(body.urls);
121
+ const release = pickLatestFlathubRelease(body.releases);
122
+ return {
123
+ appId: stringField(body.id) || appId,
124
+ name: stringField(body.name),
125
+ summary: stringField(body.summary),
126
+ developer: stringField(body.developer_name),
127
+ license: stringField(body.project_license),
128
+ isFreeLicense: body.is_free_license === true,
129
+ isEol: body.is_eol === true,
130
+ categories: joinFlathubList(body.categories),
131
+ keywords: joinFlathubList(body.keywords, 8),
132
+ latestVersion: release.version,
133
+ latestReleaseDate: release.date,
134
+ homepage: stringField(urls.homepage) || null,
135
+ bugtracker: stringField(urls.bugtracker) || null,
136
+ donation: stringField(urls.donation) || null,
137
+ url: `${APP_BASE}/${appId}`,
138
+ };
139
+ }
140
+
141
+ async function fetchJson(
142
+ url: URL | string,
143
+ label: string,
144
+ init?: RequestInit,
145
+ ): Promise<unknown> {
146
+ const response = await fetch(url, {
147
+ ...init,
148
+ headers: {
149
+ accept: "application/json",
150
+ "user-agent": "unicli-flathub (https://github.com/olo-dot-io/Uni-CLI)",
151
+ ...(init?.headers as Record<string, string> | undefined),
152
+ },
153
+ });
154
+ if (response.status === 404) throw new Error(`${label} returned no result.`);
155
+ if (response.status === 429) throw new Error(`${label} returned HTTP 429.`);
156
+ if (!response.ok)
157
+ throw new Error(`${label} returned HTTP ${response.status}.`);
158
+ return response.json();
159
+ }
160
+
161
+ cli({
162
+ site: "flathub",
163
+ name: "search",
164
+ description: "Search Flathub apps by keyword",
165
+ domain: "flathub.org",
166
+ strategy: Strategy.PUBLIC,
167
+ args: [
168
+ {
169
+ name: "query",
170
+ type: "str",
171
+ required: true,
172
+ positional: true,
173
+ description: "Search keyword",
174
+ },
175
+ { name: "limit", type: "int", default: 25, description: "Max rows" },
176
+ ],
177
+ columns: [
178
+ "rank",
179
+ "appId",
180
+ "name",
181
+ "summary",
182
+ "developer",
183
+ "license",
184
+ "isFreeLicense",
185
+ "mainCategories",
186
+ "installsLastMonth",
187
+ "updatedAt",
188
+ "url",
189
+ ],
190
+ func: async (_page, kwargs) => {
191
+ const query = requireNonEmpty(kwargs.query, "query");
192
+ const limit = requireFlathubLimit(kwargs.limit);
193
+ const body = (await fetchJson(`${API_BASE}/search`, "flathub search", {
194
+ body: JSON.stringify({ hitsPerPage: limit, page: 1, query }),
195
+ headers: { "content-type": "application/json" },
196
+ method: "POST",
197
+ })) as { hits?: unknown };
198
+ const rows = mapFlathubSearchRows(
199
+ Array.isArray(body.hits)
200
+ ? (body.hits as Array<Record<string, unknown>>)
201
+ : [],
202
+ limit,
203
+ );
204
+ if (rows.length === 0)
205
+ throw new Error(`flathub search returned no rows for "${query}".`);
206
+ return rows;
207
+ },
208
+ });
209
+
210
+ cli({
211
+ site: "flathub",
212
+ name: "app",
213
+ description: "Fetch full Flathub appstream metadata by app id",
214
+ domain: "flathub.org",
215
+ strategy: Strategy.PUBLIC,
216
+ args: [
217
+ {
218
+ name: "appId",
219
+ type: "str",
220
+ required: true,
221
+ positional: true,
222
+ description: "AppStream id",
223
+ },
224
+ ],
225
+ columns: [
226
+ "appId",
227
+ "name",
228
+ "summary",
229
+ "developer",
230
+ "license",
231
+ "isFreeLicense",
232
+ "isEol",
233
+ "categories",
234
+ "keywords",
235
+ "latestVersion",
236
+ "latestReleaseDate",
237
+ "homepage",
238
+ "bugtracker",
239
+ "donation",
240
+ "url",
241
+ ],
242
+ func: async (_page, kwargs) => {
243
+ const appId = requireFlathubAppId(kwargs.appId);
244
+ const body = objectField(
245
+ await fetchJson(
246
+ `${API_BASE}/appstream/${encodeURIComponent(appId)}`,
247
+ "flathub app",
248
+ ),
249
+ );
250
+ if (!body.id)
251
+ throw new Error(`flathub app returned no row for "${appId}".`);
252
+ return [mapFlathubAppRow(appId, body)];
253
+ },
254
+ });
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ mapGoProxyModuleRow,
4
+ mapGoProxyVersionRows,
5
+ requireGoModulePath,
6
+ requireGoProxyLimit,
7
+ sortGoVersionsDescending,
8
+ } from "./module.js";
9
+
10
+ describe("goproxy agent-facing commands", () => {
11
+ it("validates module paths and limits", () => {
12
+ expect(requireGoModulePath("github.com/gin-gonic/gin")).toBe(
13
+ "github.com/gin-gonic/gin",
14
+ );
15
+ expect(() => requireGoModulePath("")).toThrow("module path is required");
16
+ expect(() => requireGoModulePath("noslash")).toThrow("not recognised");
17
+ expect(requireGoProxyLimit(undefined)).toBe(30);
18
+ expect(requireGoProxyLimit("200")).toBe(200);
19
+ expect(() => requireGoProxyLimit("0")).toThrow("goproxy limit must");
20
+ });
21
+
22
+ it("sorts Go semver tags descending", () => {
23
+ expect(
24
+ sortGoVersionsDescending(["bad", "v1.2.0-beta.1", "v1.10.0", "v1.2.0"]),
25
+ ).toEqual(["v1.10.0", "v1.2.0", "v1.2.0-beta.1"]);
26
+ });
27
+
28
+ it("maps module metadata rows", () => {
29
+ expect(
30
+ mapGoProxyModuleRow("github.com/gin-gonic/gin", {
31
+ Version: "v1.10.0",
32
+ Time: "2026-05-01T12:00:00.123Z",
33
+ Origin: {
34
+ VCS: "git",
35
+ URL: "https://github.com/gin-gonic/gin",
36
+ Hash: "abc",
37
+ Ref: "refs/tags/v1.10.0",
38
+ },
39
+ }),
40
+ ).toEqual({
41
+ module: "github.com/gin-gonic/gin",
42
+ version: "v1.10.0",
43
+ publishedAt: "2026-05-01T12:00:00Z",
44
+ vcs: "git",
45
+ repository: "https://github.com/gin-gonic/gin",
46
+ commit: "abc",
47
+ ref: "refs/tags/v1.10.0",
48
+ pkgGoDevUrl: "https://pkg.go.dev/github.com/gin-gonic/gin",
49
+ url: "https://proxy.golang.org/github.com/gin-gonic/gin/@latest",
50
+ });
51
+ expect(() => mapGoProxyModuleRow("github.com/x/y", {})).toThrow(
52
+ "returned no @latest entry",
53
+ );
54
+ });
55
+
56
+ it("maps version rows without sentinel output", () => {
57
+ expect(
58
+ mapGoProxyVersionRows("github.com/gin-gonic/gin", ["v1.0.0", "bad"], 10),
59
+ ).toEqual([
60
+ {
61
+ rank: 1,
62
+ module: "github.com/gin-gonic/gin",
63
+ version: "v1.0.0",
64
+ publishedAt: null,
65
+ url: "https://proxy.golang.org/github.com/gin-gonic/gin/@v/v1.0.0.info",
66
+ },
67
+ ]);
68
+ expect(() => mapGoProxyVersionRows("github.com/x/y", ["bad"], 10)).toThrow(
69
+ "no semver-shaped tags",
70
+ );
71
+ });
72
+ });