@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,123 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ decodeXmlEntities,
4
+ mapPublicationHit,
5
+ mapRecordXml,
6
+ mapVenueHit,
7
+ normalizeDblpAuthors,
8
+ requireDblpLimit,
9
+ requireDblpQuery,
10
+ requirePid,
11
+ requireRecordKey,
12
+ splitAuthorRecords,
13
+ } from "./publications.js";
14
+
15
+ describe("dblp agent-facing publication commands", () => {
16
+ it("validates query, limit, record key, and PID inputs", () => {
17
+ expect(requireDblpQuery(" attention ")).toBe("attention");
18
+ expect(() => requireDblpQuery("")).toThrow("cannot be empty");
19
+ expect(requireDblpLimit(undefined, 20, 100)).toBe(20);
20
+ expect(requireDblpLimit("100", 20, 100)).toBe(100);
21
+ expect(() => requireDblpLimit("101", 20, 100)).toThrow(
22
+ "limit must be an integer",
23
+ );
24
+ expect(requireRecordKey("conf/nips/VaswaniSPUJGKP17")).toBe(
25
+ "conf/nips/VaswaniSPUJGKP17",
26
+ );
27
+ expect(() => requireRecordKey("https://dblp.org")).toThrow("not valid");
28
+ expect(requirePid("56/953")).toBe("56/953");
29
+ expect(() => requirePid("../56/953")).toThrow("not valid");
30
+ });
31
+
32
+ it("normalizes XML entities, authors, and publication search hits", () => {
33
+ expect(decodeXmlEntities("A & B 😀")).toBe("A & B 😀");
34
+ expect(
35
+ normalizeDblpAuthors({
36
+ author: [{ text: "Jane Doe 0001" }, { "#text": "Max & Co" }],
37
+ }),
38
+ ).toEqual(["Jane Doe", "Max & Co"]);
39
+ expect(
40
+ mapPublicationHit(
41
+ {
42
+ info: {
43
+ key: "conf/nips/Paper24",
44
+ title: "A Paper.",
45
+ authors: { author: { text: "Jane Doe 0001" } },
46
+ venue: "NeurIPS",
47
+ year: "2024",
48
+ type: "Conference and Workshop Papers",
49
+ doi: "10.0000/test",
50
+ ee: "https://doi.org/10.0000/test",
51
+ },
52
+ },
53
+ 1,
54
+ ),
55
+ ).toEqual({
56
+ rank: 1,
57
+ key: "conf/nips/Paper24",
58
+ title: "A Paper",
59
+ authors: "Jane Doe",
60
+ venue: "NeurIPS",
61
+ year: "2024",
62
+ type: "conf",
63
+ doi: "10.0000/test",
64
+ url: "https://doi.org/10.0000/test",
65
+ });
66
+ });
67
+
68
+ it("maps record XML and author publication records", () => {
69
+ const xml = `
70
+ <dblp>
71
+ <inproceedings key="conf/nips/Paper24">
72
+ <author>Jane Doe 0001</author>
73
+ <author>Max &amp; Co</author>
74
+ <title>A Paper.</title>
75
+ <booktitle>NeurIPS</booktitle>
76
+ <year>2024</year>
77
+ <pages>1-10</pages>
78
+ <ee type="oa">https://openaccess.example/paper</ee>
79
+ <ee>https://doi.org/10.0000/test</ee>
80
+ </inproceedings>
81
+ </dblp>
82
+ `;
83
+ expect(mapRecordXml(xml)).toEqual({
84
+ key: "conf/nips/Paper24",
85
+ type: "conf",
86
+ title: "A Paper",
87
+ authors: "Jane Doe, Max & Co",
88
+ venue: "NeurIPS",
89
+ year: "2024",
90
+ pages: "1-10",
91
+ doi: "10.0000/test",
92
+ open_access_url: "https://openaccess.example/paper",
93
+ dblp_url: "https://dblp.org/rec/conf/nips/Paper24.html",
94
+ });
95
+ expect(
96
+ splitAuthorRecords(
97
+ `<dblpperson><r>${xml}</r><r><crossref /></r></dblpperson>`,
98
+ ),
99
+ ).toHaveLength(1);
100
+ });
101
+
102
+ it("maps venue hits with relative URLs", () => {
103
+ expect(
104
+ mapVenueHit(
105
+ {
106
+ info: {
107
+ acronym: "ICLR",
108
+ venue: "International Conference on Learning Representations",
109
+ type: "Conference or Workshop",
110
+ url: "/db/conf/iclr/",
111
+ },
112
+ },
113
+ 2,
114
+ ),
115
+ ).toEqual({
116
+ rank: 2,
117
+ acronym: "ICLR",
118
+ venue: "International Conference on Learning Representations",
119
+ type: "conf",
120
+ url: "https://dblp.org/db/conf/iclr/",
121
+ });
122
+ });
123
+ });
@@ -0,0 +1,494 @@
1
+ /**
2
+ * @owner src/adapters/dblp/publications.ts
3
+ * @does Register agent-facing dblp publication, author, paper, and venue commands.
4
+ * @needs dblp public JSON/XML APIs, conservative XML field extraction, bounded result limits.
5
+ * @feeds surface coverage ledger, scholarly search surface, CS bibliography inspection.
6
+ * @breaks dblp API envelope drift, record-key parsing, or silent empty rows hide bibliography lookup failures.
7
+ */
8
+
9
+ import { cli, Strategy } from "../../registry.js";
10
+
11
+ const DBLP_ORIGIN = "https://dblp.org";
12
+ const RECORD_KEY_RE = /^[a-z]+(?:\/[A-Za-z0-9_.-]+)+$/;
13
+ const PID_RE = /^[0-9a-z]+(?:\/[0-9a-z-]+)+$/i;
14
+
15
+ interface DblpAuthorField {
16
+ author?: unknown;
17
+ }
18
+
19
+ interface DblpHitInfo {
20
+ key?: unknown;
21
+ title?: unknown;
22
+ authors?: DblpAuthorField;
23
+ author?: unknown;
24
+ venue?: unknown;
25
+ year?: unknown;
26
+ type?: unknown;
27
+ doi?: unknown;
28
+ ee?: unknown;
29
+ url?: unknown;
30
+ acronym?: unknown;
31
+ }
32
+
33
+ interface DblpHit {
34
+ info?: DblpHitInfo;
35
+ }
36
+
37
+ interface DblpSearchBody {
38
+ result?: {
39
+ status?: { "@code"?: unknown; text?: unknown };
40
+ hits?: { hit?: DblpHit[] | DblpHit };
41
+ };
42
+ }
43
+
44
+ function stringField(value: unknown): string {
45
+ return typeof value === "string" ? value.trim() : "";
46
+ }
47
+
48
+ export function requireDblpQuery(value: unknown, label = "query"): string {
49
+ const query = String(value ?? "").trim();
50
+ if (!query) throw new Error(`dblp ${label} cannot be empty.`);
51
+ return query;
52
+ }
53
+
54
+ export function requireDblpLimit(
55
+ value: unknown,
56
+ fallback: number,
57
+ max: number,
58
+ ): number {
59
+ if (value === undefined || value === null || value === "") return fallback;
60
+ const n = Number(value);
61
+ if (!Number.isInteger(n) || n < 1 || n > max) {
62
+ throw new Error(`limit must be an integer in [1, ${max}].`);
63
+ }
64
+ return n;
65
+ }
66
+
67
+ export function requireRecordKey(value: unknown): string {
68
+ const key = String(value ?? "").trim();
69
+ if (!key) throw new Error("dblp paper key is required.");
70
+ if (!RECORD_KEY_RE.test(key)) {
71
+ throw new Error(`dblp paper key "${String(value)}" is not valid.`);
72
+ }
73
+ return key;
74
+ }
75
+
76
+ export function requirePid(value: unknown): string {
77
+ const pid = String(value ?? "").trim();
78
+ if (!PID_RE.test(pid)) throw new Error(`dblp pid "${pid}" is not valid.`);
79
+ return pid;
80
+ }
81
+
82
+ export function decodeXmlEntities(value: unknown): string {
83
+ return String(value ?? "")
84
+ .replace(/&amp;/g, "&")
85
+ .replace(/&lt;/g, "<")
86
+ .replace(/&gt;/g, ">")
87
+ .replace(/&apos;/g, "'")
88
+ .replace(/&quot;/g, '"')
89
+ .replace(/&#x([0-9a-fA-F]+);/g, (_m, hex: string) =>
90
+ String.fromCodePoint(Number.parseInt(hex, 16)),
91
+ )
92
+ .replace(/&#(\d+);/g, (_m, dec: string) =>
93
+ String.fromCodePoint(Number.parseInt(dec, 10)),
94
+ );
95
+ }
96
+
97
+ function stripTrailingDot(value: string): string {
98
+ return value.replace(/\.\s*$/, "");
99
+ }
100
+
101
+ function trimAuthorHomonym(value: string): string {
102
+ return value.replace(/\s+\d{4,}$/, "").trim();
103
+ }
104
+
105
+ function compactType(value: unknown): string {
106
+ const type = stringField(value);
107
+ if (/Conference and Workshop/i.test(type)) return "conf";
108
+ if (/Conference or Workshop/i.test(type)) return "conf";
109
+ if (/Journal/i.test(type)) return "journal";
110
+ if (/Books and Theses/i.test(type)) return "book";
111
+ if (/Editorship/i.test(type)) return "editorship";
112
+ if (/Reference/i.test(type)) return "reference";
113
+ if (/Informal/i.test(type)) return "preprint";
114
+ if (/Series/i.test(type)) return "series";
115
+ return type ? type.toLowerCase().split(/\s+/)[0] : "";
116
+ }
117
+
118
+ export function normalizeDblpAuthors(authorsField: unknown): string[] {
119
+ const raw =
120
+ authorsField && typeof authorsField === "object"
121
+ ? (authorsField as DblpAuthorField).author
122
+ : undefined;
123
+ const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
124
+ return list
125
+ .map((author) => {
126
+ if (author && typeof author === "object") {
127
+ const record = author as { text?: unknown; "#text"?: unknown };
128
+ return trimAuthorHomonym(
129
+ decodeXmlEntities(record.text ?? record["#text"]),
130
+ );
131
+ }
132
+ return trimAuthorHomonym(decodeXmlEntities(author));
133
+ })
134
+ .filter(Boolean);
135
+ }
136
+
137
+ function hitList(body: DblpSearchBody, label: string): DblpHit[] {
138
+ const code = stringField(body.result?.status?.["@code"]);
139
+ if (code && code !== "200") {
140
+ throw new Error(`${label} returned API status ${code}.`);
141
+ }
142
+ const hits = body.result?.hits?.hit;
143
+ return Array.isArray(hits) ? hits : hits ? [hits] : [];
144
+ }
145
+
146
+ export function mapPublicationHit(
147
+ hit: DblpHit,
148
+ rank: number,
149
+ ): Record<string, unknown> {
150
+ const info = hit.info ?? {};
151
+ const key = stringField(info.key);
152
+ return {
153
+ rank,
154
+ key,
155
+ title: stripTrailingDot(decodeXmlEntities(info.title)).trim(),
156
+ authors: normalizeDblpAuthors(info.authors).join(", "),
157
+ venue: decodeXmlEntities(info.venue),
158
+ year: stringField(info.year),
159
+ type: compactType(info.type),
160
+ doi: stringField(info.doi),
161
+ url: stringField(info.ee) || stringField(info.url),
162
+ };
163
+ }
164
+
165
+ export function mapVenueHit(
166
+ hit: DblpHit,
167
+ rank: number,
168
+ ): Record<string, unknown> {
169
+ const info = hit.info ?? {};
170
+ const url = stringField(info.url);
171
+ return {
172
+ rank,
173
+ acronym: stringField(info.acronym),
174
+ venue: decodeXmlEntities(info.venue),
175
+ type: compactType(info.type),
176
+ url: url.startsWith("http")
177
+ ? url
178
+ : url
179
+ ? `${DBLP_ORIGIN}${url.startsWith("/") ? "" : "/"}${url}`
180
+ : "",
181
+ };
182
+ }
183
+
184
+ export function extractFirst(xml: string, tag: string): string {
185
+ const match = xml.match(
186
+ new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`),
187
+ );
188
+ return match ? match[1] : "";
189
+ }
190
+
191
+ export function extractAll(xml: string, tag: string): string[] {
192
+ const out: string[] = [];
193
+ const re = new RegExp(`<${tag}\\b[^>]*>([\\s\\S]*?)<\\/${tag}>`, "g");
194
+ let match: RegExpExecArray | null;
195
+ while ((match = re.exec(xml)) !== null) out.push(match[1]);
196
+ return out;
197
+ }
198
+
199
+ export function extractRecordKey(xml: string): string {
200
+ const match = xml.match(
201
+ /<(?:article|inproceedings|incollection|proceedings|book|phdthesis|mastersthesis)\b[^>]*\bkey="([^"]+)"/,
202
+ );
203
+ return match ? match[1] : "";
204
+ }
205
+
206
+ function extractRecordType(xml: string): string {
207
+ const match = xml.match(
208
+ /<(article|inproceedings|incollection|proceedings|book|phdthesis|mastersthesis)\b/,
209
+ );
210
+ if (!match) return "";
211
+ switch (match[1]) {
212
+ case "inproceedings":
213
+ return "conf";
214
+ case "article":
215
+ return "journal";
216
+ case "incollection":
217
+ return "incollection";
218
+ case "proceedings":
219
+ return "editorship";
220
+ default:
221
+ return match[1];
222
+ }
223
+ }
224
+
225
+ function extractOpenAccessUrl(xml: string): string {
226
+ const oa = xml.match(/<ee\b[^>]*type=["']oa["'][^>]*>([\s\S]*?)<\/ee>/);
227
+ if (oa) return stringField(decodeXmlEntities(oa[1]));
228
+ const any = xml.match(/<ee\b[^>]*>([\s\S]*?)<\/ee>/);
229
+ return any ? stringField(decodeXmlEntities(any[1])) : "";
230
+ }
231
+
232
+ export function mapRecordXml(xml: string): Record<string, unknown> {
233
+ const key = extractRecordKey(xml);
234
+ const type = extractRecordType(xml);
235
+ const venue =
236
+ type === "conf"
237
+ ? decodeXmlEntities(extractFirst(xml, "booktitle"))
238
+ : decodeXmlEntities(extractFirst(xml, "journal"));
239
+ const ee = extractAll(xml, "ee")
240
+ .map(decodeXmlEntities)
241
+ .find((value) => /(?:doi\.org\/|^10\.)/i.test(value));
242
+ const doi = ee ? ee.replace(/^https?:\/\/(?:dx\.)?doi\.org\//i, "") : "";
243
+ return {
244
+ key,
245
+ type,
246
+ title: stripTrailingDot(decodeXmlEntities(extractFirst(xml, "title"))),
247
+ authors: extractAll(xml, "author")
248
+ .map((author) => trimAuthorHomonym(decodeXmlEntities(author)))
249
+ .filter(Boolean)
250
+ .join(", "),
251
+ venue,
252
+ year: decodeXmlEntities(extractFirst(xml, "year")),
253
+ pages: decodeXmlEntities(extractFirst(xml, "pages")),
254
+ doi: doi.startsWith("10.") ? doi : "",
255
+ open_access_url: extractOpenAccessUrl(xml),
256
+ dblp_url: key ? `${DBLP_ORIGIN}/rec/${key}.html` : "",
257
+ };
258
+ }
259
+
260
+ export function splitAuthorRecords(xml: string): string[] {
261
+ const records: string[] = [];
262
+ const re = /<r>\s*([\s\S]*?)\s*<\/r>/g;
263
+ let match: RegExpExecArray | null;
264
+ while ((match = re.exec(xml)) !== null) {
265
+ const record = match[1];
266
+ if (!record.startsWith("<crossref")) records.push(record);
267
+ }
268
+ return records;
269
+ }
270
+
271
+ function extractPidFromAuthorHit(hit: DblpHit): string {
272
+ const url = stringField(hit.info?.url);
273
+ const match = url.match(/\/pid\/([^/]+(?:\/[^/]+)+)$/);
274
+ return match ? match[1] : "";
275
+ }
276
+
277
+ async function fetchDblp(
278
+ path: string,
279
+ label: string,
280
+ accept: string,
281
+ ): Promise<Response> {
282
+ const response = await fetch(`${DBLP_ORIGIN}${path}`, {
283
+ headers: {
284
+ "User-Agent": "unicli-dblp/1.0 (https://github.com/olo-dot-io/Uni-CLI)",
285
+ Accept: accept,
286
+ },
287
+ });
288
+ if (response.status === 404) throw new Error(`${label} returned no result.`);
289
+ if (response.status === 429) throw new Error(`${label} returned HTTP 429.`);
290
+ if (!response.ok)
291
+ throw new Error(`${label} returned HTTP ${response.status}.`);
292
+ return response;
293
+ }
294
+
295
+ async function fetchDblpJson(
296
+ path: string,
297
+ label: string,
298
+ ): Promise<DblpSearchBody> {
299
+ return (
300
+ await fetchDblp(path, label, "application/json")
301
+ ).json() as Promise<DblpSearchBody>;
302
+ }
303
+
304
+ async function fetchDblpXml(path: string, label: string): Promise<string> {
305
+ return (await fetchDblp(path, label, "application/xml")).text();
306
+ }
307
+
308
+ cli({
309
+ site: "dblp",
310
+ name: "search",
311
+ description: "Search dblp computer-science bibliography",
312
+ domain: "dblp.org",
313
+ strategy: Strategy.PUBLIC,
314
+ args: [
315
+ {
316
+ name: "query",
317
+ type: "str",
318
+ required: true,
319
+ positional: true,
320
+ description: "Publication search query",
321
+ },
322
+ { name: "limit", type: "int", default: 20, description: "Max results" },
323
+ ],
324
+ columns: [
325
+ "rank",
326
+ "key",
327
+ "title",
328
+ "authors",
329
+ "venue",
330
+ "year",
331
+ "type",
332
+ "doi",
333
+ "url",
334
+ ],
335
+ func: async (_page, kwargs) => {
336
+ const query = requireDblpQuery(kwargs.query);
337
+ const limit = requireDblpLimit(kwargs.limit, 20, 100);
338
+ const body = await fetchDblpJson(
339
+ `/search/publ/api?q=${encodeURIComponent(query)}&format=json&h=${limit}`,
340
+ "dblp search",
341
+ );
342
+ const rows = hitList(body, "dblp search")
343
+ .slice(0, limit)
344
+ .map((hit, index) => mapPublicationHit(hit, index + 1));
345
+ if (rows.length === 0)
346
+ throw new Error(`No dblp publications matched "${query}".`);
347
+ return rows;
348
+ },
349
+ });
350
+
351
+ cli({
352
+ site: "dblp",
353
+ name: "paper",
354
+ description: "Fetch a dblp record by canonical key",
355
+ domain: "dblp.org",
356
+ strategy: Strategy.PUBLIC,
357
+ args: [
358
+ {
359
+ name: "key",
360
+ type: "str",
361
+ required: true,
362
+ positional: true,
363
+ description: "dblp record key",
364
+ },
365
+ ],
366
+ columns: [
367
+ "key",
368
+ "type",
369
+ "title",
370
+ "authors",
371
+ "venue",
372
+ "year",
373
+ "pages",
374
+ "doi",
375
+ "open_access_url",
376
+ "dblp_url",
377
+ ],
378
+ func: async (_page, kwargs) => {
379
+ const key = requireRecordKey(kwargs.key);
380
+ const xml = await fetchDblpXml(
381
+ `/rec/${encodeURI(key)}.xml`,
382
+ `dblp paper ${key}`,
383
+ );
384
+ const row = mapRecordXml(xml);
385
+ if (!row.key && !row.title) {
386
+ throw new Error(`dblp returned an empty record for key "${key}".`);
387
+ }
388
+ return [row];
389
+ },
390
+ });
391
+
392
+ cli({
393
+ site: "dblp",
394
+ name: "venue",
395
+ description: "Search dblp venue registry by name or acronym",
396
+ domain: "dblp.org",
397
+ strategy: Strategy.PUBLIC,
398
+ args: [
399
+ {
400
+ name: "query",
401
+ type: "str",
402
+ required: true,
403
+ positional: true,
404
+ description: "Venue name or acronym",
405
+ },
406
+ { name: "limit", type: "int", default: 20, description: "Max venues" },
407
+ ],
408
+ columns: ["rank", "acronym", "venue", "type", "url"],
409
+ func: async (_page, kwargs) => {
410
+ const query = requireDblpQuery(kwargs.query);
411
+ const limit = requireDblpLimit(kwargs.limit, 20, 100);
412
+ const body = await fetchDblpJson(
413
+ `/search/venue/api?q=${encodeURIComponent(query)}&format=json&h=${limit}`,
414
+ "dblp venue",
415
+ );
416
+ const rows = hitList(body, "dblp venue")
417
+ .slice(0, limit)
418
+ .map((hit, index) => mapVenueHit(hit, index + 1));
419
+ if (rows.length === 0)
420
+ throw new Error(`No dblp venues matched "${query}".`);
421
+ return rows;
422
+ },
423
+ });
424
+
425
+ cli({
426
+ site: "dblp",
427
+ name: "author",
428
+ description: "List dblp publications by author name or PID",
429
+ domain: "dblp.org",
430
+ strategy: Strategy.PUBLIC,
431
+ args: [
432
+ {
433
+ name: "author",
434
+ type: "str",
435
+ positional: true,
436
+ description: "Author name; optional when pid is given",
437
+ },
438
+ { name: "pid", type: "str", description: "Canonical dblp PID" },
439
+ {
440
+ name: "limit",
441
+ type: "int",
442
+ default: 20,
443
+ description: "Max publications",
444
+ },
445
+ ],
446
+ columns: [
447
+ "rank",
448
+ "key",
449
+ "title",
450
+ "authors",
451
+ "venue",
452
+ "year",
453
+ "type",
454
+ "doi",
455
+ "pid",
456
+ "url",
457
+ ],
458
+ func: async (_page, kwargs) => {
459
+ const limit = requireDblpLimit(kwargs.limit, 20, 200);
460
+ let pid = kwargs.pid ? requirePid(kwargs.pid) : "";
461
+ if (!pid) {
462
+ const author = requireDblpQuery(kwargs.author, "author");
463
+ const body = await fetchDblpJson(
464
+ `/search/author/api?q=${encodeURIComponent(author)}&format=json&h=20`,
465
+ "dblp author search",
466
+ );
467
+ const hits = hitList(body, "dblp author search");
468
+ if (hits.length === 0)
469
+ throw new Error(`No dblp author matched "${author}".`);
470
+ pid = extractPidFromAuthorHit(hits[0]);
471
+ if (!pid)
472
+ throw new Error(`dblp author search returned no PID for "${author}".`);
473
+ }
474
+ const xml = await fetchDblpXml(`/pid/${pid}.xml`, `dblp pid ${pid}`);
475
+ const records = splitAuthorRecords(xml);
476
+ if (records.length === 0)
477
+ throw new Error(`dblp PID ${pid} has no publications.`);
478
+ return records.slice(0, limit).map((record, index) => {
479
+ const row = mapRecordXml(`<root>${record}</root>`);
480
+ return {
481
+ rank: index + 1,
482
+ key: row.key || extractRecordKey(record),
483
+ title: row.title,
484
+ authors: row.authors,
485
+ venue: row.venue,
486
+ year: row.year,
487
+ type: row.type,
488
+ doi: row.doi,
489
+ pid,
490
+ url: row.open_access_url || row.dblp_url,
491
+ };
492
+ });
493
+ },
494
+ });
@@ -0,0 +1,69 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { resolveCommand } from "../../registry.js";
3
+ import { parseDeepSeekConversationId } from "./web.js";
4
+
5
+ const CONVERSATION_ID = "749e6bbd-6a45-4440-beaa-ae5238bf06d8";
6
+
7
+ function pageMock(evaluateResults: unknown[]) {
8
+ return {
9
+ goto: vi.fn().mockResolvedValue(undefined),
10
+ click: vi.fn().mockResolvedValue(undefined),
11
+ insertText: vi.fn().mockResolvedValue(undefined),
12
+ press: vi.fn().mockResolvedValue(undefined),
13
+ wait: vi.fn().mockResolvedValue(undefined),
14
+ evaluate: vi.fn(async () => evaluateResults.shift()),
15
+ };
16
+ }
17
+
18
+ describe("deepseek agent-facing web commands", () => {
19
+ it("validates conversation IDs and URLs", () => {
20
+ expect(parseDeepSeekConversationId(CONVERSATION_ID.toUpperCase())).toBe(
21
+ CONVERSATION_ID,
22
+ );
23
+ expect(
24
+ parseDeepSeekConversationId(
25
+ `https://chat.deepseek.com/a/chat/s/${CONVERSATION_ID}`,
26
+ ),
27
+ ).toBe(CONVERSATION_ID);
28
+ expect(() => parseDeepSeekConversationId("not-a-uuid")).toThrow(
29
+ "Invalid DeepSeek",
30
+ );
31
+ });
32
+
33
+ it("reads a specific conversation detail", async () => {
34
+ const command = resolveCommand("deepseek", "detail")?.command;
35
+ expect(command?.func).toBeTypeOf("function");
36
+ const page = pageMock([[{ index: 1, role: "assistant", text: "Answer" }]]);
37
+ const result = await command!.func!(page, { id: CONVERSATION_ID });
38
+ expect(page.goto).toHaveBeenCalledWith(
39
+ `https://chat.deepseek.com/a/chat/s/${CONVERSATION_ID}`,
40
+ { settleMs: 1800 },
41
+ );
42
+ expect(result).toEqual([{ index: 1, role: "assistant", text: "Answer" }]);
43
+ });
44
+
45
+ it("fails closed when a detail conversation has no visible messages", async () => {
46
+ const command = resolveCommand("deepseek", "detail")?.command;
47
+ await expect(
48
+ command!.func!(pageMock([[]]), { id: CONVERSATION_ID }),
49
+ ).rejects.toThrow("No visible DeepSeek messages");
50
+ });
51
+
52
+ it("sends text to a specific conversation", async () => {
53
+ const command = resolveCommand("deepseek", "send")?.command;
54
+ expect(command?.func).toBeTypeOf("function");
55
+ const page = pageMock([]);
56
+ const result = await command!.func!(page, {
57
+ id: CONVERSATION_ID,
58
+ prompt: "hello",
59
+ });
60
+ expect(page.goto).toHaveBeenCalledWith(
61
+ `https://chat.deepseek.com/a/chat/s/${CONVERSATION_ID}`,
62
+ { settleMs: 1800 },
63
+ );
64
+ expect(page.click).toHaveBeenCalled();
65
+ expect(page.insertText).toHaveBeenCalledWith("hello");
66
+ expect(page.press).toHaveBeenCalledWith("Enter");
67
+ expect(result).toEqual([{ status: "success", injectedText: "hello" }]);
68
+ });
69
+ });