@jackwener/opencli 1.7.12 → 1.7.13

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 (407) hide show
  1. package/README.md +8 -7
  2. package/README.zh-CN.md +9 -8
  3. package/cli-manifest.json +12194 -6843
  4. package/clis/1point3acres/digest.js +35 -0
  5. package/clis/1point3acres/forum.js +51 -0
  6. package/clis/1point3acres/forums.js +44 -0
  7. package/clis/1point3acres/hot.js +35 -0
  8. package/clis/1point3acres/latest.js +35 -0
  9. package/clis/1point3acres/notifications.js +64 -0
  10. package/clis/1point3acres/search.js +71 -0
  11. package/clis/1point3acres/thread.js +117 -0
  12. package/clis/1point3acres/user.js +77 -0
  13. package/clis/1point3acres/utils.js +247 -0
  14. package/clis/_shared/desktop-commands.js +4 -0
  15. package/clis/aibase/news.js +110 -0
  16. package/clis/aibase/news.test.js +59 -0
  17. package/clis/amazon/discussion.test.js +1 -28
  18. package/clis/antigravity/watch.js +3 -2
  19. package/clis/arxiv/author.js +44 -0
  20. package/clis/baidu-scholar/search.js +0 -1
  21. package/clis/bbc/topic.js +57 -0
  22. package/clis/bbc/utils.js +79 -0
  23. package/clis/chaoxing/assignments.js +1 -1
  24. package/clis/chaoxing/exams.js +1 -1
  25. package/clis/chatgpt/ask.js +57 -0
  26. package/clis/chatgpt/commands.test.js +45 -0
  27. package/clis/chatgpt/detail.js +46 -0
  28. package/clis/chatgpt/history.js +39 -0
  29. package/clis/chatgpt/image.js +12 -11
  30. package/clis/chatgpt/image.test.js +23 -0
  31. package/clis/chatgpt/new.js +25 -0
  32. package/clis/chatgpt/read.js +43 -0
  33. package/clis/chatgpt/send.js +46 -0
  34. package/clis/chatgpt/status.js +29 -0
  35. package/clis/chatgpt/utils.js +294 -4
  36. package/clis/chatgpt/utils.test.js +13 -0
  37. package/clis/chatgpt-app/ask.js +6 -3
  38. package/clis/chatwise/ask.js +16 -43
  39. package/clis/chatwise/composer.test.js +186 -0
  40. package/clis/chatwise/send.js +2 -24
  41. package/clis/chatwise/utils.js +143 -0
  42. package/clis/claude/ask.js +1 -1
  43. package/clis/claude/detail.js +1 -0
  44. package/clis/claude/history.js +1 -0
  45. package/clis/claude/new.js +1 -0
  46. package/clis/claude/read.js +1 -0
  47. package/clis/claude/send.js +1 -0
  48. package/clis/claude/status.js +1 -0
  49. package/clis/codex/ask.js +15 -9
  50. package/clis/codex/history.js +16 -33
  51. package/clis/codex/projects.js +28 -0
  52. package/clis/codex/read.js +10 -4
  53. package/clis/codex/send.js +10 -3
  54. package/clis/codex/sidebar.js +356 -0
  55. package/clis/codex/sidebar.test.js +329 -0
  56. package/clis/coingecko/categories.js +75 -0
  57. package/clis/coingecko/coin.js +107 -0
  58. package/clis/coingecko/coingecko.test.js +109 -0
  59. package/clis/coingecko/derivatives.js +84 -0
  60. package/clis/coingecko/exchanges.js +74 -0
  61. package/clis/coingecko/global.js +71 -0
  62. package/clis/coingecko/top.js +64 -0
  63. package/clis/coingecko/trending.js +55 -0
  64. package/clis/coupang/add-to-cart.js +21 -13
  65. package/clis/coupang/coupang.test.js +159 -0
  66. package/clis/coupang/product.js +257 -0
  67. package/clis/coupang/search.js +38 -16
  68. package/clis/coupang/utils.js +55 -1
  69. package/clis/crates/crate.js +62 -0
  70. package/clis/crates/search.js +44 -0
  71. package/clis/crates/utils.js +72 -0
  72. package/clis/ctrip/ctrip.test.js +234 -0
  73. package/clis/ctrip/hotel-suggest.js +45 -0
  74. package/clis/ctrip/search.js +22 -68
  75. package/clis/ctrip/utils.js +175 -0
  76. package/clis/cursor/ask.js +6 -3
  77. package/clis/dblp/author.js +133 -0
  78. package/clis/dblp/venue.js +64 -0
  79. package/clis/deepseek/ask.js +12 -7
  80. package/clis/deepseek/ask.test.js +13 -13
  81. package/clis/deepseek/detail.js +38 -0
  82. package/clis/deepseek/detail.test.js +81 -0
  83. package/clis/deepseek/history.js +1 -0
  84. package/clis/deepseek/new.js +1 -0
  85. package/clis/deepseek/read.js +1 -0
  86. package/clis/deepseek/send.js +140 -0
  87. package/clis/deepseek/send.test.js +107 -0
  88. package/clis/deepseek/status.js +1 -0
  89. package/clis/deepseek/utils.js +66 -0
  90. package/clis/deepseek/utils.test.js +107 -1
  91. package/clis/defillama/defillama.test.js +99 -0
  92. package/clis/defillama/protocol.js +84 -0
  93. package/clis/defillama/protocols.js +55 -0
  94. package/clis/defillama/utils.js +99 -0
  95. package/clis/devto/latest.js +74 -0
  96. package/clis/dockerhub/image.js +52 -0
  97. package/clis/dockerhub/search.js +47 -0
  98. package/clis/dockerhub/utils.js +100 -0
  99. package/clis/doubao/ask.js +7 -3
  100. package/clis/doubao/detail.js +1 -0
  101. package/clis/doubao/history.js +1 -0
  102. package/clis/doubao/meeting-summary.js +1 -0
  103. package/clis/doubao/meeting-transcript.js +1 -0
  104. package/clis/doubao/new.js +1 -0
  105. package/clis/doubao/read.js +1 -0
  106. package/clis/doubao/send.js +1 -0
  107. package/clis/doubao/status.js +1 -0
  108. package/clis/douyin/draft.test.js +1 -30
  109. package/clis/endoflife/endoflife.test.js +51 -0
  110. package/clis/endoflife/product.js +55 -0
  111. package/clis/endoflife/utils.js +89 -0
  112. package/clis/facebook/__fixtures__/notifications-page.html +13 -0
  113. package/clis/facebook/notifications.js +326 -30
  114. package/clis/facebook/notifications.test.js +458 -0
  115. package/clis/flathub/app.js +71 -0
  116. package/clis/flathub/flathub.test.js +90 -0
  117. package/clis/flathub/search.js +80 -0
  118. package/clis/flathub/utils.js +114 -0
  119. package/clis/gemini/ask.js +7 -3
  120. package/clis/gemini/ask.test.js +2 -2
  121. package/clis/gemini/deep-research-result.js +6 -2
  122. package/clis/gemini/deep-research-result.test.js +15 -14
  123. package/clis/gemini/deep-research.js +8 -4
  124. package/clis/gemini/deep-research.test.js +15 -18
  125. package/clis/gemini/image.js +7 -2
  126. package/clis/gemini/new.js +1 -0
  127. package/clis/gemini/utils.js +0 -4
  128. package/clis/google-scholar/cite.js +0 -1
  129. package/clis/google-scholar/profile.js +0 -1
  130. package/clis/google-scholar/search.js +0 -1
  131. package/clis/goproxy/goproxy.test.js +103 -0
  132. package/clis/goproxy/module.js +47 -0
  133. package/clis/goproxy/utils.js +165 -0
  134. package/clis/goproxy/versions.js +59 -0
  135. package/clis/gov-law/recent.js +0 -1
  136. package/clis/gov-law/search.js +0 -1
  137. package/clis/gov-policy/__fixtures__/recent.html +16 -0
  138. package/clis/gov-policy/__fixtures__/search.html +41 -0
  139. package/clis/gov-policy/gov-policy.test.js +224 -0
  140. package/clis/gov-policy/recent.js +66 -24
  141. package/clis/gov-policy/search.js +65 -23
  142. package/clis/gov-policy/utils.js +54 -0
  143. package/clis/grok/ask.js +49 -265
  144. package/clis/grok/ask.test.js +21 -46
  145. package/clis/grok/detail.js +60 -0
  146. package/clis/grok/history.js +48 -0
  147. package/clis/grok/{image.ts → image.js} +56 -70
  148. package/clis/grok/image.test.ts +20 -0
  149. package/clis/grok/new.js +20 -0
  150. package/clis/grok/read.js +39 -0
  151. package/clis/grok/send.js +50 -0
  152. package/clis/grok/status.js +41 -0
  153. package/clis/grok/utils.js +326 -0
  154. package/clis/grok/utils.test.js +103 -0
  155. package/clis/hf/datasets.js +88 -0
  156. package/clis/hf/hf.test.js +16 -0
  157. package/clis/hf/models.js +91 -0
  158. package/clis/hf/paper.js +79 -0
  159. package/clis/hf/spaces.js +101 -0
  160. package/clis/hf/top.js +1 -0
  161. package/clis/homebrew/cask.js +39 -0
  162. package/clis/homebrew/formula.js +41 -0
  163. package/clis/homebrew/popular.js +54 -0
  164. package/clis/homebrew/utils.js +100 -0
  165. package/clis/hupu/__fixtures__/hot-home.html +64 -0
  166. package/clis/hupu/detail.js +0 -1
  167. package/clis/hupu/hot.js +156 -35
  168. package/clis/hupu/hot.test.js +224 -0
  169. package/clis/hupu/search.js +0 -1
  170. package/clis/instagram/note.js +1 -1
  171. package/clis/instagram/note.test.js +1 -29
  172. package/clis/instagram/post.js +1 -1
  173. package/clis/instagram/post.test.js +1 -1
  174. package/clis/instagram/reel.js +1 -1
  175. package/clis/instagram/story.js +1 -1
  176. package/clis/instagram/story.test.js +1 -34
  177. package/clis/jd/commands.test.js +1 -24
  178. package/clis/lichess/lichess.test.js +85 -0
  179. package/clis/lichess/top.js +46 -0
  180. package/clis/lichess/user.js +91 -0
  181. package/clis/lichess/utils.js +97 -0
  182. package/clis/linkedin/search.js +107 -10
  183. package/clis/linkedin/search.test.js +222 -0
  184. package/clis/linux-do/feed.js +2 -5
  185. package/clis/linux-do/feed.test.js +35 -0
  186. package/clis/lobsters/domain.js +92 -0
  187. package/clis/maven/artifact.js +49 -0
  188. package/clis/maven/search.js +51 -0
  189. package/clis/maven/utils.js +110 -0
  190. package/clis/mdn/search.js +97 -0
  191. package/clis/medium/tag.js +135 -0
  192. package/clis/npm/downloads.js +59 -0
  193. package/clis/npm/package.js +70 -0
  194. package/clis/npm/search.js +49 -0
  195. package/clis/npm/utils.js +76 -0
  196. package/clis/nuget/nuget.test.js +111 -0
  197. package/clis/nuget/package.js +101 -0
  198. package/clis/nuget/search.js +69 -0
  199. package/clis/nuget/utils.js +87 -0
  200. package/clis/nvd/cve.js +121 -0
  201. package/clis/oeis/oeis.test.js +88 -0
  202. package/clis/oeis/search.js +63 -0
  203. package/clis/oeis/sequence.js +71 -0
  204. package/clis/oeis/utils.js +88 -0
  205. package/clis/openalex/search.js +69 -0
  206. package/clis/openalex/utils.js +160 -0
  207. package/clis/openalex/work.js +65 -0
  208. package/clis/openfda/drug-label.js +74 -0
  209. package/clis/openfda/food-recall.js +65 -0
  210. package/clis/openfda/openfda.test.js +114 -0
  211. package/clis/openfda/utils.js +67 -0
  212. package/clis/osv/osv.test.js +97 -0
  213. package/clis/osv/query.js +72 -0
  214. package/clis/osv/utils.js +169 -0
  215. package/clis/osv/vulnerability.js +54 -0
  216. package/clis/packagist/package.js +49 -0
  217. package/clis/packagist/search.js +43 -0
  218. package/clis/packagist/utils.js +113 -0
  219. package/clis/paperreview/feedback.js +1 -1
  220. package/clis/paperreview/review.js +1 -1
  221. package/clis/paperreview/submit.js +1 -1
  222. package/clis/pixiv/download.test.js +1 -1
  223. package/clis/pixiv/illusts.test.js +1 -1
  224. package/clis/pixiv/search.test.js +1 -1
  225. package/clis/pubmed/article.js +50 -0
  226. package/clis/pubmed/author.js +64 -0
  227. package/clis/pubmed/citations.js +36 -0
  228. package/clis/pubmed/pubmed.test.js +276 -0
  229. package/clis/pubmed/related.js +45 -0
  230. package/clis/pubmed/search.js +75 -0
  231. package/clis/pubmed/utils.js +309 -0
  232. package/clis/pypi/downloads.js +66 -0
  233. package/clis/pypi/package.js +79 -0
  234. package/clis/pypi/utils.js +55 -0
  235. package/clis/quark/mv.js +1 -1
  236. package/clis/quark/save.js +1 -1
  237. package/clis/qwen/ask.js +85 -0
  238. package/clis/qwen/detail.js +62 -0
  239. package/clis/qwen/history.js +61 -0
  240. package/clis/qwen/image.js +179 -0
  241. package/clis/qwen/new.js +23 -0
  242. package/clis/qwen/read.js +41 -0
  243. package/clis/qwen/send.js +55 -0
  244. package/clis/qwen/status.js +37 -0
  245. package/clis/qwen/utils.js +409 -0
  246. package/clis/qwen/utils.test.js +45 -0
  247. package/clis/rest-countries/country.js +65 -0
  248. package/clis/rest-countries/region.js +64 -0
  249. package/clis/rest-countries/rest-countries.test.js +83 -0
  250. package/clis/rest-countries/utils.js +126 -0
  251. package/clis/reuters/article-detail.js +53 -0
  252. package/clis/reuters/reuters.test.js +299 -0
  253. package/clis/reuters/search.js +45 -34
  254. package/clis/reuters/utils.js +159 -0
  255. package/clis/rfc/rfc.js +52 -0
  256. package/clis/rfc/rfc.test.js +74 -0
  257. package/clis/rfc/utils.js +72 -0
  258. package/clis/rubygems/gem.js +42 -0
  259. package/clis/rubygems/search.js +47 -0
  260. package/clis/rubygems/utils.js +86 -0
  261. package/clis/stackoverflow/related.js +66 -0
  262. package/clis/stackoverflow/stackoverflow.test.js +58 -0
  263. package/clis/stackoverflow/tag.js +60 -0
  264. package/clis/stackoverflow/user.js +50 -0
  265. package/clis/stackoverflow/utils.js +118 -0
  266. package/clis/steam/app.js +67 -0
  267. package/clis/steam/search.js +58 -0
  268. package/clis/steam/steam.test.js +46 -0
  269. package/clis/steam/utils.js +107 -0
  270. package/clis/taobao/commands.test.js +1 -24
  271. package/clis/test-utils.js +61 -0
  272. package/clis/tieba/hot.js +0 -1
  273. package/clis/tiktok/comment.js +128 -41
  274. package/clis/tiktok/creator-videos.js +270 -0
  275. package/clis/tiktok/creator-videos.test.js +113 -0
  276. package/clis/tiktok/explore.js +137 -29
  277. package/clis/tiktok/follow.js +115 -33
  278. package/clis/tiktok/following.js +157 -36
  279. package/clis/tiktok/friends.js +139 -37
  280. package/clis/tiktok/live.js +137 -41
  281. package/clis/tiktok/notifications.js +141 -38
  282. package/clis/tiktok/refactor.test.js +389 -0
  283. package/clis/tiktok/unfollow.js +124 -38
  284. package/clis/tiktok/user.js +203 -29
  285. package/clis/tiktok/utils.js +505 -0
  286. package/clis/tiktok/write-refactor.test.js +370 -0
  287. package/clis/toutiao/articles.js +36 -62
  288. package/clis/toutiao/hot.js +63 -0
  289. package/clis/toutiao/toutiao.test.js +378 -0
  290. package/clis/toutiao/utils.js +161 -0
  291. package/clis/tvmaze/search.js +61 -0
  292. package/clis/tvmaze/show.js +60 -0
  293. package/clis/tvmaze/tvmaze.test.js +93 -0
  294. package/clis/tvmaze/utils.js +110 -0
  295. package/clis/twitter/accept.js +1 -1
  296. package/clis/twitter/followers.js +134 -69
  297. package/clis/twitter/reply-dm.js +1 -1
  298. package/clis/twitter/reply.test.js +1 -29
  299. package/clis/uisdc/news.js +105 -0
  300. package/clis/uisdc/news.test.js +66 -0
  301. package/clis/wanfang/search.js +0 -1
  302. package/clis/web/read.js +47 -17
  303. package/clis/web/read.test.js +101 -1
  304. package/clis/weixin/create-draft.js +1 -1
  305. package/clis/weixin/drafts.js +1 -1
  306. package/clis/weixin/drafts.test.js +5 -1
  307. package/clis/weixin/search.js +157 -0
  308. package/clis/weixin/search.test.js +227 -0
  309. package/clis/wikidata/entity.js +60 -0
  310. package/clis/wikidata/search.js +50 -0
  311. package/clis/wikidata/utils.js +117 -0
  312. package/clis/wikidata/wikidata.test.js +83 -0
  313. package/clis/wikipedia/page.js +95 -0
  314. package/clis/wttr/current.js +63 -0
  315. package/clis/wttr/forecast.js +71 -0
  316. package/clis/wttr/utils.js +50 -0
  317. package/clis/wttr/wttr.test.js +84 -0
  318. package/clis/xianyu/chat.js +16 -4
  319. package/clis/xianyu/chat.test.js +64 -0
  320. package/clis/xianyu/publish.js +485 -0
  321. package/clis/xianyu/publish.test.js +220 -0
  322. package/clis/xiaoe/catalog.js +105 -40
  323. package/clis/xiaoe/content.js +164 -29
  324. package/clis/xiaoe/courses.js +86 -29
  325. package/clis/xiaoe/xiaoe.test.js +486 -0
  326. package/clis/xiaohongshu/creator-notes-summary.js +1 -1
  327. package/clis/xiaohongshu/publish.js +16 -3
  328. package/clis/xiaohongshu/publish.test.js +46 -1
  329. package/clis/youtube/transcript.js +13 -19
  330. package/clis/youtube/transcript.test.js +17 -0
  331. package/clis/yuanbao/ask.js +17 -66
  332. package/clis/yuanbao/ask.test.js +5 -5
  333. package/clis/yuanbao/detail.js +65 -0
  334. package/clis/yuanbao/history.js +51 -0
  335. package/clis/yuanbao/new.js +1 -0
  336. package/clis/yuanbao/read.js +38 -0
  337. package/clis/yuanbao/send.js +57 -0
  338. package/clis/yuanbao/shared.js +297 -5
  339. package/clis/yuanbao/shared.test.js +80 -0
  340. package/clis/yuanbao/status.js +44 -0
  341. package/clis/zlibrary/commands.test.js +1 -11
  342. package/dist/src/browser/base-page.d.ts +9 -0
  343. package/dist/src/browser/base-page.js +44 -1
  344. package/dist/src/browser/base-page.test.js +66 -0
  345. package/dist/src/browser/cdp.d.ts +1 -0
  346. package/dist/src/browser/cdp.js +51 -9
  347. package/dist/src/browser/daemon-client.d.ts +4 -0
  348. package/dist/src/browser/errors.js +1 -1
  349. package/dist/src/browser/page.d.ts +1 -1
  350. package/dist/src/browser/page.js +3 -1
  351. package/dist/src/browser/page.test.js +29 -0
  352. package/dist/src/browser/target-errors.d.ts +2 -1
  353. package/dist/src/browser/target-errors.js +1 -0
  354. package/dist/src/browser/target-resolver.d.ts +25 -0
  355. package/dist/src/browser/target-resolver.js +43 -0
  356. package/dist/src/build-manifest.js +9 -4
  357. package/dist/src/build-manifest.test.js +2 -8
  358. package/dist/src/capabilityRouting.d.ts +16 -1
  359. package/dist/src/capabilityRouting.js +24 -1
  360. package/dist/src/capabilityRouting.test.js +19 -1
  361. package/dist/src/cli.js +76 -11
  362. package/dist/src/cli.test.js +150 -0
  363. package/dist/src/commanderAdapter.js +0 -5
  364. package/dist/src/commanderAdapter.test.js +0 -1
  365. package/dist/src/discovery.js +2 -5
  366. package/dist/src/errors.js +1 -1
  367. package/dist/src/execution.d.ts +1 -1
  368. package/dist/src/execution.js +111 -27
  369. package/dist/src/execution.test.js +326 -17
  370. package/dist/src/help.d.ts +23 -2
  371. package/dist/src/help.js +41 -19
  372. package/dist/src/help.test.d.ts +1 -0
  373. package/dist/src/help.test.js +54 -0
  374. package/dist/src/main.js +14 -1
  375. package/dist/src/manifest-types.d.ts +5 -3
  376. package/dist/src/pipeline/executor.js +1 -1
  377. package/dist/src/pipeline/executor.test.js +8 -0
  378. package/dist/src/pipeline/registry.d.ts +9 -0
  379. package/dist/src/pipeline/registry.js +13 -1
  380. package/dist/src/pipeline/steps/browser.d.ts +1 -0
  381. package/dist/src/pipeline/steps/browser.js +10 -0
  382. package/dist/src/pipeline/steps/download.test.js +1 -0
  383. package/dist/src/registry-api.d.ts +1 -1
  384. package/dist/src/registry.d.ts +12 -11
  385. package/dist/src/registry.js +16 -6
  386. package/dist/src/registry.test.js +2 -2
  387. package/dist/src/runtime.d.ts +2 -1
  388. package/dist/src/runtime.js +1 -1
  389. package/dist/src/serialization.d.ts +2 -2
  390. package/dist/src/serialization.js +4 -6
  391. package/dist/src/serialization.test.js +17 -0
  392. package/dist/src/types.d.ts +17 -0
  393. package/dist/src/validate.js +15 -11
  394. package/dist/src/validate.test.d.ts +9 -0
  395. package/dist/src/validate.test.js +90 -0
  396. package/package.json +1 -1
  397. package/scripts/fetch-adapters.js +1 -1
  398. package/scripts/typed-error-lint-baseline.json +5 -77
  399. package/clis/ctrip/search.test.js +0 -64
  400. package/clis/gov-policy/commands.test.js +0 -27
  401. package/clis/linux-do/category.js +0 -37
  402. package/clis/linux-do/hot.js +0 -26
  403. package/clis/linux-do/latest.js +0 -19
  404. package/clis/pixiv/test-utils.js +0 -23
  405. package/clis/toutiao/articles.test.js +0 -30
  406. package/dist/src/analysis.d.ts +0 -40
  407. package/dist/src/analysis.js +0 -172
