@zenalexa/unicli 0.219.0 → 0.220.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 (601) hide show
  1. package/AGENTS.md +15 -24
  2. package/README.md +62 -250
  3. package/README.zh-CN.md +62 -250
  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/anilist/web.d.ts +11 -0
  13. package/dist/adapters/anilist/web.d.ts.map +1 -0
  14. package/dist/adapters/anilist/web.js +284 -0
  15. package/dist/adapters/anilist/web.js.map +1 -0
  16. package/dist/adapters/arxiv/papers.d.ts +27 -0
  17. package/dist/adapters/arxiv/papers.d.ts.map +1 -0
  18. package/dist/adapters/arxiv/papers.js +193 -0
  19. package/dist/adapters/arxiv/papers.js.map +1 -0
  20. package/dist/adapters/bangumi/web.d.ts +14 -0
  21. package/dist/adapters/bangumi/web.d.ts.map +1 -0
  22. package/dist/adapters/bangumi/web.js +257 -0
  23. package/dist/adapters/bangumi/web.js.map +1 -0
  24. package/dist/adapters/bbc/topic.d.ts +24 -0
  25. package/dist/adapters/bbc/topic.d.ts.map +1 -0
  26. package/dist/adapters/bbc/topic.js +122 -0
  27. package/dist/adapters/bbc/topic.js.map +1 -0
  28. package/dist/adapters/chatgpt/web.d.ts +24 -0
  29. package/dist/adapters/chatgpt/web.d.ts.map +1 -0
  30. package/dist/adapters/chatgpt/web.js +242 -0
  31. package/dist/adapters/chatgpt/web.js.map +1 -0
  32. package/dist/adapters/claude/web.d.ts +24 -0
  33. package/dist/adapters/claude/web.d.ts.map +1 -0
  34. package/dist/adapters/claude/web.js +575 -0
  35. package/dist/adapters/claude/web.js.map +1 -0
  36. package/dist/adapters/codex/projects.d.ts +27 -0
  37. package/dist/adapters/codex/projects.d.ts.map +1 -0
  38. package/dist/adapters/codex/projects.js +147 -0
  39. package/dist/adapters/codex/projects.js.map +1 -0
  40. package/dist/adapters/coingecko/markets.d.ts +19 -0
  41. package/dist/adapters/coingecko/markets.d.ts.map +1 -0
  42. package/dist/adapters/coingecko/markets.js +474 -0
  43. package/dist/adapters/coingecko/markets.js.map +1 -0
  44. package/dist/adapters/coupang/product.d.ts +27 -0
  45. package/dist/adapters/coupang/product.d.ts.map +1 -0
  46. package/dist/adapters/coupang/product.js +211 -0
  47. package/dist/adapters/coupang/product.js.map +1 -0
  48. package/dist/adapters/crates/registry.d.ts +44 -0
  49. package/dist/adapters/crates/registry.d.ts.map +1 -0
  50. package/dist/adapters/crates/registry.js +186 -0
  51. package/dist/adapters/crates/registry.js.map +1 -0
  52. package/dist/adapters/ctrip/travel.d.ts +83 -0
  53. package/dist/adapters/ctrip/travel.d.ts.map +1 -0
  54. package/dist/adapters/ctrip/travel.js +630 -0
  55. package/dist/adapters/ctrip/travel.js.map +1 -0
  56. package/dist/adapters/dblp/publications.d.ts +41 -0
  57. package/dist/adapters/dblp/publications.d.ts.map +1 -0
  58. package/dist/adapters/dblp/publications.js +409 -0
  59. package/dist/adapters/dblp/publications.js.map +1 -0
  60. package/dist/adapters/deepseek/web.d.ts +1 -1
  61. package/dist/adapters/deepseek/web.d.ts.map +1 -1
  62. package/dist/adapters/deepseek/web.js +66 -1
  63. package/dist/adapters/deepseek/web.js.map +1 -1
  64. package/dist/adapters/defillama/protocols.d.ts +13 -0
  65. package/dist/adapters/defillama/protocols.d.ts.map +1 -0
  66. package/dist/adapters/defillama/protocols.js +218 -0
  67. package/dist/adapters/defillama/protocols.js.map +1 -0
  68. package/dist/adapters/devto/read.d.ts +26 -0
  69. package/dist/adapters/devto/read.d.ts.map +1 -0
  70. package/dist/adapters/devto/read.js +110 -0
  71. package/dist/adapters/devto/read.js.map +1 -0
  72. package/dist/adapters/dianping/shop.d.ts +38 -0
  73. package/dist/adapters/dianping/shop.d.ts.map +1 -0
  74. package/dist/adapters/dianping/shop.js +194 -0
  75. package/dist/adapters/dianping/shop.js.map +1 -0
  76. package/dist/adapters/dlsite/web.d.ts +31 -0
  77. package/dist/adapters/dlsite/web.d.ts.map +1 -0
  78. package/dist/adapters/dlsite/web.js +455 -0
  79. package/dist/adapters/dlsite/web.js.map +1 -0
  80. package/dist/adapters/dockerhub/registry.d.ts +36 -0
  81. package/dist/adapters/dockerhub/registry.d.ts.map +1 -0
  82. package/dist/adapters/dockerhub/registry.js +172 -0
  83. package/dist/adapters/dockerhub/registry.js.map +1 -0
  84. package/dist/adapters/ehentai/web.d.ts +66 -0
  85. package/dist/adapters/ehentai/web.d.ts.map +1 -0
  86. package/dist/adapters/ehentai/web.js +608 -0
  87. package/dist/adapters/ehentai/web.js.map +1 -0
  88. package/dist/adapters/endoflife/product.d.ts +11 -0
  89. package/dist/adapters/endoflife/product.d.ts.map +1 -0
  90. package/dist/adapters/endoflife/product.js +113 -0
  91. package/dist/adapters/endoflife/product.js.map +1 -0
  92. package/dist/adapters/facebook/marketplace-extra.d.ts +9 -0
  93. package/dist/adapters/facebook/marketplace-extra.d.ts.map +1 -0
  94. package/dist/adapters/facebook/marketplace-extra.js +170 -0
  95. package/dist/adapters/facebook/marketplace-extra.js.map +1 -0
  96. package/dist/adapters/flathub/apps.d.ts +17 -0
  97. package/dist/adapters/flathub/apps.d.ts.map +1 -0
  98. package/dist/adapters/flathub/apps.js +220 -0
  99. package/dist/adapters/flathub/apps.js.map +1 -0
  100. package/dist/adapters/goproxy/module.d.ts +24 -0
  101. package/dist/adapters/goproxy/module.d.ts.map +1 -0
  102. package/dist/adapters/goproxy/module.js +221 -0
  103. package/dist/adapters/goproxy/module.js.map +1 -0
  104. package/dist/adapters/grok/web.d.ts +29 -0
  105. package/dist/adapters/grok/web.d.ts.map +1 -0
  106. package/dist/adapters/grok/web.js +553 -0
  107. package/dist/adapters/grok/web.js.map +1 -0
  108. package/dist/adapters/hackernews/read.d.ts +31 -0
  109. package/dist/adapters/hackernews/read.d.ts.map +1 -0
  110. package/dist/adapters/hackernews/read.js +201 -0
  111. package/dist/adapters/hackernews/read.js.map +1 -0
  112. package/dist/adapters/hf/paper.d.ts +22 -0
  113. package/dist/adapters/hf/paper.d.ts.map +1 -0
  114. package/dist/adapters/hf/paper.js +112 -0
  115. package/dist/adapters/hf/paper.js.map +1 -0
  116. package/dist/adapters/homebrew/packages.d.ts +52 -0
  117. package/dist/adapters/homebrew/packages.d.ts.map +1 -0
  118. package/dist/adapters/homebrew/packages.js +240 -0
  119. package/dist/adapters/homebrew/packages.js.map +1 -0
  120. package/dist/adapters/indeed/jobs.d.ts +38 -0
  121. package/dist/adapters/indeed/jobs.d.ts.map +1 -0
  122. package/dist/adapters/indeed/jobs.js +300 -0
  123. package/dist/adapters/indeed/jobs.js.map +1 -0
  124. package/dist/adapters/instagram/collections.d.ts +9 -0
  125. package/dist/adapters/instagram/collections.d.ts.map +1 -0
  126. package/dist/adapters/instagram/collections.js +174 -0
  127. package/dist/adapters/instagram/collections.js.map +1 -0
  128. package/dist/adapters/jikan/web.d.ts +9 -0
  129. package/dist/adapters/jikan/web.d.ts.map +1 -0
  130. package/dist/adapters/jikan/web.js +154 -0
  131. package/dist/adapters/jikan/web.js.map +1 -0
  132. package/dist/adapters/kitsu/web.d.ts +9 -0
  133. package/dist/adapters/kitsu/web.d.ts.map +1 -0
  134. package/dist/adapters/kitsu/web.js +97 -0
  135. package/dist/adapters/kitsu/web.js.map +1 -0
  136. package/dist/adapters/lichess/players.d.ts +46 -0
  137. package/dist/adapters/lichess/players.d.ts.map +1 -0
  138. package/dist/adapters/lichess/players.js +221 -0
  139. package/dist/adapters/lichess/players.js.map +1 -0
  140. package/dist/adapters/lobsters/read-domain.d.ts +35 -0
  141. package/dist/adapters/lobsters/read-domain.d.ts.map +1 -0
  142. package/dist/adapters/lobsters/read-domain.js +306 -0
  143. package/dist/adapters/lobsters/read-domain.js.map +1 -0
  144. package/dist/adapters/mangadex/web.d.ts +10 -0
  145. package/dist/adapters/mangadex/web.d.ts.map +1 -0
  146. package/dist/adapters/mangadex/web.js +188 -0
  147. package/dist/adapters/mangadex/web.js.map +1 -0
  148. package/dist/adapters/maven/artifact.d.ts +30 -0
  149. package/dist/adapters/maven/artifact.d.ts.map +1 -0
  150. package/dist/adapters/maven/artifact.js +121 -0
  151. package/dist/adapters/maven/artifact.js.map +1 -0
  152. package/dist/adapters/mdn/search.d.ts +11 -0
  153. package/dist/adapters/mdn/search.d.ts.map +1 -0
  154. package/dist/adapters/mdn/search.js +115 -0
  155. package/dist/adapters/mdn/search.js.map +1 -0
  156. package/dist/adapters/medium/tag.d.ts +15 -0
  157. package/dist/adapters/medium/tag.d.ts.map +1 -0
  158. package/dist/adapters/medium/tag.js +148 -0
  159. package/dist/adapters/medium/tag.js.map +1 -0
  160. package/dist/adapters/moegirl/web.d.ts +23 -0
  161. package/dist/adapters/moegirl/web.d.ts.map +1 -0
  162. package/dist/adapters/moegirl/web.js +269 -0
  163. package/dist/adapters/moegirl/web.js.map +1 -0
  164. package/dist/adapters/npm/package.d.ts +32 -0
  165. package/dist/adapters/npm/package.d.ts.map +1 -0
  166. package/dist/adapters/npm/package.js +141 -0
  167. package/dist/adapters/npm/package.js.map +1 -0
  168. package/dist/adapters/nuget/package.d.ts +34 -0
  169. package/dist/adapters/nuget/package.d.ts.map +1 -0
  170. package/dist/adapters/nuget/package.js +135 -0
  171. package/dist/adapters/nuget/package.js.map +1 -0
  172. package/dist/adapters/nvd/cve.d.ts +42 -0
  173. package/dist/adapters/nvd/cve.d.ts.map +1 -0
  174. package/dist/adapters/nvd/cve.js +132 -0
  175. package/dist/adapters/nvd/cve.js.map +1 -0
  176. package/dist/adapters/oeis/sequences.d.ts +14 -0
  177. package/dist/adapters/oeis/sequences.d.ts.map +1 -0
  178. package/dist/adapters/oeis/sequences.js +219 -0
  179. package/dist/adapters/oeis/sequences.js.map +1 -0
  180. package/dist/adapters/openalex/works.d.ts +43 -0
  181. package/dist/adapters/openalex/works.d.ts.map +1 -0
  182. package/dist/adapters/openalex/works.js +267 -0
  183. package/dist/adapters/openalex/works.js.map +1 -0
  184. package/dist/adapters/openfda/records.d.ts +18 -0
  185. package/dist/adapters/openfda/records.d.ts.map +1 -0
  186. package/dist/adapters/openfda/records.js +209 -0
  187. package/dist/adapters/openfda/records.js.map +1 -0
  188. package/dist/adapters/openreview/papers.d.ts +34 -0
  189. package/dist/adapters/openreview/papers.d.ts.map +1 -0
  190. package/dist/adapters/openreview/papers.js +463 -0
  191. package/dist/adapters/openreview/papers.js.map +1 -0
  192. package/dist/adapters/osv/security.d.ts +36 -0
  193. package/dist/adapters/osv/security.d.ts.map +1 -0
  194. package/dist/adapters/osv/security.js +247 -0
  195. package/dist/adapters/osv/security.js.map +1 -0
  196. package/dist/adapters/packagist/package.d.ts +31 -0
  197. package/dist/adapters/packagist/package.d.ts.map +1 -0
  198. package/dist/adapters/packagist/package.js +108 -0
  199. package/dist/adapters/packagist/package.js.map +1 -0
  200. package/dist/adapters/pubmed/articles.d.ts +31 -0
  201. package/dist/adapters/pubmed/articles.d.ts.map +1 -0
  202. package/dist/adapters/pubmed/articles.js +385 -0
  203. package/dist/adapters/pubmed/articles.js.map +1 -0
  204. package/dist/adapters/pypi/package.d.ts +38 -0
  205. package/dist/adapters/pypi/package.d.ts.map +1 -0
  206. package/dist/adapters/pypi/package.js +235 -0
  207. package/dist/adapters/pypi/package.js.map +1 -0
  208. package/dist/adapters/qwen/web.d.ts +26 -0
  209. package/dist/adapters/qwen/web.d.ts.map +1 -0
  210. package/dist/adapters/qwen/web.js +672 -0
  211. package/dist/adapters/qwen/web.js.map +1 -0
  212. package/dist/adapters/reddit/account.d.ts +12 -0
  213. package/dist/adapters/reddit/account.d.ts.map +1 -0
  214. package/dist/adapters/reddit/account.js +409 -0
  215. package/dist/adapters/reddit/account.js.map +1 -0
  216. package/dist/adapters/rednote/web.d.ts +30 -0
  217. package/dist/adapters/rednote/web.d.ts.map +1 -0
  218. package/dist/adapters/rednote/web.js +858 -0
  219. package/dist/adapters/rednote/web.js.map +1 -0
  220. package/dist/adapters/rest-countries/countries.d.ts +14 -0
  221. package/dist/adapters/rest-countries/countries.d.ts.map +1 -0
  222. package/dist/adapters/rest-countries/countries.js +231 -0
  223. package/dist/adapters/rest-countries/countries.js.map +1 -0
  224. package/dist/adapters/reuters/article-detail.d.ts +37 -0
  225. package/dist/adapters/reuters/article-detail.d.ts.map +1 -0
  226. package/dist/adapters/reuters/article-detail.js +139 -0
  227. package/dist/adapters/reuters/article-detail.js.map +1 -0
  228. package/dist/adapters/rfc/rfc.d.ts +11 -0
  229. package/dist/adapters/rfc/rfc.d.ts.map +1 -0
  230. package/dist/adapters/rfc/rfc.js +121 -0
  231. package/dist/adapters/rfc/rfc.js.map +1 -0
  232. package/dist/adapters/rubygems/gem.d.ts +26 -0
  233. package/dist/adapters/rubygems/gem.d.ts.map +1 -0
  234. package/dist/adapters/rubygems/gem.js +96 -0
  235. package/dist/adapters/rubygems/gem.js.map +1 -0
  236. package/dist/adapters/safebooru/web.d.ts +10 -0
  237. package/dist/adapters/safebooru/web.d.ts.map +1 -0
  238. package/dist/adapters/safebooru/web.js +120 -0
  239. package/dist/adapters/safebooru/web.js.map +1 -0
  240. package/dist/adapters/stackoverflow/questions.d.ts +79 -0
  241. package/dist/adapters/stackoverflow/questions.d.ts.map +1 -0
  242. package/dist/adapters/stackoverflow/questions.js +504 -0
  243. package/dist/adapters/stackoverflow/questions.js.map +1 -0
  244. package/dist/adapters/steam/app.d.ts +39 -0
  245. package/dist/adapters/steam/app.d.ts.map +1 -0
  246. package/dist/adapters/steam/app.js +165 -0
  247. package/dist/adapters/steam/app.js.map +1 -0
  248. package/dist/adapters/tiktok/creator-videos.d.ts +52 -0
  249. package/dist/adapters/tiktok/creator-videos.d.ts.map +1 -0
  250. package/dist/adapters/tiktok/creator-videos.js +267 -0
  251. package/dist/adapters/tiktok/creator-videos.js.map +1 -0
  252. package/dist/adapters/tvmaze/shows.d.ts +13 -0
  253. package/dist/adapters/tvmaze/shows.d.ts.map +1 -0
  254. package/dist/adapters/tvmaze/shows.js +240 -0
  255. package/dist/adapters/tvmaze/shows.js.map +1 -0
  256. package/dist/adapters/twitter/bookmark-folders.d.ts +33 -0
  257. package/dist/adapters/twitter/bookmark-folders.d.ts.map +1 -0
  258. package/dist/adapters/twitter/bookmark-folders.js +290 -0
  259. package/dist/adapters/twitter/bookmark-folders.js.map +1 -0
  260. package/dist/adapters/twitter/quote.d.ts +18 -0
  261. package/dist/adapters/twitter/quote.d.ts.map +1 -0
  262. package/dist/adapters/twitter/quote.js +285 -0
  263. package/dist/adapters/twitter/quote.js.map +1 -0
  264. package/dist/adapters/twitter/tweet-actions.d.ts +12 -0
  265. package/dist/adapters/twitter/tweet-actions.d.ts.map +1 -0
  266. package/dist/adapters/twitter/tweet-actions.js +145 -0
  267. package/dist/adapters/twitter/tweet-actions.js.map +1 -0
  268. package/dist/adapters/twitter/tweet-url.d.ts +15 -0
  269. package/dist/adapters/twitter/tweet-url.d.ts.map +1 -0
  270. package/dist/adapters/twitter/tweet-url.js +57 -0
  271. package/dist/adapters/twitter/tweet-url.js.map +1 -0
  272. package/dist/adapters/uisdc/news.d.ts +22 -0
  273. package/dist/adapters/uisdc/news.d.ts.map +1 -0
  274. package/dist/adapters/uisdc/news.js +91 -0
  275. package/dist/adapters/uisdc/news.js.map +1 -0
  276. package/dist/adapters/vndb/web.d.ts +10 -0
  277. package/dist/adapters/vndb/web.d.ts.map +1 -0
  278. package/dist/adapters/vndb/web.js +321 -0
  279. package/dist/adapters/vndb/web.js.map +1 -0
  280. package/dist/adapters/weibo/favorites-publish.d.ts +28 -0
  281. package/dist/adapters/weibo/favorites-publish.d.ts.map +1 -0
  282. package/dist/adapters/weibo/favorites-publish.js +356 -0
  283. package/dist/adapters/weibo/favorites-publish.js.map +1 -0
  284. package/dist/adapters/wikidata/entities.d.ts +15 -0
  285. package/dist/adapters/wikidata/entities.d.ts.map +1 -0
  286. package/dist/adapters/wikidata/entities.js +219 -0
  287. package/dist/adapters/wikidata/entities.js.map +1 -0
  288. package/dist/adapters/wikipedia/page.d.ts +21 -0
  289. package/dist/adapters/wikipedia/page.d.ts.map +1 -0
  290. package/dist/adapters/wikipedia/page.js +116 -0
  291. package/dist/adapters/wikipedia/page.js.map +1 -0
  292. package/dist/adapters/wttr/weather.d.ts +12 -0
  293. package/dist/adapters/wttr/weather.d.ts.map +1 -0
  294. package/dist/adapters/wttr/weather.js +207 -0
  295. package/dist/adapters/wttr/weather.js.map +1 -0
  296. package/dist/adapters/xianyu/publish.d.ts +31 -0
  297. package/dist/adapters/xianyu/publish.d.ts.map +1 -0
  298. package/dist/adapters/xianyu/publish.js +349 -0
  299. package/dist/adapters/xianyu/publish.js.map +1 -0
  300. package/dist/adapters/xiaohongshu/user-helpers.d.ts +2 -2
  301. package/dist/adapters/xiaohongshu/user-helpers.d.ts.map +1 -1
  302. package/dist/adapters/xiaohongshu/user-helpers.js +5 -4
  303. package/dist/adapters/xiaohongshu/user-helpers.js.map +1 -1
  304. package/dist/adapters/yuanbao/web.d.ts +27 -0
  305. package/dist/adapters/yuanbao/web.d.ts.map +1 -0
  306. package/dist/adapters/yuanbao/web.js +365 -0
  307. package/dist/adapters/yuanbao/web.js.map +1 -0
  308. package/dist/adapters/zhihu/collection.d.ts +33 -0
  309. package/dist/adapters/zhihu/collection.d.ts.map +1 -0
  310. package/dist/adapters/zhihu/collection.js +185 -0
  311. package/dist/adapters/zhihu/collection.js.map +1 -0
  312. package/dist/adapters/zlibrary/web.d.ts +19 -0
  313. package/dist/adapters/zlibrary/web.d.ts.map +1 -0
  314. package/dist/adapters/zlibrary/web.js +153 -0
  315. package/dist/adapters/zlibrary/web.js.map +1 -0
  316. package/dist/agents/codex-pack.d.ts +62 -0
  317. package/dist/agents/codex-pack.d.ts.map +1 -0
  318. package/dist/agents/codex-pack.js +163 -0
  319. package/dist/agents/codex-pack.js.map +1 -0
  320. package/dist/browser/daemon-client.js +2 -2
  321. package/dist/browser/daemon-client.js.map +1 -1
  322. package/dist/commands/agents.d.ts.map +1 -1
  323. package/dist/commands/agents.js +6 -43
  324. package/dist/commands/agents.js.map +1 -1
  325. package/dist/commands/browser/adapter.d.ts.map +1 -1
  326. package/dist/commands/browser/adapter.js +17 -3
  327. package/dist/commands/browser/adapter.js.map +1 -1
  328. package/dist/commands/describe.d.ts.map +1 -1
  329. package/dist/commands/describe.js +6 -7
  330. package/dist/commands/describe.js.map +1 -1
  331. package/dist/commands/dispatch.d.ts +1 -1
  332. package/dist/commands/dispatch.d.ts.map +1 -1
  333. package/dist/commands/dispatch.js +4 -2
  334. package/dist/commands/dispatch.js.map +1 -1
  335. package/dist/commands/mcp.d.ts +1 -1
  336. package/dist/commands/mcp.d.ts.map +1 -1
  337. package/dist/commands/mcp.js +10 -5
  338. package/dist/commands/mcp.js.map +1 -1
  339. package/dist/core/command-contract-lint.d.ts +10 -0
  340. package/dist/core/command-contract-lint.d.ts.map +1 -0
  341. package/dist/core/command-contract-lint.js +41 -0
  342. package/dist/core/command-contract-lint.js.map +1 -0
  343. package/dist/core/command-contract.d.ts +100 -0
  344. package/dist/core/command-contract.d.ts.map +1 -0
  345. package/dist/core/command-contract.js +174 -0
  346. package/dist/core/command-contract.js.map +1 -0
  347. package/dist/core/index.d.ts +2 -0
  348. package/dist/core/index.d.ts.map +1 -1
  349. package/dist/core/index.js +2 -0
  350. package/dist/core/index.js.map +1 -1
  351. package/dist/discovery/aliases.d.ts +2 -2
  352. package/dist/discovery/aliases.d.ts.map +1 -1
  353. package/dist/discovery/aliases.js +464 -6
  354. package/dist/discovery/aliases.js.map +1 -1
  355. package/dist/discovery/macos-dynamic.d.ts.map +1 -1
  356. package/dist/discovery/macos-dynamic.js +17 -3
  357. package/dist/discovery/macos-dynamic.js.map +1 -1
  358. package/dist/discovery/search.d.ts.map +1 -1
  359. package/dist/discovery/search.js +147 -2
  360. package/dist/discovery/search.js.map +1 -1
  361. package/dist/engine/args.d.ts.map +1 -1
  362. package/dist/engine/args.js +18 -1
  363. package/dist/engine/args.js.map +1 -1
  364. package/dist/engine/artifact-validation.d.ts +29 -0
  365. package/dist/engine/artifact-validation.d.ts.map +1 -0
  366. package/dist/engine/artifact-validation.js +211 -0
  367. package/dist/engine/artifact-validation.js.map +1 -0
  368. package/dist/engine/browser/diagnostics.d.ts +38 -0
  369. package/dist/engine/browser/diagnostics.d.ts.map +1 -0
  370. package/dist/engine/browser/diagnostics.js +40 -0
  371. package/dist/engine/browser/diagnostics.js.map +1 -0
  372. package/dist/engine/invoke.d.ts +1 -0
  373. package/dist/engine/invoke.d.ts.map +1 -1
  374. package/dist/engine/invoke.js +1 -0
  375. package/dist/engine/invoke.js.map +1 -1
  376. package/dist/engine/kernel/errors.d.ts +11 -0
  377. package/dist/engine/kernel/errors.d.ts.map +1 -0
  378. package/dist/engine/kernel/errors.js +15 -0
  379. package/dist/engine/kernel/errors.js.map +1 -0
  380. package/dist/engine/kernel/execute.d.ts +7 -18
  381. package/dist/engine/kernel/execute.d.ts.map +1 -1
  382. package/dist/engine/kernel/execute.js +25 -410
  383. package/dist/engine/kernel/execute.js.map +1 -1
  384. package/dist/engine/kernel/stages.d.ts +44 -0
  385. package/dist/engine/kernel/stages.d.ts.map +1 -0
  386. package/dist/engine/kernel/stages.js +428 -0
  387. package/dist/engine/kernel/stages.js.map +1 -0
  388. package/dist/engine/kernel/types.d.ts +21 -1
  389. package/dist/engine/kernel/types.d.ts.map +1 -1
  390. package/dist/engine/steps/download.d.ts +1 -0
  391. package/dist/engine/steps/download.d.ts.map +1 -1
  392. package/dist/engine/steps/download.js +10 -6
  393. package/dist/engine/steps/download.js.map +1 -1
  394. package/dist/fast-path/render.js +1 -1
  395. package/dist/fast-path/render.js.map +1 -1
  396. package/dist/manifest-compact.txt +10 -10
  397. package/dist/manifest-search.json +1 -1
  398. package/dist/manifest.json +8421 -332
  399. package/dist/mcp/handler.d.ts.map +1 -1
  400. package/dist/mcp/handler.js +11 -1
  401. package/dist/mcp/handler.js.map +1 -1
  402. package/dist/mcp/server.d.ts +1 -1
  403. package/dist/mcp/server.js +1 -1
  404. package/dist/mcp/tools.d.ts.map +1 -1
  405. package/dist/mcp/tools.js +18 -10
  406. package/dist/mcp/tools.js.map +1 -1
  407. package/dist/output/error-map.d.ts.map +1 -1
  408. package/dist/output/error-map.js +1 -1
  409. package/dist/output/error-map.js.map +1 -1
  410. package/dist/registry.d.ts.map +1 -1
  411. package/dist/registry.js +2 -1
  412. package/dist/registry.js.map +1 -1
  413. package/dist/transport/refs.d.ts +5 -3
  414. package/dist/transport/refs.d.ts.map +1 -1
  415. package/dist/transport/refs.js +8 -1
  416. package/dist/transport/refs.js.map +1 -1
  417. package/dist/transport/sidecar-binary.d.ts +7 -0
  418. package/dist/transport/sidecar-binary.d.ts.map +1 -1
  419. package/dist/transport/sidecar-binary.js +28 -8
  420. package/dist/transport/sidecar-binary.js.map +1 -1
  421. package/package.json +4 -3
  422. package/server.json +3 -3
  423. package/skills/unicli/SKILL.md +1 -1
  424. package/skills/unicli-claude-code/SKILL.md +1 -1
  425. package/skills/unicli-hermes/SKILL.md +1 -1
  426. package/src/adapters/1point3acres/forum.test.ts +300 -0
  427. package/src/adapters/1point3acres/forum.ts +852 -0
  428. package/src/adapters/aibase/news.test.ts +42 -0
  429. package/src/adapters/aibase/news.ts +118 -0
  430. package/src/adapters/anilist/web.test.ts +93 -0
  431. package/src/adapters/anilist/web.ts +341 -0
  432. package/src/adapters/arxiv/download.yaml +53 -0
  433. package/src/adapters/arxiv/papers.test.ts +59 -0
  434. package/src/adapters/arxiv/papers.ts +226 -0
  435. package/src/adapters/bangumi/web.test.ts +109 -0
  436. package/src/adapters/bangumi/web.ts +295 -0
  437. package/src/adapters/bbc/topic.test.ts +52 -0
  438. package/src/adapters/bbc/topic.ts +149 -0
  439. package/src/adapters/chatgpt/web.test.ts +121 -0
  440. package/src/adapters/chatgpt/web.ts +286 -0
  441. package/src/adapters/claude/web.test.ts +206 -0
  442. package/src/adapters/claude/web.ts +684 -0
  443. package/src/adapters/codex/projects.test.ts +77 -0
  444. package/src/adapters/codex/projects.ts +178 -0
  445. package/src/adapters/coingecko/markets.test.ts +156 -0
  446. package/src/adapters/coingecko/markets.ts +574 -0
  447. package/src/adapters/coupang/product.test.ts +111 -0
  448. package/src/adapters/coupang/product.ts +256 -0
  449. package/src/adapters/crates/registry.test.ts +89 -0
  450. package/src/adapters/crates/registry.ts +247 -0
  451. package/src/adapters/ctrip/travel.test.ts +359 -0
  452. package/src/adapters/ctrip/travel.ts +792 -0
  453. package/src/adapters/danbooru/artists.yaml +44 -0
  454. package/src/adapters/danbooru/comments.yaml +45 -0
  455. package/src/adapters/danbooru/detail.yaml +78 -0
  456. package/src/adapters/danbooru/download.yaml +51 -0
  457. package/src/adapters/danbooru/pools.yaml +56 -0
  458. package/src/adapters/danbooru/search.yaml +69 -0
  459. package/src/adapters/danbooru/tags.yaml +42 -0
  460. package/src/adapters/danbooru/wiki.yaml +44 -0
  461. package/src/adapters/dblp/publications.test.ts +123 -0
  462. package/src/adapters/dblp/publications.ts +494 -0
  463. package/src/adapters/deepseek/web.test.ts +69 -0
  464. package/src/adapters/deepseek/web.ts +78 -1
  465. package/src/adapters/defillama/protocols.test.ts +75 -0
  466. package/src/adapters/defillama/protocols.ts +253 -0
  467. package/src/adapters/devto/read.test.ts +49 -0
  468. package/src/adapters/devto/read.ts +145 -0
  469. package/src/adapters/dianping/shop.test.ts +134 -0
  470. package/src/adapters/dianping/shop.ts +261 -0
  471. package/src/adapters/dlsite/web.test.ts +132 -0
  472. package/src/adapters/dlsite/web.ts +557 -0
  473. package/src/adapters/dockerhub/registry.test.ts +97 -0
  474. package/src/adapters/dockerhub/registry.ts +223 -0
  475. package/src/adapters/ehentai/web.test.ts +157 -0
  476. package/src/adapters/ehentai/web.ts +750 -0
  477. package/src/adapters/endoflife/product.test.ts +50 -0
  478. package/src/adapters/endoflife/product.ts +128 -0
  479. package/src/adapters/facebook/marketplace-extra.test.ts +132 -0
  480. package/src/adapters/facebook/marketplace-extra.ts +213 -0
  481. package/src/adapters/flathub/apps.test.ts +85 -0
  482. package/src/adapters/flathub/apps.ts +254 -0
  483. package/src/adapters/goproxy/module.test.ts +72 -0
  484. package/src/adapters/goproxy/module.ts +258 -0
  485. package/src/adapters/grok/web.test.ts +181 -0
  486. package/src/adapters/grok/web.ts +640 -0
  487. package/src/adapters/hackernews/read.test.ts +68 -0
  488. package/src/adapters/hackernews/read.ts +265 -0
  489. package/src/adapters/hf/paper.test.ts +48 -0
  490. package/src/adapters/hf/paper.ts +138 -0
  491. package/src/adapters/homebrew/packages.test.ts +109 -0
  492. package/src/adapters/homebrew/packages.ts +304 -0
  493. package/src/adapters/indeed/jobs.test.ts +230 -0
  494. package/src/adapters/indeed/jobs.ts +375 -0
  495. package/src/adapters/instagram/collections.test.ts +94 -0
  496. package/src/adapters/instagram/collections.ts +206 -0
  497. package/src/adapters/jikan/web.test.ts +50 -0
  498. package/src/adapters/jikan/web.ts +177 -0
  499. package/src/adapters/kitsu/web.test.ts +29 -0
  500. package/src/adapters/kitsu/web.ts +109 -0
  501. package/src/adapters/konachan/detail.yaml +62 -0
  502. package/src/adapters/konachan/download.yaml +55 -0
  503. package/src/adapters/konachan/search.yaml +65 -0
  504. package/src/adapters/konachan/tags.yaml +40 -0
  505. package/src/adapters/lichess/players.test.ts +99 -0
  506. package/src/adapters/lichess/players.ts +277 -0
  507. package/src/adapters/lobsters/read-domain.test.ts +121 -0
  508. package/src/adapters/lobsters/read-domain.ts +400 -0
  509. package/src/adapters/mangadex/web.test.ts +46 -0
  510. package/src/adapters/mangadex/web.ts +210 -0
  511. package/src/adapters/maven/artifact.test.ts +67 -0
  512. package/src/adapters/maven/artifact.ts +155 -0
  513. package/src/adapters/mdn/search.test.ts +39 -0
  514. package/src/adapters/mdn/search.ts +133 -0
  515. package/src/adapters/medium/tag.test.ts +64 -0
  516. package/src/adapters/medium/tag.ts +164 -0
  517. package/src/adapters/moegirl/web.test.ts +87 -0
  518. package/src/adapters/moegirl/web.ts +343 -0
  519. package/src/adapters/npm/package.test.ts +53 -0
  520. package/src/adapters/npm/package.ts +177 -0
  521. package/src/adapters/nuget/package.test.ts +102 -0
  522. package/src/adapters/nuget/package.ts +193 -0
  523. package/src/adapters/nvd/cve.test.ts +66 -0
  524. package/src/adapters/nvd/cve.ts +182 -0
  525. package/src/adapters/oeis/sequences.test.ts +71 -0
  526. package/src/adapters/oeis/sequences.ts +234 -0
  527. package/src/adapters/openalex/works.test.ts +99 -0
  528. package/src/adapters/openalex/works.ts +319 -0
  529. package/src/adapters/openfda/records.test.ts +90 -0
  530. package/src/adapters/openfda/records.ts +239 -0
  531. package/src/adapters/openreview/papers.test.ts +139 -0
  532. package/src/adapters/openreview/papers.ts +560 -0
  533. package/src/adapters/osv/security.test.ts +91 -0
  534. package/src/adapters/osv/security.ts +298 -0
  535. package/src/adapters/packagist/package.test.ts +62 -0
  536. package/src/adapters/packagist/package.ts +146 -0
  537. package/src/adapters/pdf/read.yaml +49 -0
  538. package/src/adapters/pixiv/download.yaml +15 -2
  539. package/src/adapters/pubmed/articles.test.ts +96 -0
  540. package/src/adapters/pubmed/articles.ts +497 -0
  541. package/src/adapters/pypi/package.test.ts +131 -0
  542. package/src/adapters/pypi/package.ts +297 -0
  543. package/src/adapters/qwen/web.test.ts +176 -0
  544. package/src/adapters/qwen/web.ts +758 -0
  545. package/src/adapters/reddit/account.test.ts +56 -0
  546. package/src/adapters/reddit/account.ts +493 -0
  547. package/src/adapters/rednote/web.test.ts +354 -0
  548. package/src/adapters/rednote/web.ts +968 -0
  549. package/src/adapters/rest-countries/countries.test.ts +80 -0
  550. package/src/adapters/rest-countries/countries.ts +271 -0
  551. package/src/adapters/reuters/article-detail.test.ts +65 -0
  552. package/src/adapters/reuters/article-detail.ts +186 -0
  553. package/src/adapters/rfc/rfc.test.ts +37 -0
  554. package/src/adapters/rfc/rfc.ts +133 -0
  555. package/src/adapters/rubygems/gem.test.ts +43 -0
  556. package/src/adapters/rubygems/gem.ts +126 -0
  557. package/src/adapters/safebooru/detail.yaml +63 -0
  558. package/src/adapters/safebooru/download.yaml +58 -0
  559. package/src/adapters/safebooru/search.yaml +69 -0
  560. package/src/adapters/safebooru/web.test.ts +60 -0
  561. package/src/adapters/safebooru/web.ts +130 -0
  562. package/src/adapters/stackoverflow/questions.test.ts +207 -0
  563. package/src/adapters/stackoverflow/questions.ts +765 -0
  564. package/src/adapters/steam/app.test.ts +68 -0
  565. package/src/adapters/steam/app.ts +218 -0
  566. package/src/adapters/tiktok/creator-videos.test.ts +158 -0
  567. package/src/adapters/tiktok/creator-videos.ts +370 -0
  568. package/src/adapters/tvmaze/shows.test.ts +93 -0
  569. package/src/adapters/tvmaze/shows.ts +271 -0
  570. package/src/adapters/twitter/bookmark-folders.test.ts +164 -0
  571. package/src/adapters/twitter/bookmark-folders.ts +366 -0
  572. package/src/adapters/twitter/quote.test.ts +157 -0
  573. package/src/adapters/twitter/quote.ts +332 -0
  574. package/src/adapters/twitter/tweet-actions.test.ts +51 -0
  575. package/src/adapters/twitter/tweet-actions.ts +187 -0
  576. package/src/adapters/twitter/tweet-url.ts +65 -0
  577. package/src/adapters/uisdc/news.test.ts +46 -0
  578. package/src/adapters/uisdc/news.ts +111 -0
  579. package/src/adapters/vndb/web.test.ts +86 -0
  580. package/src/adapters/vndb/web.ts +393 -0
  581. package/src/adapters/weibo/favorites-publish.test.ts +177 -0
  582. package/src/adapters/weibo/favorites-publish.ts +426 -0
  583. package/src/adapters/wikidata/entities.test.ts +103 -0
  584. package/src/adapters/wikidata/entities.ts +253 -0
  585. package/src/adapters/wikipedia/page.test.ts +49 -0
  586. package/src/adapters/wikipedia/page.ts +158 -0
  587. package/src/adapters/wttr/weather.test.ts +99 -0
  588. package/src/adapters/wttr/weather.ts +239 -0
  589. package/src/adapters/xianyu/publish.test.ts +210 -0
  590. package/src/adapters/xianyu/publish.ts +420 -0
  591. package/src/adapters/xiaohongshu/user-helpers.ts +5 -2
  592. package/src/adapters/yandere/detail.yaml +61 -0
  593. package/src/adapters/yandere/download.yaml +56 -0
  594. package/src/adapters/yandere/search.yaml +67 -0
  595. package/src/adapters/yandere/tags.yaml +41 -0
  596. package/src/adapters/yuanbao/web.test.ts +144 -0
  597. package/src/adapters/yuanbao/web.ts +435 -0
  598. package/src/adapters/zhihu/collection.test.ts +96 -0
  599. package/src/adapters/zhihu/collection.ts +239 -0
  600. package/src/adapters/zlibrary/web.test.ts +104 -0
  601. package/src/adapters/zlibrary/web.ts +192 -0
