@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,426 @@
1
+ /**
2
+ * @owner src/adapters/weibo/favorites-publish.ts
3
+ * @does Register agent-facing Weibo favorites reader and publish command.
4
+ * @needs Logged-in weibo.com browser session and stable Weibo compose/favorites DOM.
5
+ * @feeds surface coverage ledger, Weibo collection reading, and Weibo publishing workflows.
6
+ * @breaks Weibo Vue config changes, favorites virtual-scroller DOM drift, compose selector drift, or upload UI changes.
7
+ */
8
+
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ import { cli, Strategy } from "../../registry.js";
12
+ import type { IPage } from "../../types.js";
13
+
14
+ const FAVORITES_DEFAULT_LIMIT = 20;
15
+ const FAVORITES_MAX_LIMIT = 50;
16
+ const MAX_IMAGES = 9;
17
+ const IMAGE_EXTENSIONS = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp"]);
18
+ const TEXTAREA_SELECTOR = "textarea._input_13iqr_8";
19
+ const FILE_INPUT_SELECTOR = 'input[type="file"][class*="_file_"]';
20
+
21
+ interface FavoriteCard {
22
+ text?: unknown;
23
+ url?: unknown;
24
+ }
25
+
26
+ interface FavoriteRow {
27
+ author: string;
28
+ text: string;
29
+ time: string;
30
+ source: string;
31
+ likes: string;
32
+ comments: string;
33
+ reposts: string;
34
+ url: string;
35
+ }
36
+
37
+ interface UiResult {
38
+ ok?: unknown;
39
+ found?: unknown;
40
+ visible?: unknown;
41
+ rectTop?: unknown;
42
+ message?: unknown;
43
+ label?: unknown;
44
+ valueLength?: unknown;
45
+ count?: unknown;
46
+ }
47
+
48
+ export function requireWeiboFavoritesLimit(value: unknown): number {
49
+ const limit = Number(value ?? FAVORITES_DEFAULT_LIMIT);
50
+ if (!Number.isInteger(limit) || limit <= 0) {
51
+ throw new Error("weibo favorites limit must be a positive integer.");
52
+ }
53
+ if (limit > FAVORITES_MAX_LIMIT) {
54
+ throw new Error(`weibo favorites limit must be <= ${FAVORITES_MAX_LIMIT}.`);
55
+ }
56
+ return limit;
57
+ }
58
+
59
+ export function validateWeiboPublishText(value: unknown): string {
60
+ const text = String(value ?? "").trim();
61
+ if (!text) throw new Error("weibo publish text cannot be empty.");
62
+ if (text.length > 2000) {
63
+ throw new Error("weibo publish text exceeds 2000 characters.");
64
+ }
65
+ return text;
66
+ }
67
+
68
+ export function validateWeiboImagePaths(value: unknown): string[] {
69
+ if (!value) return [];
70
+ const parts = String(value)
71
+ .split(",")
72
+ .map((item) => item.trim())
73
+ .filter(Boolean);
74
+ if (parts.length > MAX_IMAGES) {
75
+ throw new Error(`Too many images: ${parts.length} (max ${MAX_IMAGES}).`);
76
+ }
77
+ return parts.map((item) => {
78
+ const absolutePath = path.resolve(item);
79
+ const extension = path.extname(absolutePath).toLowerCase();
80
+ if (!IMAGE_EXTENSIONS.has(extension)) {
81
+ throw new Error(
82
+ `Unsupported image format "${extension}". Supported: jpg, png, gif, webp.`,
83
+ );
84
+ }
85
+ const stat = fs.statSync(absolutePath, { throwIfNoEntry: false });
86
+ if (!stat?.isFile()) {
87
+ throw new Error(`Not a valid file: ${absolutePath}.`);
88
+ }
89
+ return absolutePath;
90
+ });
91
+ }
92
+
93
+ async function getWeiboSelfUid(page: IPage): Promise<string> {
94
+ const uid = await page.evaluate(`(() => {
95
+ const app = document.querySelector('#app')?.__vue_app__;
96
+ const store = app?.config?.globalProperties?.$store;
97
+ const uid = store?.state?.config?.config?.uid;
98
+ return uid ? String(uid) : null;
99
+ })()`);
100
+ if (uid) return String(uid);
101
+ const configUid = await page.evaluate(`(async () => {
102
+ const response = await fetch('/ajax/config/get_config', { credentials: 'include' });
103
+ if (!response.ok) return null;
104
+ const data = await response.json();
105
+ return data.ok && data.data?.uid ? String(data.data.uid) : null;
106
+ })()`);
107
+ if (configUid) return String(configUid);
108
+ throw new Error("Weibo login is required.");
109
+ }
110
+
111
+ export function parseWeiboFavoriteCard(
112
+ card: FavoriteCard,
113
+ fallbackUrl: string,
114
+ ): FavoriteRow | null {
115
+ const raw = String(card?.text ?? "");
116
+ const lines = raw.split("\n");
117
+ let author = "";
118
+ let time = "";
119
+ let source = "";
120
+ let content = "";
121
+ let likes = "0";
122
+ let comments = "0";
123
+ let reposts = "0";
124
+ for (const line of lines) {
125
+ const text = line.trim();
126
+ if (!text || text === "添加") continue;
127
+ if (
128
+ !time &&
129
+ /\d+小时前|\d+分钟前|\d+秒前|昨天|前天|\d{1,2}:\d{2}/.test(text)
130
+ ) {
131
+ time = text;
132
+ continue;
133
+ }
134
+ if (text.startsWith("来自")) {
135
+ source = text;
136
+ continue;
137
+ }
138
+ if (content) {
139
+ const n = Number.parseInt(text, 10);
140
+ if (!Number.isNaN(n) && n > 0 && n < 1_000_000 && text === String(n)) {
141
+ if (likes === "0") likes = text;
142
+ else if (comments === "0") comments = text;
143
+ else if (reposts === "0") reposts = text;
144
+ continue;
145
+ }
146
+ }
147
+ if (!author && text.length < 40) {
148
+ author = text;
149
+ continue;
150
+ }
151
+ if (!content && author) {
152
+ content = text;
153
+ continue;
154
+ }
155
+ if (content) content += ` ${text}`;
156
+ }
157
+ if (!content || !author) return null;
158
+ return {
159
+ author,
160
+ text: content.substring(0, 300),
161
+ time,
162
+ source,
163
+ likes,
164
+ comments,
165
+ reposts,
166
+ url: String(card?.url || fallbackUrl),
167
+ };
168
+ }
169
+
170
+ export function dedupeWeiboFavorites(
171
+ items: FavoriteRow[],
172
+ fallbackUrl: string,
173
+ ): FavoriteRow[] {
174
+ const seen = new Set<string>();
175
+ const result: FavoriteRow[] = [];
176
+ for (const item of items) {
177
+ const key =
178
+ item.url && item.url !== fallbackUrl
179
+ ? item.url
180
+ : `${item.author}\n${item.text}\n${item.time}`;
181
+ if (seen.has(key)) continue;
182
+ seen.add(key);
183
+ result.push(item);
184
+ }
185
+ return result;
186
+ }
187
+
188
+ function buildFavoritesExtractScript(limit: number): string {
189
+ return `(() => {
190
+ const scrollers = document.querySelectorAll('.wbpro-scroller-item, .vue-recycle-scroller__item-view');
191
+ const rows = [];
192
+ for (const scroller of scrollers) {
193
+ const body = scroller.querySelector('[class*="_body_"]') || scroller.querySelector('.wbpro-item-body') || scroller;
194
+ const rawText = body.innerText || scroller.innerText || '';
195
+ let postUrl = '';
196
+ const anchors = scroller.querySelectorAll('a[href]');
197
+ for (const anchor of anchors) {
198
+ const match = String(anchor.href).match(/weibo\\.com\\/(\\d+)\\/([a-zA-Z0-9]+)/);
199
+ if (match) {
200
+ postUrl = 'https://weibo.com/' + match[1] + '/' + match[2];
201
+ break;
202
+ }
203
+ }
204
+ if (rawText.length > 20) rows.push({ text: rawText, url: postUrl });
205
+ if (rows.length >= ${JSON.stringify(limit)}) break;
206
+ }
207
+ return rows;
208
+ })()`;
209
+ }
210
+
211
+ async function waitForTruthy(
212
+ page: IPage,
213
+ script: string,
214
+ attempts: number,
215
+ intervalSeconds: number,
216
+ ): Promise<UiResult | null> {
217
+ for (let index = 0; index < attempts; index += 1) {
218
+ const result = (await page.evaluate(script)) as UiResult | null;
219
+ if (result?.ok || result?.found || result?.message) return result;
220
+ await page.wait(intervalSeconds);
221
+ }
222
+ return null;
223
+ }
224
+
225
+ cli({
226
+ site: "weibo",
227
+ name: "favorites",
228
+ description: "Read your Weibo favorites",
229
+ domain: "weibo.com",
230
+ strategy: Strategy.COOKIE,
231
+ browser: true,
232
+ args: [{ name: "limit", type: "int", default: FAVORITES_DEFAULT_LIMIT }],
233
+ columns: [
234
+ "author",
235
+ "text",
236
+ "time",
237
+ "source",
238
+ "likes",
239
+ "comments",
240
+ "reposts",
241
+ "url",
242
+ ],
243
+ func: async (page, kwargs) => {
244
+ const limit = requireWeiboFavoritesLimit(kwargs.limit);
245
+ const p = page as IPage;
246
+ await p.goto("https://weibo.com");
247
+ await p.wait(2);
248
+ const uid = await getWeiboSelfUid(p);
249
+ const favoritesUrl = `https://www.weibo.com/u/page/fav/${uid}`;
250
+ await p.goto(favoritesUrl);
251
+ await p.wait(4);
252
+ for (let index = 0; index < 3; index += 1) {
253
+ await p.evaluate("() => window.scrollBy(0, 800)");
254
+ await p.wait(1);
255
+ }
256
+ const rawData = await p.evaluate(buildFavoritesExtractScript(limit));
257
+ if (!Array.isArray(rawData) || rawData.length === 0) {
258
+ throw new Error("No Weibo favorites were visible on the favorites page.");
259
+ }
260
+ const rows = rawData
261
+ .map((card) => parseWeiboFavoriteCard(card as FavoriteCard, favoritesUrl))
262
+ .filter((item): item is FavoriteRow => Boolean(item));
263
+ const uniqueRows = dedupeWeiboFavorites(rows, favoritesUrl);
264
+ if (!uniqueRows.length) {
265
+ throw new Error("Failed to parse visible Weibo favorites.");
266
+ }
267
+ return uniqueRows.slice(0, limit);
268
+ },
269
+ });
270
+
271
+ cli({
272
+ site: "weibo",
273
+ name: "publish",
274
+ description: "Publish a new Weibo post immediately",
275
+ domain: "weibo.com",
276
+ strategy: Strategy.UI,
277
+ browser: true,
278
+ args: [
279
+ { name: "text", type: "str", required: true, positional: true },
280
+ { name: "images", type: "str", required: false },
281
+ ],
282
+ columns: ["status", "message", "text"],
283
+ func: async (page, kwargs) => {
284
+ const p = page as IPage;
285
+ const text = validateWeiboPublishText(kwargs.text);
286
+ const imagePaths = validateWeiboImagePaths(kwargs.images);
287
+ await p.goto("https://weibo.com", { waitUntil: "load", settleMs: 2000 });
288
+ await p.wait(2);
289
+ await getWeiboSelfUid(p);
290
+ const openResult = (await p.evaluate(`(() => {
291
+ const visible = (element) => Boolean(element && element.offsetParent !== null && !element.disabled);
292
+ const buttons = document.querySelectorAll('button[title="发微博"], button[title="写微博"]');
293
+ for (const button of buttons) {
294
+ if (visible(button)) {
295
+ button.click();
296
+ return { ok: true };
297
+ }
298
+ }
299
+ return { ok: false, message: 'Could not find 发微博 button' };
300
+ })()`)) as UiResult;
301
+ if (!openResult?.ok) {
302
+ throw new Error(
303
+ String(openResult?.message || "Could not open compose editor."),
304
+ );
305
+ }
306
+ const editorResult = await waitForTruthy(
307
+ p,
308
+ `(() => {
309
+ const textarea = document.querySelector(${JSON.stringify(TEXTAREA_SELECTOR)});
310
+ if (!textarea) return { found: false };
311
+ const visible = textarea.offsetParent !== null;
312
+ return { found: true, visible, rectTop: visible ? textarea.getBoundingClientRect().top : -1 };
313
+ })()`,
314
+ 34,
315
+ 0.3,
316
+ );
317
+ if (
318
+ !editorResult?.found ||
319
+ !editorResult.visible ||
320
+ Number(editorResult.rectTop) < 0
321
+ ) {
322
+ throw new Error("Weibo compose editor did not appear.");
323
+ }
324
+ if (imagePaths.length > 0) {
325
+ const fileInputFound = await p.evaluate(`(() => {
326
+ return Boolean(document.querySelector(${JSON.stringify(FILE_INPUT_SELECTOR)}));
327
+ })()`);
328
+ if (!fileInputFound) {
329
+ throw new Error(
330
+ "Could not find image file input on Weibo compose page.",
331
+ );
332
+ }
333
+ await p.setFileInput(FILE_INPUT_SELECTOR, imagePaths);
334
+ const uploadResult = await waitForTruthy(
335
+ p,
336
+ `(() => {
337
+ const expectedCount = ${JSON.stringify(imagePaths.length)};
338
+ const uploading = document.querySelector('[class*="upload"], [class*="progress"]');
339
+ if (uploading && uploading.offsetParent !== null) return null;
340
+ const images = document.querySelectorAll('img[class*="pic"], [class*="imgItem"], [class*="picture"] img');
341
+ if (images.length >= expectedCount) return { ok: true, count: images.length };
342
+ return null;
343
+ })()`,
344
+ 20,
345
+ 1.5,
346
+ );
347
+ if (!uploadResult?.ok) {
348
+ throw new Error("Image upload did not complete before timeout.");
349
+ }
350
+ }
351
+ const insertResult = (await p.evaluate(`(() => {
352
+ const textarea = document.querySelector(${JSON.stringify(TEXTAREA_SELECTOR)});
353
+ if (!textarea || textarea.offsetParent === null) return { ok: false, message: 'textarea not visible' };
354
+ textarea.focus();
355
+ const nativeSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value')?.set;
356
+ if (nativeSetter) nativeSetter.call(textarea, ${JSON.stringify(text)});
357
+ else textarea.value = ${JSON.stringify(text)};
358
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
359
+ textarea.dispatchEvent(new Event('change', { bubbles: true }));
360
+ return { ok: true, valueLength: textarea.value.length };
361
+ })()`)) as UiResult;
362
+ if (!insertResult?.ok) {
363
+ throw new Error(
364
+ String(insertResult?.message || "Could not insert text."),
365
+ );
366
+ }
367
+ await p.wait(0.5);
368
+ const publishClick = (await p.evaluate(`(() => {
369
+ const visible = (element) => Boolean(element && element.offsetParent !== null && !element.disabled);
370
+ for (const label of ['发送', '发布']) {
371
+ const buttons = document.querySelectorAll('button, [role="button"]');
372
+ for (const button of buttons) {
373
+ const value = (button.innerText || button.textContent || '').trim();
374
+ if (value === label && visible(button)) {
375
+ button.click();
376
+ return { ok: true, label };
377
+ }
378
+ }
379
+ }
380
+ return { ok: false, message: 'Could not find send button' };
381
+ })()`)) as UiResult;
382
+ if (!publishClick?.ok) {
383
+ throw new Error(
384
+ String(publishClick?.message || "Could not click publish."),
385
+ );
386
+ }
387
+ const finalResult = await waitForTruthy(
388
+ p,
389
+ `(() => {
390
+ const successMarkers = ['发布成功', '已发布', '发送成功'];
391
+ const errorMarkers = ['发布失败', '发送失败', '内容违规', '请稍后再试', '频繁'];
392
+ for (const element of document.querySelectorAll('*')) {
393
+ if (element.children.length > 3) continue;
394
+ const value = (element.innerText || '').trim();
395
+ if (!value || value.length > 100) continue;
396
+ for (const marker of successMarkers) {
397
+ if (value.includes(marker) && (value.includes('成功') || value.includes('微博'))) {
398
+ return { ok: true, message: value };
399
+ }
400
+ }
401
+ for (const marker of errorMarkers) {
402
+ if (value.includes(marker)) return { ok: false, message: value };
403
+ }
404
+ }
405
+ return null;
406
+ })()`,
407
+ 40,
408
+ 0.5,
409
+ );
410
+ if (!finalResult) {
411
+ throw new Error(
412
+ "Publish button clicked but result was unclear. Check Weibo manually.",
413
+ );
414
+ }
415
+ if (!finalResult.ok) {
416
+ throw new Error(String(finalResult.message || "Weibo publish failed."));
417
+ }
418
+ return [
419
+ {
420
+ status: "success",
421
+ message: String(finalResult.message || "Published successfully"),
422
+ text,
423
+ },
424
+ ];
425
+ },
426
+ });
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ joinLocalizedAliases,
4
+ mapWikidataEntityRow,
5
+ mapWikidataSearchRows,
6
+ pickLocalizedValue,
7
+ requireWikidataEntityId,
8
+ requireWikidataLanguage,
9
+ requireWikidataLimit,
10
+ } from "./entities.js";
11
+
12
+ describe("wikidata agent-facing entity commands", () => {
13
+ it("validates language, limit, and entity ids", () => {
14
+ expect(requireWikidataLimit(undefined)).toBe(20);
15
+ expect(requireWikidataLimit("50")).toBe(50);
16
+ expect(() => requireWikidataLimit("51")).toThrow("wikidata limit must");
17
+ expect(requireWikidataLanguage("zh-hans")).toBe("zh-hans");
18
+ expect(() => requireWikidataLanguage("english")).toThrow("not valid");
19
+ expect(requireWikidataEntityId("https://www.wikidata.org/wiki/Q937")).toBe(
20
+ "Q937",
21
+ );
22
+ expect(() => requireWikidataEntityId("A1")).toThrow("not valid");
23
+ });
24
+
25
+ it("picks localized values and aliases with English fallback", () => {
26
+ expect(
27
+ pickLocalizedValue(
28
+ {
29
+ en: { value: "Albert Einstein" },
30
+ fr: { value: "Albert Einstein FR" },
31
+ },
32
+ "fr",
33
+ ),
34
+ ).toBe("Albert Einstein FR");
35
+ expect(
36
+ joinLocalizedAliases(
37
+ {
38
+ en: [
39
+ { value: "Einstein" },
40
+ { value: "A. Einstein" },
41
+ { value: "third" },
42
+ { value: "fourth" },
43
+ { value: "fifth" },
44
+ { value: "sixth" },
45
+ ],
46
+ },
47
+ "zh",
48
+ ),
49
+ ).toBe("Einstein, A. Einstein, third, fourth, fifth, (+1)");
50
+ });
51
+
52
+ it("maps search rows", () => {
53
+ expect(
54
+ mapWikidataSearchRows(
55
+ [
56
+ {
57
+ id: "Q937",
58
+ label: "Albert Einstein",
59
+ description: "German-born physicist",
60
+ match: { type: "label", text: "Albert Einstein" },
61
+ },
62
+ ],
63
+ 20,
64
+ ),
65
+ ).toEqual([
66
+ {
67
+ rank: 1,
68
+ qid: "Q937",
69
+ label: "Albert Einstein",
70
+ description: "German-born physicist",
71
+ matchType: "label",
72
+ matchText: "Albert Einstein",
73
+ url: "https://www.wikidata.org/wiki/Q937",
74
+ },
75
+ ]);
76
+ });
77
+
78
+ it("maps entity detail rows", () => {
79
+ expect(
80
+ mapWikidataEntityRow(
81
+ "Q937",
82
+ {
83
+ type: "item",
84
+ labels: { en: { value: "Albert Einstein" } },
85
+ descriptions: { en: { value: "German-born physicist" } },
86
+ aliases: { en: [{ value: "Einstein" }] },
87
+ claims: { P31: [], P21: [] },
88
+ sitelinks: { enwiki: { title: "Albert Einstein" }, dewiki: {} },
89
+ modified: "2026-05-12T00:00:00Z",
90
+ },
91
+ "en",
92
+ ),
93
+ ).toMatchObject({
94
+ qid: "Q937",
95
+ type: "item",
96
+ label: "Albert Einstein",
97
+ aliases: "Einstein",
98
+ claimPropertyCount: 2,
99
+ sitelinkCount: 2,
100
+ enwikiTitle: "Albert Einstein",
101
+ });
102
+ });
103
+ });