@thevinci/web 1.0.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 (337) hide show
  1. package/README.md +197 -0
  2. package/bin/cli-entry.js +55 -0
  3. package/bin/cli-output.js +145 -0
  4. package/bin/cli.js +4887 -0
  5. package/bin/cli.test.js +64 -0
  6. package/dist/apple-touch-icon-120x120.png +0 -0
  7. package/dist/apple-touch-icon-152x152.png +0 -0
  8. package/dist/apple-touch-icon-167x167.png +0 -0
  9. package/dist/apple-touch-icon-180x180.png +0 -0
  10. package/dist/apple-touch-icon.png +0 -0
  11. package/dist/apple-touch-icon.svg +528 -0
  12. package/dist/assets/JsonTreeView-CSm9OzXG.js +1 -0
  13. package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  14. package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  15. package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  16. package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  17. package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  18. package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  19. package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  20. package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  21. package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  22. package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  23. package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  24. package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  25. package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  26. package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  27. package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  28. package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  29. package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  30. package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  31. package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  32. package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  33. package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  34. package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  35. package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  36. package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  37. package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  38. package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  39. package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  40. package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  41. package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  42. package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  43. package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  44. package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  45. package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  46. package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  47. package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  48. package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  49. package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  50. package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  51. package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  52. package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  53. package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  54. package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  55. package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  56. package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  57. package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  58. package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  59. package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  60. package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  61. package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  62. package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  63. package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  64. package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  65. package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  66. package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  67. package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  68. package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  69. package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  70. package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  71. package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  72. package/dist/assets/MarkdownRendererImpl-DensKOLc.js +6 -0
  73. package/dist/assets/MultiRunWindow-Bo7THayo.js +1 -0
  74. package/dist/assets/OnboardingScreen-BDqmzTVR.js +2 -0
  75. package/dist/assets/SettingsWindow-coz__Ykw.js +1 -0
  76. package/dist/assets/TerminalView-DrZ-i3Dr.js +1 -0
  77. package/dist/assets/ToolOutputDialog-Eglzslt3.js +16 -0
  78. package/dist/assets/es-4o9ciP61.js +15 -0
  79. package/dist/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  80. package/dist/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  81. package/dist/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  82. package/dist/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  83. package/dist/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
  84. package/dist/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
  85. package/dist/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  86. package/dist/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  87. package/dist/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  88. package/dist/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  89. package/dist/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  90. package/dist/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  91. package/dist/assets/index-DLTDToSP.css +1 -0
  92. package/dist/assets/index-DgiFEKGN.js +1 -0
  93. package/dist/assets/ko-B20imCHE.js +15 -0
  94. package/dist/assets/main-BV3KOtdA.css +1 -0
  95. package/dist/assets/main-CDKJj0sH.js +226 -0
  96. package/dist/assets/main-LC-PSNVM.js +2 -0
  97. package/dist/assets/miniChat-CQUiG_cr.js +2 -0
  98. package/dist/assets/modelPrefsAutoSave-Dm799vzR.js +6986 -0
  99. package/dist/assets/pl-DQJ7LSzj.js +15 -0
  100. package/dist/assets/pt-BR-OmjHUz9y.js +15 -0
  101. package/dist/assets/renderElectronMiniChatApp-CARbeW0G.js +2 -0
  102. package/dist/assets/uk-BNFxOlO4.js +15 -0
  103. package/dist/assets/vendor--DBfsbEis.css +1 -0
  104. package/dist/assets/vendor-.bun-B9l0ZNi2.js +4094 -0
  105. package/dist/assets/wasm-CG6Dc4jp.js +1 -0
  106. package/dist/assets/wasmSttWorker-Dtlxac_K.js +1 -0
  107. package/dist/assets/wasmSttWorker-oo7Dm_jy.js +1806 -0
  108. package/dist/assets/worker-CbT6TVo7.js +155 -0
  109. package/dist/assets/zh-CN-C6T-Ac7F.js +15 -0
  110. package/dist/favicon-16.png +0 -0
  111. package/dist/favicon-32.png +0 -0
  112. package/dist/favicon.png +0 -0
  113. package/dist/favicon.svg +528 -0
  114. package/dist/index.html +607 -0
  115. package/dist/logo-dark-192x192.png +0 -0
  116. package/dist/logo-dark-512x512.png +0 -0
  117. package/dist/logo-dark-512x512.svg +528 -0
  118. package/dist/logo-light-192x192.png +0 -0
  119. package/dist/logo-light-512x512.png +0 -0
  120. package/dist/logo-light-512x512.svg +528 -0
  121. package/dist/mini-chat.html +16 -0
  122. package/dist/pwa-192.png +0 -0
  123. package/dist/pwa-512.png +0 -0
  124. package/dist/pwa-maskable-192.png +0 -0
  125. package/dist/pwa-maskable-512.png +0 -0
  126. package/dist/site.webmanifest +21 -0
  127. package/dist/sw.js +1 -0
  128. package/package.json +118 -0
  129. package/public/apple-touch-icon-120x120.png +0 -0
  130. package/public/apple-touch-icon-152x152.png +0 -0
  131. package/public/apple-touch-icon-167x167.png +0 -0
  132. package/public/apple-touch-icon-180x180.png +0 -0
  133. package/public/apple-touch-icon.png +0 -0
  134. package/public/apple-touch-icon.svg +528 -0
  135. package/public/favicon-16.png +0 -0
  136. package/public/favicon-32.png +0 -0
  137. package/public/favicon.png +0 -0
  138. package/public/favicon.svg +528 -0
  139. package/public/logo-dark-192x192.png +0 -0
  140. package/public/logo-dark-512x512.png +0 -0
  141. package/public/logo-dark-512x512.svg +528 -0
  142. package/public/logo-light-192x192.png +0 -0
  143. package/public/logo-light-512x512.png +0 -0
  144. package/public/logo-light-512x512.svg +528 -0
  145. package/public/pwa-192.png +0 -0
  146. package/public/pwa-512.png +0 -0
  147. package/public/pwa-maskable-192.png +0 -0
  148. package/public/pwa-maskable-512.png +0 -0
  149. package/public/site.webmanifest +21 -0
  150. package/server/TERMINAL_WS_PROTOCOL.md +48 -0
  151. package/server/index.d.ts +39 -0
  152. package/server/index.js +1311 -0
  153. package/server/lib/cloudflare-tunnel.js +650 -0
  154. package/server/lib/event-stream/DOCUMENTATION.md +61 -0
  155. package/server/lib/event-stream/directory-ws-bridge.js +185 -0
  156. package/server/lib/event-stream/global-hub.js +158 -0
  157. package/server/lib/event-stream/global-hub.test.js +140 -0
  158. package/server/lib/event-stream/global-ws-bridge.js +206 -0
  159. package/server/lib/event-stream/index.js +25 -0
  160. package/server/lib/event-stream/protocol.js +131 -0
  161. package/server/lib/event-stream/protocol.test.js +182 -0
  162. package/server/lib/event-stream/runtime.js +180 -0
  163. package/server/lib/event-stream/runtime.test.js +512 -0
  164. package/server/lib/event-stream/upstream-reader.js +226 -0
  165. package/server/lib/event-stream/upstream-reader.test.js +276 -0
  166. package/server/lib/fs/DOCUMENTATION.md +36 -0
  167. package/server/lib/fs/routes.js +1040 -0
  168. package/server/lib/fs/search.js +238 -0
  169. package/server/lib/git/DOCUMENTATION.md +152 -0
  170. package/server/lib/git/credentials.js +74 -0
  171. package/server/lib/git/identity-storage.js +112 -0
  172. package/server/lib/git/index.js +6 -0
  173. package/server/lib/git/routes.js +972 -0
  174. package/server/lib/git/service.js +3432 -0
  175. package/server/lib/git/service.test.js +39 -0
  176. package/server/lib/github/DOCUMENTATION.md +171 -0
  177. package/server/lib/github/auth.js +307 -0
  178. package/server/lib/github/device-flow.js +50 -0
  179. package/server/lib/github/index.js +24 -0
  180. package/server/lib/github/octokit.js +10 -0
  181. package/server/lib/github/pr-status.js +519 -0
  182. package/server/lib/github/repo/fork-detection.js +102 -0
  183. package/server/lib/github/repo/index.js +55 -0
  184. package/server/lib/github/routes.js +1560 -0
  185. package/server/lib/magic-prompts/routes.js +63 -0
  186. package/server/lib/magic-prompts/runtime.js +119 -0
  187. package/server/lib/notifications/DOCUMENTATION.md +122 -0
  188. package/server/lib/notifications/emitter-runtime.js +102 -0
  189. package/server/lib/notifications/index.js +4 -0
  190. package/server/lib/notifications/message.js +52 -0
  191. package/server/lib/notifications/message.test.js +34 -0
  192. package/server/lib/notifications/push-runtime.js +304 -0
  193. package/server/lib/notifications/routes.js +315 -0
  194. package/server/lib/notifications/runtime.js +566 -0
  195. package/server/lib/notifications/template-runtime.js +349 -0
  196. package/server/lib/notifications/template-runtime.test.js +26 -0
  197. package/server/lib/opencode/DOCUMENTATION.md +362 -0
  198. package/server/lib/opencode/agents.js +634 -0
  199. package/server/lib/opencode/auth-state-runtime.js +88 -0
  200. package/server/lib/opencode/auth.js +83 -0
  201. package/server/lib/opencode/bootstrap-runtime.js +131 -0
  202. package/server/lib/opencode/cli-entry-runtime.js +43 -0
  203. package/server/lib/opencode/cli-options.js +128 -0
  204. package/server/lib/opencode/commands.js +339 -0
  205. package/server/lib/opencode/config-entity-routes.js +370 -0
  206. package/server/lib/opencode/core-routes.js +500 -0
  207. package/server/lib/opencode/core-routes.test.js +26 -0
  208. package/server/lib/opencode/env-config.js +74 -0
  209. package/server/lib/opencode/env-keys.js +68 -0
  210. package/server/lib/opencode/env-runtime.js +1162 -0
  211. package/server/lib/opencode/env-runtime.test.js +116 -0
  212. package/server/lib/opencode/feature-routes-runtime.js +244 -0
  213. package/server/lib/opencode/hmr-state-runtime.js +85 -0
  214. package/server/lib/opencode/index.js +66 -0
  215. package/server/lib/opencode/lifecycle.js +1019 -0
  216. package/server/lib/opencode/lifecycle.test.js +240 -0
  217. package/server/lib/opencode/mcp.js +278 -0
  218. package/server/lib/opencode/network-runtime.js +104 -0
  219. package/server/lib/opencode/network-runtime.test.js +37 -0
  220. package/server/lib/opencode/opencode-resolution-runtime.js +71 -0
  221. package/server/lib/opencode/path-utils.js +100 -0
  222. package/server/lib/opencode/path-utils.test.js +71 -0
  223. package/server/lib/opencode/project-directory-runtime.js +124 -0
  224. package/server/lib/opencode/project-icon-routes.js +399 -0
  225. package/server/lib/opencode/project-icon-routes.test.js +107 -0
  226. package/server/lib/opencode/providers.js +96 -0
  227. package/server/lib/opencode/proxy.js +445 -0
  228. package/server/lib/opencode/pwa-manifest-routes.js +257 -0
  229. package/server/lib/opencode/pwa-manifest-routes.test.js +133 -0
  230. package/server/lib/opencode/routes.js +541 -0
  231. package/server/lib/opencode/server-startup-runtime.js +156 -0
  232. package/server/lib/opencode/server-utils-runtime.js +168 -0
  233. package/server/lib/opencode/server-utils-runtime.test.js +135 -0
  234. package/server/lib/opencode/session-runtime.js +356 -0
  235. package/server/lib/opencode/session-runtime.test.js +151 -0
  236. package/server/lib/opencode/settings-helpers.js +770 -0
  237. package/server/lib/opencode/settings-helpers.test.js +109 -0
  238. package/server/lib/opencode/settings-normalization-runtime.js +428 -0
  239. package/server/lib/opencode/settings-runtime.js +826 -0
  240. package/server/lib/opencode/settings-runtime.test.js +85 -0
  241. package/server/lib/opencode/shared.js +615 -0
  242. package/server/lib/opencode/shutdown-runtime.js +139 -0
  243. package/server/lib/opencode/shutdown-runtime.test.js +58 -0
  244. package/server/lib/opencode/skill-routes.js +701 -0
  245. package/server/lib/opencode/skills.js +548 -0
  246. package/server/lib/opencode/startup-pipeline-runtime.js +130 -0
  247. package/server/lib/opencode/static-routes-runtime.js +65 -0
  248. package/server/lib/opencode/theme-runtime.js +167 -0
  249. package/server/lib/opencode/tunnel-auth.js +591 -0
  250. package/server/lib/opencode/tunnel-wiring-runtime.js +94 -0
  251. package/server/lib/opencode/vinci-routes.js +76 -0
  252. package/server/lib/opencode/watcher.js +115 -0
  253. package/server/lib/opencode/watcher.test.js +239 -0
  254. package/server/lib/preview/proxy-runtime.js +1333 -0
  255. package/server/lib/preview/proxy-runtime.test.js +144 -0
  256. package/server/lib/projects/project-config.js +567 -0
  257. package/server/lib/projects/project-config.test.js +175 -0
  258. package/server/lib/projects/project-id.js +13 -0
  259. package/server/lib/quota/DOCUMENTATION.md +58 -0
  260. package/server/lib/quota/index.js +25 -0
  261. package/server/lib/quota/providers/claude.js +107 -0
  262. package/server/lib/quota/providers/codex.js +113 -0
  263. package/server/lib/quota/providers/copilot.js +165 -0
  264. package/server/lib/quota/providers/google/api.js +92 -0
  265. package/server/lib/quota/providers/google/auth.js +108 -0
  266. package/server/lib/quota/providers/google/index.js +124 -0
  267. package/server/lib/quota/providers/google/transforms.js +109 -0
  268. package/server/lib/quota/providers/index.js +168 -0
  269. package/server/lib/quota/providers/interface.js +55 -0
  270. package/server/lib/quota/providers/kimi.js +108 -0
  271. package/server/lib/quota/providers/minimax-cn-coding-plan.js +140 -0
  272. package/server/lib/quota/providers/minimax-coding-plan.js +139 -0
  273. package/server/lib/quota/providers/nanogpt.js +124 -0
  274. package/server/lib/quota/providers/ollama-cloud.js +112 -0
  275. package/server/lib/quota/providers/openai.js +91 -0
  276. package/server/lib/quota/providers/openrouter.js +92 -0
  277. package/server/lib/quota/providers/zai.js +91 -0
  278. package/server/lib/quota/providers/zhipuai-coding-plan.js +133 -0
  279. package/server/lib/quota/providers/zhipuai.js +114 -0
  280. package/server/lib/quota/routes.js +27 -0
  281. package/server/lib/quota/utils/auth.js +50 -0
  282. package/server/lib/quota/utils/formatters.js +85 -0
  283. package/server/lib/quota/utils/formatters.test.js +54 -0
  284. package/server/lib/quota/utils/index.js +10 -0
  285. package/server/lib/quota/utils/transformers.js +55 -0
  286. package/server/lib/scheduled-tasks/DOCUMENTATION.md +44 -0
  287. package/server/lib/scheduled-tasks/routes.js +235 -0
  288. package/server/lib/scheduled-tasks/runtime.js +773 -0
  289. package/server/lib/scheduled-tasks/runtime.test.js +100 -0
  290. package/server/lib/security/request-security.js +115 -0
  291. package/server/lib/session-folders/routes.js +63 -0
  292. package/server/lib/session-folders/routes.test.js +102 -0
  293. package/server/lib/skills-catalog/DOCUMENTATION.md +178 -0
  294. package/server/lib/skills-catalog/cache.js +29 -0
  295. package/server/lib/skills-catalog/clawdhub/api.js +158 -0
  296. package/server/lib/skills-catalog/clawdhub/index.js +30 -0
  297. package/server/lib/skills-catalog/clawdhub/install.js +238 -0
  298. package/server/lib/skills-catalog/clawdhub/scan.js +113 -0
  299. package/server/lib/skills-catalog/curated-sources.js +21 -0
  300. package/server/lib/skills-catalog/git.js +77 -0
  301. package/server/lib/skills-catalog/index.js +42 -0
  302. package/server/lib/skills-catalog/install.js +294 -0
  303. package/server/lib/skills-catalog/scan.js +221 -0
  304. package/server/lib/skills-catalog/source.js +87 -0
  305. package/server/lib/terminal/DOCUMENTATION.md +76 -0
  306. package/server/lib/terminal/index.js +31 -0
  307. package/server/lib/terminal/output-replay-buffer.js +78 -0
  308. package/server/lib/terminal/output-replay-buffer.test.js +75 -0
  309. package/server/lib/terminal/runtime.js +850 -0
  310. package/server/lib/terminal/runtime.test.js +96 -0
  311. package/server/lib/terminal/terminal-ws-protocol.js +68 -0
  312. package/server/lib/terminal/terminal-ws-protocol.test.js +145 -0
  313. package/server/lib/text/DOCUMENTATION.md +35 -0
  314. package/server/lib/text/summarization.js +138 -0
  315. package/server/lib/text/summarization.test.js +34 -0
  316. package/server/lib/tts/DOCUMENTATION.md +146 -0
  317. package/server/lib/tts/base-url.js +62 -0
  318. package/server/lib/tts/capability-runtime.js +31 -0
  319. package/server/lib/tts/index.js +19 -0
  320. package/server/lib/tts/routes.js +261 -0
  321. package/server/lib/tts/routes.test.js +53 -0
  322. package/server/lib/tts/service.js +178 -0
  323. package/server/lib/tts/stt.js +75 -0
  324. package/server/lib/tunnels/DOCUMENTATION.md +18 -0
  325. package/server/lib/tunnels/index.js +166 -0
  326. package/server/lib/tunnels/managed-config.js +201 -0
  327. package/server/lib/tunnels/providers/cloudflare.js +260 -0
  328. package/server/lib/tunnels/registry.js +51 -0
  329. package/server/lib/tunnels/routes.js +605 -0
  330. package/server/lib/tunnels/types.js +219 -0
  331. package/server/lib/ui-auth/DOCUMENTATION.md +38 -0
  332. package/server/lib/ui-auth/ui-auth.js +673 -0
  333. package/server/lib/ui-auth/ui-passkeys.js +545 -0
  334. package/server/opencode-proxy.test.js +151 -0
  335. package/server/proxy-headers.js +61 -0
  336. package/server/proxy-headers.test.js +58 -0
  337. package/server/sse-routes.test.js +152 -0