@@ -0,0 +1,574 @@
1
+ /**
2
+ * @owner src/adapters/coingecko/markets.ts
3
+ * @does Register agent-facing CoinGecko market, coin, exchange, derivative, category, and global commands.
4
+ * @needs Public CoinGecko API, TypeScript adapter loader, bounded market argument parsing.
5
+ * @feeds surface coverage ledger, crypto market command surface, agent-readable market rows.
6
+ * @breaks CoinGecko API drift, weak slug validation, or silent empty rows hide market data failures.
7
+ */
8
+
9
+ import { cli, Strategy } from "../../registry.js";
10
+
11
+ const API_BASE = "https://api.coingecko.com/api/v3";
12
+ const CATEGORY_SORTS = new Set([
13
+ "market_cap_desc",
14
+ "market_cap_asc",
15
+ "name_desc",
16
+ "name_asc",
17
+ "market_cap_change_24h_desc",
18
+ "market_cap_change_24h_asc",
19
+ ]);
20
+
21
+ function stringField(value: unknown): string {
22
+ return typeof value === "string" ? value : "";
23
+ }
24
+
25
+ function numberField(value: unknown): number | null {
26
+ const n =
27
+ typeof value === "number"
28
+ ? value
29
+ : typeof value === "string" && value.trim()
30
+ ? Number(value)
31
+ : NaN;
32
+ return Number.isFinite(n) ? n : null;
33
+ }
34
+
35
+ function slug(value: unknown, label: string): string {
36
+ const text = String(value ?? "")
37
+ .trim()
38
+ .toLowerCase();
39
+ if (!text) throw new Error(`coingecko ${label} cannot be empty.`);
40
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(text)) {
41
+ throw new Error(`coingecko ${label} must be a lowercase slug.`);
42
+ }
43
+ return text;
44
+ }
45
+
46
+ export function requireCoinGeckoCurrency(value: unknown): string {
47
+ const currency = String(value ?? "usd")
48
+ .trim()
49
+ .toLowerCase();
50
+ if (!/^[a-z0-9-]{2,20}$/.test(currency)) {
51
+ throw new Error(`coingecko currency must look like a currency slug.`);
52
+ }
53
+ return currency;
54
+ }
55
+
56
+ export function requireCoinGeckoLimit(
57
+ value: unknown,
58
+ fallback: number,
59
+ max: number,
60
+ label = "limit",
61
+ ): number {
62
+ if (value === undefined || value === null || value === "") return fallback;
63
+ const n = Number(value);
64
+ if (!Number.isInteger(n) || n < 1 || n > max) {
65
+ throw new Error(
66
+ `coingecko ${label} must be an integer in [1, ${max}]. Got: ${String(value)}`,
67
+ );
68
+ }
69
+ return n;
70
+ }
71
+
72
+ function isoDate(value: unknown): string {
73
+ return value ? String(value).slice(0, 10) : "";
74
+ }
75
+
76
+ function pickCurrency(map: unknown, currency: string): unknown {
77
+ return map && typeof map === "object"
78
+ ? (map as Record<string, unknown>)[currency]
79
+ : null;
80
+ }
81
+
82
+ export function mapCoinRow(
83
+ data: Record<string, unknown>,
84
+ currency: string,
85
+ ): Record<string, unknown> {
86
+ const market = (data.market_data ?? {}) as Record<string, unknown>;
87
+ const price = pickCurrency(market.current_price, currency);
88
+ const marketCap = pickCurrency(market.market_cap, currency);
89
+ const volume24h = pickCurrency(market.total_volume, currency);
90
+ if (price == null && marketCap == null && volume24h == null) {
91
+ throw new Error(
92
+ `coingecko returned no market data for currency "${currency}".`,
93
+ );
94
+ }
95
+ const homepage = Array.isArray(
96
+ (data.links as { homepage?: unknown[] } | undefined)?.homepage,
97
+ )
98
+ ? ((data.links as { homepage?: unknown[] }).homepage ?? []).find(Boolean)
99
+ : "";
100
+ return {
101
+ id: stringField(data.id),
102
+ symbol: stringField(data.symbol).toUpperCase(),
103
+ name: stringField(data.name),
104
+ rank: numberField(data.market_cap_rank),
105
+ price,
106
+ marketCap,
107
+ volume24h,
108
+ change24hPct: numberField(market.price_change_percentage_24h),
109
+ change7dPct: numberField(market.price_change_percentage_7d),
110
+ change30dPct: numberField(market.price_change_percentage_30d),
111
+ ath: pickCurrency(market.ath, currency),
112
+ athDate: isoDate(pickCurrency(market.ath_date, currency)),
113
+ atl: pickCurrency(market.atl, currency),
114
+ atlDate: isoDate(pickCurrency(market.atl_date, currency)),
115
+ circulatingSupply: numberField(market.circulating_supply),
116
+ totalSupply: numberField(market.total_supply),
117
+ maxSupply: numberField(market.max_supply),
118
+ genesisDate: stringField(data.genesis_date),
119
+ homepage: homepage ? String(homepage) : "",
120
+ };
121
+ }
122
+
123
+ export function mapTopRows(
124
+ rows: Array<Record<string, unknown>>,
125
+ ): Array<Record<string, unknown>> {
126
+ return rows.map((coin) => ({
127
+ rank: numberField(coin.market_cap_rank),
128
+ symbol: stringField(coin.symbol).toUpperCase(),
129
+ name: stringField(coin.name),
130
+ price: numberField(coin.current_price),
131
+ change24hPct: numberField(coin.price_change_percentage_24h),
132
+ marketCap: numberField(coin.market_cap),
133
+ volume24h: numberField(coin.total_volume),
134
+ high24h: numberField(coin.high_24h),
135
+ low24h: numberField(coin.low_24h),
136
+ }));
137
+ }
138
+
139
+ export function mapTrendingRows(
140
+ coins: Array<{ item?: Record<string, unknown> }>,
141
+ ): Array<Record<string, unknown>> {
142
+ return coins.map((entry, index) => {
143
+ const coin = entry.item ?? {};
144
+ return {
145
+ rank: index + 1,
146
+ id: stringField(coin.id),
147
+ symbol: stringField(coin.symbol).toUpperCase(),
148
+ name: stringField(coin.name),
149
+ marketCapRank: numberField(coin.market_cap_rank),
150
+ priceBtc: numberField(coin.price_btc),
151
+ thumb:
152
+ stringField(coin.thumb) ||
153
+ stringField(coin.small) ||
154
+ stringField(coin.large),
155
+ };
156
+ });
157
+ }
158
+
159
+ export function mapCategoryRows(
160
+ rows: Array<Record<string, unknown>>,
161
+ limit: number,
162
+ ): Array<Record<string, unknown>> {
163
+ return rows.slice(0, limit).map((category, index) => ({
164
+ rank: index + 1,
165
+ id: stringField(category.id),
166
+ name: stringField(category.name),
167
+ marketCap: numberField(category.market_cap),
168
+ volume24h: numberField(category.volume_24h),
169
+ marketCapChange24hPct: numberField(category.market_cap_change_24h),
170
+ top3Coins: Array.isArray(category.top_3_coins_id)
171
+ ? category.top_3_coins_id.join(", ")
172
+ : "",
173
+ }));
174
+ }
175
+
176
+ export function mapExchangeRows(
177
+ rows: Array<Record<string, unknown>>,
178
+ page: number,
179
+ limit: number,
180
+ ): Array<Record<string, unknown>> {
181
+ return rows.map((exchange, index) => ({
182
+ rank: (page - 1) * limit + index + 1,
183
+ id: stringField(exchange.id),
184
+ name: stringField(exchange.name),
185
+ trustScore: numberField(exchange.trust_score),
186
+ volume24hBtc: numberField(exchange.trade_volume_24h_btc),
187
+ country: stringField(exchange.country),
188
+ yearEstablished: numberField(exchange.year_established),
189
+ url: stringField(exchange.url),
190
+ }));
191
+ }
192
+
193
+ export function mapDerivativeRows(
194
+ rows: Array<Record<string, unknown>>,
195
+ limit: number,
196
+ symbolFilter = "",
197
+ ): Array<Record<string, unknown>> {
198
+ const filter = symbolFilter.trim().toUpperCase();
199
+ const filtered = filter
200
+ ? rows.filter(
201
+ (row) =>
202
+ stringField(row.symbol).toUpperCase().includes(filter) ||
203
+ stringField(row.index_id).toUpperCase().includes(filter),
204
+ )
205
+ : rows;
206
+ if (filtered.length === 0) {
207
+ throw new Error(`No derivative tickers matched symbol="${filter}".`);
208
+ }
209
+ return filtered.slice(0, limit).map((row, index) => ({
210
+ rank: index + 1,
211
+ market: stringField(row.market),
212
+ symbol: stringField(row.symbol),
213
+ indexId: stringField(row.index_id),
214
+ contractType: stringField(row.contract_type),
215
+ price: numberField(row.price),
216
+ change24hPct: numberField(row.price_percentage_change_24h),
217
+ fundingRate: numberField(row.funding_rate),
218
+ openInterestUsd: numberField(row.open_interest),
219
+ volume24hUsd: numberField(row.volume_24h),
220
+ expired: row.expired_at ? String(row.expired_at) : "",
221
+ }));
222
+ }
223
+
224
+ export function mapGlobalRow(
225
+ body: Record<string, unknown>,
226
+ currency: string,
227
+ ): Record<string, unknown> {
228
+ const data = (body.data ?? {}) as Record<string, unknown>;
229
+ const totalMarketCap = pickCurrency(data.total_market_cap, currency);
230
+ const totalVolume = pickCurrency(data.total_volume, currency);
231
+ if (totalMarketCap == null && totalVolume == null) {
232
+ throw new Error(
233
+ `coingecko has no market totals for currency "${currency}".`,
234
+ );
235
+ }
236
+ const dominance = (data.market_cap_percentage ?? {}) as Record<
237
+ string,
238
+ unknown
239
+ >;
240
+ return {
241
+ currency: currency.toUpperCase(),
242
+ totalMarketCap: numberField(totalMarketCap),
243
+ totalVolume24h: numberField(totalVolume),
244
+ marketCapChange24hPct: numberField(
245
+ data.market_cap_change_percentage_24h_usd,
246
+ ),
247
+ btcDominancePct: numberField(dominance.btc),
248
+ ethDominancePct: numberField(dominance.eth),
249
+ activeCryptocurrencies: numberField(data.active_cryptocurrencies),
250
+ markets: numberField(data.markets),
251
+ ongoingIcos: numberField(data.ongoing_icos),
252
+ updatedAt: data.updated_at
253
+ ? new Date(Number(data.updated_at) * 1000).toISOString()
254
+ : "",
255
+ };
256
+ }
257
+
258
+ async function fetchJson(url: URL | string, label: string): Promise<unknown> {
259
+ const response = await fetch(url, {
260
+ headers: { "User-Agent": "unicli (https://github.com/olo-dot-io/Uni-CLI)" },
261
+ });
262
+ if (response.status === 404) throw new Error(`${label} returned no result.`);
263
+ if (response.status === 429) throw new Error(`${label} returned HTTP 429.`);
264
+ if (!response.ok)
265
+ throw new Error(`${label} returned HTTP ${response.status}.`);
266
+ return response.json();
267
+ }
268
+
269
+ cli({
270
+ site: "coingecko",
271
+ name: "coin",
272
+ description: "Fetch a single cryptocurrency's market data by CoinGecko id",
273
+ domain: "api.coingecko.com",
274
+ strategy: Strategy.PUBLIC,
275
+ args: [
276
+ {
277
+ name: "id",
278
+ type: "str",
279
+ required: true,
280
+ positional: true,
281
+ description: "CoinGecko coin id",
282
+ },
283
+ {
284
+ name: "currency",
285
+ type: "str",
286
+ default: "usd",
287
+ description: "Quote currency",
288
+ },
289
+ ],
290
+ columns: [
291
+ "id",
292
+ "symbol",
293
+ "name",
294
+ "rank",
295
+ "price",
296
+ "marketCap",
297
+ "volume24h",
298
+ "change24hPct",
299
+ "change7dPct",
300
+ "change30dPct",
301
+ "ath",
302
+ "athDate",
303
+ "atl",
304
+ "atlDate",
305
+ "circulatingSupply",
306
+ "totalSupply",
307
+ "maxSupply",
308
+ "genesisDate",
309
+ "homepage",
310
+ ],
311
+ func: async (_page, kwargs) => {
312
+ const id = slug(kwargs.id, "coin id");
313
+ const currency = requireCoinGeckoCurrency(kwargs.currency);
314
+ const url = new URL(`${API_BASE}/coins/${id}`);
315
+ url.searchParams.set("localization", "false");
316
+ url.searchParams.set("tickers", "false");
317
+ url.searchParams.set("market_data", "true");
318
+ url.searchParams.set("community_data", "false");
319
+ url.searchParams.set("developer_data", "false");
320
+ url.searchParams.set("sparkline", "false");
321
+ return [
322
+ mapCoinRow(
323
+ (await fetchJson(url, "coingecko coin")) as Record<string, unknown>,
324
+ currency,
325
+ ),
326
+ ];
327
+ },
328
+ });
329
+
330
+ cli({
331
+ site: "coingecko",
332
+ name: "top",
333
+ description: "Top coins by market cap",
334
+ domain: "api.coingecko.com",
335
+ strategy: Strategy.PUBLIC,
336
+ args: [
337
+ {
338
+ name: "currency",
339
+ type: "str",
340
+ default: "usd",
341
+ description: "Quote currency",
342
+ },
343
+ { name: "limit", type: "int", default: 10, description: "Number of coins" },
344
+ ],
345
+ columns: [
346
+ "rank",
347
+ "symbol",
348
+ "name",
349
+ "price",
350
+ "change24hPct",
351
+ "marketCap",
352
+ "volume24h",
353
+ "high24h",
354
+ "low24h",
355
+ ],
356
+ func: async (_page, kwargs) => {
357
+ const currency = requireCoinGeckoCurrency(kwargs.currency);
358
+ const limit = requireCoinGeckoLimit(kwargs.limit, 10, 250);
359
+ const url = new URL(`${API_BASE}/coins/markets`);
360
+ url.searchParams.set("vs_currency", currency);
361
+ url.searchParams.set("order", "market_cap_desc");
362
+ url.searchParams.set("per_page", String(limit));
363
+ url.searchParams.set("page", "1");
364
+ url.searchParams.set("sparkline", "false");
365
+ const rows = mapTopRows(
366
+ (await fetchJson(url, "coingecko top")) as Array<Record<string, unknown>>,
367
+ );
368
+ if (rows.length === 0)
369
+ throw new Error("coingecko returned no market data.");
370
+ return rows;
371
+ },
372
+ });
373
+
374
+ cli({
375
+ site: "coingecko",
376
+ name: "trending",
377
+ description: "Top trending cryptocurrencies on CoinGecko",
378
+ domain: "api.coingecko.com",
379
+ strategy: Strategy.PUBLIC,
380
+ args: [],
381
+ columns: [
382
+ "rank",
383
+ "id",
384
+ "symbol",
385
+ "name",
386
+ "marketCapRank",
387
+ "priceBtc",
388
+ "thumb",
389
+ ],
390
+ func: async () => {
391
+ const body = (await fetchJson(
392
+ `${API_BASE}/search/trending`,
393
+ "coingecko trending",
394
+ )) as {
395
+ coins?: Array<{ item?: Record<string, unknown> }>;
396
+ };
397
+ const rows = mapTrendingRows(Array.isArray(body.coins) ? body.coins : []);
398
+ if (rows.length === 0)
399
+ throw new Error("coingecko returned no trending coins.");
400
+ return rows;
401
+ },
402
+ });
403
+
404
+ cli({
405
+ site: "coingecko",
406
+ name: "categories",
407
+ description: "Crypto categories ranked by aggregated market cap",
408
+ domain: "api.coingecko.com",
409
+ strategy: Strategy.PUBLIC,
410
+ args: [
411
+ {
412
+ name: "sort",
413
+ type: "str",
414
+ default: "market_cap_desc",
415
+ description: "Sort order",
416
+ },
417
+ {
418
+ name: "limit",
419
+ type: "int",
420
+ default: 20,
421
+ description: "Number of categories",
422
+ },
423
+ ],
424
+ columns: [
425
+ "rank",
426
+ "id",
427
+ "name",
428
+ "marketCap",
429
+ "volume24h",
430
+ "marketCapChange24hPct",
431
+ "top3Coins",
432
+ ],
433
+ func: async (_page, kwargs) => {
434
+ const sort = String(kwargs.sort ?? "market_cap_desc")
435
+ .trim()
436
+ .toLowerCase();
437
+ if (!CATEGORY_SORTS.has(sort))
438
+ throw new Error(
439
+ `coingecko sort "${String(kwargs.sort)}" is not supported.`,
440
+ );
441
+ const limit = requireCoinGeckoLimit(kwargs.limit, 20, 100);
442
+ const rows = mapCategoryRows(
443
+ (await fetchJson(
444
+ `${API_BASE}/coins/categories?order=${encodeURIComponent(sort)}`,
445
+ "coingecko categories",
446
+ )) as Array<Record<string, unknown>>,
447
+ limit,
448
+ );
449
+ if (rows.length === 0)
450
+ throw new Error("CoinGecko returned no category data.");
451
+ return rows;
452
+ },
453
+ });
454
+
455
+ cli({
456
+ site: "coingecko",
457
+ name: "exchanges",
458
+ description: "Top crypto exchanges by 24h BTC trading volume",
459
+ domain: "api.coingecko.com",
460
+ strategy: Strategy.PUBLIC,
461
+ args: [
462
+ {
463
+ name: "limit",
464
+ type: "int",
465
+ default: 20,
466
+ description: "Number of exchanges",
467
+ },
468
+ { name: "page", type: "int", default: 1, description: "Page number" },
469
+ ],
470
+ columns: [
471
+ "rank",
472
+ "id",
473
+ "name",
474
+ "trustScore",
475
+ "volume24hBtc",
476
+ "country",
477
+ "yearEstablished",
478
+ "url",
479
+ ],
480
+ func: async (_page, kwargs) => {
481
+ const limit = requireCoinGeckoLimit(kwargs.limit, 20, 250);
482
+ const page = requireCoinGeckoLimit(kwargs.page, 1, 10_000, "page");
483
+ const url = new URL(`${API_BASE}/exchanges`);
484
+ url.searchParams.set("per_page", String(limit));
485
+ url.searchParams.set("page", String(page));
486
+ const rows = mapExchangeRows(
487
+ (await fetchJson(url, "coingecko exchanges")) as Array<
488
+ Record<string, unknown>
489
+ >,
490
+ page,
491
+ limit,
492
+ );
493
+ if (rows.length === 0)
494
+ throw new Error("CoinGecko returned no exchange data.");
495
+ return rows;
496
+ },
497
+ });
498
+
499
+ cli({
500
+ site: "coingecko",
501
+ name: "derivatives",
502
+ description: "Top crypto derivative markets by 24h volume",
503
+ domain: "api.coingecko.com",
504
+ strategy: Strategy.PUBLIC,
505
+ args: [
506
+ { name: "limit", type: "int", default: 20, description: "Max rows" },
507
+ {
508
+ name: "symbol",
509
+ type: "str",
510
+ description: "Optional symbol substring filter",
511
+ },
512
+ ],
513
+ columns: [
514
+ "rank",
515
+ "market",
516
+ "symbol",
517
+ "indexId",
518
+ "contractType",
519
+ "price",
520
+ "change24hPct",
521
+ "fundingRate",
522
+ "openInterestUsd",
523
+ "volume24hUsd",
524
+ "expired",
525
+ ],
526
+ func: async (_page, kwargs) => {
527
+ const limit = requireCoinGeckoLimit(kwargs.limit, 20, 500);
528
+ return mapDerivativeRows(
529
+ (await fetchJson(
530
+ `${API_BASE}/derivatives`,
531
+ "coingecko derivatives",
532
+ )) as Array<Record<string, unknown>>,
533
+ limit,
534
+ String(kwargs.symbol ?? ""),
535
+ );
536
+ },
537
+ });
538
+
539
+ cli({
540
+ site: "coingecko",
541
+ name: "global",
542
+ description: "Aggregate crypto market stats",
543
+ domain: "api.coingecko.com",
544
+ strategy: Strategy.PUBLIC,
545
+ args: [
546
+ {
547
+ name: "currency",
548
+ type: "str",
549
+ default: "usd",
550
+ description: "Quote currency",
551
+ },
552
+ ],
553
+ columns: [
554
+ "currency",
555
+ "totalMarketCap",
556
+ "totalVolume24h",
557
+ "marketCapChange24hPct",
558
+ "btcDominancePct",
559
+ "ethDominancePct",
560
+ "activeCryptocurrencies",
561
+ "markets",
562
+ "ongoingIcos",
563
+ "updatedAt",
564
+ ],
565
+ func: async (_page, kwargs) => [
566
+ mapGlobalRow(
567
+ (await fetchJson(`${API_BASE}/global`, "coingecko global")) as Record<
568
+ string,
569
+ unknown
570
+ >,
571
+ requireCoinGeckoCurrency(kwargs.currency),
572
+ ),
573
+ ],
574
+ });
@@ -0,0 +1,111 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { resolveCommand } from "../../registry.js";
3
+ import {
4
+ canonicalizeCoupangProductUrl,
5
+ mapCoupangProductRow,
6
+ normalizeCoupangProductId,
7
+ requireCoupangProductId,
8
+ } from "./product.js";
9
+
10
+ function pageMock(evaluateResults: unknown[]) {
11
+ return {
12
+ goto: vi.fn().mockResolvedValue(undefined),
13
+ wait: vi.fn().mockResolvedValue(undefined),
14
+ evaluate: vi.fn(async () => evaluateResults.shift()),
15
+ };
16
+ }
17
+
18
+ describe("coupang product agent-facing command", () => {
19
+ it("validates ids and maps product detail rows", () => {
20
+ expect(
21
+ normalizeCoupangProductId(
22
+ "https://www.coupang.com/vp/products/123456789?itemId=1",
23
+ ),
24
+ ).toBe("123456789");
25
+ expect(requireCoupangProductId("123456789", "product-id")).toBe(
26
+ "123456789",
27
+ );
28
+ expect(
29
+ requireCoupangProductId("/vp/products/123456789?itemId=1", "url"),
30
+ ).toBe("123456789");
31
+ expect(() => requireCoupangProductId("abc", "product-id")).toThrow(
32
+ "Coupang product-id",
33
+ );
34
+ expect(canonicalizeCoupangProductUrl("", "123456789")).toBe(
35
+ "https://www.coupang.com/vp/products/123456789",
36
+ );
37
+ expect(
38
+ mapCoupangProductRow(
39
+ "123456789",
40
+ {
41
+ title: " Mouse ",
42
+ price: "29,900원",
43
+ rating: "4.8",
44
+ review_count: "1,234",
45
+ seller: "Coupang",
46
+ },
47
+ "https://www.coupang.com/vp/products/123456789",
48
+ ),
49
+ ).toMatchObject({
50
+ product_id: "123456789",
51
+ title: "Mouse",
52
+ price: 29900,
53
+ rating: 4.8,
54
+ review_count: 1234,
55
+ seller: "Coupang",
56
+ url: "https://www.coupang.com/vp/products/123456789",
57
+ });
58
+ });
59
+
60
+ it("extracts Coupang product details", async () => {
61
+ const command = resolveCommand("coupang", "product")?.command;
62
+ const page = pageMock([
63
+ {
64
+ ok: true,
65
+ currentProductId: "123456789",
66
+ loginHints: { hasLoginLink: false, hasMyCoupang: false },
67
+ data: {
68
+ product_id: "123456789",
69
+ title: "Mouse",
70
+ price: 29900,
71
+ rating: 4.8,
72
+ review_count: 1234,
73
+ seller: "Coupang",
74
+ },
75
+ },
76
+ ]);
77
+ await expect(
78
+ command!.func!(page, { "product-id": "123456789" }),
79
+ ).resolves.toEqual([
80
+ {
81
+ product_id: "123456789",
82
+ title: "Mouse",
83
+ price: 29900,
84
+ original_price: null,
85
+ discount_rate: null,
86
+ rating: 4.8,
87
+ review_count: 1234,
88
+ seller: "Coupang",
89
+ brand: null,
90
+ rocket: null,
91
+ delivery_promise: null,
92
+ image_url: null,
93
+ url: "https://www.coupang.com/vp/products/123456789",
94
+ },
95
+ ]);
96
+ expect(page.goto).toHaveBeenCalledWith(
97
+ "https://www.coupang.com/vp/products/123456789",
98
+ { waitUntil: "load", settleMs: 3000 },
99
+ );
100
+ expect(page.wait).toHaveBeenCalledWith(2);
101
+ });
102
+
103
+ it("fails before navigation on malformed ids", async () => {
104
+ const command = resolveCommand("coupang", "product")?.command;
105
+ const page = pageMock([]);
106
+ await expect(command!.func!(page, { "product-id": "abc" })).rejects.toThrow(
107
+ "Coupang product-id",
108
+ );
109
+ expect(page.goto).not.toHaveBeenCalled();
110
+ });
111
+ });