@@ -0,0 +1,101 @@
1
+ // nuget package — full version history for a NuGet package id.
2
+ //
3
+ // Hits the registration index `api.nuget.org/v3/registration5-semver1/<id>/index.json`.
4
+ // The id path segment must be lowercase per NuGet's CDN routing.
5
+ import { cli, Strategy } from '@jackwener/opencli/registry';
6
+ import { CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
7
+ import { NUGET_REGISTRATION_BASE, joinAuthors, joinTags, nugetFetch, requirePackageId } from './utils.js';
8
+
9
+ cli({
10
+ site: 'nuget',
11
+ name: 'package',
12
+ access: 'read',
13
+ description: 'Full NuGet package version history (catalogEntry per release)',
14
+ domain: 'api.nuget.org',
15
+ strategy: Strategy.PUBLIC,
16
+ browser: false,
17
+ args: [
18
+ { name: 'id', positional: true, required: true, help: 'NuGet package id (e.g. "Newtonsoft.Json", case-insensitive)' },
19
+ ],
20
+ columns: [
21
+ 'rank',
22
+ 'id',
23
+ 'version',
24
+ 'title',
25
+ 'authors',
26
+ 'tags',
27
+ 'language',
28
+ 'licenseExpression',
29
+ 'projectUrl',
30
+ 'published',
31
+ 'listed',
32
+ 'url',
33
+ ],
34
+ func: async (args) => {
35
+ const id = requirePackageId(args.id);
36
+ const url = `${NUGET_REGISTRATION_BASE}/${encodeURIComponent(id.toLowerCase())}/index.json`;
37
+ const body = await nugetFetch(url, 'nuget package');
38
+ const pages = Array.isArray(body?.items) ? body.items : [];
39
+ // Each page can be inline (with `items`) or a stub that needs another fetch
40
+ // for older packages. Inline is the common case for everything published in
41
+ // the last few years. We follow stub pages once each — at most ~5 round-trips.
42
+ const allEntries = [];
43
+ for (const [pageIndex, page] of pages.entries()) {
44
+ let pageItems = Array.isArray(page?.items) ? page.items : null;
45
+ if (!pageItems) {
46
+ // Stub page → fetch the leaf.
47
+ const pageUrl = typeof page?.['@id'] === 'string' ? page['@id'] : null;
48
+ if (!pageUrl) {
49
+ throw new CommandExecutionError(
50
+ `nuget package registration page ${pageIndex + 1} is missing @id for package "${id}"`,
51
+ );
52
+ }
53
+ const leaf = await nugetFetch(pageUrl, 'nuget package page');
54
+ if (!Array.isArray(leaf?.items)) {
55
+ throw new CommandExecutionError(
56
+ `nuget package registration leaf ${pageUrl} did not include an items array`,
57
+ );
58
+ }
59
+ pageItems = leaf.items;
60
+ }
61
+ for (const it of pageItems) {
62
+ if (!it || typeof it !== 'object' || !it.catalogEntry || typeof it.catalogEntry !== 'object') {
63
+ throw new CommandExecutionError(
64
+ `nuget package registration page ${pageIndex + 1} contains a malformed version entry`,
65
+ );
66
+ }
67
+ allEntries.push(it);
68
+ }
69
+ }
70
+ if (!allEntries.length) {
71
+ throw new EmptyResultError('nuget package', `No published versions found for NuGet package "${id}".`);
72
+ }
73
+ // Sort by published desc; ties broken by version string descending.
74
+ const sorted = [...allEntries].sort((a, b) => {
75
+ const ap = a?.catalogEntry?.published ?? '';
76
+ const bp = b?.catalogEntry?.published ?? '';
77
+ if (ap !== bp) return bp.localeCompare(ap);
78
+ const av = a?.catalogEntry?.version ?? '';
79
+ const bv = b?.catalogEntry?.version ?? '';
80
+ return bv.localeCompare(av);
81
+ });
82
+ return sorted.map((entry, i) => {
83
+ const cat = entry?.catalogEntry ?? {};
84
+ return {
85
+ rank: i + 1,
86
+ id: typeof cat?.id === 'string' ? cat.id : id,
87
+ version: typeof cat?.version === 'string' ? cat.version : null,
88
+ title: typeof cat?.title === 'string' ? cat.title : null,
89
+ authors: joinAuthors(cat?.authors),
90
+ tags: joinTags(cat?.tags),
91
+ language: typeof cat?.language === 'string' ? cat.language : null,
92
+ licenseExpression: typeof cat?.licenseExpression === 'string' ? cat.licenseExpression : null,
93
+ projectUrl: typeof cat?.projectUrl === 'string' ? cat.projectUrl : null,
94
+ published: typeof cat?.published === 'string' ? cat.published : null,
95
+ listed: typeof cat?.listed === 'boolean' ? cat.listed : null,
96
+ url: typeof cat?.id === 'string' && typeof cat?.version === 'string'
97
+ ? `https://www.nuget.org/packages/${cat.id}/${cat.version}` : '',
98
+ };
99
+ });
100
+ },
101
+ });
@@ -0,0 +1,69 @@
1
+ // nuget search — full-text search the NuGet package index.
2
+ //
3
+ // Hits `azuresearch-usnc.nuget.org/query?q=…&take=…&prerelease=false`. The `id`
4
+ // column round-trips into `nuget package` for full version history.
5
+ import { cli, Strategy } from '@jackwener/opencli/registry';
6
+ import { EmptyResultError } from '@jackwener/opencli/errors';
7
+ import {
8
+ NUGET_SEARCH_BASE,
9
+ joinAuthors,
10
+ joinTags,
11
+ nugetFetch,
12
+ requireBoundedInt,
13
+ requireString,
14
+ } from './utils.js';
15
+
16
+ cli({
17
+ site: 'nuget',
18
+ name: 'search',
19
+ access: 'read',
20
+ description: 'Search NuGet packages by keyword',
21
+ domain: 'api.nuget.org',
22
+ strategy: Strategy.PUBLIC,
23
+ browser: false,
24
+ args: [
25
+ { name: 'query', positional: true, required: true, help: 'Search keyword' },
26
+ { name: 'limit', type: 'int', default: 20, help: 'Max packages (1-1000)' },
27
+ { name: 'prerelease', type: 'boolean', default: false, help: 'Include prerelease versions' },
28
+ ],
29
+ columns: [
30
+ 'rank',
31
+ 'id',
32
+ 'version',
33
+ 'title',
34
+ 'description',
35
+ 'authors',
36
+ 'tags',
37
+ 'totalDownloads',
38
+ 'verified',
39
+ 'projectUrl',
40
+ 'url',
41
+ ],
42
+ func: async (args) => {
43
+ const query = requireString(args.query, 'query');
44
+ const limit = requireBoundedInt(args.limit, 20, 1000);
45
+ const prerelease = args.prerelease === true ? 'true' : 'false';
46
+ const url = `${NUGET_SEARCH_BASE}/query?q=${encodeURIComponent(query)}&take=${limit}&prerelease=${prerelease}`;
47
+ const body = await nugetFetch(url, 'nuget search');
48
+ const list = Array.isArray(body?.data) ? body.data : [];
49
+ if (!list.length) {
50
+ throw new EmptyResultError('nuget search', `No NuGet packages matched "${query}".`);
51
+ }
52
+ return list.slice(0, limit).map((pkg, i) => {
53
+ const id = typeof pkg?.id === 'string' ? pkg.id : '';
54
+ return {
55
+ rank: i + 1,
56
+ id,
57
+ version: typeof pkg?.version === 'string' ? pkg.version : null,
58
+ title: typeof pkg?.title === 'string' ? pkg.title : null,
59
+ description: typeof pkg?.description === 'string' ? pkg.description : null,
60
+ authors: joinAuthors(pkg?.authors),
61
+ tags: joinTags(pkg?.tags),
62
+ totalDownloads: typeof pkg?.totalDownloads === 'number' ? pkg.totalDownloads : null,
63
+ verified: pkg?.verified === true,
64
+ projectUrl: typeof pkg?.projectUrl === 'string' ? pkg.projectUrl : null,
65
+ url: id ? `https://www.nuget.org/packages/${id}` : '',
66
+ };
67
+ });
68
+ },
69
+ });
@@ -0,0 +1,87 @@
1
+ // Shared helpers for the NuGet adapters.
2
+ //
3
+ // NuGet exposes two complementary endpoints:
4
+ // • `azuresearch-usnc.nuget.org/query` — full-text package search (V3)
5
+ // • `api.nuget.org/v3/registration5-semver1/<id>/index.json` — package detail
6
+ // No API key required. Anonymous traffic is generous; we set a polite UA.
7
+ import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
8
+
9
+ export const NUGET_SEARCH_BASE = 'https://azuresearch-usnc.nuget.org';
10
+ export const NUGET_REGISTRATION_BASE = 'https://api.nuget.org/v3/registration5-semver1';
11
+ const UA = 'opencli-nuget-adapter/1.0 (+https://github.com/jackwener/opencli; mailto:opencli@example.com)';
12
+
13
+ // NuGet ID grammar (NuGet docs §package-id): up to 100 chars, alnum + `.` + `_` + `-`,
14
+ // must start with letter/digit. Case-insensitive; we lowercase for the registration URL
15
+ // because NuGet's CDN is case-sensitive on the path.
16
+ const PACKAGE_ID_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{0,99})$/;
17
+
18
+ export function requireString(value, label) {
19
+ const s = String(value ?? '').trim();
20
+ if (!s) throw new ArgumentError(`nuget ${label} cannot be empty`);
21
+ return s;
22
+ }
23
+
24
+ export function requireBoundedInt(value, defaultValue, maxValue, label = 'limit') {
25
+ const raw = value ?? defaultValue;
26
+ const n = typeof raw === 'number' ? raw : Number(raw);
27
+ if (!Number.isInteger(n) || n <= 0) {
28
+ throw new ArgumentError(`nuget ${label} must be a positive integer`);
29
+ }
30
+ if (n > maxValue) {
31
+ throw new ArgumentError(`nuget ${label} must be <= ${maxValue}`);
32
+ }
33
+ return n;
34
+ }
35
+
36
+ export function requirePackageId(value) {
37
+ const raw = String(value ?? '').trim();
38
+ if (!raw) throw new ArgumentError('nuget package id is required (e.g. "Newtonsoft.Json")');
39
+ if (!PACKAGE_ID_PATTERN.test(raw)) {
40
+ throw new ArgumentError(
41
+ `nuget package id "${value}" is not a valid NuGet identifier`,
42
+ 'NuGet IDs are 1-100 chars: letters/digits/`.`/`_`/`-`, starting with letter or digit.',
43
+ );
44
+ }
45
+ return raw;
46
+ }
47
+
48
+ export async function nugetFetch(url, label) {
49
+ let resp;
50
+ try {
51
+ resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
52
+ }
53
+ catch (err) {
54
+ throw new CommandExecutionError(
55
+ `${label} request failed: ${err?.message ?? err}`,
56
+ 'Check that api.nuget.org is reachable from this network.',
57
+ );
58
+ }
59
+ if (resp.status === 404) {
60
+ throw new EmptyResultError(label, `NuGet returned 404 for ${url}.`);
61
+ }
62
+ if (resp.status === 429) {
63
+ throw new CommandExecutionError(`${label} returned HTTP 429 (rate limited)`);
64
+ }
65
+ if (!resp.ok) {
66
+ throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
67
+ }
68
+ let body;
69
+ try {
70
+ body = await resp.json();
71
+ }
72
+ catch (err) {
73
+ throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
74
+ }
75
+ return body;
76
+ }
77
+
78
+ export function joinTags(tags) {
79
+ if (!Array.isArray(tags)) return '';
80
+ return tags.filter((t) => typeof t === 'string' && t.trim()).join(', ');
81
+ }
82
+
83
+ export function joinAuthors(authors) {
84
+ if (Array.isArray(authors)) return authors.filter((a) => typeof a === 'string' && a.trim()).join(', ');
85
+ if (typeof authors === 'string') return authors.trim();
86
+ return '';
87
+ }
@@ -0,0 +1,121 @@
1
+ // nvd cve — fetch a single CVE from the NIST National Vulnerability Database.
2
+ //
3
+ // Hits the CVE API 2.0 (`services.nvd.nist.gov/rest/json/cves/2.0?cveId=…`).
4
+ // Returns the agent-useful projection: id, published / last-modified dates,
5
+ // vuln status, English description, CVSS v3.1 base score / severity / vector,
6
+ // CWE id(s), CISA KEV flag.
7
+ import { cli, Strategy } from '@jackwener/opencli/registry';
8
+ import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
9
+
10
+ const NVD_BASE = 'https://services.nvd.nist.gov/rest/json/cves/2.0';
11
+ const UA = 'opencli-nvd-adapter (+https://github.com/jackwener/opencli)';
12
+ const CVE_ID = /^CVE-\d{4}-\d{4,}$/i;
13
+
14
+ function requireCveId(value) {
15
+ const s = String(value ?? '').trim().toUpperCase();
16
+ if (!s) throw new ArgumentError('nvd CVE id is required (e.g. "CVE-2021-44228")');
17
+ if (!CVE_ID.test(s)) {
18
+ throw new ArgumentError(
19
+ `nvd CVE id "${value}" is not a valid CVE identifier`,
20
+ 'Expected the form "CVE-YYYY-N..." with at least 4 sequence digits.',
21
+ );
22
+ }
23
+ return s;
24
+ }
25
+
26
+ function pickEnglishDescription(descriptions) {
27
+ if (!Array.isArray(descriptions)) return '';
28
+ const en = descriptions.find((d) => d?.lang === 'en');
29
+ return String(en?.value ?? descriptions[0]?.value ?? '').trim();
30
+ }
31
+
32
+ function pickPrimaryCvss(metrics) {
33
+ if (!metrics || typeof metrics !== 'object') return null;
34
+ const candidates = [
35
+ ...(Array.isArray(metrics.cvssMetricV31) ? metrics.cvssMetricV31 : []),
36
+ ...(Array.isArray(metrics.cvssMetricV30) ? metrics.cvssMetricV30 : []),
37
+ ...(Array.isArray(metrics.cvssMetricV2) ? metrics.cvssMetricV2 : []),
38
+ ];
39
+ return candidates.find((m) => m?.type === 'Primary') || candidates[0] || null;
40
+ }
41
+
42
+ function joinCwes(weaknesses) {
43
+ if (!Array.isArray(weaknesses)) return '';
44
+ const ids = new Set();
45
+ for (const w of weaknesses) {
46
+ for (const desc of w?.description ?? []) {
47
+ if (desc?.value) ids.add(String(desc.value));
48
+ }
49
+ }
50
+ return [...ids].join(', ');
51
+ }
52
+
53
+ cli({
54
+ site: 'nvd',
55
+ name: 'cve',
56
+ access: 'read',
57
+ description: 'NIST NVD CVE detail (description, CVSS, CWE, KEV flag)',
58
+ domain: 'services.nvd.nist.gov',
59
+ strategy: Strategy.PUBLIC,
60
+ browser: false,
61
+ args: [
62
+ { name: 'id', positional: true, required: true, help: 'CVE identifier (e.g. "CVE-2021-44228")' },
63
+ ],
64
+ columns: [
65
+ 'id', 'published', 'lastModified', 'vulnStatus', 'baseScore', 'severity',
66
+ 'attackVector', 'cwe', 'kevAdded', 'description', 'url',
67
+ ],
68
+ func: async (args) => {
69
+ const id = requireCveId(args.id);
70
+ const url = `${NVD_BASE}?cveId=${encodeURIComponent(id)}`;
71
+ let resp;
72
+ try {
73
+ resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
74
+ }
75
+ catch (err) {
76
+ throw new CommandExecutionError(`nvd cve request failed: ${err?.message ?? err}`);
77
+ }
78
+ if (resp.status === 403) {
79
+ throw new CommandExecutionError(
80
+ 'nvd cve returned HTTP 403',
81
+ 'NVD enforces aggressive rate limits without an API key. Wait, then retry or set NVD_API_KEY (not yet wired).',
82
+ );
83
+ }
84
+ if (resp.status === 429) {
85
+ throw new CommandExecutionError(
86
+ 'nvd cve returned HTTP 429 (rate limited)',
87
+ 'NVD throttles unauthenticated traffic; wait several seconds before retry.',
88
+ );
89
+ }
90
+ if (!resp.ok) {
91
+ throw new CommandExecutionError(`nvd cve returned HTTP ${resp.status}`);
92
+ }
93
+ let body;
94
+ try {
95
+ body = await resp.json();
96
+ }
97
+ catch (err) {
98
+ throw new CommandExecutionError(`nvd cve returned malformed JSON: ${err?.message ?? err}`);
99
+ }
100
+ const list = Array.isArray(body?.vulnerabilities) ? body.vulnerabilities : [];
101
+ const cve = list[0]?.cve;
102
+ if (!cve || !cve.id) {
103
+ throw new EmptyResultError('nvd cve', `NVD has no record for "${id}".`);
104
+ }
105
+ const cvss = pickPrimaryCvss(cve.metrics);
106
+ const cvssData = cvss?.cvssData ?? {};
107
+ return [{
108
+ id: String(cve.id),
109
+ published: String(cve.published ?? '').slice(0, 10),
110
+ lastModified: String(cve.lastModified ?? '').slice(0, 10),
111
+ vulnStatus: String(cve.vulnStatus ?? ''),
112
+ baseScore: cvssData.baseScore != null ? Number(cvssData.baseScore) : null,
113
+ severity: String(cvssData.baseSeverity ?? cvss?.baseSeverity ?? ''),
114
+ attackVector: String(cvssData.attackVector ?? ''),
115
+ cwe: joinCwes(cve.weaknesses),
116
+ kevAdded: cve.cisaExploitAdd ? String(cve.cisaExploitAdd).slice(0, 10) : '',
117
+ description: pickEnglishDescription(cve.descriptions),
118
+ url: `https://nvd.nist.gov/vuln/detail/${cve.id}`,
119
+ }];
120
+ },
121
+ });
@@ -0,0 +1,88 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from '@jackwener/opencli/registry';
3
+ import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
4
+ import './search.js';
5
+ import './sequence.js';
6
+
7
+ afterEach(() => {
8
+ vi.unstubAllGlobals();
9
+ vi.restoreAllMocks();
10
+ });
11
+
12
+ describe('oeis search adapter', () => {
13
+ const cmd = getRegistry().get('oeis/search');
14
+
15
+ it('rejects bad args before fetching', async () => {
16
+ const fetchMock = vi.fn();
17
+ vi.stubGlobal('fetch', fetchMock);
18
+ await expect(cmd.func({ query: '' })).rejects.toThrow(ArgumentError);
19
+ await expect(cmd.func({ query: 'foo', limit: 999 })).rejects.toThrow(ArgumentError);
20
+ expect(fetchMock).not.toHaveBeenCalled();
21
+ });
22
+
23
+ it('maps HTTP 429 to CommandExecutionError', async () => {
24
+ // Fresh Response per call so we don't tickle "Body has already been read" between pages.
25
+ vi.stubGlobal('fetch', vi.fn().mockImplementation(() => Promise.resolve(new Response('throttled', { status: 429 }))));
26
+ await expect(cmd.func({ query: 'fibonacci', limit: 5 })).rejects.toThrow(CommandExecutionError);
27
+ });
28
+
29
+ it('throws EmptyResultError when first page is empty', async () => {
30
+ vi.stubGlobal('fetch', vi.fn().mockImplementation(() => Promise.resolve(new Response(JSON.stringify([]), { status: 200 }))));
31
+ await expect(cmd.func({ query: 'no-results', limit: 5 })).rejects.toThrow(EmptyResultError);
32
+ });
33
+
34
+ it('round-trips A-id from search row into oeis.org URL', async () => {
35
+ const result = [{
36
+ number: 45, name: 'Fibonacci numbers', keyword: 'core,nonn,nice',
37
+ data: '0,1,1,2,3,5,8,13,21,34,55,89',
38
+ author: 'Sloane', created: '1991-04-30T03:00:00-04:00',
39
+ }];
40
+ vi.stubGlobal('fetch', vi.fn().mockImplementation(() => Promise.resolve(new Response(JSON.stringify(result), { status: 200 }))));
41
+ const rows = await cmd.func({ query: 'fibonacci', limit: 1 });
42
+ expect(rows[0]).toMatchObject({
43
+ rank: 1, id: 'A000045', name: 'Fibonacci numbers',
44
+ keywords: 'core,nonn,nice',
45
+ preview: '0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89',
46
+ url: 'https://oeis.org/A000045',
47
+ });
48
+ });
49
+ });
50
+
51
+ describe('oeis sequence adapter', () => {
52
+ const cmd = getRegistry().get('oeis/sequence');
53
+
54
+ it('rejects malformed sequence ids before fetching', async () => {
55
+ const fetchMock = vi.fn();
56
+ vi.stubGlobal('fetch', fetchMock);
57
+ await expect(cmd.func({ id: '' })).rejects.toThrow(ArgumentError);
58
+ await expect(cmd.func({ id: 'B000045' })).rejects.toThrow(ArgumentError);
59
+ await expect(cmd.func({ id: 'A' })).rejects.toThrow(ArgumentError);
60
+ expect(fetchMock).not.toHaveBeenCalled();
61
+ });
62
+
63
+ it('throws EmptyResultError when search returns null/empty list', async () => {
64
+ // OEIS returns empty list (or sometimes null body) for unknown id.
65
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify([]), { status: 200 })));
66
+ await expect(cmd.func({ id: 'A9999999' })).rejects.toThrow(EmptyResultError);
67
+ });
68
+
69
+ it('counts comments / formulas / xrefs without dumping the full graph', async () => {
70
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue(new Response(JSON.stringify([{
71
+ number: 40, name: 'The prime numbers.', keyword: 'core,nonn',
72
+ data: '2,3,5,7,11,13,17,19,23',
73
+ offset: '1,1', author: 'Sloane', created: '1991-04-30T03:00:00-04:00', revision: 100,
74
+ comment: ['c1', 'c2', 'c3'],
75
+ formula: ['f1', 'f2'],
76
+ reference: ['r1'],
77
+ xref: ['A000001', 'A000002'],
78
+ link: ['l1', 'l2', 'l3', 'l4'],
79
+ }]), { status: 200 })));
80
+ const rows = await cmd.func({ id: 'A000040' });
81
+ expect(rows[0]).toMatchObject({
82
+ id: 'A000040', name: 'The prime numbers.',
83
+ termCount: 9, offset: '1,1', revision: 100,
84
+ commentCount: 3, formulaCount: 2, referenceCount: 1, xrefCount: 2, linkCount: 4,
85
+ url: 'https://oeis.org/A000040',
86
+ });
87
+ });
88
+ });
@@ -0,0 +1,63 @@
1
+ // oeis search — keyword / pattern search against the OEIS index.
2
+ //
3
+ // OEIS' search supports both natural-language queries ("fibonacci") and
4
+ // numeric pattern queries ("1,1,2,3,5,8"). Returns up to 10 results per
5
+ // page; we honor `--limit` by paginating via `&start=`.
6
+ import { cli, Strategy } from '@jackwener/opencli/registry';
7
+ import { EmptyResultError } from '@jackwener/opencli/errors';
8
+ import { OEIS_BASE, formatId, oeisFetch, previewTerms, requireBoundedInt, requireString } from './utils.js';
9
+
10
+ cli({
11
+ site: 'oeis',
12
+ name: 'search',
13
+ access: 'read',
14
+ description: 'Search OEIS sequences by keyword or numeric pattern',
15
+ domain: 'oeis.org',
16
+ strategy: Strategy.PUBLIC,
17
+ browser: false,
18
+ args: [
19
+ { name: 'query', positional: true, required: true, help: 'Search keyword or comma-separated terms (e.g. "fibonacci", "1,1,2,3,5,8")' },
20
+ { name: 'limit', type: 'int', default: 10, help: 'Max sequences (1-100)' },
21
+ ],
22
+ columns: ['rank', 'id', 'name', 'keywords', 'preview', 'author', 'created', 'url'],
23
+ func: async (args) => {
24
+ const query = requireString(args.query, 'query');
25
+ const limit = requireBoundedInt(args.limit, 10, 100);
26
+ // OEIS returns 10 per page; paginate via `&start=` until we have `limit` rows
27
+ // or run out of pages. No silent clamp — if upstream has fewer results, surface
28
+ // every row that came back.
29
+ const collected = [];
30
+ let start = 0;
31
+ const pageSize = 10;
32
+ // Cap iterations defensively at limit/pageSize + 1 so we never spin forever.
33
+ const maxPages = Math.ceil(limit / pageSize) + 1;
34
+ for (let page = 0; page < maxPages && collected.length < limit; page++) {
35
+ const url = `${OEIS_BASE}/search?q=${encodeURIComponent(query)}&fmt=json&start=${start}`;
36
+ const body = await oeisFetch(url, 'oeis search');
37
+ const list = Array.isArray(body) ? body : [];
38
+ if (!list.length) break;
39
+ for (const r of list) {
40
+ if (collected.length >= limit) break;
41
+ collected.push(r);
42
+ }
43
+ if (list.length < pageSize) break;
44
+ start += pageSize;
45
+ }
46
+ if (!collected.length) {
47
+ throw new EmptyResultError('oeis search', `No OEIS sequences matched "${query}".`);
48
+ }
49
+ return collected.map((r, i) => {
50
+ const id = formatId(r?.number);
51
+ return {
52
+ rank: i + 1,
53
+ id,
54
+ name: typeof r?.name === 'string' ? r.name : null,
55
+ keywords: typeof r?.keyword === 'string' ? r.keyword : null,
56
+ preview: previewTerms(r?.data),
57
+ author: typeof r?.author === 'string' ? r.author : null,
58
+ created: typeof r?.created === 'string' ? r.created : null,
59
+ url: id ? `${OEIS_BASE}/${id}` : '',
60
+ };
61
+ });
62
+ },
63
+ });
@@ -0,0 +1,71 @@
1
+ // oeis sequence — full detail for a single OEIS sequence (A-number).
2
+ //
3
+ // OEIS' search endpoint, when given `q=id:Annnnnn`, returns one full record
4
+ // with all sub-fields populated. We surface formula / xref / reference counts
5
+ // instead of dumping the full graph.
6
+ import { cli, Strategy } from '@jackwener/opencli/registry';
7
+ import { EmptyResultError } from '@jackwener/opencli/errors';
8
+ import { OEIS_BASE, formatId, oeisFetch, previewTerms, requireSequenceId } from './utils.js';
9
+
10
+ function asArray(value) {
11
+ return Array.isArray(value) ? value : [];
12
+ }
13
+
14
+ cli({
15
+ site: 'oeis',
16
+ name: 'sequence',
17
+ access: 'read',
18
+ description: 'Full OEIS sequence detail by A-number (terms, name, keywords, formula counts)',
19
+ domain: 'oeis.org',
20
+ strategy: Strategy.PUBLIC,
21
+ browser: false,
22
+ args: [
23
+ { name: 'id', positional: true, required: true, help: 'OEIS sequence id (e.g. "A000045" for Fibonacci)' },
24
+ ],
25
+ columns: [
26
+ 'id',
27
+ 'name',
28
+ 'keywords',
29
+ 'preview',
30
+ 'termCount',
31
+ 'offset',
32
+ 'author',
33
+ 'created',
34
+ 'revision',
35
+ 'commentCount',
36
+ 'formulaCount',
37
+ 'referenceCount',
38
+ 'xrefCount',
39
+ 'linkCount',
40
+ 'url',
41
+ ],
42
+ func: async (args) => {
43
+ const id = requireSequenceId(args.id);
44
+ const url = `${OEIS_BASE}/search?q=id:${encodeURIComponent(id)}&fmt=json`;
45
+ const body = await oeisFetch(url, 'oeis sequence');
46
+ const list = Array.isArray(body) ? body : [];
47
+ if (!list.length) {
48
+ throw new EmptyResultError('oeis sequence', `OEIS sequence "${id}" not found.`);
49
+ }
50
+ const r = list[0];
51
+ const data = typeof r?.data === 'string' ? r.data : '';
52
+ const termCount = data ? data.split(',').filter(Boolean).length : 0;
53
+ return [{
54
+ id: formatId(r?.number) ?? id,
55
+ name: typeof r?.name === 'string' ? r.name : null,
56
+ keywords: typeof r?.keyword === 'string' ? r.keyword : null,
57
+ preview: previewTerms(data),
58
+ termCount,
59
+ offset: typeof r?.offset === 'string' ? r.offset : null,
60
+ author: typeof r?.author === 'string' ? r.author : null,
61
+ created: typeof r?.created === 'string' ? r.created : null,
62
+ revision: typeof r?.revision === 'number' ? r.revision : null,
63
+ commentCount: asArray(r?.comment).length,
64
+ formulaCount: asArray(r?.formula).length,
65
+ referenceCount: asArray(r?.reference).length,
66
+ xrefCount: asArray(r?.xref).length,
67
+ linkCount: asArray(r?.link).length,
68
+ url: `${OEIS_BASE}/${formatId(r?.number) ?? id}`,
69
+ }];
70
+ },
71
+ });