@@ -0,0 +1,108 @@
1
+ import { readAuthFile } from '../../opencode/auth.js';
2
+ import {
3
+ getAuthEntry,
4
+ normalizeAuthEntry,
5
+ buildResult,
6
+ toUsageWindow,
7
+ toNumber,
8
+ toTimestamp,
9
+ durationToLabel,
10
+ durationToSeconds
11
+ } from '../utils/index.js';
12
+
13
+ export const providerId = 'kimi-for-coding';
14
+ export const providerName = 'Kimi for Coding';
15
+ export const aliases = ['kimi-for-coding', 'kimi'];
16
+
17
+ export const isConfigured = () => {
18
+ const auth = readAuthFile();
19
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
20
+ return Boolean(entry?.key || entry?.token);
21
+ };
22
+
23
+ export const fetchQuota = async () => {
24
+ const auth = readAuthFile();
25
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
26
+ const apiKey = entry?.key ?? entry?.token;
27
+
28
+ if (!apiKey) {
29
+ return buildResult({
30
+ providerId,
31
+ providerName,
32
+ ok: false,
33
+ configured: false,
34
+ error: 'Not configured'
35
+ });
36
+ }
37
+
38
+ try {
39
+ const response = await fetch('https://api.kimi.com/coding/v1/usages', {
40
+ method: 'GET',
41
+ headers: {
42
+ Authorization: `Bearer ${apiKey}`,
43
+ 'Content-Type': 'application/json'
44
+ }
45
+ });
46
+
47
+ if (!response.ok) {
48
+ return buildResult({
49
+ providerId,
50
+ providerName,
51
+ ok: false,
52
+ configured: true,
53
+ error: `API error: ${response.status}`
54
+ });
55
+ }
56
+
57
+ const payload = await response.json();
58
+ const windows = {};
59
+ const usage = payload?.usage ?? null;
60
+ if (usage) {
61
+ const limit = toNumber(usage.limit);
62
+ const remaining = toNumber(usage.remaining);
63
+ const usedPercent = limit && remaining !== null
64
+ ? Math.max(0, Math.min(100, 100 - (remaining / limit) * 100))
65
+ : null;
66
+ windows.weekly = toUsageWindow({
67
+ usedPercent,
68
+ windowSeconds: null,
69
+ resetAt: toTimestamp(usage.resetTime)
70
+ });
71
+ }
72
+
73
+ const limits = Array.isArray(payload?.limits) ? payload.limits : [];
74
+ for (const limit of limits) {
75
+ const window = limit?.window;
76
+ const detail = limit?.detail;
77
+ const rawLabel = durationToLabel(window?.duration, window?.timeUnit);
78
+ const windowSeconds = durationToSeconds(window?.duration, window?.timeUnit);
79
+ const label = windowSeconds === 5 * 60 * 60 ? `Rate Limit (${rawLabel})` : rawLabel;
80
+ const total = toNumber(detail?.limit);
81
+ const remaining = toNumber(detail?.remaining);
82
+ const usedPercent = total && remaining !== null
83
+ ? Math.max(0, Math.min(100, 100 - (remaining / total) * 100))
84
+ : null;
85
+ windows[label] = toUsageWindow({
86
+ usedPercent,
87
+ windowSeconds,
88
+ resetAt: toTimestamp(detail?.resetTime)
89
+ });
90
+ }
91
+
92
+ return buildResult({
93
+ providerId,
94
+ providerName,
95
+ ok: true,
96
+ configured: true,
97
+ usage: { windows }
98
+ });
99
+ } catch (error) {
100
+ return buildResult({
101
+ providerId,
102
+ providerName,
103
+ ok: false,
104
+ configured: true,
105
+ error: error instanceof Error ? error.message : 'Request failed'
106
+ });
107
+ }
108
+ };
@@ -0,0 +1,140 @@
1
+ // MiniMax Coding Plan Provider (minimaxi.com)
2
+ import { readAuthFile } from '../../opencode/auth.js';
3
+ import {
4
+ getAuthEntry,
5
+ normalizeAuthEntry,
6
+ buildResult,
7
+ toUsageWindow,
8
+ toNumber,
9
+ toTimestamp,
10
+ } from '../utils/index.js';
11
+
12
+ export const providerId = 'minimax-cn-coding-plan';
13
+ export const providerName = 'MiniMax Coding Plan (minimaxi.com)';
14
+ export const aliases = ['minimax-cn-coding-plan'];
15
+
16
+ export const isConfigured = () => {
17
+ const auth = readAuthFile();
18
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
19
+ return Boolean(entry?.key || entry?.token);
20
+ };
21
+
22
+ export const fetchQuota = async () => {
23
+ const auth = readAuthFile();
24
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
25
+ const apiKey = entry?.key ?? entry?.token;
26
+
27
+ if (!apiKey) {
28
+ return buildResult({
29
+ providerId,
30
+ providerName,
31
+ ok: false,
32
+ configured: false,
33
+ error: 'Not configured',
34
+ });
35
+ }
36
+
37
+ try {
38
+ const response = await fetch(
39
+ 'https://www.minimaxi.com/v1/api/openplatform/coding_plan/remains',
40
+ {
41
+ method: 'GET',
42
+ headers: {
43
+ Authorization: `Bearer ${apiKey}`,
44
+ 'Content-Type': 'application/json',
45
+ },
46
+ }
47
+ );
48
+
49
+ if (!response.ok) {
50
+ return buildResult({
51
+ providerId,
52
+ providerName,
53
+ ok: false,
54
+ configured: true,
55
+ error: `API error: ${response.status}`,
56
+ });
57
+ }
58
+
59
+ const payload = await response.json();
60
+ const baseResp = payload?.base_resp;
61
+ if (baseResp && baseResp.status_code !== 0) {
62
+ return buildResult({
63
+ providerId,
64
+ providerName,
65
+ ok: false,
66
+ configured: true,
67
+ error: baseResp.status_msg || `API error: ${baseResp.status_code}`,
68
+ });
69
+ }
70
+
71
+ const firstModel = payload?.model_remains?.[0];
72
+ if (!firstModel) {
73
+ return buildResult({
74
+ providerId,
75
+ providerName,
76
+ ok: false,
77
+ configured: true,
78
+ error: 'No model quota data available',
79
+ });
80
+ }
81
+
82
+ const intervalTotal = toNumber(firstModel.current_interval_total_count);
83
+ const intervalUsage = toNumber(firstModel.current_interval_usage_count);
84
+ const intervalStartAt = toTimestamp(firstModel.start_time);
85
+ const intervalResetAt = toTimestamp(firstModel.end_time);
86
+ const weeklyTotal = toNumber(firstModel.current_weekly_total_count);
87
+ const weeklyUsage = toNumber(firstModel.current_weekly_usage_count);
88
+ const weeklyStartAt = toTimestamp(firstModel.weekly_start_time);
89
+ const weeklyResetAt = toTimestamp(firstModel.weekly_end_time);
90
+
91
+ const intervalUsed = intervalTotal - intervalUsage;
92
+ const weeklyUsed = weeklyTotal - weeklyUsage;
93
+
94
+ const intervalUsedPercent =
95
+ intervalTotal > 0 && intervalUsed != null
96
+ ? Math.max(0, Math.min(100, (intervalUsed / intervalTotal) * 100))
97
+ : null;
98
+ const intervalWindowSeconds =
99
+ intervalStartAt && intervalResetAt && intervalResetAt > intervalStartAt
100
+ ? Math.floor((intervalResetAt - intervalStartAt) / 1000)
101
+ : null;
102
+ const weeklyUsedPercent =
103
+ weeklyTotal > 0 && weeklyUsed != null
104
+ ? Math.max(0, Math.min(100, (weeklyUsed / weeklyTotal) * 100))
105
+ : null;
106
+ const weeklyWindowSeconds =
107
+ weeklyStartAt && weeklyResetAt && weeklyResetAt > weeklyStartAt
108
+ ? Math.floor((weeklyResetAt - weeklyStartAt) / 1000)
109
+ : null;
110
+
111
+ const windows = {
112
+ '5h': toUsageWindow({
113
+ usedPercent: intervalUsedPercent,
114
+ windowSeconds: intervalWindowSeconds,
115
+ resetAt: intervalResetAt,
116
+ }),
117
+ weekly: toUsageWindow({
118
+ usedPercent: weeklyUsedPercent,
119
+ windowSeconds: weeklyWindowSeconds,
120
+ resetAt: weeklyResetAt,
121
+ }),
122
+ };
123
+
124
+ return buildResult({
125
+ providerId,
126
+ providerName,
127
+ ok: true,
128
+ configured: true,
129
+ usage: { windows },
130
+ });
131
+ } catch (error) {
132
+ return buildResult({
133
+ providerId,
134
+ providerName,
135
+ ok: false,
136
+ configured: true,
137
+ error: error instanceof Error ? error.message : 'Request failed',
138
+ });
139
+ }
140
+ };
@@ -0,0 +1,139 @@
1
+ import { readAuthFile } from '../../opencode/auth.js';
2
+ import {
3
+ getAuthEntry,
4
+ normalizeAuthEntry,
5
+ buildResult,
6
+ toUsageWindow,
7
+ toNumber,
8
+ toTimestamp,
9
+ } from '../utils/index.js';
10
+
11
+ export const providerId = 'minimax-coding-plan';
12
+ export const providerName = 'MiniMax Coding Plan (minimax.io)';
13
+ export const aliases = ['minimax-coding-plan'];
14
+
15
+ export const isConfigured = () => {
16
+ const auth = readAuthFile();
17
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
18
+ return Boolean(entry?.key || entry?.token);
19
+ };
20
+
21
+ export const fetchQuota = async () => {
22
+ const auth = readAuthFile();
23
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
24
+ const apiKey = entry?.key ?? entry?.token;
25
+
26
+ if (!apiKey) {
27
+ return buildResult({
28
+ providerId,
29
+ providerName,
30
+ ok: false,
31
+ configured: false,
32
+ error: 'Not configured',
33
+ });
34
+ }
35
+
36
+ try {
37
+ const response = await fetch(
38
+ 'https://api.minimax.io/v1/api/openplatform/coding_plan/remains',
39
+ {
40
+ method: 'GET',
41
+ headers: {
42
+ Authorization: `Bearer ${apiKey}`,
43
+ 'Content-Type': 'application/json',
44
+ },
45
+ }
46
+ );
47
+
48
+ if (!response.ok) {
49
+ return buildResult({
50
+ providerId,
51
+ providerName,
52
+ ok: false,
53
+ configured: true,
54
+ error: `API error: ${response.status}`,
55
+ });
56
+ }
57
+
58
+ const payload = await response.json();
59
+ const baseResp = payload?.base_resp;
60
+ if (baseResp && baseResp.status_code !== 0) {
61
+ return buildResult({
62
+ providerId,
63
+ providerName,
64
+ ok: false,
65
+ configured: true,
66
+ error: baseResp.status_msg || `API error: ${baseResp.status_code}`,
67
+ });
68
+ }
69
+
70
+ const firstModel = payload?.model_remains?.[0];
71
+ if (!firstModel) {
72
+ return buildResult({
73
+ providerId,
74
+ providerName,
75
+ ok: false,
76
+ configured: true,
77
+ error: 'No model quota data available',
78
+ });
79
+ }
80
+
81
+ const intervalTotal = toNumber(firstModel.current_interval_total_count);
82
+ const intervalUsage = toNumber(firstModel.current_interval_usage_count);
83
+ const intervalStartAt = toTimestamp(firstModel.start_time);
84
+ const intervalResetAt = toTimestamp(firstModel.end_time);
85
+ const weeklyTotal = toNumber(firstModel.current_weekly_total_count);
86
+ const weeklyUsage = toNumber(firstModel.current_weekly_usage_count);
87
+ const weeklyStartAt = toTimestamp(firstModel.weekly_start_time);
88
+ const weeklyResetAt = toTimestamp(firstModel.weekly_end_time);
89
+
90
+ const intervalUsed = intervalUsage;
91
+ const weeklyUsed = weeklyUsage;
92
+
93
+ const intervalUsedPercent =
94
+ intervalTotal > 0 && intervalUsed !== null
95
+ ? Math.max(0, Math.min(100, (intervalUsed / intervalTotal) * 100))
96
+ : null;
97
+ const intervalWindowSeconds =
98
+ intervalStartAt && intervalResetAt && intervalResetAt > intervalStartAt
99
+ ? Math.floor((intervalResetAt - intervalStartAt) / 1000)
100
+ : null;
101
+ const weeklyUsedPercent =
102
+ weeklyTotal > 0 && weeklyUsed !== null
103
+ ? Math.max(0, Math.min(100, (weeklyUsed / weeklyTotal) * 100))
104
+ : null;
105
+ const weeklyWindowSeconds =
106
+ weeklyStartAt && weeklyResetAt && weeklyResetAt > weeklyStartAt
107
+ ? Math.floor((weeklyResetAt - weeklyStartAt) / 1000)
108
+ : null;
109
+
110
+ const windows = {
111
+ '5h': toUsageWindow({
112
+ usedPercent: intervalUsedPercent,
113
+ windowSeconds: intervalWindowSeconds,
114
+ resetAt: intervalResetAt,
115
+ }),
116
+ weekly: toUsageWindow({
117
+ usedPercent: weeklyUsedPercent,
118
+ windowSeconds: weeklyWindowSeconds,
119
+ resetAt: weeklyResetAt,
120
+ }),
121
+ };
122
+
123
+ return buildResult({
124
+ providerId,
125
+ providerName,
126
+ ok: true,
127
+ configured: true,
128
+ usage: { windows },
129
+ });
130
+ } catch (error) {
131
+ return buildResult({
132
+ providerId,
133
+ providerName,
134
+ ok: false,
135
+ configured: true,
136
+ error: error instanceof Error ? error.message : 'Request failed',
137
+ });
138
+ }
139
+ };
@@ -0,0 +1,124 @@
1
+ import { readAuthFile } from '../../opencode/auth.js';
2
+ import {
3
+ getAuthEntry,
4
+ normalizeAuthEntry,
5
+ buildResult,
6
+ toUsageWindow,
7
+ toNumber,
8
+ toTimestamp
9
+ } from '../utils/index.js';
10
+
11
+ const NANO_GPT_DAILY_WINDOW_SECONDS = 86400;
12
+
13
+ export const providerId = 'nano-gpt';
14
+ export const providerName = 'NanoGPT';
15
+ export const aliases = ['nano-gpt', 'nanogpt', 'nano_gpt'];
16
+
17
+ export const isConfigured = () => {
18
+ const auth = readAuthFile();
19
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
20
+ return Boolean(entry?.key || entry?.token);
21
+ };
22
+
23
+ export const fetchQuota = async () => {
24
+ const auth = readAuthFile();
25
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
26
+ const apiKey = entry?.key ?? entry?.token;
27
+
28
+ if (!apiKey) {
29
+ return buildResult({
30
+ providerId,
31
+ providerName,
32
+ ok: false,
33
+ configured: false,
34
+ error: 'Not configured'
35
+ });
36
+ }
37
+
38
+ try {
39
+ const response = await fetch('https://nano-gpt.com/api/subscription/v1/usage', {
40
+ method: 'GET',
41
+ headers: {
42
+ Authorization: `Bearer ${apiKey}`,
43
+ 'Content-Type': 'application/json'
44
+ }
45
+ });
46
+
47
+ if (!response.ok) {
48
+ return buildResult({
49
+ providerId,
50
+ providerName,
51
+ ok: false,
52
+ configured: true,
53
+ error: `API error: ${response.status}`
54
+ });
55
+ }
56
+
57
+ const payload = await response.json();
58
+ const windows = {};
59
+ const period = payload?.period ?? null;
60
+ const daily = payload?.daily ?? null;
61
+ const monthly = payload?.monthly ?? null;
62
+ const state = payload?.state ?? 'active';
63
+
64
+ if (daily) {
65
+ let usedPercent = null;
66
+ const percentUsed = daily?.percentUsed;
67
+ if (typeof percentUsed === 'number') {
68
+ usedPercent = Math.max(0, Math.min(100, percentUsed * 100));
69
+ } else {
70
+ const used = toNumber(daily?.used);
71
+ const limit = toNumber(daily?.limit ?? daily?.limits?.daily);
72
+ if (used !== null && limit !== null && limit > 0) {
73
+ usedPercent = Math.max(0, Math.min(100, (used / limit) * 100));
74
+ }
75
+ }
76
+ const resetAt = toTimestamp(daily?.resetAt);
77
+ const valueLabel = state !== 'active' ? `(${state})` : null;
78
+ windows['daily'] = toUsageWindow({
79
+ usedPercent,
80
+ windowSeconds: NANO_GPT_DAILY_WINDOW_SECONDS,
81
+ resetAt,
82
+ valueLabel
83
+ });
84
+ }
85
+
86
+ if (monthly) {
87
+ let usedPercent = null;
88
+ const percentUsed = monthly?.percentUsed;
89
+ if (typeof percentUsed === 'number') {
90
+ usedPercent = Math.max(0, Math.min(100, percentUsed * 100));
91
+ } else {
92
+ const used = toNumber(monthly?.used);
93
+ const limit = toNumber(monthly?.limit ?? monthly?.limits?.monthly);
94
+ if (used !== null && limit !== null && limit > 0) {
95
+ usedPercent = Math.max(0, Math.min(100, (used / limit) * 100));
96
+ }
97
+ }
98
+ const resetAt = toTimestamp(monthly?.resetAt ?? period?.currentPeriodEnd);
99
+ const valueLabel = state !== 'active' ? `(${state})` : null;
100
+ windows['monthly'] = toUsageWindow({
101
+ usedPercent,
102
+ windowSeconds: null,
103
+ resetAt,
104
+ valueLabel
105
+ });
106
+ }
107
+
108
+ return buildResult({
109
+ providerId,
110
+ providerName,
111
+ ok: true,
112
+ configured: true,
113
+ usage: { windows }
114
+ });
115
+ } catch (error) {
116
+ return buildResult({
117
+ providerId,
118
+ providerName,
119
+ ok: false,
120
+ configured: true,
121
+ error: error instanceof Error ? error.message : 'Request failed'
122
+ });
123
+ }
124
+ };
@@ -0,0 +1,112 @@
1
+ import { homedir } from 'os';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { buildResult, toUsageWindow, toNumber } from '../utils/index.js';
5
+
6
+ const COOKIE_PATH = join(homedir(), '.config', 'ollama-quota', 'cookie');
7
+
8
+ export const providerId = 'ollama-cloud';
9
+ export const providerName = 'Ollama Cloud';
10
+ export const aliases = ['ollama-cloud', 'ollamacloud'];
11
+
12
+ const readCookieFile = () => {
13
+ try {
14
+ if (!existsSync(COOKIE_PATH)) return null;
15
+ const content = readFileSync(COOKIE_PATH, 'utf-8');
16
+ const trimmed = content.trim();
17
+ return trimmed || null;
18
+ } catch {
19
+ return null;
20
+ }
21
+ };
22
+
23
+ const parseOllamaSettingsHtml = (html) => {
24
+ const windows = {};
25
+ const sessionMatch = html.match(/Session\s+usage[^0-9]*([0-9.]+)%/i);
26
+ if (sessionMatch) {
27
+ windows.session = toUsageWindow({
28
+ usedPercent: toNumber(sessionMatch[1]),
29
+ windowSeconds: null,
30
+ resetAt: null
31
+ });
32
+ }
33
+ const weeklyMatch = html.match(/Weekly\s+usage[^0-9]*([0-9.]+)%/i);
34
+ if (weeklyMatch) {
35
+ windows.weekly = toUsageWindow({
36
+ usedPercent: toNumber(weeklyMatch[1]),
37
+ windowSeconds: null,
38
+ resetAt: null
39
+ });
40
+ }
41
+ const premiumMatch = html.match(/Premium[^0-9]*([0-9]+)\s*\/\s*([0-9]+)/i);
42
+ if (premiumMatch) {
43
+ const used = toNumber(premiumMatch[1]);
44
+ const total = toNumber(premiumMatch[2]);
45
+ const usedPercent = total && used !== null ? Math.min(100, (used / total) * 100) : null;
46
+ windows.premium = toUsageWindow({
47
+ usedPercent,
48
+ windowSeconds: null,
49
+ resetAt: null,
50
+ valueLabel: `${used ?? 0} / ${total ?? 0}`
51
+ });
52
+ }
53
+ return windows;
54
+ };
55
+
56
+ export const isConfigured = () => {
57
+ const cookie = readCookieFile();
58
+ return Boolean(cookie);
59
+ };
60
+
61
+ export const fetchQuota = async () => {
62
+ const cookie = readCookieFile();
63
+
64
+ if (!cookie) {
65
+ return buildResult({
66
+ providerId,
67
+ providerName,
68
+ ok: false,
69
+ configured: false,
70
+ error: 'Not configured'
71
+ });
72
+ }
73
+
74
+ try {
75
+ const response = await fetch('https://ollama.com/settings', {
76
+ method: 'GET',
77
+ headers: {
78
+ Cookie: cookie,
79
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
80
+ }
81
+ });
82
+
83
+ if (!response.ok) {
84
+ return buildResult({
85
+ providerId,
86
+ providerName,
87
+ ok: false,
88
+ configured: true,
89
+ error: `API error: ${response.status}`
90
+ });
91
+ }
92
+
93
+ const html = await response.text();
94
+ const windows = parseOllamaSettingsHtml(html);
95
+
96
+ return buildResult({
97
+ providerId,
98
+ providerName,
99
+ ok: true,
100
+ configured: true,
101
+ usage: { windows }
102
+ });
103
+ } catch (error) {
104
+ return buildResult({
105
+ providerId,
106
+ providerName,
107
+ ok: false,
108
+ configured: true,
109
+ error: error instanceof Error ? error.message : 'Request failed'
110
+ });
111
+ }
112
+ };
@@ -0,0 +1,91 @@
1
+ import { readAuthFile } from '../../opencode/auth.js';
2
+ import {
3
+ getAuthEntry,
4
+ normalizeAuthEntry,
5
+ buildResult,
6
+ toUsageWindow,
7
+ toNumber,
8
+ toTimestamp
9
+ } from '../utils/index.js';
10
+
11
+ export const providerId = 'openai';
12
+ export const providerName = 'OpenAI';
13
+ export const aliases = ['openai', 'codex', 'chatgpt'];
14
+
15
+ export const isConfigured = () => {
16
+ const auth = readAuthFile();
17
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
18
+ return Boolean(entry?.access || entry?.token);
19
+ };
20
+
21
+ export const fetchQuota = async () => {
22
+ const auth = readAuthFile();
23
+ const entry = normalizeAuthEntry(getAuthEntry(auth, aliases));
24
+ const accessToken = entry?.access ?? entry?.token;
25
+
26
+ if (!accessToken) {
27
+ return buildResult({
28
+ providerId,
29
+ providerName,
30
+ ok: false,
31
+ configured: false,
32
+ error: 'Not configured'
33
+ });
34
+ }
35
+
36
+ try {
37
+ const response = await fetch('https://chatgpt.com/backend-api/wham/usage', {
38
+ method: 'GET',
39
+ headers: {
40
+ Authorization: `Bearer ${accessToken}`,
41
+ 'Content-Type': 'application/json'
42
+ }
43
+ });
44
+
45
+ if (!response.ok) {
46
+ return buildResult({
47
+ providerId,
48
+ providerName,
49
+ ok: false,
50
+ configured: true,
51
+ error: `API error: ${response.status}`
52
+ });
53
+ }
54
+
55
+ const payload = await response.json();
56
+ const primary = payload?.rate_limit?.primary_window ?? null;
57
+ const secondary = payload?.rate_limit?.secondary_window ?? null;
58
+
59
+ const windows = {};
60
+ if (primary) {
61
+ windows['5h'] = toUsageWindow({
62
+ usedPercent: primary.used_percent ?? null,
63
+ windowSeconds: primary.limit_window_seconds ?? null,
64
+ resetAt: primary.reset_at ? primary.reset_at * 1000 : null
65
+ });
66
+ }
67
+ if (secondary) {
68
+ windows['weekly'] = toUsageWindow({
69
+ usedPercent: secondary.used_percent ?? null,
70
+ windowSeconds: secondary.limit_window_seconds ?? null,
71
+ resetAt: secondary.reset_at ? secondary.reset_at * 1000 : null
72
+ });
73
+ }
74
+
75
+ return buildResult({
76
+ providerId,
77
+ providerName,
78
+ ok: true,
79
+ configured: true,
80
+ usage: { windows }
81
+ });
82
+ } catch (error) {
83
+ return buildResult({
84
+ providerId,
85
+ providerName,
86
+ ok: false,
87
+ configured: true,
88
+ error: error instanceof Error ? error.message : 'Request failed'
89
+ });
90
+ }
91
+ };